Merge branch '1.20.1' into 1.20.2

This commit is contained in:
Sollace 2024-06-24 01:15:50 +01:00
commit 74a08b4820
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
224 changed files with 4953 additions and 2679 deletions

View file

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

View file

@ -10,6 +10,7 @@ import com.google.common.base.Strings;
import com.minelittlepony.unicopia.ability.Abilities; import com.minelittlepony.unicopia.ability.Abilities;
import com.minelittlepony.unicopia.ability.Ability; import com.minelittlepony.unicopia.ability.Ability;
import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.util.RegistryUtils; import com.minelittlepony.unicopia.util.RegistryUtils;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
@ -44,6 +45,7 @@ public record Race (
public static final String DEFAULT_ID = "unicopia:unset"; public static final String DEFAULT_ID = "unicopia:unset";
public static final Registry<Race> REGISTRY = RegistryUtils.createDefaulted(Unicopia.id("race"), DEFAULT_ID); public static final Registry<Race> REGISTRY = RegistryUtils.createDefaulted(Unicopia.id("race"), DEFAULT_ID);
public static final Registry<Race> COMMAND_REGISTRY = RegistryUtils.createDefaulted(Unicopia.id("race/grantable"), DEFAULT_ID); public static final Registry<Race> COMMAND_REGISTRY = RegistryUtils.createDefaulted(Unicopia.id("race/grantable"), DEFAULT_ID);
public static final TrackableDataType<Race> TRACKABLE_TYPE = TrackableDataType.RACE;
public static final RegistryKey<? extends Registry<Race>> REGISTRY_KEY = REGISTRY.getKey(); public static final RegistryKey<? extends Registry<Race>> REGISTRY_KEY = REGISTRY.getKey();
private static final DynamicCommandExceptionType UNKNOWN_RACE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("commands.race.fail", id)); private static final DynamicCommandExceptionType UNKNOWN_RACE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("commands.race.fail", id));
private static final Function<Race, Composite> COMPOSITES = Util.memoize(race -> new Composite(race, null, null)); private static final Function<Race, Composite> COMPOSITES = Util.memoize(race -> new Composite(race, null, null));

View file

@ -107,6 +107,7 @@ public interface UTags {
TagKey<Block> MIMIC_CHESTS = block("mimic_chests"); TagKey<Block> MIMIC_CHESTS = block("mimic_chests");
TagKey<Block> BUTTERFLIES_SPAWNABLE_ON = block("butterflies_spawn_on"); TagKey<Block> BUTTERFLIES_SPAWNABLE_ON = block("butterflies_spawn_on");
TagKey<Block> ANGERS_GUARDIANS = block("angers_guardians");
private static TagKey<Block> block(String name) { private static TagKey<Block> block(String name) {
return TagKey.of(RegistryKeys.BLOCK, Unicopia.id(name)); return TagKey.of(RegistryKeys.BLOCK, Unicopia.id(name));

View file

@ -2,6 +2,7 @@ package com.minelittlepony.unicopia;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper; import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.minecraft.resource.ResourceType; import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@ -22,6 +23,7 @@ import com.minelittlepony.unicopia.container.UScreenHandlers;
import com.minelittlepony.unicopia.diet.DietsLoader; import com.minelittlepony.unicopia.diet.DietsLoader;
import com.minelittlepony.unicopia.diet.affliction.AfflictionType; import com.minelittlepony.unicopia.diet.affliction.AfflictionType;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.effect.SeaponyGraceStatusEffect;
import com.minelittlepony.unicopia.entity.effect.UPotions; import com.minelittlepony.unicopia.entity.effect.UPotions;
import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.UItems;
@ -76,6 +78,7 @@ public class Unicopia implements ModInitializer {
Debug.runTests(w); Debug.runTests(w);
} }
}); });
PlayerBlockBreakEvents.AFTER.register(SeaponyGraceStatusEffect::processBlockChange);
NocturnalSleepManager.bootstrap(); NocturnalSleepManager.bootstrap();
registerServerDataReloaders(ResourceManagerHelper.get(ResourceType.SERVER_DATA)); registerServerDataReloaders(ResourceManagerHelper.get(ResourceType.SERVER_DATA));

View file

@ -172,6 +172,16 @@ public class AbilityDispatcher implements Tickable, NbtSerialisable {
} }
public void tick() { public void tick() {
Optional<Ability<?>> activeAbility = getActiveAbility();
if (activeAbility.isEmpty()) {
if (warmup > 0) {
warmup--;
}
if (cooldown > 0) {
cooldown--;
}
}
getActiveAbility().ifPresent(ability -> { getActiveAbility().ifPresent(ability -> {
if (warmup > 0) { if (warmup > 0) {
warmup--; warmup--;

View file

@ -10,8 +10,10 @@ import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.FriendshipBraceletItem; import com.minelittlepony.unicopia.item.FriendshipBraceletItem;
import com.minelittlepony.unicopia.item.UItems;
import net.minecraft.particle.ParticleTypes; import net.minecraft.particle.ParticleTypes;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
@ -73,6 +75,11 @@ public class ChangeFormAbility implements Ability<Hit> {
List<Pony> targets = getTargets(player).toList(); List<Pony> targets = getTargets(player).toList();
player.subtractEnergyCost(5 * targets.size()); player.subtractEnergyCost(5 * targets.size());
TrinketsDelegate.EquippedStack amulet = UItems.PEARL_NECKLACE.getForEntity(player.asEntity());
if (!amulet.stack().isEmpty()) {
amulet.stack().damage(1, player.asEntity(), amulet.breakStatusSender());
}
boolean isTransforming = player.getSuppressedRace().isUnset(); boolean isTransforming = player.getSuppressedRace().isUnset();
targets.forEach(target -> { targets.forEach(target -> {
Race supressed = target.getSuppressedRace(); Race supressed = target.getSuppressedRace();

View file

@ -51,7 +51,7 @@ public class ChangelingDisguiseAbility extends ChangelingFeedAbility {
player.getEntityWorld().playSound(null, player.getBlockPos(), USounds.ENTITY_PLAYER_CHANGELING_TRANSFORM, SoundCategory.PLAYERS, 1.4F, 0.4F); player.getEntityWorld().playSound(null, player.getBlockPos(), USounds.ENTITY_PLAYER_CHANGELING_TRANSFORM, SoundCategory.PLAYERS, 1.4F, 0.4F);
Disguise currentDisguise = iplayer.getSpellSlot().get(SpellType.CHANGELING_DISGUISE, true) Disguise currentDisguise = iplayer.getSpellSlot().get(SpellType.CHANGELING_DISGUISE)
.orElseGet(() -> SpellType.CHANGELING_DISGUISE.withTraits().apply(iplayer, CastingMethod.INNATE)); .orElseGet(() -> SpellType.CHANGELING_DISGUISE.withTraits().apply(iplayer, CastingMethod.INNATE));
if (currentDisguise.isOf(looked)) { if (currentDisguise.isOf(looked)) {
@ -64,7 +64,6 @@ public class ChangelingDisguiseAbility extends ChangelingFeedAbility {
} }
player.calculateDimensions(); player.calculateDimensions();
iplayer.setDirty();
return true; return true;
} }

View file

@ -6,9 +6,11 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.AwaitTickQueue; import com.minelittlepony.unicopia.AwaitTickQueue;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.UTags; import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.LandingEventHandler;
import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
@ -80,7 +82,9 @@ public class EarthPonyStompAbility implements Ability<Hit> {
@Override @Override
public Optional<Hit> prepare(Pony player) { public Optional<Hit> prepare(Pony player) {
if (player.asEntity().getVelocity().y * player.getPhysics().getGravitySignum() < 0 if (player.asEntity().getVelocity().y * player.getPhysics().getGravitySignum() < 0
&& !player.asEntity().getAbilities().flying) { && !player.asEntity().getAbilities().flying
&& !player.asEntity().isFallFlying()
&& !player.asEntity().isUsingRiptide()) {
thrustDownwards(player); thrustDownwards(player);
return Hit.INSTANCE; return Hit.INSTANCE;
} }
@ -104,9 +108,18 @@ public class EarthPonyStompAbility implements Ability<Hit> {
@Override @Override
public boolean apply(Pony iplayer, Hit data) { public boolean apply(Pony iplayer, Hit data) {
PlayerEntity player = iplayer.asEntity(); final PlayerEntity player = iplayer.asEntity();
final double initialY = player.getY() + 5;
Runnable r = () -> { var r = new LandingEventHandler.Callback() {
@Override
public float dispatch(float fallDistance) {
// fail if landing above the starting position
if (player.getY() > initialY) {
return fallDistance;
}
player.fallDistance = 0;
BlockPos center = PosHelper.findSolidGroundAt(player.getEntityWorld(), player.getBlockPos(), iplayer.getPhysics().getGravitySignum()); BlockPos center = PosHelper.findSolidGroundAt(player.getEntityWorld(), player.getBlockPos(), iplayer.getPhysics().getGravitySignum());
float heavyness = 1 + EnchantmentHelper.getEquipmentLevel(UEnchantments.HEAVY, player); float heavyness = 1 + EnchantmentHelper.getEquipmentLevel(UEnchantments.HEAVY, player);
@ -163,13 +176,20 @@ public class EarthPonyStompAbility implements Ability<Hit> {
iplayer.subtractEnergyCost(rad); iplayer.subtractEnergyCost(rad);
iplayer.asEntity().addExhaustion(3); iplayer.asEntity().addExhaustion(3);
return 0F;
}
@Override
public void onCancelled() {
iplayer.playSound(USounds.GUI_ABILITY_FAIL, 1F);
}
}; };
if (iplayer.asEntity().isOnGround()) { if (iplayer.asEntity().isOnGround()) {
iplayer.setAnimation(Animation.STOMP, Animation.Recipient.ANYONE, 10); iplayer.setAnimation(Animation.STOMP, Animation.Recipient.ANYONE, 10);
iplayer.asEntity().jump(); iplayer.asEntity().jump();
iplayer.updateVelocity(); iplayer.updateVelocity();
AwaitTickQueue.scheduleTask(iplayer.asWorld(), w -> r.run(), 5); AwaitTickQueue.scheduleTask(iplayer.asWorld(), w -> r.dispatch(0F), 5);
} else { } else {
thrustDownwards(iplayer); thrustDownwards(iplayer);
iplayer.waitForFall(r); iplayer.waitForFall(r);

View file

@ -57,9 +57,7 @@ public class TimeChangeAbility implements Ability<Rot> {
return false; return false;
} }
if (player.getSpellSlot().contains(SpellType.TIME_CONTROL)) { if (!player.getSpellSlot().removeWhere(SpellType.TIME_CONTROL)) {
player.getSpellSlot().removeWhere(SpellType.TIME_CONTROL, true);
} else {
SpellType.TIME_CONTROL.withTraits().apply(player, CastingMethod.INNATE).update(player, data.applyTo(player)); SpellType.TIME_CONTROL.withTraits().apply(player, CastingMethod.INNATE).update(player, data.applyTo(player));
} }

View file

@ -27,7 +27,7 @@ public class ToggleFlightAbility implements Ability<Hit> {
@Nullable @Nullable
@Override @Override
public Optional<Hit> prepare(Pony player) { public Optional<Hit> prepare(Pony player) {
return Hit.of(!player.asEntity().isCreative() && !player.getPhysics().getFlightType().isGrounded()); return Hit.of(!player.asEntity().hasVehicle() && !player.asEntity().isCreative() && !player.getPhysics().getFlightType().isGrounded());
} }
@Override @Override
@ -65,7 +65,6 @@ public class ToggleFlightAbility implements Ability<Hit> {
} else { } else {
player.getPhysics().cancelFlight(true); player.getPhysics().cancelFlight(true);
} }
player.setDirty();
player.setAnimation(Animation.SPREAD_WINGS, Animation.Recipient.ANYONE); player.setAnimation(Animation.SPREAD_WINGS, Animation.Recipient.ANYONE);
return true; return true;
} }

View file

@ -100,12 +100,21 @@ public class UnicornCastingAbility extends AbstractSpellCastingAbility {
if (newSpell.getResult() != ActionResult.FAIL && canCast(newSpell.getValue().type())) { if (newSpell.getResult() != ActionResult.FAIL && canCast(newSpell.getValue().type())) {
CustomisedSpellType<?> spell = newSpell.getValue(); CustomisedSpellType<?> spell = newSpell.getValue();
if (newSpell.getResult() == ActionResult.CONSUME) {
CustomisedSpellType<?> equippedType = player.getCharms().getEquippedSpell(player.getCharms().getHand());
if (equippedType.type() == spell.type()) {
player.getCharms().equipSpell(player.getCharms().getHand(), spell);
}
}
boolean removed = player.getSpellSlot().removeWhere(s -> { if (spell.isEmpty()) {
return s.findMatches(spell).findAny().isPresent() && (spell.isEmpty() || !SpellType.PLACED_SPELL.test(s)); return false;
}, true); }
boolean has = !spell.isStackable() && player.getSpellSlot().contains(spell);
boolean removed = !spell.isStackable() && player.getSpellSlot().removeWhere(spell.type());
player.subtractEnergyCost(removed ? 2 : 4); player.subtractEnergyCost(removed ? 2 : 4);
if (!removed) { if (!has) {
Spell s = spell.apply(player, CastingMethod.DIRECT); Spell s = spell.apply(player, CastingMethod.DIRECT);
if (s == null) { if (s == null) {
player.spawnParticles(ParticleTypes.LARGE_SMOKE, 6); player.spawnParticles(ParticleTypes.LARGE_SMOKE, 6);

View file

@ -7,7 +7,6 @@ import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
@ -93,11 +92,7 @@ public class UnicornDispellAbility implements Ability<Pos> {
public boolean apply(Pony player, Pos data) { public boolean apply(Pony player, Pos data) {
player.setAnimation(Animation.WOLOLO, Animation.Recipient.ANYONE); player.setAnimation(Animation.WOLOLO, Animation.Recipient.ANYONE);
Caster.stream(VecHelper.findInRange(player.asEntity(), player.asWorld(), data.vec(), 3, EquinePredicates.IS_PLACED_SPELL).stream()).forEach(target -> { Caster.stream(VecHelper.findInRange(player.asEntity(), player.asWorld(), data.vec(), 3, EquinePredicates.IS_PLACED_SPELL).stream()).forEach(target -> {
target.getSpellSlot().forEach(spell -> { target.getSpellSlot().clear(false);
spell.setDead();
spell.tickDying(target);
return Operation.ofBoolean(!spell.isDead());
}, true);
}); });
return true; return true;
} }

View file

@ -24,7 +24,7 @@ import net.minecraft.util.TypedActionResult;
public class UnicornProjectileAbility extends AbstractSpellCastingAbility { public class UnicornProjectileAbility extends AbstractSpellCastingAbility {
@Override @Override
public int getWarmupTime(Pony player) { public int getWarmupTime(Pony player) {
return 8; return 1;
} }
@Override @Override

View file

@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.*;
import com.minelittlepony.unicopia.ability.Ability; import com.minelittlepony.unicopia.ability.Ability;
import com.minelittlepony.unicopia.ability.magic.spell.effect.AreaProtectionSpell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.*; import com.minelittlepony.unicopia.entity.*;
import com.minelittlepony.unicopia.entity.damage.UDamageSources; import com.minelittlepony.unicopia.entity.damage.UDamageSources;
@ -40,7 +41,7 @@ public interface Caster<E extends Entity> extends
Physics getPhysics(); Physics getPhysics();
SpellContainer getSpellSlot(); SpellSlots getSpellSlot();
/** /**
* Removes the desired amount of mana or health from this caster in exchange for a spell's benefits. * Removes the desired amount of mana or health from this caster in exchange for a spell's benefits.
@ -111,7 +112,19 @@ public interface Caster<E extends Entity> extends
} }
default boolean canCastAt(Vec3d pos) { default boolean canCastAt(Vec3d pos) {
return !Ether.get(asWorld()).anyMatch(SpellType.ARCANE_PROTECTION, (spell, caster) -> spell.blocksMagicFor(caster, this, pos)); return !Ether.get(asWorld()).anyMatch(SpellType.ARCANE_PROTECTION, entry -> {
var target = entry.entity.getTarget().orElse(null);
if (target != null && target.pos().distanceTo(pos) <= entry.getRadius()) {
Caster<?> caster = entry.getCaster();
if (caster != null) {
AreaProtectionSpell spell = entry.getSpell();
if (spell != null) {
return spell.blocksMagicFor(caster, this, pos);
}
}
}
return false;
});
} }
default boolean canUse(Ability<?> ability) { default boolean canUse(Ability<?> ability) {

View file

@ -1,33 +1,32 @@
package com.minelittlepony.unicopia.ability.magic; package com.minelittlepony.unicopia.ability.magic;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier; import java.util.function.IntSupplier;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.math.MathHelper;
/** /**
* Object with levelling capabilities. * Object with levelling capabilities.
*/ */
public interface Levelled { public interface Levelled {
LevelStore EMPTY = fixed(0); LevelStore ZERO = of(0, 1);
static LevelStore fixed(int level) { static LevelStore of(IntSupplier getter, IntConsumer setter, IntSupplier max) {
return of(() -> level);
}
static LevelStore of(IntSupplier supplier) {
return new LevelStore() { return new LevelStore() {
@Override @Override
public int get() { public int get() {
return supplier.getAsInt(); return getter.getAsInt();
} }
@Override @Override
public void set(int level) { public void set(int level) {
setter.accept(level);
} }
@Override @Override
public int getMax() { public int getMax() {
return get(); return max.getAsInt();
} }
}; };
} }
@ -37,7 +36,9 @@ public interface Levelled {
} }
static LevelStore fromNbt(NbtCompound compound) { static LevelStore fromNbt(NbtCompound compound) {
return of(compound.getInt("value"), compound.getInt("max")); int max = Math.max(1, compound.getInt("max"));
int value = MathHelper.clamp(compound.getInt("value"), 0, max);
return of(value, max);
} }
static LevelStore of(int level, int max) { static LevelStore of(int level, int max) {
@ -70,6 +71,9 @@ public interface Levelled {
void set(int level); void set(int level);
default float getScaled(float max) { default float getScaled(float max) {
if (getMax() == 0) {
return max;
}
return ((float)get() / getMax()) * max; return ((float)get() / getMax()) * max;
} }

View file

@ -0,0 +1,191 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.SpellReference;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.network.track.MsgTrackedValues;
import com.minelittlepony.unicopia.network.track.ObjectTracker;
import com.minelittlepony.unicopia.network.track.Trackable;
import com.minelittlepony.unicopia.network.track.TrackableObject;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import com.minelittlepony.unicopia.util.serialization.PacketCodec;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
/**
* Container for multiple spells
*
* @param <T> The owning entity
*/
class MultiSpellSlot implements SpellSlots, NbtSerialisable {
private final Caster<?> owner;
private final ObjectTracker<Entry<Spell>> tracker;
public MultiSpellSlot(Caster<?> owner) {
this.owner = owner;
this.tracker = Trackable.of(owner.asEntity()).getDataTrackers().checkoutTracker(() -> new Entry<>(owner));
}
public ObjectTracker<?> getTracker() {
return tracker;
}
@Override
public boolean contains(UUID id) {
return tracker.contains(id)
|| tracker.values().stream().anyMatch(s -> s.spell.equalsOrContains(id));
}
@Override
public void put(@Nullable Spell effect) {
if (effect != null) {
tracker.add(effect.getUuid(), new Entry<>(owner, effect));
}
}
@Override
public void remove(UUID id, boolean force) {
tracker.remove(id, force);
}
@Override
public <T extends Spell> Stream<T> stream(@Nullable SpellPredicate<T> type) {
return tracker.values().stream().flatMap(s -> s.spell.findMatches(type));
}
@Override
public boolean clear(boolean force) {
return tracker.clear(force);
}
@Override
public void toNBT(NbtCompound compound) {
compound.put("spells", NbtSerialisable.writeMap(tracker.entries(), UUID::toString, entry -> entry.spell.toNBT()));
}
@Override
public void fromNBT(NbtCompound compound) {
tracker.load(NbtSerialisable.readMap(compound.getCompound("spells"), key -> {
try {
return UUID.fromString(key);
} catch (Throwable ignore) {}
return null;
}, (key, nbt) -> {
try {
Entry<Spell> entry = new Entry<>(owner);
entry.spell.fromNBT((NbtCompound)nbt);
return entry;
} catch (Throwable t) {
Unicopia.LOGGER.warn("Exception loading tracked object: {}", t.getMessage());
}
return null;
}));
}
static final class Entry<T extends Spell> implements TrackableObject<Entry<T>> {
private final Caster<?> owner;
final SpellReference<T> spell = new SpellReference<>();
private boolean hasValue;
public Entry(Caster<?> owner) {
this.owner = owner;
}
public Entry(Caster<?> owner, T spell) {
this.owner = owner;
this.spell.set(spell);
if (owner instanceof UpdateCallback callback) {
callback.onSpellAdded(spell);
}
}
@Override
public void discard(boolean immediate) {
if (immediate) {
spell.set(null, owner);
} else {
Spell s = spell.get();
if (s != null) {
s.setDead();
s.tickDying(owner);
}
}
}
@Override
public Status getStatus() {
boolean hasValue = spell.get() != null;
if (hasValue != this.hasValue) {
this.hasValue = hasValue;
return hasValue ? Status.NEW : Status.REMOVED;
}
return spell.hasDirtySpell() ? Status.UPDATED : Status.DEFAULT;
}
@Override
public void readTrackedNbt(NbtCompound nbt) {
spell.fromNBT(nbt);
}
@Override
public NbtCompound writeTrackedNbt() {
return spell.toNBT();
}
@Override
public void read(PacketByteBuf buffer) {
byte contentType = buffer.readByte();
if (contentType == 1) {
readTrackedNbt(PacketCodec.COMPRESSED_NBT.read(buffer));
} else {
T spell = this.spell.get();
if (spell != null) {
spell.getDataTracker().load(new MsgTrackedValues.TrackerEntries(buffer));
}
}
}
@Override
public Optional<PacketByteBuf> write(Status status) {
if (status != Status.DEFAULT) {
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
buffer.writeByte(1);
PacketCodec.COMPRESSED_NBT.write(buffer, spell.toNBT());
return Optional.of(buffer);
}
@Nullable T spell = this.spell.get();
if (spell == null) {
return Optional.empty();
}
return spell.getDataTracker().getDirtyPairs().map(entries -> {
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
buffer.writeByte(0);
entries.write(buffer);
return buffer;
});
}
@Override
public void copyTo(Entry<T> destination) {
destination.spell.set(spell.get());
}
}
@Override
public void copyFrom(SpellSlots other, boolean alive) {
if (alive) {
other.stream().forEach(this::put);
} else {
other.stream().filter(SpellType.PLACE_CONTROL_SPELL).forEach(this::put);
}
}
}

View file

@ -0,0 +1,73 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.UUID;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.network.track.Trackable;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.nbt.NbtCompound;
/**
* Container for a single spell
*
* @param <T> The owning entity
*/
class SingleSpellSlot implements SpellSlots, NbtSerialisable {
private final Caster<?> owner;
private final MultiSpellSlot.Entry<Spell> entry;
public SingleSpellSlot(Caster<?> owner) {
this.owner = owner;
this.entry = new MultiSpellSlot.Entry<>(owner);
Trackable.of(owner.asEntity()).getDataTrackers().getPrimaryTracker().startTracking(entry);
}
@Override
public boolean contains(UUID id) {
return entry.spell.equalsOrContains(id);
}
@Override
public void put(@Nullable Spell effect) {
entry.spell.set(effect, owner);
}
@Override
public void remove(UUID id, boolean force) {
if (contains(id)) {
entry.discard(force);
}
}
@Override
public <T extends Spell> Stream<T> stream(@Nullable SpellPredicate<T> type) {
return entry.spell.findMatches(type);
}
@Override
public boolean clear(boolean force) {
if (entry.spell.get() != null) {
entry.discard(force);
return true;
}
return false;
}
@Override
public void toNBT(NbtCompound compound) {
compound.put("effect", entry.spell.toNBT());
}
@Override
public void fromNBT(NbtCompound compound) {
entry.spell.fromNBT(compound.getCompound("effect"));
}
@Override
public void copyFrom(SpellSlots other, boolean alive) {
other.get().ifPresent(this::put);
}
}

View file

@ -1,97 +0,0 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
public interface SpellContainer {
/**
* Checks if a spell with the given uuid is present.
*/
boolean contains(UUID id);
/**
* Checks if any matching spells are active.
*/
default boolean contains(@Nullable SpellPredicate<?> type) {
return get(type, true).isPresent();
}
/**
* Gets the active effect for this caster updating it if needed.
*/
default <T extends Spell> Optional<T> get(boolean update) {
return get(null, update);
}
/**
* Gets the active effect for this caster updating it if needed.
*/
<T extends Spell> Optional<T> get(@Nullable SpellPredicate<T> type, boolean update);
/**
* Sets the active effect.
*/
void put(@Nullable Spell effect);
/**
* Cleanly removes a spell from this spell container.
*
* @param spellid ID of the spell to remove.
*/
void remove(UUID spellid);
/**
* Removes all active effects that match or contain a matching effect.
*
* @return True if the collection was changed
*/
default boolean removeIf(Predicate<Spell> test, boolean update) {
return removeWhere(spell -> spell.findMatches(test).findFirst().isPresent(), update);
}
/**
* Removes all matching top level active effects.
*
* @return True if the collection was changed
*/
boolean removeWhere(Predicate<Spell> test, boolean update);
/**
* Iterates active spells and optionally removes matching ones.
*
* @return True if any matching spells remain active
*/
boolean forEach(Function<Spell, Operation> action, boolean update);
/**
* Gets all active effects for this caster updating it if needed.
*/
Stream<Spell> stream(boolean update);
/**
* Gets all active effects for this caster that match the given type updating it if needed.
*/
<T extends Spell> Stream<T> stream(@Nullable SpellPredicate<T> type, boolean update);
/**
* Removes all effects currently active in this slot.
*/
boolean clear();
public enum Operation {
SKIP,
KEEP,
REMOVE;
public static Operation ofBoolean(boolean result) {
return result ? KEEP : REMOVE;
}
}
}

View file

@ -0,0 +1,75 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.function.Function;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
public class SpellInventory {
private final Caster<?> owner;
private final SpellSlots slots;
public SpellInventory(Caster<?> owner, SpellSlots slots) {
this.owner = owner;
this.slots = slots;
}
public SpellSlots getSlots() {
return slots;
}
public boolean tick(Situation situation) {
return tick(spell -> {
if (spell.isDying()) {
spell.tickDying(owner);
return Operation.ofBoolean(!spell.isDead());
}
return Operation.ofBoolean(spell.tick(owner, situation));
});
}
public boolean tick(Function<Spell, Operation> tickAction) {
try {
return forEach(spell -> {
try {
return tickAction.apply(spell);
} catch (Throwable t) {
Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner, t);
}
return Operation.REMOVE;
});
} catch (Exception e) {
Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner.asEntity(), e);
}
return false;
}
/**
* Iterates active spells and optionally removes matching ones.
*
* @return True if any matching spells remain active
*/
public boolean forEach(Function<Spell, Operation> test) {
return slots.reduce((initial, spell) -> {
Operation op = test.apply(spell);
if (op == Operation.REMOVE) {
slots.remove(spell.getUuid(), true);
} else {
initial |= op != Operation.SKIP;
}
return initial;
});
}
public enum Operation {
SKIP,
KEEP,
REMOVE;
public static Operation ofBoolean(boolean result) {
return result ? KEEP : REMOVE;
}
}
}

View file

@ -7,19 +7,17 @@ import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.*;
import com.minelittlepony.unicopia.ability.magic.spell.effect.MimicSpell; import com.minelittlepony.unicopia.ability.magic.spell.effect.MimicSpell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.ShieldSpell; import com.minelittlepony.unicopia.ability.magic.spell.effect.ShieldSpell;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
public interface SpellPredicate<T extends Spell> extends Predicate<Spell> { public interface SpellPredicate<T extends Spell> extends Predicate<Spell> {
SpellPredicate<?> ALL = spell -> true;
SpellPredicate<IllusionarySpell> CAN_SUPPRESS = s -> s instanceof IllusionarySpell; SpellPredicate<IllusionarySpell> CAN_SUPPRESS = s -> s instanceof IllusionarySpell;
SpellPredicate<PlaceableSpell> IS_PLACED = s -> s instanceof PlaceableSpell;
SpellPredicate<AbstractDisguiseSpell> IS_DISGUISE = s -> s instanceof AbstractDisguiseSpell; SpellPredicate<AbstractDisguiseSpell> IS_DISGUISE = s -> s instanceof AbstractDisguiseSpell;
SpellPredicate<MimicSpell> IS_MIMIC = s -> s instanceof MimicSpell; SpellPredicate<MimicSpell> IS_MIMIC = s -> s instanceof MimicSpell;
SpellPredicate<ShieldSpell> IS_SHIELD_LIKE = spell -> spell instanceof ShieldSpell; SpellPredicate<ShieldSpell> IS_SHIELD_LIKE = spell -> spell instanceof ShieldSpell;
SpellPredicate<TimedSpell> IS_TIMED = spell -> spell instanceof TimedSpell; SpellPredicate<TimedSpell> IS_TIMED = spell -> spell instanceof TimedSpell;
SpellPredicate<OrientedSpell> IS_ORIENTED = spell -> spell instanceof OrientedSpell; SpellPredicate<OrientedSpell> IS_ORIENTED = spell -> spell instanceof OrientedSpell;
SpellPredicate<?> IS_NOT_PLACED = IS_PLACED.negate();
SpellPredicate<?> IS_VISIBLE = spell -> spell != null && !spell.isHidden(); SpellPredicate<?> IS_VISIBLE = spell -> spell != null && !spell.isHidden();
SpellPredicate<?> IS_CORRUPTING = spell -> spell.getAffinity() == Affinity.BAD; SpellPredicate<?> IS_CORRUPTING = spell -> spell.getAffinity() == Affinity.BAD;

View file

@ -0,0 +1,124 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.Optional;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.util.Copyable;
import com.minelittlepony.unicopia.util.NbtSerialisable;
public interface SpellSlots extends NbtSerialisable, Copyable<SpellSlots> {
static SpellInventory ofUnbounded(Caster<?> caster) {
return new SpellInventory(caster, new MultiSpellSlot(caster));
}
static SpellInventory ofSingle(Caster<?> caster) {
return new SpellInventory(caster, new SingleSpellSlot(caster));
}
/**
* Gets all active effects for this caster that match the given type.
*/
<T extends Spell> Stream<T> stream(@Nullable SpellPredicate<T> type);
/**
* Sets the active effect.
*/
void put(@Nullable Spell effect);
/**
* Removes all effects currently active in this slot.
*/
boolean clear(boolean force);
/**
* Cleanly removes a spell from this spell container.
*
* @param spellid ID of the spell to remove.
*/
void remove(UUID spellid, boolean force);
/**
* Checks if a spell with the given uuid is present.
*/
boolean contains(UUID id);
/**
* Gets the active effect for this caster
*/
default <T extends Spell> Optional<T> get() {
return get(null);
}
/**
* Checks if any matching spells are active.
*/
default boolean contains(@Nullable SpellPredicate<?> type) {
return get(type).isPresent();
}
/**
* Gets the active effect for this caster updating it if needed.
*/
default <T extends Spell> Optional<T> get(@Nullable SpellPredicate<T> type) {
return stream(type).findFirst();
}
/**
* Removes all effects currently active in this slot.
*/
default boolean clear() {
return clear(false);
}
/**
* Cleanly removes a spell from this spell container.
*
* @param spellid ID of the spell to remove.
*/
default void remove(UUID spellid) {
remove(spellid, false);
}
/**
* Removes all active effects that match or contain a matching effect.
*
* @return True if the collection was changed
*/
default boolean removeIf(SpellPredicate<?> test) {
return removeWhere(spell -> spell.findMatches(test).findFirst().isPresent());
}
/**
* Removes all matching top level active effects.
*
* @return True if the collection was changed
*/
default boolean removeWhere(SpellPredicate<?> test) {
return reduce((initial, spell) -> {
if (test.test(spell)) {
remove(spell.getUuid());
return true;
}
return initial;
});
}
/**
* Gets all active effects for this caster updating it if needed.
*/
default Stream<Spell> stream() {
return stream(null);
}
default boolean reduce(BiFunction<Boolean, Spell, Boolean> alteration) {
return stream().reduce(false, alteration, (a, b) -> b);
}
public interface UpdateCallback {
void onSpellAdded(Spell spell);
}
}

View file

@ -1,9 +1,17 @@
package com.minelittlepony.unicopia.ability.magic.spell; package com.minelittlepony.unicopia.ability.magic.spell;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
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.SpellAttributeType;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory;
import com.minelittlepony.unicopia.ability.magic.spell.effect.*; import com.minelittlepony.unicopia.ability.magic.spell.effect.*;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
public abstract class AbstractAreaEffectSpell extends AbstractSpell { public abstract class AbstractAreaEffectSpell extends AbstractSpell {
protected static final SpellAttribute<Float> RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, power -> Math.max(0, 4 + power));
public static final TooltipFactory TOOLTIP = RANGE;
protected AbstractAreaEffectSpell(CustomisedSpellType<?> type) { protected AbstractAreaEffectSpell(CustomisedSpellType<?> type) {
super(type); super(type);
} }

View file

@ -1,64 +1,60 @@
package com.minelittlepony.unicopia.ability.magic.spell; package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.*; import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.minelittlepony.unicopia.Affinity; import com.google.common.base.MoreObjects;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
public abstract class AbstractDelegatingSpell implements Spell,
ProjectileDelegate.ConfigurationListener, ProjectileDelegate.BlockHitListener, ProjectileDelegate.EntityHitListener {
private boolean dirty;
private boolean hidden;
private boolean destroyed;
public abstract class AbstractDelegatingSpell implements Spell {
private UUID uuid = UUID.randomUUID(); private UUID uuid = UUID.randomUUID();
private final SpellType<?> type; private final CustomisedSpellType<?> type;
protected final SpellReference<Spell> delegate = new SpellReference<>();
public AbstractDelegatingSpell(CustomisedSpellType<?> type) { public AbstractDelegatingSpell(CustomisedSpellType<?> type) {
this.type = type.type(); this.type = type;
} }
public abstract Collection<Spell> getDelegates(); public AbstractDelegatingSpell(CustomisedSpellType<?> type, Spell delegate) {
this.type = type;
this.delegate.set(delegate);
}
public final Spell getDelegate() {
return delegate.get();
}
@Override
public final DataTracker getDataTracker() {
return getOrEmpty().getDataTracker();
}
private Spell getOrEmpty() {
return MoreObjects.firstNonNull(delegate.get(), EmptySpell.INSTANCE);
}
@Override @Override
public boolean equalsOrContains(UUID id) { public boolean equalsOrContains(UUID id) {
return Spell.super.equalsOrContains(id) || getDelegates().stream().anyMatch(s -> s.equalsOrContains(id)); return Spell.super.equalsOrContains(id) || delegate.equalsOrContains(id);
} }
@Override @Override
public Stream<Spell> findMatches(Predicate<Spell> predicate) { public <T extends Spell> Stream<T> findMatches(SpellPredicate<T> predicate) {
return Stream.concat(Spell.super.findMatches(predicate), getDelegates().stream().flatMap(s -> s.findMatches(predicate))); return Stream.concat(Spell.super.findMatches(predicate), delegate.findMatches(predicate));
} }
@Override @Override
public Affinity getAffinity() { public CustomisedSpellType<?> getTypeAndTraits() {
return Affinity.NEUTRAL;
}
@Override
public SpellType<?> getType() {
return type; return type;
} }
@Override
public SpellTraits getTraits() {
return getDelegates().stream().map(Spell::getTraits).reduce(SpellTraits.EMPTY, SpellTraits::union);
}
@Override @Override
public final UUID getUuid() { public final UUID getUuid() {
return uuid; return uuid;
@ -66,109 +62,80 @@ public abstract class AbstractDelegatingSpell implements Spell,
@Override @Override
public void setDead() { public void setDead() {
getDelegates().forEach(Spell::setDead); getOrEmpty().setDead();
} }
@Override @Override
public void tickDying(Caster<?> caster) { public void tickDying(Caster<?> caster) {
getOrEmpty().tickDying(caster);
} }
@Override @Override
public boolean isDead() { public boolean isDead() {
return getDelegates().isEmpty() || getDelegates().stream().allMatch(Spell::isDead); return getOrEmpty().isDead();
} }
@Override @Override
public boolean isDying() { public boolean isDying() {
return false; return getOrEmpty().isDying();
} }
@Deprecated
@Override @Override
public boolean isDirty() { public boolean isDirty() {
return dirty || getDelegates().stream().anyMatch(Spell::isDirty); return delegate.hasDirtySpell();
} }
@Deprecated
@Override @Override
public void setDirty() { public void setDirty() {
dirty = true; getOrEmpty().setDirty();
} }
@Override @Override
public boolean isHidden() { public boolean isHidden() {
return hidden || getDelegates().stream().allMatch(Spell::isHidden); return getOrEmpty().isHidden();
} }
@Override @Override
public void setHidden(boolean hidden) { public void setHidden(boolean hidden) {
this.hidden = hidden; getOrEmpty().setHidden(hidden);
} }
@Override @Override
public final void destroy(Caster<?> caster) { public final void destroy(Caster<?> caster) {
if (destroyed) { if (!caster.isClient()) {
return; Ether.get(caster.asWorld()).remove(this, caster);
} }
destroyed = true; getOrEmpty().destroy(caster);
setDead();
onDestroyed(caster);
}
protected void onDestroyed(Caster<?> caster) {
getDelegates().forEach(a -> a.destroy(caster));
} }
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
return execute(getDelegates().stream(), a -> { Spell s = getOrEmpty();
if (a.isDying()) { if (s.isDying()) {
a.tickDying(source); s.tickDying(source);
return !a.isDead(); return !s.isDead();
} }
return a.tick(source, situation); return s.tick(source, situation);
});
}
@Override
public void onImpact(MagicProjectileEntity projectile, BlockHitResult hit) {
getDelegates(BlockHitListener.PREDICATE).forEach(a -> a.onImpact(projectile, hit));
}
@Override
public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) {
getDelegates(EntityHitListener.PREDICATE).forEach(a -> a.onImpact(projectile, hit));
}
@Override
public void configureProjectile(MagicProjectileEntity projectile, Caster<?> caster) {
getDelegates(ConfigurationListener.PREDICATE).forEach(a -> a.configureProjectile(projectile, caster));
} }
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
compound.putUuid("uuid", uuid); compound.putUuid("uuid", uuid);
compound.putBoolean("hidden", hidden); compound.put("spell", delegate.toNBT());
saveDelegates(compound);
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
dirty = false;
hidden = compound.getBoolean("hidden");
if (compound.contains("uuid")) { if (compound.contains("uuid")) {
uuid = compound.getUuid("uuid"); uuid = compound.getUuid("uuid");
} }
loadDelegates(compound); delegate.fromNBT(compound.getCompound("spell"));
} }
protected abstract void loadDelegates(NbtCompound compound); @Override
public final String toString() {
protected abstract void saveDelegates(NbtCompound compound); return "Delegate{" + getTypeAndTraits() + "}[uuid=" + uuid + "][spell=" + delegate.get() + "]";
protected <T> Stream<T> getDelegates(Function<? super Spell, T> cast) {
return getDelegates().stream().map(cast).filter(Objects::nonNull);
}
protected static boolean execute(Stream<Spell> spells, Function<Spell, Boolean> action) {
return spells.reduce(false, (u, a) -> action.apply(a), (a, b) -> a || b);
} }
} }

View file

@ -28,10 +28,11 @@ public abstract class AbstractDisguiseSpell extends AbstractSpell implements Dis
@Override @Override
protected void onDestroyed(Caster<?> caster) { protected void onDestroyed(Caster<?> caster) {
super.onDestroyed(caster);
caster.asEntity().calculateDimensions(); caster.asEntity().calculateDimensions();
caster.asEntity().setInvisible(false); caster.asEntity().setInvisible(false);
if (caster instanceof Pony) { if (caster instanceof Pony pony) {
((Pony) caster).setInvisible(false); pony.setInvisible(false);
} }
disguise.remove(); disguise.remove();
} }
@ -71,7 +72,7 @@ public abstract class AbstractDisguiseSpell extends AbstractSpell implements Dis
public static Entity getAppearance(Entity e) { public static Entity getAppearance(Entity e) {
return e instanceof PlayerEntity ? Pony.of((PlayerEntity)e) return e instanceof PlayerEntity ? Pony.of((PlayerEntity)e)
.getSpellSlot() .getSpellSlot()
.get(SpellPredicate.IS_DISGUISE, true) .get(SpellPredicate.IS_DISGUISE)
.map(AbstractDisguiseSpell::getDisguise) .map(AbstractDisguiseSpell::getDisguise)
.map(EntityAppearance::getAppearance) .map(EntityAppearance::getAppearance)
.orElse(e) : e; .orElse(e) : e;

View file

@ -9,6 +9,8 @@ import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellTyp
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@ -21,6 +23,7 @@ import net.minecraft.nbt.NbtCompound;
*/ */
public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements IllusionarySpell { public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements IllusionarySpell {
private final DataTracker.Entry<Boolean> suppressed = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private int suppressionCounter; private int suppressionCounter;
public DispersableDisguiseSpell(CustomisedSpellType<?> type) { public DispersableDisguiseSpell(CustomisedSpellType<?> type) {
@ -36,8 +39,8 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I
@Override @Override
public void onSuppressed(Caster<?> otherSource, float time) { public void onSuppressed(Caster<?> otherSource, float time) {
time /= getTraits().getOrDefault(Trait.STRENGTH, 1); time /= getTraits().getOrDefault(Trait.STRENGTH, 1);
suppressionCounter = (int)(100 * time); suppressionCounter = (int)time;
setDirty(); suppressed.set(true);
} }
@Override @Override
@ -64,7 +67,9 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I
Entity appearance = getDisguise().getAppearance(); Entity appearance = getDisguise().getAppearance();
if (isSuppressed()) { if (isSuppressed()) {
suppressionCounter--; if (--suppressionCounter <= 0) {
suppressed.set(false);
}
owner.setInvisible(false); owner.setInvisible(false);
if (source instanceof Pony) { if (source instanceof Pony) {
@ -92,6 +97,9 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
suppressionCounter = compound.getInt("suppressionCounter"); suppressionCounter = compound.getInt("suppressionCounter");
if (suppressionCounter > 0) {
suppressed.set(true);
}
} }
@Override @Override

View file

@ -0,0 +1,83 @@
package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.UUID;
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.network.track.DataTracker;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.Util;
public final class EmptySpell implements Spell {
public static final EmptySpell INSTANCE = new EmptySpell();
private EmptySpell() {}
@Override
public void toNBT(NbtCompound compound) { }
@Override
public void fromNBT(NbtCompound compound) { }
@Override
public CustomisedSpellType<?> getTypeAndTraits() {
return SpellType.EMPTY_KEY.withTraits();
}
@Override
public UUID getUuid() {
return Util.NIL_UUID;
}
@Override
public void setDead() { }
@Override
public boolean isDead() {
return true;
}
@Override
public boolean isDying() {
return false;
}
@Override
public boolean isDirty() {
return false;
}
@Override
public boolean tick(Caster<?> caster, Situation situation) {
return false;
}
@Override
public void tickDying(Caster<?> caster) { }
@Override
public void setDirty() { }
@Override
public boolean isHidden() {
return true;
}
@Override
public void setHidden(boolean hidden) { }
@Override
public void destroy(Caster<?> caster) { }
@Override
public String toString() {
return "EmptySpell{}";
}
@Override
public DataTracker getDataTracker() {
return null;
}
}

View file

@ -1,11 +1,12 @@
package com.minelittlepony.unicopia.ability.magic.spell; package com.minelittlepony.unicopia.ability.magic.spell;
import com.minelittlepony.unicopia.ability.data.Rot; import com.minelittlepony.unicopia.ability.data.Rot;
import com.minelittlepony.unicopia.ability.magic.Caster;
public interface OrientedSpell extends Spell { public interface OrientedSpell extends Spell {
void setOrientation(float pitch, float yaw); void setOrientation(Caster<?> caster, float pitch, float yaw);
default void setOrientation(Rot rotation) { default void setOrientation(Caster<?> caster, Rot rotation) {
setOrientation(rotation.pitch(), rotation.yaw()); setOrientation(caster, rotation.pitch(), rotation.yaw());
} }
} }

View file

@ -1,292 +0,0 @@
package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.*;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.EntityReference.EntityValues;
import com.minelittlepony.unicopia.entity.mob.CastSpellEntity;
import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.MsgCasterLookRequest;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.nbt.*;
import net.minecraft.registry.*;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
/**
* A spell that can be attached to a specific location in the world.
* <p>
* The spell's effects are still powered by the casting player, so if the player dies or leaves the area, their
* spell loses affect until they return.
*/
public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedSpell {
/**
* Dimension the spell was originally cast in
*/
@Nullable
private RegistryKey<World> dimension;
/**
* ID of the placed counterpart of this spell.
*/
@Nullable
private UUID placedSpellId;
/**
* The cast spell entity
*/
private final EntityReference<CastSpellEntity> castEntity = new EntityReference<>();
/**
* The spell being cast
*/
private final SpellReference<Spell> spell = new SpellReference<>();
public float pitch;
public float yaw;
private int prevAge;
private int age;
private boolean dead;
private int prevDeathTicks;
private int deathTicks;
private Optional<Vec3d> position = Optional.empty();
public PlaceableSpell(CustomisedSpellType<?> type) {
super(type);
}
public PlaceableSpell setSpell(Spell spell) {
this.spell.set(spell);
return this;
}
public float getAge(float tickDelta) {
return MathHelper.lerp(tickDelta, prevAge, age);
}
public float getScale(float tickDelta) {
float add = MathHelper.clamp(getAge(tickDelta) / 25F, 0, 1);
float subtract = dead ? 1 - (MathHelper.lerp(tickDelta, prevDeathTicks, deathTicks) / 20F) : 0;
return MathHelper.clamp(add - subtract, 0, 1);
}
@Override
public boolean isDying() {
return dead && deathTicks > 0;
}
@Override
public void setDead() {
super.setDead();
dead = true;
deathTicks = 20;
}
@Override
public boolean isDead() {
return dead && deathTicks <= 0 && super.isDead();
}
@Override
public Collection<Spell> getDelegates() {
Spell spell = this.spell.get();
return spell == null ? List.of() : List.of(spell);
}
@Override
public boolean tick(Caster<?> source, Situation situation) {
if (situation == Situation.BODY) {
if (!source.isClient()) {
if (dimension == null) {
dimension = source.asWorld().getRegistryKey();
if (source instanceof Pony) {
Channel.SERVER_REQUEST_PLAYER_LOOK.sendToPlayer(new MsgCasterLookRequest(getUuid()), (ServerPlayerEntity)source.asEntity());
}
setDirty();
}
castEntity.getTarget().ifPresentOrElse(
target -> checkDetachment(source, target),
() -> spawnPlacedEntity(source)
);
}
return !isDead();
}
if (situation == Situation.GROUND_ENTITY) {
if (!source.isClient()) {
if (Ether.get(source.asWorld()).get(this, source) == null) {
setDead();
return false;
}
}
prevAge = age;
if (age < 25) {
age++;
}
return super.tick(source, Situation.GROUND);
}
return !isDead();
}
@Override
public void tickDying(Caster<?> caster) {
prevDeathTicks = deathTicks;
deathTicks--;
}
private void checkDetachment(Caster<?> source, EntityValues<?> target) {
if (getWorld(source).map(Ether::get).map(ether -> ether.get(getType(), target, placedSpellId)).isEmpty()) {
setDead();
}
}
private void spawnPlacedEntity(Caster<?> source) {
CastSpellEntity entity = UEntities.CAST_SPELL.create(source.asWorld());
Vec3d pos = getPosition().orElse(position.orElse(source.asEntity().getPos()));
entity.updatePositionAndAngles(pos.x, pos.y, pos.z, source.asEntity().getYaw(), source.asEntity().getPitch());
PlaceableSpell copy = spell.get().toPlaceable();
if (spell.get() instanceof PlacementDelegate delegate) {
delegate.onPlaced(source, copy, entity);
}
entity.getSpellSlot().put(copy);
entity.setCaster(source);
entity.getWorld().spawnEntity(entity);
placedSpellId = copy.getUuid();
Ether.get(entity.getWorld()).getOrCreate(copy, entity);
castEntity.set(entity);
setDirty();
}
@Override
public void setOrientation(float pitch, float yaw) {
this.pitch = -90 - pitch;
this.yaw = -yaw;
getDelegates(spell -> spell instanceof OrientedSpell o ? o : null)
.forEach(spell -> spell.setOrientation(pitch, yaw));
setDirty();
}
public void setPosition(Caster<?> source, Vec3d position) {
this.position = Optional.of(position);
this.dimension = source.asWorld().getRegistryKey();
castEntity.ifPresent(source.asWorld(), entity -> {
entity.updatePositionAndAngles(position.x, position.y, position.z, entity.getYaw(), entity.getPitch());
});
getDelegates(spell -> spell instanceof PlaceableSpell o ? o : null)
.forEach(spell -> spell.setPosition(source, position));
setDirty();
}
@Override
protected void onDestroyed(Caster<?> source) {
if (!source.isClient()) {
castEntity.getTarget().ifPresent(target -> {
getWorld(source).map(Ether::get)
.ifPresent(ether -> ether.remove(getType(), target.uuid()));
});
castEntity.set(null);
getSpellEntity(source).ifPresent(e -> {
castEntity.set(null);
});
if (source.asEntity() instanceof CastSpellEntity) {
Ether.get(source.asWorld()).remove(this, source);
}
}
super.onDestroyed(source);
}
public Optional<CastSpellEntity> getSpellEntity(Caster<?> source) {
return getWorld(source).map(castEntity::get);
}
public Optional<Vec3d> getPosition() {
return castEntity.getTarget().map(EntityValues::pos);
}
protected Optional<World> getWorld(Caster<?> source) {
return Optional.ofNullable(dimension)
.map(dim -> source.asWorld().getServer().getWorld(dim));
}
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
compound.putBoolean("dead", dead);
compound.putInt("deathTicks", deathTicks);
compound.putInt("age", age);
compound.putFloat("pitch", pitch);
compound.putFloat("yaw", yaw);
position.ifPresent(pos -> {
compound.put("position", NbtSerialisable.writeVector(pos));
});
if (placedSpellId != null) {
compound.putUuid("placedSpellId", placedSpellId);
}
if (dimension != null) {
compound.putString("dimension", dimension.getValue().toString());
}
compound.put("castEntity", castEntity.toNBT());
}
@Override
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
dead = compound.getBoolean("dead");
deathTicks = compound.getInt("deathTicks");
age = compound.getInt("age");
pitch = compound.getFloat("pitch");
yaw = compound.getFloat("yaw");
position = compound.contains("position") ? Optional.of(NbtSerialisable.readVector(compound.getList("position", NbtElement.FLOAT_TYPE))) : Optional.empty();
placedSpellId = compound.containsUuid("placedSpellId") ? compound.getUuid("placedSpellId") : null;
if (compound.contains("dimension", NbtElement.STRING_TYPE)) {
Identifier id = Identifier.tryParse(compound.getString("dimension"));
if (id != null) {
dimension = RegistryKey.of(RegistryKeys.WORLD, id);
}
}
if (compound.contains("castEntity")) {
castEntity.fromNBT(compound.getCompound("castEntity"));
}
}
@Override
protected void loadDelegates(NbtCompound compound) {
spell.fromNBT(compound.getCompound("spell"));
}
@Override
protected void saveDelegates(NbtCompound compound) {
compound.put("spell", spell.toNBT());
}
@Override
public PlaceableSpell toPlaceable() {
return this;
}
public interface PlacementDelegate {
void onPlaced(Caster<?> source, PlaceableSpell parent, CastSpellEntity entity);
}
}

View file

@ -0,0 +1,154 @@
package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.Optional;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.AbstractSpell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.mob.CastSpellEntity;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class PlacementControlSpell extends AbstractSpell implements OrientedSpell {
@Nullable
private UUID placedEntityId;
private Optional<RegistryKey<World>> dimension = Optional.empty();
private Optional<Vec3d> position = Optional.empty();
private Optional<Vec3d> orientation = Optional.empty();
@Nullable
private Spell delegate;
public PlacementControlSpell(CustomisedSpellType<?> type) {
super(type);
}
PlacementControlSpell(Spell delegate) {
this(SpellType.PLACE_CONTROL_SPELL.withTraits(delegate.getTypeAndTraits().traits()));
this.delegate = delegate;
}
@Nullable
public Spell getDelegate() {
return delegate;
}
public Optional<Vec3d> getPosition() {
return position;
}
public void setDimension(RegistryKey<World> dimension) {
this.dimension = Optional.of(dimension);
setDirty();
}
public void setPosition(Vec3d position) {
this.position = Optional.of(position);
setDirty();
}
@Override
public void setOrientation(Caster<?> caster, float pitch, float yaw) {
this.orientation = Optional.of(new Vec3d(pitch, yaw, 0));
if (delegate instanceof OrientedSpell o) {
o.setOrientation(caster, pitch, yaw);
}
setDirty();
if (!caster.isClient()) {
var entry = getConnection(caster);
if (entry != null) {
entry.setPitch(pitch);
entry.setYaw(yaw);
}
}
}
@Override
public boolean apply(Caster<?> caster) {
if (delegate == null) {
return false;
}
boolean result = super.apply(caster);
if (result) {
if (dimension.isEmpty()) {
setDimension(caster.asWorld().getRegistryKey());
}
if (position.isEmpty()) {
setPosition(caster.asEntity().getPos());
}
if (delegate instanceof PlacementDelegate) {
((PlacementDelegate)delegate).onPlaced(caster, this);
}
CastSpellEntity entity = new CastSpellEntity(caster.asWorld(), caster, this);
Vec3d pos = position.get();
Vec3d rot = orientation.orElse(Vec3d.ZERO);
entity.updatePositionAndAngles(pos.x, pos.y, pos.z, (float)rot.y, (float)rot.x);
entity.getWorld().spawnEntity(entity);
placedEntityId = entity.getUuid();
}
return result;
}
@Override
public boolean tick(Caster<?> source, Situation situation) {
if (!source.isClient() && getConnection(source) == null) {
setDead();
}
return !isDead();
}
@Nullable
private Ether.Entry<?> getConnection(Caster<?> source) {
return delegate == null || placedEntityId == null ? null : getWorld(source)
.map(world -> Ether.get(world).get(getDelegate().getTypeAndTraits().type(), placedEntityId, delegate.getUuid()))
.orElse(null);
}
private Optional<World> getWorld(Caster<?> source) {
return dimension.map(source.asWorld().getServer()::getWorld);
}
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
compound.put("spell", Spell.writeNbt(delegate));
position.ifPresent(pos -> compound.put("position", NbtSerialisable.writeVector(pos)));
orientation.ifPresent(o -> compound.put("orientation", NbtSerialisable.writeVector(o)));
dimension.ifPresent(d -> compound.putString("dimension", d.getValue().toString()));
if (placedEntityId != null) {
compound.putUuid("placedEntityId", placedEntityId);
}
}
@Override
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
delegate = Spell.readNbt(compound.getCompound("spell"));
placedEntityId = compound.containsUuid("placedEntityId") ? compound.getUuid("placedEntityId") : null;
position = compound.contains("position") ? Optional.of(NbtSerialisable.readVector(compound.getList("position", NbtElement.DOUBLE_TYPE))) : Optional.empty();
orientation = compound.contains("orientation") ? Optional.of(NbtSerialisable.readVector(compound.getList("orientation", NbtElement.DOUBLE_TYPE))) : Optional.empty();
if (compound.contains("dimension", NbtElement.STRING_TYPE)) {
dimension = Optional.ofNullable(Identifier.tryParse(compound.getString("dimension"))).map(id -> RegistryKey.of(RegistryKeys.WORLD, id));
}
}
public interface PlacementDelegate {
void onPlaced(Caster<?> source, PlacementControlSpell parent);
}
}

View file

@ -54,7 +54,6 @@ public class RageAbilitySpell extends AbstractSpell {
ticksExtenguishing++; ticksExtenguishing++;
source.playSound(USounds.Vanilla.ENTITY_GENERIC_EXTINGUISH_FIRE, 1); source.playSound(USounds.Vanilla.ENTITY_GENERIC_EXTINGUISH_FIRE, 1);
source.spawnParticles(ParticleTypes.CLOUD, 12); source.spawnParticles(ParticleTypes.CLOUD, 12);
setDirty();
} else { } else {
ticksExtenguishing = 0; ticksExtenguishing = 0;
} }
@ -127,9 +126,6 @@ public class RageAbilitySpell extends AbstractSpell {
age++; age++;
source.asEntity().setInvulnerable(age < 25); source.asEntity().setInvulnerable(age < 25);
setDirty();
return true; return true;
} }

View file

@ -1,17 +1,20 @@
package com.minelittlepony.unicopia.ability.magic.spell; package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.UUID; import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.spongepowered.include.com.google.common.base.Objects; import org.spongepowered.include.com.google.common.base.Objects;
import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
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.effect.SpellType;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
@ -24,14 +27,20 @@ public interface Spell extends NbtSerialisable, Affine {
Serializer<Spell> SERIALIZER = Serializer.of(Spell::readNbt, Spell::writeNbt); 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();
/** DataTracker getDataTracker();
* Gets the traits of this spell.
*/ default boolean isOf(SpellType<?> type) {
SpellTraits getTraits(); return getTypeAndTraits().type() == type;
}
@Override
default Affinity getAffinity() {
return getTypeAndTraits().type().getAffinity();
}
/** /**
* The unique id of this particular spell instance. * The unique id of this particular spell instance.
@ -48,8 +57,9 @@ public interface Spell extends NbtSerialisable, Affine {
/** /**
* Returns an optional containing the spell that matched the given predicate. * Returns an optional containing the spell that matched the given predicate.
*/ */
default Stream<Spell> findMatches(Predicate<Spell> predicate) { @SuppressWarnings("unchecked")
return predicate.test(this) ? Stream.of(this) : Stream.empty(); default <T extends Spell> Stream<T> findMatches(SpellPredicate<T> predicate) {
return predicate == null || predicate.test(this) ? Stream.of((T)this) : Stream.empty();
} }
/** /**
@ -67,14 +77,24 @@ public interface Spell extends NbtSerialisable, Affine {
/** /**
* Returns true if this effect has changes that need to be sent to the client. * Returns true if this effect has changes that need to be sent to the client.
*/ */
@Deprecated
boolean isDirty(); boolean isDirty();
/**
* Marks this effect as dirty.
*/
@Deprecated
void setDirty();
/** /**
* Applies this spell to the supplied caster. * Applies this spell to the supplied caster.
* @param caster The caster to apply the spell to * @param caster The caster to apply the spell to
*/ */
default boolean apply(Caster<?> caster) { default boolean apply(Caster<?> caster) {
caster.getSpellSlot().put(this); caster.getSpellSlot().put(this);
if (!caster.isClient()) {
Ether.get(caster.asWorld()).getOrCreate(this, caster);
}
return true; return true;
} }
@ -100,11 +120,6 @@ public interface Spell extends NbtSerialisable, Affine {
*/ */
void tickDying(Caster<?> caster); void tickDying(Caster<?> caster);
/**
* Marks this effect as dirty.
*/
void setDirty();
boolean isHidden(); boolean isHidden();
void setHidden(boolean hidden); void setHidden(boolean hidden);
@ -117,8 +132,8 @@ public interface Spell extends NbtSerialisable, Affine {
/** /**
* Converts this spell into a placeable spell. * Converts this spell into a placeable spell.
*/ */
default PlaceableSpell toPlaceable() { default PlacementControlSpell toPlaceable() {
return SpellType.PLACED_SPELL.withTraits().create().setSpell(this); return new PlacementControlSpell(this);
} }
/** /**
@ -126,21 +141,14 @@ public interface Spell extends NbtSerialisable, Affine {
* @return * @return
*/ */
default ThrowableSpell toThrowable() { default ThrowableSpell toThrowable() {
return SpellType.THROWN_SPELL.withTraits().create().setSpell(this); return new ThrowableSpell(this);
} }
@Nullable @Nullable
static <T extends Spell> T readNbt(@Nullable NbtCompound compound) { static <T extends Spell> T readNbt(@Nullable NbtCompound compound) {
try { try {
if (compound != null && compound.contains("effect_id")) { if (compound != null) {
@SuppressWarnings("unchecked") return CustomisedSpellType.<T>fromNBT(compound).create(compound);
T effect = (T)SpellType.getKey(compound).withTraits().create();
if (effect != null) {
effect.fromNBT(compound);
}
return effect;
} }
} catch (Exception e) { } catch (Exception e) {
Unicopia.LOGGER.fatal("Invalid spell nbt {}", e); Unicopia.LOGGER.fatal("Invalid spell nbt {}", e);
@ -153,9 +161,16 @@ public interface Spell extends NbtSerialisable, Affine {
return compound == null || !compound.containsUuid("uuid") ? Util.NIL_UUID : compound.getUuid("uuid"); return compound == null || !compound.containsUuid("uuid") ? Util.NIL_UUID : compound.getUuid("uuid");
} }
static NbtCompound writeNbt(Spell effect) { static NbtCompound writeNbt(@Nullable Spell effect) {
if (effect == null) {
return new NbtCompound();
}
NbtCompound compound = effect.toNBT(); NbtCompound compound = effect.toNBT();
effect.getType().toNbt(compound); effect.getTypeAndTraits().toNbt(compound);
return compound; return compound;
} }
static <T extends Spell> Spell copy(T spell) {
return readNbt(writeNbt(spell));
}
} }

View file

@ -1,9 +1,13 @@
package com.minelittlepony.unicopia.ability.magic.spell; package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
@ -11,7 +15,6 @@ import net.minecraft.nbt.NbtCompound;
public final class SpellReference<T extends Spell> implements NbtSerialisable { public final class SpellReference<T extends Spell> implements NbtSerialisable {
@Nullable @Nullable
private transient T spell; private transient T spell;
private int nbtHash;
@Nullable @Nullable
public T get() { public T get() {
@ -22,6 +25,7 @@ public final class SpellReference<T extends Spell> implements NbtSerialisable {
set(spell, null); set(spell, null);
} }
@Deprecated
public boolean hasDirtySpell() { public boolean hasDirtySpell() {
return spell != null && spell.isDirty(); return spell != null && spell.isDirty();
} }
@ -33,36 +37,36 @@ public final class SpellReference<T extends Spell> implements NbtSerialisable {
} }
T oldValue = this.spell; T oldValue = this.spell;
this.spell = spell; this.spell = spell;
nbtHash = 0;
if (owner != null && oldValue != null && (spell == null || !oldValue.getUuid().equals(spell.getUuid()))) { if (owner != null && oldValue != null && (spell == null || !oldValue.getUuid().equals(spell.getUuid()))) {
oldValue.destroy(owner); oldValue.destroy(owner);
} }
return true; return true;
} }
public boolean equalsOrContains(UUID id) {
@Nullable T spell = get();
return spell != null && spell.equalsOrContains(id);
}
@SuppressWarnings("unchecked")
public <V extends Spell> Stream<V> findMatches(SpellPredicate<V> predicate) {
@Nullable T spell = get();
return spell != null ? (predicate == null ? Stream.of((V)spell) : spell.findMatches(predicate)) : Stream.empty();
}
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
if (spell != null && !spell.isDead()) { if (spell != null && !spell.isDead()) {
spell.toNBT(compound); spell.toNBT(compound);
spell.getType().toNbt(compound); spell.getTypeAndTraits().toNbt(compound);
} }
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
fromNBT(compound, true);
}
public void fromNBT(NbtCompound compound, boolean force) {
final int hash = compound.hashCode();
if (nbtHash == hash) {
return;
}
nbtHash = hash;
if (spell == null || !Objects.equals(Spell.getUuid(compound), spell.getUuid())) { if (spell == null || !Objects.equals(Spell.getUuid(compound), spell.getUuid())) {
spell = Spell.readNbt(compound); spell = Spell.readNbt(compound);
} else if (force || !spell.isDirty()) { } else {
spell.fromNBT(compound); spell.fromNBT(compound);
} }
} }

View file

@ -1,32 +1,28 @@
package com.minelittlepony.unicopia.ability.magic.spell; package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.Collection;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.projectile.MagicBeamEntity; import com.minelittlepony.unicopia.projectile.MagicBeamEntity;
import net.minecraft.nbt.NbtCompound; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.world.World; import net.minecraft.world.World;
public final class ThrowableSpell extends AbstractDelegatingSpell { public final class ThrowableSpell extends AbstractDelegatingSpell implements
ProjectileDelegate.ConfigurationListener, ProjectileDelegate.BlockHitListener, ProjectileDelegate.EntityHitListener {
private final SpellReference<Spell> spell = new SpellReference<>();
public ThrowableSpell(CustomisedSpellType<?> type) { public ThrowableSpell(CustomisedSpellType<?> type) {
super(type); super(type);
} }
public ThrowableSpell setSpell(Spell spell) { public ThrowableSpell(Spell delegate) {
this.spell.set(spell); super(SpellType.THROWN_SPELL.withTraits(delegate.getTypeAndTraits().traits()), delegate);
return this;
}
@Override
public Collection<Spell> getDelegates() {
return List.of(spell.get());
} }
@Override @Override
@ -57,7 +53,7 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
return Optional.empty(); return Optional.empty();
} }
return Optional.ofNullable(spell.get().prepareForCast(caster, CastingMethod.STORED)).map(s -> { return Optional.ofNullable(delegate.get().prepareForCast(caster, CastingMethod.STORED)).map(s -> {
MagicBeamEntity projectile = new MagicBeamEntity(world, caster.asEntity(), divergance, s); MagicBeamEntity projectile = new MagicBeamEntity(world, caster.asEntity(), divergance, s);
configureProjectile(projectile, caster); configureProjectile(projectile, caster);
@ -67,16 +63,6 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
}); });
} }
@Override
protected void loadDelegates(NbtCompound compound) {
spell.fromNBT(compound.getCompound("spell"));
}
@Override
protected void saveDelegates(NbtCompound compound) {
compound.put("spell", spell.toNBT());
}
@Override @Override
public ThrowableSpell toThrowable() { public ThrowableSpell toThrowable() {
return this; return this;
@ -90,4 +76,25 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
@Override @Override
public void setHidden(boolean hidden) { public void setHidden(boolean hidden) {
} }
@Override
public void onImpact(MagicProjectileEntity projectile, BlockHitResult hit) {
if (delegate.get() instanceof BlockHitListener listener) {
listener.onImpact(projectile, hit);
}
}
@Override
public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) {
if (delegate.get() instanceof EntityHitListener listener) {
listener.onImpact(projectile, hit);
}
}
@Override
public void configureProjectile(MagicProjectileEntity projectile, Caster<?> caster) {
if (delegate.get() instanceof ConfigurationListener listener) {
listener.configureProjectile(projectile, caster);
}
}
} }

View file

@ -1,5 +1,9 @@
package com.minelittlepony.unicopia.ability.magic.spell; package com.minelittlepony.unicopia.ability.magic.spell;
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.SpellAttributeType;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.NbtSerialisable;
import com.minelittlepony.unicopia.util.Tickable; import com.minelittlepony.unicopia.util.Tickable;
@ -10,6 +14,9 @@ import net.minecraft.util.math.MathHelper;
* A magic effect with a set duration capable of reporting how long it has until it runs out. * A magic effect with a set duration capable of reporting how long it has until it runs out.
*/ */
public interface TimedSpell extends Spell { public interface TimedSpell extends Spell {
int BASE_DURATION = 120 * 20;
SpellAttribute<Integer> TIME = SpellAttribute.create(SpellAttributeType.SOAPINESS, AttributeFormat.TIME, AttributeFormat.PERCENTAGE, Trait.FOCUS, focus -> BASE_DURATION + (int)(MathHelper.clamp(focus, 0, 160) * 19) * 20);
Timer getTimer(); Timer getTimer();
class Timer implements Tickable, NbtSerialisable { class Timer implements Tickable, NbtSerialisable {

View file

@ -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;
}
}

View file

@ -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);
}
public static MutableText formatAttributeLine(Text attributeName) {
return Text.literal(" ").append(attributeName).formatted(Formatting.LIGHT_PURPLE);
}
}

View file

@ -0,0 +1,6 @@
package com.minelittlepony.unicopia.ability.magic.spell.attribute;
public enum CastOn {
LOCATION,
SELF
}

View file

@ -0,0 +1,8 @@
package com.minelittlepony.unicopia.ability.magic.spell.attribute;
public enum Permits {
ITEMS,
PASSIVE,
HOSTILE,
PLAYER
}

View file

@ -0,0 +1,106 @@
package com.minelittlepony.unicopia.ability.magic.spell.attribute;
import java.util.List;
import java.util.Locale;
import java.util.function.BiFunction;
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.Util;
public record SpellAttribute<T> (
Trait trait,
BiFunction<SpellTraits, Float, T> valueGetter,
TooltipFactory tooltipFactory
) implements TooltipFactory {
@Override
public void appendTooltip(CustomisedSpellType<?> type, List<Text> tooltip) {
tooltipFactory.appendTooltip(type, tooltip);
}
public T get(SpellTraits traits) {
return valueGetter.apply(traits, traits.get(trait));
}
public static <T extends Number> SpellAttribute<T> create(SpellAttributeType id, AttributeFormat format, Trait trait, BiFunction<SpellTraits, Float, @NotNull T> valueGetter) {
return create(id, format, format, trait, valueGetter, false);
}
public static <T extends Number> SpellAttribute<T> create(SpellAttributeType id, AttributeFormat format, Trait trait, Float2ObjectFunction<@NotNull T> valueGetter) {
return create(id, format, format, trait, valueGetter, false);
}
public static <T extends Number> SpellAttribute<T> create(SpellAttributeType id, AttributeFormat baseFormat, AttributeFormat relativeFormat, Trait trait, Float2ObjectFunction<@NotNull T> valueGetter) {
return create(id, baseFormat, relativeFormat, trait, valueGetter, false);
}
public static <T extends Number> SpellAttribute<T> create(SpellAttributeType id, AttributeFormat baseFormat, AttributeFormat relativeFormat, Trait trait, BiFunction<SpellTraits, Float, @NotNull T> valueGetter) {
return create(id, baseFormat, relativeFormat, trait, valueGetter, false);
}
public static <T extends @NotNull Number> SpellAttribute<T> create(SpellAttributeType id, AttributeFormat baseFormat, AttributeFormat relativeFormat, Trait trait, Float2ObjectFunction<@NotNull T> valueGetter, boolean detrimental) {
return create(id, baseFormat, relativeFormat, trait, (traits, value) -> valueGetter.get(value.floatValue()), detrimental);
}
public static <T extends @NotNull Number> SpellAttribute<T> create(SpellAttributeType id, AttributeFormat baseFormat, AttributeFormat relativeFormat, Trait trait, BiFunction<SpellTraits, Float, @NotNull T> valueGetter, boolean detrimental) {
return new SpellAttribute<>(trait, valueGetter, (CustomisedSpellType<?> type, List<Text> tooltip) -> {
float traitAmount = type.traits().get(trait);
float traitDifference = type.relativeTraits().get(trait);
float value = valueGetter.apply(type.traits(), traitAmount).floatValue();
var b = baseFormat.getBase(id.name(), value, "equals", Formatting.LIGHT_PURPLE);
if (traitDifference != 0) {
tooltip.add(b.append(relativeFormat.getRelative(Text.empty(), valueGetter.apply(type.traits(), traitAmount - traitDifference).floatValue(), value, detrimental)));
tooltip.add(AttributeFormat.formatTraitDifference(trait, traitDifference));
} else {
tooltip.add(b);
}
});
}
public static SpellAttribute<Boolean> createConditional(SpellAttributeType id, Trait trait, Float2ObjectFunction<Boolean> valueGetter) {
return createConditional(id, trait, (traits, value) -> valueGetter.get(value.floatValue()));
}
public static SpellAttribute<Boolean> createConditional(SpellAttributeType id, Trait trait, BiFunction<SpellTraits, Float, @NotNull Boolean> valueGetter) {
return new SpellAttribute<>(trait, valueGetter, (CustomisedSpellType<?> type, List<Text> tooltip) -> {
float difference = type.relativeTraits().get(trait);
Text value = AttributeFormat.formatAttributeLine(id.name());
if (!valueGetter.apply(type.traits(), 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 <T extends Enum<T>> SpellAttribute<T> createEnumerated(SpellAttributeType id, Trait trait, Float2ObjectFunction<T> valueGetter) {
return createEnumerated(id, trait, (traits, value) -> valueGetter.get(value.floatValue()));
}
public static <T extends Enum<T>> SpellAttribute<T> createEnumerated(SpellAttributeType id, Trait trait, BiFunction<SpellTraits, Float, @NotNull T> valueGetter) {
Function<T, Text> cache = Util.memoize(t -> Text.translatable(Util.createTranslationKey("spell_attribute", id.id().withPath(p -> p + "." + t.name().toLowerCase(Locale.ROOT)))));
return new SpellAttribute<>(trait, valueGetter, (CustomisedSpellType<?> type, List<Text> tooltip) -> {
T t = valueGetter.apply(type.traits(), 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));
}
});
}
}

View file

@ -0,0 +1,59 @@
package com.minelittlepony.unicopia.ability.magic.spell.attribute;
import java.util.ArrayList;
import java.util.List;
import com.minelittlepony.unicopia.Unicopia;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.Util;
public record SpellAttributeType(Identifier id, Text name) {
public static final List<SpellAttributeType> REGISTRY = new ArrayList<>();
@Deprecated
public static final SpellAttributeType CAST_ON_LOCATION = register("cast_on.location");
public static final SpellAttributeType FOLLOWS_TARGET = register("follows_target");
public static final SpellAttributeType PERMIT_ITEMS = register("permit_items");
public static final SpellAttributeType PERMIT_PASSIVE = register("permit_passive");
public static final SpellAttributeType PERMIT_HOSTILE = register("permit_hostile");
public static final SpellAttributeType PERMIT_PLAYER = register("permit_player");
public static final SpellAttributeType FOCUSED_ENTITY = register("focused_entity");
public static final SpellAttributeType RANGE = register("range");
public static final SpellAttributeType DURATION = register("duration");
public static final SpellAttributeType STRENGTH = register("strength");
public static final SpellAttributeType VELOCITY = register("velocity");
public static final SpellAttributeType VERTICAL_VELOCITY = register("vertical_velocity");
public static final SpellAttributeType HANG_TIME = register("hang_time");
public static final SpellAttributeType PUSHING_POWER = register("pushing_power");
public static final SpellAttributeType CAUSES_LEVITATION = register("causes_levitation");
public static final SpellAttributeType AFFECTS = register("affects");
public static final SpellAttributeType DAMAGE_TO_TARGET = register("damage_to_target");
public static final SpellAttributeType SIMULTANIOUS_TARGETS = register("simultanious_targets");
public static final SpellAttributeType COST_PER_INDIVIDUAL = register("cost_per_individual");
public static final SpellAttributeType EXPLOSION_STRENGTH = register("explosion_strength");
public static final SpellAttributeType PROJECTILE_COUNT = register("projectile_count");
public static final SpellAttributeType ORB_COUNT = register("orb_count");
public static final SpellAttributeType WAVE_SIZE = register("wave_size");
public static final SpellAttributeType FOLLOW_RANGE = register("follow_range");
public static final SpellAttributeType LIGHT_TARGET = register("light_target");
public static final SpellAttributeType STICK_TO_TARGET = register("stick_to_target");
public static final SpellAttributeType SOAPINESS = register("soapiness");
public static final SpellAttributeType CAST_ON = register("cast_on");
public static final SpellAttributeType TARGET_PREFERENCE = register("target_preference");
public static final SpellAttributeType CASTER_PREFERENCE = register("caster_preference");
public static final SpellAttributeType NEGATES_FALL_DAMAGE = register("negates_fall_damage");
public SpellAttributeType(Identifier id) {
this(id, Text.translatable(Util.createTranslationKey("spell_attribute", id)));
}
public static SpellAttributeType register(String name) {
SpellAttributeType type = new SpellAttributeType(Unicopia.id(name));
REGISTRY.add(type);
return type;
}
}

View file

@ -0,0 +1,36 @@
package com.minelittlepony.unicopia.ability.magic.spell.attribute;
import java.util.List;
import java.util.function.Predicate;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import net.minecraft.text.Text;
public interface TooltipFactory {
TooltipFactory EMPTY = (type, tooltip) -> {};
void appendTooltip(CustomisedSpellType<?> type, List<Text> tooltip);
static TooltipFactory of(TooltipFactory...lines) {
return (type, tooltip) -> {
for (var line : lines) {
line.appendTooltip(type, tooltip);
}
};
}
static TooltipFactory of(Text line) {
return (type, tooltip) -> tooltip.add(line);
}
default TooltipFactory conditionally(Predicate<SpellTraits> condition) {
TooltipFactory self = this;
return (type, tooltip) -> {
if (condition.test(type.traits())) {
self.appendTooltip(type, tooltip);
}
};
}
}

View file

@ -2,69 +2,77 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.UUID; import java.util.UUID;
import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.server.world.Ether;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
public abstract class AbstractSpell implements Spell { public abstract class AbstractSpell implements Spell {
private boolean dead;
private boolean dying;
private boolean dirty;
private boolean hidden;
private boolean destroyed;
private CustomisedSpellType<?> type;
private UUID uuid = UUID.randomUUID(); private UUID uuid = UUID.randomUUID();
private final CustomisedSpellType<?> type;
protected final DataTracker dataTracker = new DataTracker(0);
private final DataTracker.Entry<Boolean> dead = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private final DataTracker.Entry<Boolean> dying = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private boolean dirty;
private final DataTracker.Entry<Boolean> hidden = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private boolean destroyed;
protected AbstractSpell(CustomisedSpellType<?> type) { protected AbstractSpell(CustomisedSpellType<?> type) {
this.type = type; this.type = type;
} }
@Override
public final DataTracker getDataTracker() {
return dataTracker;
}
@Override @Override
public final UUID getUuid() { public final UUID getUuid() {
return uuid; return uuid;
} }
@Override protected final SpellType<?> getType() {
public final SpellType<?> getType() {
return type.type(); return type.type();
} }
@Override
public final CustomisedSpellType<?> getTypeAndTraits() { public final CustomisedSpellType<?> getTypeAndTraits() {
return type; return type;
} }
@Override protected final SpellTraits getTraits() {
public final SpellTraits getTraits() {
return type.traits(); return type.traits();
} }
@Override @Override
public final void setDead() { public final void setDead() {
dying = true; dying.set(true);
setDirty();
} }
@Override @Override
public final boolean isDead() { public final boolean isDead() {
return dead; return dead.get();
} }
@Override @Override
public final boolean isDying() { public final boolean isDying() {
return dying; return dying.get();
} }
@Deprecated
@Override @Override
public final boolean isDirty() { public final boolean isDirty() {
return dirty; return dirty;
} }
@Deprecated
@Override @Override
public final void setDirty() { public final void setDirty() {
dirty = true; dirty = true;
@ -72,25 +80,17 @@ public abstract class AbstractSpell implements Spell {
@Override @Override
public final boolean isHidden() { public final boolean isHidden() {
return hidden; return hidden.get();
} }
@Override @Override
public final void setHidden(boolean hidden) { public final void setHidden(boolean hidden) {
this.hidden = hidden; this.hidden.set(hidden);
}
@Override
public Affinity getAffinity() {
return getType().getAffinity();
}
protected void onDestroyed(Caster<?> caster) {
} }
@Override @Override
public void tickDying(Caster<?> caster) { public void tickDying(Caster<?> caster) {
dead = true; dead.set(true);
} }
@Override @Override
@ -103,11 +103,17 @@ public abstract class AbstractSpell implements Spell {
onDestroyed(caster); onDestroyed(caster);
} }
protected void onDestroyed(Caster<?> caster) {
if (!caster.isClient()) {
Ether.get(caster.asWorld()).remove(this, caster);
}
}
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
compound.putBoolean("dying", dying); compound.putBoolean("dying", dying.get());
compound.putBoolean("dead", dead); compound.putBoolean("dead", dead.get());
compound.putBoolean("hidden", hidden); compound.putBoolean("hidden", hidden.get());
compound.putUuid("uuid", uuid); compound.putUuid("uuid", uuid);
compound.put("traits", getTraits().toNbt()); compound.put("traits", getTraits().toNbt());
} }
@ -118,12 +124,9 @@ public abstract class AbstractSpell implements Spell {
if (compound.containsUuid("uuid")) { if (compound.containsUuid("uuid")) {
uuid = compound.getUuid("uuid"); uuid = compound.getUuid("uuid");
} }
dying = compound.getBoolean("dying"); dying.set(compound.getBoolean("dying"));
dead = compound.getBoolean("dead"); dead.set(compound.getBoolean("dead"));
hidden = compound.getBoolean("hidden"); hidden.set(compound.getBoolean("hidden"));
if (compound.contains("traits")) {
type = type.type().withTraits(SpellTraits.fromNbt(compound.getCompound("traits")).orElse(SpellTraits.EMPTY));
}
} }
@Override @Override

View file

@ -2,7 +2,13 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell; 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.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.CastOn;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType;
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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.entity.mob.UEntities;
@ -22,10 +28,19 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
.with(Trait.STRENGTH, 30) .with(Trait.STRENGTH, 30)
.build(); .build();
private static final SpellAttribute<CastOn> CAST_ON = SpellAttribute.createEnumerated(SpellAttributeType.CAST_ON, Trait.FOCUS, focus -> focus > 0 ? CastOn.SELF : CastOn.LOCATION);
static final TooltipFactory TOOLTIP = TooltipFactory.of(CAST_ON, RANGE);
protected AreaProtectionSpell(CustomisedSpellType<?> type) { protected AreaProtectionSpell(CustomisedSpellType<?> type) {
super(type); super(type);
} }
@Override
public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
return method == CastingMethod.STAFF || CAST_ON.get(getTraits()) == CastOn.LOCATION ? toPlaceable() : this;
}
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
@ -33,7 +48,7 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
return false; return false;
} }
float radius = (float)getDrawDropOffRange(source); float radius = (float)getRange(source);
if (source.isClient()) { if (source.isClient()) {
Vec3d origin = source.getOriginVector(); Vec3d origin = source.getOriginVector();
@ -44,7 +59,7 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
} }
}); });
} else { } else {
Ether.get(source.asWorld()).getOrCreate(this, source); Ether.get(source.asWorld()).getOrCreate(this, source).setRadius(radius);
} }
source.findAllSpellsInRange(radius, e -> isValidTarget(source, e)).filter(caster -> !caster.hasCommonOwner(source)).forEach(caster -> { source.findAllSpellsInRange(radius, e -> isValidTarget(source, e)).filter(caster -> !caster.hasCommonOwner(source)).forEach(caster -> {
@ -54,17 +69,9 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
return !isDead(); return !isDead();
} }
@Override private double getRange(Caster<?> source) {
protected void onDestroyed(Caster<?> caster) {
Ether.get(caster.asWorld()).remove(this, caster);
}
/**
* Calculates the maximum radius of the shield. aka The area of effect.
*/
public double getDrawDropOffRange(Caster<?> source) {
float multiplier = source instanceof Pony pony && pony.asEntity().isSneaking() ? 1 : 2; float multiplier = source instanceof Pony pony && pony.asEntity().isSneaking() ? 1 : 2;
float min = 4 + getTraits().get(Trait.POWER); float min = RANGE.get(getTraits());
double range = (min + (source.getLevel().getScaled(4) * 2)) / multiplier; double range = (min + (source.getLevel().getScaled(4) * 2)) / multiplier;
if (source instanceof Pony && range > 2) { if (source instanceof Pony && range > 2) {
range = Math.sqrt(range); range = Math.sqrt(range);
@ -74,7 +81,7 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
public boolean blocksMagicFor(Caster<?> source, Caster<?> other, Vec3d position) { public boolean blocksMagicFor(Caster<?> source, Caster<?> other, Vec3d position) {
return !FriendshipBraceletItem.isComrade(other, other.asEntity()) return !FriendshipBraceletItem.isComrade(other, other.asEntity())
&& source.getOriginVector().distanceTo(position) <= getDrawDropOffRange(source); && source.getOriginVector().distanceTo(position) <= getRange(source);
} }
protected boolean isValidTarget(Caster<?> source, Entity entity) { protected boolean isValidTarget(Caster<?> source, Entity entity) {

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.effect.EffectUtils;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.enchantment.UEnchantments; import com.minelittlepony.unicopia.item.enchantment.UEnchantments;
@ -61,12 +62,10 @@ public interface AttractionUtils {
return Pony.of(entity).map(pony -> { return Pony.of(entity).map(pony -> {
double force = 0.75; double force = 0.75;
if (pony.getCompositeRace().canUseEarth()) { if (EffectUtils.hasExtraDefenses(pony.asEntity())) {
force /= 12;
} else if (pony.getCompositeRace().canUseEarth()) {
force /= 2; force /= 2;
if (pony.asEntity().isSneaking()) {
force /= 6;
}
} else if (pony.getCompositeRace().canFly()) { } else if (pony.getCompositeRace().canFly()) {
force *= 2; force *= 2;
} }

View file

@ -2,6 +2,9 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.*;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.Living;
@ -13,7 +16,6 @@ import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
import com.minelittlepony.unicopia.util.shape.Sphere; import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.ItemEntity;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.particle.ParticleTypes; import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.hit.EntityHitResult;
@ -21,6 +23,10 @@ import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSpell, ProjectileDelegate.EntityHitListener { public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSpell, ProjectileDelegate.EntityHitListener {
static final SpellAttribute<Boolean> TARGET_FOCUSED_ENTITY = SpellAttribute.createConditional(SpellAttributeType.FOCUSED_ENTITY, Trait.ORDER, order -> order >= 20);
static final SpellAttribute<Boolean> STICK_TO_TARGET = SpellAttribute.createConditional(SpellAttributeType.STICK_TO_TARGET, Trait.CHAOS, chaos -> chaos > 0);
static final TooltipFactory TARGET = (type, tooltip) -> (TARGET_FOCUSED_ENTITY.get(type.traits()) ? TARGET_FOCUSED_ENTITY : ShieldSpell.TARGET).appendTooltip(type, tooltip);
static final TooltipFactory TOOLTIP = TooltipFactory.of(TIME, RANGE, TARGET, STICK_TO_TARGET, CAST_ON);
private final EntityReference<Entity> target = new EntityReference<>(); private final EntityReference<Entity> target = new EntityReference<>();
@ -28,7 +34,8 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
protected AttractiveSpell(CustomisedSpellType<?> type) { protected AttractiveSpell(CustomisedSpellType<?> type) {
super(type); super(type);
timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); timer = new Timer(TIME.get(getTraits()));
dataTracker.startTracking(target);
} }
@Override @Override
@ -38,16 +45,12 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
@Override @Override
public boolean tick(Caster<?> caster, Situation situation) { public boolean tick(Caster<?> caster, Situation situation) {
if (getType() != SpellType.DARK_VORTEX) {
timer.tick(); timer.tick();
if (timer.getTicksRemaining() <= 0) { if (timer.getTicksRemaining() <= 0) {
return false; return false;
} }
setDirty();
}
target.getOrEmpty(caster.asWorld()) target.getOrEmpty(caster.asWorld())
.filter(entity -> entity.distanceTo(caster.asEntity()) > getDrawDropOffRange(caster)) .filter(entity -> entity.distanceTo(caster.asEntity()) > getDrawDropOffRange(caster))
.ifPresent(entity -> { .ifPresent(entity -> {
@ -73,17 +76,9 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
}); });
} }
@Override
public double getDrawDropOffRange(Caster<?> caster) {
return 10 + (caster.getLevel().getScaled(8) * 2);
}
@Override @Override
protected boolean isValidTarget(Caster<?> source, Entity entity) { protected boolean isValidTarget(Caster<?> source, Entity entity) {
if (target.referenceEquals(entity)) { return target.referenceEquals(entity) || super.isValidTarget(source, entity);
return true;
}
return getTraits().get(Trait.KNOWLEDGE) > 10 ? entity instanceof ItemEntity : super.isValidTarget(source, entity);
} }
@Override @Override
@ -133,7 +128,7 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
@Override @Override
public boolean setTarget(Entity target) { public boolean setTarget(Entity target) {
if (getTraits().get(Trait.ORDER) >= 20) { if (TARGET_FOCUSED_ENTITY.get(getTraits())) {
this.target.set(target); this.target.set(target);
target.setGlowing(true); target.setGlowing(true);
return true; return true;
@ -149,7 +144,7 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
@Override @Override
public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) { public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) {
if (!isDead() && getTraits().get(Trait.CHAOS) > 0) { if (!isDead() && STICK_TO_TARGET.get(getTraits())) {
setDead(); setDead();
Caster.of(hit.getEntity()).ifPresent(caster -> getTypeAndTraits().apply(caster, CastingMethod.INDIRECT)); Caster.of(hit.getEntity()).ifPresent(caster -> getTypeAndTraits().apply(caster, CastingMethod.INDIRECT));
} }

View file

@ -40,8 +40,6 @@ public class AwkwardSpell extends AbstractSpell implements TimedSpell {
if (timer.getTicksRemaining() <= 0) { if (timer.getTicksRemaining() <= 0) {
return false; return false;
} }
setDirty();
} }
if (source.isClient()) { if (source.isClient()) {

View file

@ -6,18 +6,24 @@ import java.util.UUID;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.*;
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.SpellAttributeType;
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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.*; import com.minelittlepony.unicopia.entity.*;
import com.minelittlepony.unicopia.entity.AttributeContainer;
import com.minelittlepony.unicopia.entity.mob.UEntityAttributes; import com.minelittlepony.unicopia.entity.mob.UEntityAttributes;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
import com.minelittlepony.unicopia.util.shape.Sphere; import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.attribute.*; import net.minecraft.entity.attribute.*;
import net.minecraft.entity.attribute.EntityAttributeModifier.Operation; import net.minecraft.entity.attribute.EntityAttributeModifier.Operation;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
@ -46,17 +52,21 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
.with(Trait.POWER, 1) .with(Trait.POWER, 1)
.build(); .build();
private static final SpellAttribute<Integer> SOAPINESS = SpellAttribute.create(SpellAttributeType.SOAPINESS, AttributeFormat.REGULAR, Trait.POWER, power -> (int)(power * 2));
static final TooltipFactory TOOLTIP = TooltipFactory.of(TimedSpell.TIME, SOAPINESS);
private final Timer timer; private final Timer timer;
private int struggles;
private float prevRadius; private float prevRadius;
private float radius; private DataTracker.Entry<Float> radius;
private DataTracker.Entry<Integer> struggles;
protected BubbleSpell(CustomisedSpellType<?> type) { protected BubbleSpell(CustomisedSpellType<?> type) {
super(type); super(type);
timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); timer = new Timer(TIME.get(getTraits()));
struggles = (int)(getTraits().get(Trait.POWER) * 2); radius = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
struggles = dataTracker.startTracking(TrackableDataType.INT, SOAPINESS.get(getTraits()));
} }
@Override @Override
@ -65,27 +75,22 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
} }
public float getRadius(float tickDelta) { public float getRadius(float tickDelta) {
return MathHelper.lerp(tickDelta, prevRadius, radius); return MathHelper.lerp(tickDelta, prevRadius, radius.get());
} }
@Override @Override
public boolean apply(Caster<?> source) { public boolean apply(Caster<?> source) {
if (getType().isOn(source)) { if (source.getSpellSlot().removeWhere(getType())) {
source.getSpellSlot().removeWhere(getType(), true);
return false; return false;
} }
Entity entity = source.asEntity(); Entity entity = source.asEntity();
if (entity instanceof LivingEntity l) { if (source instanceof AttributeContainer l) {
MODIFIERS.forEach((attribute, modifier) -> { l.applyAttributeModifiers(MODIFIERS, false, true);
if (l.getAttributes().hasAttribute(attribute)) {
l.getAttributeInstance(attribute).addPersistentModifier(modifier);
} }
}); radius.set(Math.max(entity.getHeight(), entity.getWidth()) * 1.2F);
}
radius = Math.max(entity.getHeight(), entity.getWidth()) * 1.2F;
source.playSound(USounds.ENTITY_PLAYER_UNICORN_TELEPORT, 1); source.playSound(USounds.ENTITY_PLAYER_UNICORN_TELEPORT, 1);
entity.addVelocity(0, 0.2F * source.getPhysics().getGravitySignum(), 0); entity.addVelocity(0, 0.2F * source.getPhysics().getGravitySignum(), 0);
Living.updateVelocity(entity); Living.updateVelocity(entity);
@ -105,7 +110,7 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
boolean done = timer.getTicksRemaining() <= 0; boolean done = timer.getTicksRemaining() <= 0;
source.spawnParticles(source.getOriginVector().add(0, 1, 0), new Sphere(true, radius * (done ? 0.25F : 0.5F)), done ? 13 : 1, pos -> { source.spawnParticles(source.getOriginVector().add(0, 1, 0), new Sphere(true, radius.get() * (done ? 0.25F : 0.5F)), done ? 13 : 1, pos -> {
source.addParticle(done ? ParticleTypes.BUBBLE_POP : UParticles.BUBBLE, pos, Vec3d.ZERO); source.addParticle(done ? ParticleTypes.BUBBLE_POP : UParticles.BUBBLE, pos, Vec3d.ZERO);
}); });
@ -113,8 +118,6 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
return false; return false;
} }
setDirty();
source.asEntity().addVelocity( source.asEntity().addVelocity(
MathHelper.sin(source.asEntity().age / 6F) / 50F, MathHelper.sin(source.asEntity().age / 6F) / 50F,
MathHelper.sin(source.asEntity().age / 6F) / 50F, MathHelper.sin(source.asEntity().age / 6F) / 50F,
@ -123,13 +126,14 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
source.asEntity().fallDistance = 0; source.asEntity().fallDistance = 0;
prevRadius = radius; prevRadius = radius.get();
if (source instanceof Pony pony && pony.sneakingChanged() && pony.asEntity().isSneaking()) { if (source instanceof Pony pony && pony.sneakingChanged() && pony.asEntity().isSneaking()) {
setDirty(); radius.set(radius.get() + 0.5F);
radius += 0.5F;
source.playSound(USounds.SPELL_BUBBLE_DISTURB, 1); source.playSound(USounds.SPELL_BUBBLE_DISTURB, 1);
if (struggles-- <= 0) { int s = struggles.get() - 1;
struggles.set(s);
if (s <= 0) {
setDead(); setDead();
return false; return false;
} }
@ -140,12 +144,9 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
@Override @Override
protected void onDestroyed(Caster<?> source) { protected void onDestroyed(Caster<?> source) {
if (source.asEntity() instanceof LivingEntity l) { super.onDestroyed(source);
MODIFIERS.forEach((attribute, modifier) -> { if (source instanceof AttributeContainer l) {
if (l.getAttributes().hasAttribute(attribute)) { l.applyAttributeModifiers(MODIFIERS, false, false);
l.getAttributeInstance(attribute).removeModifier(modifier.getId());
}
});
} }
source.playSound(USounds.ENTITY_PLAYER_UNICORN_TELEPORT, 1); source.playSound(USounds.ENTITY_PLAYER_UNICORN_TELEPORT, 1);
} }
@ -161,16 +162,16 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
super.toNBT(compound); super.toNBT(compound);
compound.putInt("struggles", struggles); compound.putInt("struggles", struggles.get());
compound.putFloat("radius", radius); compound.putFloat("radius", radius.get());
timer.toNBT(compound); timer.toNBT(compound);
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
struggles = compound.getInt("struggles"); struggles.set(compound.getInt("struggles"));
radius = compound.getFloat("radius"); radius.set(compound.getFloat("radius"));
timer.fromNBT(compound); timer.fromNBT(compound);
} }
} }

View file

@ -1,5 +1,6 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect; package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -7,6 +8,11 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.UTags; import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Situation;
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.SpellAttributeType;
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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.mixin.MixinFallingBlockEntity; import com.minelittlepony.unicopia.mixin.MixinFallingBlockEntity;
@ -17,10 +23,16 @@ import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.FallingBlockEntity; import net.minecraft.entity.FallingBlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.text.Text;
import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.World; import net.minecraft.world.World;
/** /**
@ -37,20 +49,51 @@ public class CatapultSpell extends AbstractSpell implements ProjectileDelegate.B
private static final float HORIZONTAL_VARIANCE = 0.25F; private static final float HORIZONTAL_VARIANCE = 0.25F;
private static final float MAX_STRENGTH = 120; private static final float MAX_STRENGTH = 120;
private static final SpellAttribute<Float> LAUNCH_SPEED = SpellAttribute.create(SpellAttributeType.VERTICAL_VELOCITY, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.STRENGTH, strength -> 0.1F + (MathHelper.clamp(strength, -MAX_STRENGTH, MAX_STRENGTH) - 40) / 16F);
private static final SpellAttribute<Float> HANG_TIME = SpellAttribute.create(SpellAttributeType.HANG_TIME, AttributeFormat.TIME, AttributeFormat.PERCENTAGE, Trait.AIR, air -> 50 + (int)MathHelper.clamp(air, 0, 10) * 20F);
private static final SpellAttribute<Float> PUSHING_POWER = SpellAttribute.create(SpellAttributeType.PUSHING_POWER, AttributeFormat.REGULAR, Trait.POWER, power -> 1 + MathHelper.clamp(power, 0, 10) / 10F);
private static final SpellAttribute<Boolean> CAUSES_LEVITATION = SpellAttribute.createConditional(SpellAttributeType.CAUSES_LEVITATION, Trait.FOCUS, focus -> focus > 50);
private static final SpellAttribute<Affects> AFFECTS = SpellAttribute.createEnumerated(SpellAttributeType.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<? extends CatapultSpell> type, List<Text> tooltip) {
TOOLTIP.appendTooltip(type, tooltip);
}
protected CatapultSpell(CustomisedSpellType<?> type) { protected CatapultSpell(CustomisedSpellType<?> type) {
super(type); super(type);
} }
@Override @Override
public void onImpact(MagicProjectileEntity projectile, BlockHitResult hit) { public void onImpact(MagicProjectileEntity projectile, BlockHitResult hit) {
if (!AFFECTS.get(getTraits()).allowsBlocks()) {
return;
}
if (!projectile.isClient() && projectile instanceof MagicBeamEntity source && source.canModifyAt(hit.getBlockPos())) { if (!projectile.isClient() && projectile instanceof MagicBeamEntity source && source.canModifyAt(hit.getBlockPos())) {
createBlockEntity(projectile.getWorld(), hit.getBlockPos(), e -> apply(source, e)); createBlockEntity(projectile.getWorld(), hit.getBlockPos(), e -> {
e.setOnGround(true);
apply(source, e);
e.setOnGround(false);
});
} }
} }
@Override @Override
public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) { public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) {
if (!projectile.isClient() && projectile instanceof MagicBeamEntity source) { if (!projectile.isClient() && projectile instanceof MagicBeamEntity source) {
Entity e = hit.getEntity();
if (!(e instanceof FallingBlockEntity) && !AFFECTS.get(getTraits()).allowsEntities()) {
return;
}
apply(source, hit.getEntity()); apply(source, hit.getEntity());
} }
} }
@ -67,16 +110,40 @@ public class CatapultSpell extends AbstractSpell implements ProjectileDelegate.B
} }
protected void apply(Caster<?> caster, Entity e) { protected void apply(Caster<?> caster, Entity e) {
Vec3d vel = caster.asEntity().getVelocity();
if (Math.abs(e.getVelocity().y) > 0.5) { float power = 1 + getTraits().get(Trait.POWER, 0, 10) / 10F;
e.setVelocity(caster.asEntity().getVelocity());
if (!e.isOnGround()) {
e.setVelocity(caster.asEntity().getVelocity().multiply(power));
} else { } else {
Random rng = caster.asWorld().random;
e.addVelocity( e.addVelocity(
((caster.asWorld().random.nextFloat() * HORIZONTAL_VARIANCE) - HORIZONTAL_VARIANCE + vel.x * 0.8F) * 0.1F, rng.nextTriangular(0, HORIZONTAL_VARIANCE) * 0.1F,
0.1F + (getTraits().get(Trait.STRENGTH, -MAX_STRENGTH, MAX_STRENGTH) - 40) / 16D, LAUNCH_SPEED.get(getTraits()),
((caster.asWorld().random.nextFloat() * HORIZONTAL_VARIANCE) - HORIZONTAL_VARIANCE + vel.z * 0.8F) * 0.1F rng.nextTriangular(0, HORIZONTAL_VARIANCE) * 0.1F
); );
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;
e.velocityModified = true;
} }
static void createBlockEntity(World world, BlockPos bpos, @Nullable Consumer<Entity> apply) { static void createBlockEntity(World world, BlockPos bpos, @Nullable Consumer<Entity> apply) {
@ -101,7 +168,5 @@ public class CatapultSpell extends AbstractSpell implements ProjectileDelegate.B
apply.accept(e); apply.accept(e);
} }
world.spawnEntity(e); world.spawnEntity(e);
e.updateVelocity(HORIZONTAL_VARIANCE, pos);
} }
} }

View file

@ -1,26 +1,45 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect; 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; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.client.TextHelper;
import com.minelittlepony.unicopia.entity.effect.EffectUtils;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.TypedActionResult; import net.minecraft.util.TypedActionResult;
public record CustomisedSpellType<T extends Spell> ( public record CustomisedSpellType<T extends Spell> (
SpellType<T> type, SpellType<T> type,
SpellTraits traits SpellTraits traits,
Supplier<SpellTraits> traitsDifferenceSupplier
) implements SpellPredicate<T> { ) implements SpellPredicate<T> {
public boolean isEmpty() { public boolean isEmpty() {
return type.isEmpty(); return type.isEmpty();
} }
public boolean isStackable() {
return type().isStackable();
}
public SpellTraits relativeTraits() {
return traitsDifferenceSupplier.get();
}
public T create() { public T create() {
try { try {
return type.getFactory().create(this); return type.getFactory().create(this);
@ -31,6 +50,14 @@ public record CustomisedSpellType<T extends Spell> (
return null; return null;
} }
@Nullable
public T create(NbtCompound compound) {
T spell = create();
if (spell != null) {
spell.fromNBT(compound);
}
return spell;
}
@Nullable @Nullable
public T apply(Caster<?> caster, CastingMethod method) { public T apply(Caster<?> caster, CastingMethod method) {
@ -50,29 +77,47 @@ public record CustomisedSpellType<T extends Spell> (
} }
@Override @Override
public boolean test(Spell spell) { public boolean test(@Nullable Spell spell) {
return type.test(spell) && spell.getTraits().equals(traits); return spell != null && spell.getTypeAndTraits().equals(this);
} }
public ItemStack getDefaultStack() { public ItemStack getDefaultStack() {
return traits.applyTo(type.getDefualtStack()); return traits.applyTo(type.getDefualtStack());
} }
public void appendTooltip(List<Text> lines) {
MutableText lore = Text.translatable(type().getTranslationKey() + ".lore").formatted(type().getAffinity().getColor());
if (!InteractionManager.getInstance().getClientSpecies().canCast()) {
lore = lore.formatted(Formatting.OBFUSCATED);
}
lines.addAll(TextHelper.wrap(lore, 180).toList());
float corruption = ((int)traits().getCorruption() * 10) + type().getAffinity().getCorruption();
List<Text> modifiers = new ArrayList<>();
type.getTooltip().appendTooltip(this, modifiers);
if (corruption != 0) {
modifiers.add(EffectUtils.formatModifierChange("affinity.unicopia.corruption", corruption, true));
}
if (!modifiers.isEmpty()) {
lines.add(Text.empty());
lines.add(Text.translatable("affinity.unicopia.when_cast").formatted(Formatting.GRAY));
lines.addAll(modifiers);
}
}
public TypedActionResult<CustomisedSpellType<?>> toAction() { public TypedActionResult<CustomisedSpellType<?>> toAction() {
return isEmpty() ? TypedActionResult.fail(this) : TypedActionResult.pass(this); return isEmpty() ? TypedActionResult.fail(this) : TypedActionResult.pass(this);
} }
public NbtCompound toNBT() { public NbtCompound toNbt(NbtCompound compound) {
NbtCompound tag = new NbtCompound(); type.toNbt(compound);
type.toNbt(tag); compound.put("traits", traits.toNbt());
tag.put("traits", traits.toNbt()); return compound;
return tag;
} }
public static CustomisedSpellType<?> fromNBT(NbtCompound compound) { public static <T extends Spell> CustomisedSpellType<T> fromNBT(NbtCompound compound) {
SpellType<?> type = SpellType.getKey(compound); SpellType<T> type = SpellType.getKey(compound);
SpellTraits traits = SpellTraits.fromNbt(compound.getCompound("traits")).orElse(type.getTraits()); return type.withTraits(SpellTraits.fromNbt(compound.getCompound("traits")).orElse(type.getTraits()));
return type.withTraits(traits);
} }
} }

View file

@ -17,6 +17,8 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; import com.minelittlepony.unicopia.entity.mob.CastSpellEntity;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.FollowingParticleEffect;
import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.ParticleUtils;
@ -56,7 +58,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
.with(Trait.DARKNESS, 100) .with(Trait.DARKNESS, 100)
.build(); .build();
private float accumulatedMass = 0; private final DataTracker.Entry<Float> accumulatedMass = this.dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private final TargetSelecter targetSelecter = new TargetSelecter(this).setFilter(this::isValidTarget).setTargetowner(true).setTargetAllies(true); private final TargetSelecter targetSelecter = new TargetSelecter(this).setFilter(this::isValidTarget).setTargetowner(true).setTargetAllies(true);
@ -70,7 +72,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
// 3. force reaches 0 at distance of drawDropOffRange // 3. force reaches 0 at distance of drawDropOffRange
private double getMass() { private double getMass() {
return 0.1F + accumulatedMass / 10F; return 0.1F + accumulatedMass.get() / 10F;
} }
public double getEventHorizonRadius() { public double getEventHorizonRadius() {
@ -174,11 +176,12 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
@Override @Override
public void tickDying(Caster<?> source) { public void tickDying(Caster<?> source) {
accumulatedMass -= 0.8F; float m = accumulatedMass.get() - 0.8F;
accumulatedMass.set(m);
double mass = getMass() * 0.1; double mass = getMass() * 0.1;
double logarithm = 1 - (1D / (1 + (mass * mass))); double logarithm = 1 - (1D / (1 + (mass * mass)));
radius.update((float)Math.max(0.1, logarithm * source.asWorld().getGameRules().getInt(UGameRules.MAX_DARK_VORTEX_SIZE)), 200L); radius.update((float)Math.max(0.1, logarithm * source.asWorld().getGameRules().getInt(UGameRules.MAX_DARK_VORTEX_SIZE)), 200L);
if (accumulatedMass < 1) { if (m < 1) {
super.tickDying(source); super.tickDying(source);
} }
@ -202,7 +205,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
@Override @Override
public boolean isFriendlyTogether(Affine other) { public boolean isFriendlyTogether(Affine other) {
return accumulatedMass < 4; return accumulatedMass.get() < 4;
} }
private boolean isValidTarget(Caster<?> source, Entity entity) { private boolean isValidTarget(Caster<?> source, Entity entity) {
@ -274,8 +277,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
double massOfTarget = AttractionUtils.getMass(target); double massOfTarget = AttractionUtils.getMass(target);
if (!source.isClient() && massOfTarget != 0) { if (!source.isClient() && massOfTarget != 0) {
accumulatedMass += massOfTarget; accumulatedMass.set((float)(accumulatedMass.get() + massOfTarget));
setDirty();
} }
target.damage(source.damageOf(UDamageTypes.GAVITY_WELL_RECOIL, source), Integer.MAX_VALUE); target.damage(source.damageOf(UDamageTypes.GAVITY_WELL_RECOIL, source), Integer.MAX_VALUE);
@ -303,12 +305,12 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
super.toNBT(compound); super.toNBT(compound);
compound.putFloat("accumulatedMass", accumulatedMass); compound.putFloat("accumulatedMass", accumulatedMass.get());
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
accumulatedMass = compound.getFloat("accumulatedMass"); accumulatedMass.set(compound.getFloat("accumulatedMass"));
} }
} }

View file

@ -2,6 +2,10 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.*;
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.SpellAttributeType;
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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect;
@ -18,6 +22,10 @@ public class DispellEvilSpell extends AbstractSpell implements ProjectileDelegat
.with(Trait.POWER, 1) .with(Trait.POWER, 1)
.build(); .build();
private static final SpellAttribute<Double> RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.TIME, AttributeFormat.PERCENTAGE, Trait.POWER, power -> (1 + power) * 10D);
static final TooltipFactory TOOLTIP = RANGE;
protected DispellEvilSpell(CustomisedSpellType<?> type) { protected DispellEvilSpell(CustomisedSpellType<?> type) {
super(type); super(type);
} }
@ -28,7 +36,7 @@ public class DispellEvilSpell extends AbstractSpell implements ProjectileDelegat
return !isDead(); return !isDead();
} }
source.findAllEntitiesInRange(getTraits().get(Trait.POWER) * 10, e -> e.getType() == EntityType.PHANTOM).forEach(entity -> { source.findAllEntitiesInRange(RANGE.get(getTraits()), e -> e.getType() == EntityType.PHANTOM).forEach(entity -> {
entity.damage(entity.getDamageSources().magic(), 50); entity.damage(entity.getDamageSources().magic(), 50);
if (entity instanceof LivingEntity l) { if (entity instanceof LivingEntity l) {
double d = source.getOriginVector().getX() - entity.getX(); double d = source.getOriginVector().getX() - entity.getX();

View file

@ -5,6 +5,10 @@ import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell; import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell;
import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Situation;
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.SpellAttributeType;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.util.shape.Sphere; import com.minelittlepony.unicopia.util.shape.Sphere;
@ -15,6 +19,10 @@ import net.minecraft.util.math.Vec3d;
* An area-effect spell that disperses illusions. * An area-effect spell that disperses illusions.
*/ */
public class DisperseIllusionSpell extends AbstractAreaEffectSpell { public class DisperseIllusionSpell extends AbstractAreaEffectSpell {
private static final SpellAttribute<Float> RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, power -> Math.max(0, 15 + power));
private static final SpellAttribute<Long> DURATION = SpellAttribute.create(SpellAttributeType.DURATION, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.STRENGTH, strength -> (1 + (long)strength) * 100);
static final TooltipFactory TOOLTIP = TooltipFactory.of(RANGE, DURATION);
protected DisperseIllusionSpell(CustomisedSpellType<?> type) { protected DisperseIllusionSpell(CustomisedSpellType<?> type) {
super(type); super(type);
} }
@ -22,7 +30,7 @@ public class DisperseIllusionSpell extends AbstractAreaEffectSpell {
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
float range = Math.max(0, 15 + getTraits().get(Trait.POWER)); float range = RANGE.get(getTraits());
if (range == 0) { if (range == 0) {
return false; return false;
@ -38,10 +46,10 @@ public class DisperseIllusionSpell extends AbstractAreaEffectSpell {
} }
source.findAllSpellsInRange(range).forEach(e -> { source.findAllSpellsInRange(range).forEach(e -> {
e.getSpellSlot().get(SpellPredicate.CAN_SUPPRESS, false) e.getSpellSlot().get(SpellPredicate.CAN_SUPPRESS)
.filter(spell -> spell.isVulnerable(source, this)) .filter(spell -> spell.isVulnerable(source, this))
.ifPresent(spell -> { .ifPresent(spell -> {
spell.onSuppressed(source, 1 + getTraits().get(Trait.STRENGTH)); spell.onSuppressed(source, DURATION.get(getTraits()));
e.playSound(USounds.SPELL_ILLUSION_DISPERSE, 0.2F, 0.5F); e.playSound(USounds.SPELL_ILLUSION_DISPERSE, 0.2F, 0.5F);
}); });
}); });

View file

@ -3,6 +3,10 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.*;
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.SpellAttributeType;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
@ -16,6 +20,10 @@ import net.minecraft.util.math.Vec3d;
public class DisplacementSpell extends AbstractSpell implements HomingSpell, ProjectileDelegate.EntityHitListener { public class DisplacementSpell extends AbstractSpell implements HomingSpell, ProjectileDelegate.EntityHitListener {
private static final SpellAttribute<Float> DAMAGE_TO_TARGET = SpellAttribute.create(SpellAttributeType.DAMAGE_TO_TARGET, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.BLOOD, blood -> blood);
static final TooltipFactory TOOLTIP = DAMAGE_TO_TARGET;
private final EntityReference<Entity> target = new EntityReference<>(); private final EntityReference<Entity> target = new EntityReference<>();
private int ticks = 10; private int ticks = 10;
@ -71,7 +79,7 @@ public class DisplacementSpell extends AbstractSpell implements HomingSpell, Pro
entity.setGlowing(false); entity.setGlowing(false);
entity.playSound(USounds.SPELL_DISPLACEMENT_TELEPORT, 1, 1); entity.playSound(USounds.SPELL_DISPLACEMENT_TELEPORT, 1, 1);
float damage = getTraits().get(Trait.BLOOD); float damage = DAMAGE_TO_TARGET.get(getTraits());
if (damage > 0) { if (damage > 0) {
entity.damage(source.damageOf(UDamageTypes.EXHAUSTION, source), damage); entity.damage(source.damageOf(UDamageTypes.EXHAUSTION, source), damage);
} }
@ -90,6 +98,7 @@ public class DisplacementSpell extends AbstractSpell implements HomingSpell, Pro
@Override @Override
protected void onDestroyed(Caster<?> caster) { protected void onDestroyed(Caster<?> caster) {
super.onDestroyed(caster);
caster.getOriginatingCaster().asEntity().setGlowing(false); caster.getOriginatingCaster().asEntity().setGlowing(false);
target.ifPresent(caster.asWorld(), e -> e.setGlowing(false)); target.ifPresent(caster.asWorld(), e -> e.setGlowing(false));
} }

View file

@ -6,6 +6,10 @@ import java.util.stream.Stream;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.TimedSpell; import com.minelittlepony.unicopia.ability.magic.spell.TimedSpell;
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.SpellAttributeType;
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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.item.FriendshipBraceletItem; import com.minelittlepony.unicopia.item.FriendshipBraceletItem;
@ -28,6 +32,25 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell {
private static final float POWERS_RANGE_WEIGHT = 0.3F; private static final float POWERS_RANGE_WEIGHT = 0.3F;
private static final float MAX_GENEROSITY_FACTOR = 19F; private static final float MAX_GENEROSITY_FACTOR = 19F;
private static final SpellAttribute<Integer> DURATION = SpellAttribute.create(SpellAttributeType.DURATION, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.FOCUS, focus -> 10 + (int)(MathHelper.clamp(focus, 0, 160)));
private static final SpellAttribute<Float> STRENGTH = SpellAttribute.create(SpellAttributeType.STRENGTH, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.STRENGTH, strength -> MathHelper.clamp(strength, 2, 9));
private static final SpellAttribute<Float> RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, power -> MathHelper.clamp((power - 10) * POWERS_RANGE_WEIGHT, MIN_RANGE, MAX_RANGE));
private static final SpellAttribute<Long> SIMULTANIOUS_TARGETS = SpellAttribute.create(SpellAttributeType.SIMULTANIOUS_TARGETS, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.GENEROSITY, (traits, generosity) -> {
return (long)(generosity + traits.get(Trait.FOCUS, MIN_TARGETS, MAX_TARGETS) * 2);
});
private static final SpellAttribute<Float> COST_PER_INDIVIDUAL = SpellAttribute.create(SpellAttributeType.COST_PER_INDIVIDUAL, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, (traits, power) -> {
return MathHelper.clamp(((Math.max(power, 10) - 10) * POWERS_RANGE_WEIGHT) - ((Math.max(traits.get(Trait.FOCUS), 80) - 80) * FOCUS_RANGE_WEIGHT), 1, 7);
});
private static final SpellAttribute<Float> TARGET_PREFERENCE = SpellAttribute.create(SpellAttributeType.TARGET_PREFERENCE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.GENEROSITY, generosity -> {
return MathHelper.clamp(generosity, 1, MAX_GENEROSITY_FACTOR) / MAX_GENEROSITY_FACTOR;
});
private static final SpellAttribute<Float> CASTER_PREFERENCE = SpellAttribute.create(SpellAttributeType.CASTER_PREFERENCE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.GENEROSITY, (traits, generosity) -> {
return 1 - TARGET_PREFERENCE.get(traits);
});
private static final SpellAttribute<Boolean> NEGATES_FALL_DAMAGE = SpellAttribute.createConditional(SpellAttributeType.NEGATES_FALL_DAMAGE, Trait.GENEROSITY, (generosity) -> generosity > 0.5F);
static final TooltipFactory TOOLTIP = TooltipFactory.of(DURATION, STRENGTH, RANGE, SIMULTANIOUS_TARGETS, COST_PER_INDIVIDUAL, TARGET_PREFERENCE, CASTER_PREFERENCE, NEGATES_FALL_DAMAGE);
public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder() public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder()
.with(Trait.FOCUS, 80) .with(Trait.FOCUS, 80)
.with(Trait.POWER, 10) .with(Trait.POWER, 10)
@ -40,7 +63,7 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell {
protected FeatherFallSpell(CustomisedSpellType<?> type) { protected FeatherFallSpell(CustomisedSpellType<?> type) {
super(type); super(type);
timer = new Timer(10 + (int)(getTraits().get(Trait.FOCUS, 0, 160))); timer = new Timer(DURATION.get(getTraits()));
} }
@Override @Override
@ -56,26 +79,22 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell {
return false; return false;
} }
setDirty();
List<Entity> targets = getTargets(caster).toList(); List<Entity> targets = getTargets(caster).toList();
if (targets.isEmpty()) { if (targets.isEmpty()) {
return true; return true;
} }
final float strength = 1F / (getTraits().get(Trait.STRENGTH, 2, 9) / targets.size()); final float strength = 1F / (STRENGTH.get(getTraits()) / targets.size());
final float generosity = getTraits().get(Trait.GENEROSITY, 1, MAX_GENEROSITY_FACTOR) / MAX_GENEROSITY_FACTOR; final float targetPreference = TARGET_PREFERENCE.get(getTraits());
final float casterPreference = 1 - targetPreference;
final boolean negateFallDamage = NEGATES_FALL_DAMAGE.get(getTraits());
Entity entity = caster.asEntity(); Entity entity = caster.asEntity();
Vec3d masterVelocity = entity.getVelocity().multiply(0.1); Vec3d masterVelocity = entity.getVelocity().multiply(0.1);
targets.forEach(target -> { targets.forEach(target -> {
if (target.getVelocity().y < 0) { if (target.getVelocity().y < 0) {
if (negateFallDamage) {
boolean isSelf = caster.isOwnedBy(target) || target == entity;
float delta = strength * (isSelf ? (1F - generosity) : generosity);
if (!isSelf || generosity < 0.5F) {
target.verticalCollision = true; target.verticalCollision = true;
target.setOnGround(true); target.setOnGround(true);
target.fallDistance = 0; target.fallDistance = 0;
@ -83,6 +102,8 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell {
if (target instanceof PlayerEntity) { if (target instanceof PlayerEntity) {
((PlayerEntity)target).getAbilities().flying = false; ((PlayerEntity)target).getAbilities().flying = false;
} }
float delta = strength * ((caster.isOwnedBy(target) || target == entity) ? casterPreference : targetPreference);
target.setVelocity(target.getVelocity().multiply(1, delta, 1)); target.setVelocity(target.getVelocity().multiply(1, delta, 1));
if (situation == Situation.PROJECTILE && target != entity) { if (situation == Situation.PROJECTILE && target != entity) {
target.addVelocity(masterVelocity.x, 0, masterVelocity.z); target.addVelocity(masterVelocity.x, 0, masterVelocity.z);
@ -91,35 +112,16 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell {
ParticleUtils.spawnParticles(new MagicParticleEffect(getType().getColor()), target, 7); 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 ? COST_PER_INDIVIDUAL.get(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;
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<Entity> getTargets(Caster<?> caster) { protected Stream<Entity> 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(RANGE.get(getTraits())).sorted((a, b) -> {
return Integer.compare( return Integer.compare(
FriendshipBraceletItem.isComrade(caster, a) ? 1 : 0, FriendshipBraceletItem.isComrade(caster, a) ? 1 : 0,
FriendshipBraceletItem.isComrade(caster, b) ? 1 : 0 FriendshipBraceletItem.isComrade(caster, b) ? 1 : 0
); );
}).distinct()).limit(getMaxTargets()); }).distinct()).limit(SIMULTANIOUS_TARGETS.get(getTraits()));
} }
@Override @Override

View file

@ -4,6 +4,10 @@ import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.HomingSpell; import com.minelittlepony.unicopia.ability.magic.spell.HomingSpell;
import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Situation;
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.SpellAttributeType;
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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.EntityReference;
@ -15,6 +19,7 @@ import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.math.MathHelper;
public class FireBoltSpell extends AbstractSpell implements HomingSpell, public class FireBoltSpell extends AbstractSpell implements HomingSpell,
ProjectileDelegate.ConfigurationListener, ProjectileDelegate.EntityHitListener { ProjectileDelegate.ConfigurationListener, ProjectileDelegate.EntityHitListener {
@ -31,6 +36,15 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
.with(Trait.FIRE, 60) .with(Trait.FIRE, 60)
.build(); .build();
private static final SpellAttribute<Float> VELOCITY = SpellAttribute.create(SpellAttributeType.VELOCITY, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.STRENGTH, strength -> 1.3F + (strength / 11F));
private static final SpellAttribute<Integer> PROJECTILE_COUNT = SpellAttribute.create(SpellAttributeType.PROJECTILE_COUNT, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.EARTH, earth -> 11 + (int)earth * 3);
private static final SpellAttribute<Boolean> FOLLOWS_TARGET = SpellAttribute.createConditional(SpellAttributeType.FOLLOWS_TARGET, Trait.FOCUS, focus -> focus >= 50);
private static final SpellAttribute<Float> FOLLOW_RANGE = SpellAttribute.create(SpellAttributeType.FOLLOW_RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.FOCUS, focus -> Math.max(0F, focus - 49));
private static final SpellAttribute<Float> MAX_EXPLOSION_STRENGTH = SpellAttribute.create(SpellAttributeType.EXPLOSION_STRENGTH, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.FOCUS, focus -> focus >= 50 ? 10F : 1F);
private static final SpellAttribute<Float> EXPLOSION_STRENGTH = SpellAttribute.create(SpellAttributeType.EXPLOSION_STRENGTH, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, (traits, focus) -> MathHelper.clamp(focus / 50, 0, MAX_EXPLOSION_STRENGTH.get(traits)));
static final TooltipFactory TOOLTIP = TooltipFactory.of(MAX_EXPLOSION_STRENGTH, EXPLOSION_STRENGTH, VELOCITY, PROJECTILE_COUNT, FOLLOWS_TARGET, FOLLOW_RANGE.conditionally(FOLLOWS_TARGET::get));
private final EntityReference<Entity> target = new EntityReference<>(); private final EntityReference<Entity> target = new EntityReference<>();
protected FireBoltSpell(CustomisedSpellType<?> type) { protected FireBoltSpell(CustomisedSpellType<?> type) {
@ -44,10 +58,12 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
@Override @Override
public boolean tick(Caster<?> caster, Situation situation) { public boolean tick(Caster<?> caster, Situation situation) {
boolean followTarget = FOLLOWS_TARGET.get(getTraits());
float followRage = FOLLOW_RANGE.get(getTraits());
if (situation == Situation.PROJECTILE) { if (situation == Situation.PROJECTILE) {
if (caster instanceof MagicProjectileEntity projectile && getTraits().get(Trait.FOCUS) >= 50) { if (caster instanceof MagicProjectileEntity projectile && followTarget) {
caster.findAllEntitiesInRange( caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49, followRage,
EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.validTarget(this, caster)) EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.validTarget(this, caster))
).findFirst().ifPresent(target -> projectile.setHomingTarget(target)); ).findFirst().ifPresent(target -> projectile.setHomingTarget(target));
} }
@ -55,9 +71,9 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
return true; return true;
} }
if (getTraits().get(Trait.FOCUS) >= 50 && target.getOrEmpty(caster.asWorld()).isEmpty()) { if (followTarget && target.getOrEmpty(caster.asWorld()).isEmpty()) {
target.set(caster.findAllEntitiesInRange( target.set(caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49, followRage,
EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.validTarget(this, caster)) EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.validTarget(this, caster))
).findFirst().orElse(null)); ).findFirst().orElse(null));
} }
@ -75,18 +91,18 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
@Override @Override
public void configureProjectile(MagicProjectileEntity projectile, Caster<?> caster) { public void configureProjectile(MagicProjectileEntity projectile, Caster<?> caster) {
projectile.setItem(Items.FIRE_CHARGE.getDefaultStack()); projectile.setItem(Items.FIRE_CHARGE.getDefaultStack());
projectile.addThrowDamage(getTraits().get(Trait.POWER, 0, getTraits().get(Trait.FOCUS) >= 50 ? 500 : 50) / 10F); projectile.addThrowDamage(EXPLOSION_STRENGTH.get(getTraits()));
projectile.setFireTicks(900000); projectile.setFireTicks(900000);
projectile.setVelocity(projectile.getVelocity().multiply(1.3 + getTraits().get(Trait.STRENGTH) / 11F)); projectile.setVelocity(projectile.getVelocity().multiply(VELOCITY.get(getTraits())));
} }
protected int getNumberOfBalls(Caster<?> caster) { protected int getNumberOfBalls(Caster<?> caster) {
return 1 + caster.asWorld().random.nextInt(3) + (int)getTraits().get(Trait.EARTH) * 3; return PROJECTILE_COUNT.get(getTraits()) + caster.asWorld().random.nextInt(3);
} }
@Override @Override
public boolean setTarget(Entity target) { public boolean setTarget(Entity target) {
if (getTraits().get(Trait.FOCUS) >= 50) { if (FOLLOWS_TARGET.get(getTraits())) {
this.target.set(target); this.target.set(target);
return true; return true;
} }

View file

@ -67,14 +67,14 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileDele
generateParticles(source); generateParticles(source);
} }
return new Sphere(false, Math.max(0, 4 + getTraits().get(Trait.POWER))).translate(source.getOrigin()).getBlockPositions().reduce(false, return new Sphere(false, RANGE.get(getTraits())).translate(source.getOrigin()).getBlockPositions().reduce(false,
(r, i) -> source.canModifyAt(i) && applyBlocks(source.asWorld(), i), (r, i) -> source.canModifyAt(i) && applyBlocks(source.asWorld(), i),
(a, b) -> a || b) (a, b) -> a || b)
|| applyEntities(source, source.getOriginVector()); || applyEntities(source, source.getOriginVector());
} }
protected void generateParticles(Caster<?> source) { 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, RANGE.get(getTraits())), (int)(1 + source.getLevel().getScaled(8)) * 6, pos -> {
source.addParticle(ParticleTypes.LARGE_SMOKE, pos, Vec3d.ZERO); source.addParticle(ParticleTypes.LARGE_SMOKE, pos, Vec3d.ZERO);
}); });
} }
@ -121,8 +121,12 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileDele
return false; return false;
} }
protected float getEntityEffectRange() {
return Math.max(0, RANGE.get(getTraits()) - 1);
}
protected boolean applyEntities(Caster<?> source, Vec3d pos) { protected boolean applyEntities(Caster<?> source, Vec3d pos) {
return source.findAllEntitiesInRange(Math.max(0, 3 + getTraits().get(Trait.POWER)), e -> { return source.findAllEntitiesInRange(getEntityEffectRange(), e -> {
LivingEntity master = source.getMaster(); LivingEntity master = source.getMaster();
return (!(e.equals(source.asEntity()) || e.equals(master)) || return (!(e.equals(source.asEntity()) || e.equals(master)) ||
(master instanceof PlayerEntity && !EquinePredicates.PLAYER_UNICORN.test(master))) && !(e instanceof ItemEntity) (master instanceof PlayerEntity && !EquinePredicates.PLAYER_UNICORN.test(master))) && !(e instanceof ItemEntity)

View file

@ -8,6 +8,8 @@ import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.CastOn;
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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.advancement.UCriteria;
@ -35,6 +37,8 @@ public class HydrophobicSpell extends AbstractSpell {
.with(Trait.KNOWLEDGE, 1) .with(Trait.KNOWLEDGE, 1)
.build(); .build();
static final TooltipFactory TOOLTIP = TooltipFactory.of(ShieldSpell.CAST_ON, ShieldSpell.RANGE);
private final TagKey<Fluid> affectedFluid; private final TagKey<Fluid> affectedFluid;
private final Set<Entry> storedFluidPositions = new HashSet<>(); private final Set<Entry> storedFluidPositions = new HashSet<>();
@ -46,7 +50,7 @@ public class HydrophobicSpell extends AbstractSpell {
@Override @Override
public Spell prepareForCast(Caster<?> caster, CastingMethod method) { public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
if ((method == CastingMethod.DIRECT || method == CastingMethod.STAFF) && getTraits().get(Trait.GENEROSITY) > 0) { if ((method == CastingMethod.DIRECT || method == CastingMethod.STAFF) && ShieldSpell.CAST_ON.get(getTraits()) == CastOn.LOCATION) {
return toPlaceable(); return toPlaceable();
} }
return this; return this;
@ -92,8 +96,7 @@ public class HydrophobicSpell extends AbstractSpell {
} }
double range = getRange(source); double range = getRange(source);
var entry = Ether.get(source.asWorld()).getOrCreate(this, source); Ether.get(source.asWorld()).getOrCreate(this, source).setRadius((float)range);
entry.radius = (float)range;
source.spawnParticles(new Sphere(true, range), 10, pos -> { source.spawnParticles(new Sphere(true, range), 10, pos -> {
BlockPos bp = BlockPos.ofFloored(pos); BlockPos bp = BlockPos.ofFloored(pos);
@ -116,7 +119,7 @@ public class HydrophobicSpell extends AbstractSpell {
@Override @Override
protected void onDestroyed(Caster<?> caster) { protected void onDestroyed(Caster<?> caster) {
Ether.get(caster.asWorld()).remove(this, caster); super.onDestroyed(caster);
storedFluidPositions.removeIf(entry -> { storedFluidPositions.removeIf(entry -> {
if (caster.canModifyAt(entry.pos())) { if (caster.canModifyAt(entry.pos())) {
entry.restore(caster.asWorld()); entry.restore(caster.asWorld());
@ -142,7 +145,7 @@ public class HydrophobicSpell extends AbstractSpell {
*/ */
public double getRange(Caster<?> source) { public double getRange(Caster<?> source) {
float multiplier = 1; float multiplier = 1;
float min = (source instanceof Pony ? 4 : 6) + getTraits().get(Trait.POWER); float min = (source instanceof Pony ? 0 : 2) + ShieldSpell.RANGE.get(getTraits());
boolean isLimitedRange = source instanceof Pony || source instanceof MagicProjectileEntity; boolean isLimitedRange = source instanceof Pony || source instanceof MagicProjectileEntity;
double range = (min + (source.getLevel().getScaled(isLimitedRange ? 4 : 40) * (isLimitedRange ? 2 : 10))) / multiplier; double range = (min + (source.getLevel().getScaled(isLimitedRange ? 4 : 40) * (isLimitedRange ? 2 : 10))) / multiplier;
return range; return range;
@ -175,19 +178,21 @@ public class HydrophobicSpell extends AbstractSpell {
} }
public boolean blocksFlow(Ether.Entry<?> entry, Vec3d center, BlockPos pos, FluidState fluid) { public boolean blocksFlow(Ether.Entry<?> entry, Vec3d center, BlockPos pos, FluidState fluid) {
return fluid.isIn(affectedFluid) && pos.isWithinDistance(center, (double)entry.radius + 1); return fluid.isIn(affectedFluid) && pos.isWithinDistance(center, (double)entry.getRadius() + 1);
} }
public static boolean blocksFluidFlow(BlockView world, BlockPos pos, FluidState state) { public static boolean blocksFluidFlow(BlockView world, BlockPos pos, FluidState state) {
if (world instanceof ServerWorld sw) { if (!(world instanceof ServerWorld sw)) {
return false;
}
return Ether.get(sw).anyMatch(SpellType.HYDROPHOBIC, entry -> { return Ether.get(sw).anyMatch(SpellType.HYDROPHOBIC, entry -> {
var spell = entry.getSpell();
var target = entry.entity.getTarget().orElse(null); var target = entry.entity.getTarget().orElse(null);
if (target == null || !pos.isWithinDistance(target.pos(), entry.getRadius() + 1)) {
return false;
}
var spell = entry.getSpell();
return spell != null && target != null && spell.blocksFlow(entry, target.pos(), pos, state); return spell != null && target != null && spell.blocksFlow(entry, target.pos(), pos, state);
}); });
} }
return false;
}
} }

View file

@ -5,6 +5,10 @@ import java.util.List;
import com.minelittlepony.unicopia.Owned; import com.minelittlepony.unicopia.Owned;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Situation;
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.SpellAttributeType;
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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.block.state.StateMaps; import com.minelittlepony.unicopia.block.state.StateMaps;
@ -12,7 +16,6 @@ import com.minelittlepony.unicopia.block.state.StatePredicate;
import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.util.PosHelper; import com.minelittlepony.unicopia.util.PosHelper;
import com.minelittlepony.unicopia.util.VecHelper; import com.minelittlepony.unicopia.util.VecHelper;
import com.minelittlepony.unicopia.util.shape.Shape;
import com.minelittlepony.unicopia.util.shape.Sphere; import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.block.*; import net.minecraft.block.*;
@ -33,8 +36,9 @@ public class IceSpell extends AbstractSpell {
.with(Trait.ICE, 15) .with(Trait.ICE, 15)
.build(); .build();
private static final int RADIUS = 3; private static final SpellAttribute<Float> RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, power -> Math.max(0, 3 + power));
private static final Shape OUTER_RANGE = new Sphere(false, RADIUS);
static final TooltipFactory TOOLTIP = RANGE;
protected IceSpell(CustomisedSpellType<?> type) { protected IceSpell(CustomisedSpellType<?> type) {
super(type); super(type);
@ -43,11 +47,12 @@ public class IceSpell extends AbstractSpell {
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
boolean submerged = source.asEntity().isSubmergedInWater() || source.asEntity().isSubmergedIn(FluidTags.LAVA); boolean submerged = source.asEntity().isSubmergedInWater() || source.asEntity().isSubmergedIn(FluidTags.LAVA);
float radius = RANGE.get(getTraits());
long blocksAffected = OUTER_RANGE.translate(source.getOrigin()).getBlockPositions().filter(i -> { long blocksAffected = new Sphere(false, radius).translate(source.getOrigin()).getBlockPositions().filter(i -> {
if (source.canModifyAt(i) && applyBlockSingle(source.asEntity(), source.asWorld(), i, situation)) { if (source.canModifyAt(i) && applyBlockSingle(source.asEntity(), source.asWorld(), i, situation)) {
if (submerged & source.getOrigin().isWithinDistance(i, RADIUS - 1)) { if (submerged & source.getOrigin().isWithinDistance(i, RANGE.get(getTraits()) - 1)) {
BlockState state = source.asWorld().getBlockState(i); BlockState state = source.asWorld().getBlockState(i);
if (state.isIn(BlockTags.ICE) || state.isOf(Blocks.OBSIDIAN)) { if (state.isIn(BlockTags.ICE) || state.isOf(Blocks.OBSIDIAN)) {
source.asWorld().setBlockState(i, Blocks.AIR.getDefaultState(), Block.NOTIFY_NEIGHBORS); source.asWorld().setBlockState(i, Blocks.AIR.getDefaultState(), Block.NOTIFY_NEIGHBORS);

View file

@ -7,6 +7,10 @@ import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.TimedSpell; import com.minelittlepony.unicopia.ability.magic.spell.TimedSpell;
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.SpellAttributeType;
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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.EntityReference;
@ -20,6 +24,7 @@ import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtList; import net.minecraft.nbt.NbtList;
import net.minecraft.util.math.MathHelper;
public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileDelegate.HitListener { public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileDelegate.HitListener {
public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder() public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder()
@ -29,13 +34,17 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD
.with(Trait.ORDER, 25) .with(Trait.ORDER, 25)
.build(); .build();
private static final SpellAttribute<Integer> ORB_COUNT = SpellAttribute.create(SpellAttributeType.ORB_COUNT, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.LIFE, life -> 2 + (int)(MathHelper.clamp(life, 10, 20) / 10F));
static final TooltipFactory TOOLTIP = TooltipFactory.of(TIME, ORB_COUNT);
private final Timer timer; private final Timer timer;
private final List<EntityReference<FairyEntity>> lights = new ArrayList<>(); private final List<EntityReference<FairyEntity>> lights = new ArrayList<>();
protected LightSpell(CustomisedSpellType<?> type) { protected LightSpell(CustomisedSpellType<?> type) {
super(type); super(type);
timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); timer = new Timer(TIME.get(getTraits()));
} }
@Override @Override
@ -56,11 +65,9 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD
return false; return false;
} }
setDirty();
if (!caster.isClient()) { if (!caster.isClient()) {
if (lights.isEmpty()) { if (lights.isEmpty()) {
int size = 2 + caster.asWorld().random.nextInt(2) + (int)(getTraits().get(Trait.LIFE, 10, 20) - 10)/10; int size = caster.asWorld().random.nextInt(2) + ORB_COUNT.get(getTraits());
while (lights.size() < size) { while (lights.size() < size) {
lights.add(new EntityReference<>()); lights.add(new EntityReference<>());
} }
@ -76,7 +83,6 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD
entity.getWorld().spawnEntity(entity); entity.getWorld().spawnEntity(entity);
ref.set(entity); ref.set(entity);
setDirty();
} }
}); });
} }
@ -91,6 +97,7 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD
@Override @Override
protected void onDestroyed(Caster<?> caster) { protected void onDestroyed(Caster<?> caster) {
super.onDestroyed(caster);
if (caster.isClient()) { if (caster.isClient()) {
return; return;
} }

View file

@ -2,17 +2,20 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.*;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
public class MimicSpell extends AbstractDisguiseSpell implements HomingSpell, TimedSpell { public class MimicSpell extends AbstractDisguiseSpell implements HomingSpell, TimedSpell {
static final TooltipFactory TOOLTIP = TimedSpell.TIME;
private final Timer timer; private final Timer timer;
protected MimicSpell(CustomisedSpellType<?> type) { protected MimicSpell(CustomisedSpellType<?> type) {
super(type); super(type);
timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); timer = new Timer(TIME.get(getTraits()));
} }
@Override @Override
@ -28,8 +31,6 @@ public class MimicSpell extends AbstractDisguiseSpell implements HomingSpell, Ti
return false; return false;
} }
setDirty();
return super.tick(caster, situation); return super.tick(caster, situation);
} }

View file

@ -60,8 +60,8 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti
LivingEntity master = caster.getMaster(); LivingEntity master = caster.getMaster();
Caster<?> other = Caster.of(e).get(); Caster<?> other = Caster.of(e).get();
other.getSpellSlot().removeIf(SpellType.MIMIC, true); other.getSpellSlot().removeIf(SpellType.MIMIC);
caster.getSpellSlot().removeIf(getType(), true); caster.getSpellSlot().removeIf(getType());
if (!isValidTarget(master) || !isValidTarget(e)) { if (!isValidTarget(master) || !isValidTarget(e)) {
master.damage(caster.asWorld().getDamageSources().magic(), Float.MAX_VALUE); master.damage(caster.asWorld().getDamageSources().magic(), Float.MAX_VALUE);
@ -135,7 +135,6 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti
caster.playSound(USounds.SPELL_MINDSWAP_SWAP, 1); caster.playSound(USounds.SPELL_MINDSWAP_SWAP, 1);
}); });
initialized = true; initialized = true;
setDirty();
} }
if (counterpart.isSet()) { if (counterpart.isSet()) {
@ -143,13 +142,12 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti
if (other == null) { if (other == null) {
caster.getOriginatingCaster().asEntity().damage(caster.asWorld().getDamageSources().magic(), Float.MAX_VALUE); caster.getOriginatingCaster().asEntity().damage(caster.asWorld().getDamageSources().magic(), Float.MAX_VALUE);
setDead(); destroy(caster);
return false; return false;
} }
if (!Caster.of(other).get().getSpellSlot().contains(SpellType.MIMIC)) { if (!Caster.of(other).get().getSpellSlot().contains(SpellType.MIMIC)) {
onDestroyed(caster); destroy(caster);
setDead();
return false; return false;
} }
} }
@ -158,8 +156,7 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti
counterpart.ifPresent(caster.asWorld(), e -> { counterpart.ifPresent(caster.asWorld(), e -> {
e.damage(e.getDamageSources().magic(), Float.MAX_VALUE); e.damage(e.getDamageSources().magic(), Float.MAX_VALUE);
}); });
onDestroyed(caster); destroy(caster);
setDead();
return false; return false;
} }
} }

View file

@ -8,6 +8,10 @@ import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell; import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell;
import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Situation;
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.SpellAttributeType;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.Creature; import com.minelittlepony.unicopia.entity.Creature;
import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.EntityReference;
@ -79,6 +83,9 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
return e -> e.getType() == type; return e -> e.getType() == type;
} }
static final SpellAttribute<Integer> WAVE_SIZE = SpellAttribute.create(SpellAttributeType.WAVE_SIZE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.CHAOS, chaos -> 10 + (int)MathHelper.clamp(chaos, 0, 10));
static final TooltipFactory TOOLTIP = TooltipFactory.of(RANGE, WAVE_SIZE);
private final List<EntityReference<LivingEntity>> summonedEntities = new ArrayList<>(); private final List<EntityReference<LivingEntity>> summonedEntities = new ArrayList<>();
private int spawnCountdown; private int spawnCountdown;
@ -90,7 +97,7 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
float radius = 4 + source.getLevel().getScaled(4) * 4 + getTraits().get(Trait.POWER); float radius = source.getLevel().getScaled(4) * 4 + RANGE.get(getTraits());
if (radius <= 0) { if (radius <= 0) {
return false; return false;
@ -122,15 +129,16 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
return true; return true;
}).isEmpty()); }).isEmpty());
float additional = source.asWorld().getLocalDifficulty(source.getOrigin()).getLocalDifficulty() + getTraits().get(Trait.CHAOS, 0, 10);
setDirty();
if (--spawnCountdown > 0 && !summonedEntities.isEmpty()) { if (--spawnCountdown > 0 && !summonedEntities.isEmpty()) {
return true; return true;
} }
// TODO: refactory speed attribute
// TODO: weather resistant attribute
spawnCountdown = 1200 + source.asWorld().random.nextInt(rainy ? 2000 : 1000); spawnCountdown = 1200 + source.asWorld().random.nextInt(rainy ? 2000 : 1000);
if (summonedEntities.size() > 10 + additional) { float additional = source.asWorld().getLocalDifficulty(source.getOrigin()).getLocalDifficulty() + WAVE_SIZE.get(getTraits());
if (summonedEntities.size() > additional) {
return true; return true;
} }
@ -153,6 +161,7 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
@Override @Override
protected void onDestroyed(Caster<?> caster) { protected void onDestroyed(Caster<?> caster) {
super.onDestroyed(caster);
if (caster.isClient()) { if (caster.isClient()) {
return; return;
} }
@ -205,7 +214,6 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
source.asWorld().spawnEntity(minion); source.asWorld().spawnEntity(minion);
summonedEntities.add(new EntityReference<>(minion)); summonedEntities.add(new EntityReference<>(minion));
setDirty();
} }
@Override @Override

View file

@ -1,19 +1,21 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect; package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.*;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.MsgCasterLookRequest;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.particle.*; import com.minelittlepony.unicopia.particle.*;
import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.shape.*; import com.minelittlepony.unicopia.util.shape.*;
@ -21,17 +23,18 @@ import com.minelittlepony.unicopia.util.shape.*;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.packet.s2c.play.PositionFlag; import net.minecraft.network.packet.s2c.play.PositionFlag;
import net.minecraft.particle.ParticleTypes; import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Util;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.WorldEvents; import net.minecraft.world.WorldEvents;
public class PortalSpell extends AbstractSpell implements PlaceableSpell.PlacementDelegate, OrientedSpell { public class PortalSpell extends AbstractSpell implements PlacementControlSpell.PlacementDelegate, OrientedSpell {
public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder() public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder()
.with(Trait.LIFE, 10) .with(Trait.LIFE, 10)
.with(Trait.KNOWLEDGE, 1) .with(Trait.KNOWLEDGE, 1)
@ -40,15 +43,13 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
private static final Shape PARTICLE_AREA = new Sphere(true, 2, 1, 1, 0); private static final Shape PARTICLE_AREA = new Sphere(true, 2, 1, 1, 0);
@Nullable @Nullable
private UUID targetPortalId; private final DataTracker.Entry<UUID> targetPortalId = dataTracker.startTracking(TrackableDataType.UUID, Util.NIL_UUID);
private float targetPortalPitch; private final DataTracker.Entry<Float> targetPortalPitch = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private float targetPortalYaw; private final DataTracker.Entry<Float> targetPortalYaw = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private final EntityReference<Entity> teleportationTarget = new EntityReference<>(); private final EntityReference<Entity> teleportationTarget = new EntityReference<>();
private boolean publishedPosition; private final DataTracker.Entry<Float> pitch = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private final DataTracker.Entry<Float> yaw = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private float pitch;
private float yaw;
private Shape particleArea = PARTICLE_AREA; private Shape particleArea = PARTICLE_AREA;
@ -56,45 +57,56 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
super(type); super(type);
} }
public boolean isLinked() { public EntityReference<Entity> getDestinationReference() {
return teleportationTarget.isSet(); return teleportationTarget;
}
public Optional<EntityReference.EntityValues<Entity>> getTarget() {
return teleportationTarget.getTarget();
} }
public float getPitch() { public float getPitch() {
return pitch; return pitch.get();
} }
public float getYaw() { public float getYaw() {
return yaw; return yaw.get();
} }
public float getTargetPitch() { public float getTargetPitch() {
return targetPortalPitch; return targetPortalPitch.get();
} }
public float getTargetYaw() { public float getTargetYaw() {
return targetPortalYaw; return targetPortalYaw.get();
} }
public float getYawDifference() { public float getYawDifference() {
return MathHelper.wrapDegrees(180 + targetPortalYaw - yaw); return MathHelper.wrapDegrees(180 + getTargetYaw() - getYaw());
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Optional<Ether.Entry<PortalSpell>> getDestination(Caster<?> source) { private Ether.Entry<PortalSpell> getDestination(Caster<?> source) {
return getTarget().map(target -> Ether.get(source.asWorld()).get((SpellType<PortalSpell>)getType(), target, targetPortalId)); return Util.NIL_UUID.equals(targetPortalId.get()) ? null : getDestinationReference()
.getTarget()
.map(target -> Ether.get(source.asWorld()).get((SpellType<PortalSpell>)getType(), target.uuid(), targetPortalId.get()))
.filter(destination -> destination.isClaimedBy(getUuid()))
.orElse(null);
} }
@Override @Override
public boolean apply(Caster<?> caster) { public boolean apply(Caster<?> caster) {
setOrientation(caster.asEntity().getPitch(), caster.asEntity().getYaw());
return toPlaceable().apply(caster); return toPlaceable().apply(caster);
} }
protected void setDestination(@Nullable Ether.Entry<?> destination) {
if (destination == null) {
teleportationTarget.set(null);
targetPortalId.set(Util.NIL_UUID);
} else {
teleportationTarget.copyFrom(destination.entity);
targetPortalId.set(destination.getSpellId());
targetPortalPitch.set(destination.getPitch());
targetPortalYaw.set(destination.getYaw());
}
}
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
if (situation == Situation.GROUND) { if (situation == Situation.GROUND) {
@ -103,53 +115,56 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
source.addParticle(ParticleTypes.ELECTRIC_SPARK, pos, Vec3d.ZERO); source.addParticle(ParticleTypes.ELECTRIC_SPARK, pos, Vec3d.ZERO);
}); });
} else { } else {
teleportationTarget.getTarget().ifPresent(target -> { var ownEntry = Ether.get(source.asWorld()).get(this, source);
if (Ether.get(source.asWorld()).get(getType(), target, targetPortalId) == null) { synchronized (ownEntry) {
Unicopia.LOGGER.debug("Lost sibling, breaking connection to " + target.uuid()); var targetEntry = getDestination(source);
teleportationTarget.set(null);
setDirty(); if (targetEntry == null) {
if (teleportationTarget.isSet()) {
setDestination(null);
source.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, source.getOrigin(), Block.getRawIdFromState(Blocks.GLASS.getDefaultState())); source.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, source.getOrigin(), Block.getRawIdFromState(Blocks.GLASS.getDefaultState()));
} else {
Ether.get(source.asWorld()).anyMatch(getType(), entry -> {
if (entry.isAlive() && !entry.hasClaimant() && !entry.entityMatches(source.asEntity().getUuid())) {
entry.claim(getUuid());
ownEntry.claim(entry.getSpellId());
synchronized (entry) {
if (entry.getSpell() instanceof PortalSpell portal) {
portal.setDestination(ownEntry);
} }
}
setDestination(entry);
}
return false;
}); });
}
getDestination(source).ifPresentOrElse( } else {
entry -> tickWithTargetLink(source, entry), tickActive(source, targetEntry);
() -> findLink(source) }
); }
} }
Ether ether = Ether.get(source.asWorld()); var entry = Ether.get(source.asWorld()).getOrCreate(this, source);
var entry = ether.getOrCreate(this, source); entry.setPitch(pitch.get());
entry.pitch = pitch; entry.setYaw(yaw.get());
entry.yaw = yaw;
ether.markDirty();
} }
return !isDead(); return !isDead();
} }
private void tickWithTargetLink(Caster<?> source, Ether.Entry<?> destination) { private void tickActive(Caster<?> source, Ether.Entry<?> destination) {
if (!MathHelper.approximatelyEquals(targetPortalPitch, destination.pitch)) {
targetPortalPitch = destination.pitch;
setDirty();
}
if (!MathHelper.approximatelyEquals(targetPortalYaw, destination.yaw)) {
targetPortalYaw = destination.yaw;
setDirty();
}
destination.entity.getTarget().ifPresent(target -> { destination.entity.getTarget().ifPresent(target -> {
source.findAllEntitiesInRange(1).forEach(entity -> { source.findAllEntitiesInRange(1).forEach(entity -> {
if (!entity.hasPortalCooldown()) { if (!entity.hasPortalCooldown()) {
float approachYaw = Math.abs(MathHelper.wrapDegrees(entity.getYaw() - this.yaw)); float approachYaw = Math.abs(MathHelper.wrapDegrees(entity.getYaw() - this.yaw.get()));
if (approachYaw > 80) { if (approachYaw > 80) {
return; return;
} }
Vec3d offset = entity.getPos().subtract(source.getOriginVector()); Vec3d offset = entity.getPos().subtract(source.asEntity().getPos())
float yawDifference = pitch < 15 ? getYawDifference() : 0; .add(new Vec3d(0, 0, -0.7F).rotateY(-getTargetYaw() * MathHelper.RADIANS_PER_DEGREE));
float yawDifference = getYawDifference();
Vec3d dest = target.pos().add(offset.rotateY(yawDifference * MathHelper.RADIANS_PER_DEGREE)).add(0, 0.1, 0); Vec3d dest = target.pos().add(offset.rotateY(yawDifference * MathHelper.RADIANS_PER_DEGREE)).add(0, 0.1, 0);
if (entity.getWorld().isTopSolid(BlockPos.ofFloored(dest).up(), entity)) { if (entity.getWorld().isTopSolid(BlockPos.ofFloored(dest).up(), entity)) {
@ -165,7 +180,6 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1); entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1);
entity.teleport((ServerWorld)entity.getWorld(), dest.x, dest.y, dest.z, PositionFlag.VALUES, yaw, entity.getPitch()); entity.teleport((ServerWorld)entity.getWorld(), dest.x, dest.y, dest.z, PositionFlag.VALUES, yaw, entity.getPitch());
entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1); entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1);
setDirty();
Living.updateVelocity(entity); Living.updateVelocity(entity);
@ -179,73 +193,61 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
}); });
} }
private void findLink(Caster<?> source) {
if (source.isClient()) {
return;
}
Ether.get(source.asWorld()).anyMatch(getType(), entry -> {
if (!entry.entity.referenceEquals(source.asEntity()) && entry.claim()) {
teleportationTarget.copyFrom(entry.entity);
targetPortalId = entry.getSpellId();
setDirty();
}
return false;
});
}
@Override @Override
public void setOrientation(float pitch, float yaw) { public void setOrientation(Caster<?> caster, float pitch, float yaw) {
this.pitch = pitch; this.pitch.set(90 - pitch);
this.yaw = yaw; this.yaw.set(-yaw);
particleArea = PARTICLE_AREA.rotate( particleArea = PARTICLE_AREA.rotate(
pitch * MathHelper.RADIANS_PER_DEGREE, this.pitch.get() * MathHelper.RADIANS_PER_DEGREE,
(180 - yaw) * MathHelper.RADIANS_PER_DEGREE (180 - this.yaw.get()) * MathHelper.RADIANS_PER_DEGREE
); );
setDirty();
} }
@Override @Override
public void onPlaced(Caster<?> source, PlaceableSpell parent, CastSpellEntity entity) { public void onPlaced(Caster<?> source, PlacementControlSpell parent) {
LivingEntity caster = source.getMaster(); Entity caster = source.asEntity();
Vec3d targetPos = caster.getRotationVector().multiply(3).add(caster.getEyePos()); Vec3d targetPos = caster.getRotationVector().multiply(3).add(caster.getEyePos());
parent.setOrientation(pitch, yaw); parent.setOrientation(source, -90 - source.asEntity().getPitch(), -source.asEntity().getYaw());
entity.setPos(targetPos.x, Math.abs(pitch) > 15 ? targetPos.y : caster.getPos().y, targetPos.z); parent.setPosition(new Vec3d(targetPos.x, caster.getPos().y, targetPos.z));
if (source instanceof Pony pony) {
Channel.SERVER_REQUEST_PLAYER_LOOK.sendToPlayer(new MsgCasterLookRequest(parent.getUuid()), (ServerPlayerEntity)pony.asEntity());
}
} }
@Override @Override
protected void onDestroyed(Caster<?> caster) { protected void onDestroyed(Caster<?> caster) {
Ether.get(caster.asWorld()).remove(getType(), caster); super.onDestroyed(caster);
getDestination(caster).ifPresent(Ether.Entry::release); if (!caster.isClient()) {
var destination = getDestination(caster);
if (destination != null) {
destination.release(getUuid());
}
}
} }
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
super.toNBT(compound); super.toNBT(compound);
if (targetPortalId != null) { compound.putUuid("targetPortalId", targetPortalId.get());
compound.putUuid("targetPortalId", targetPortalId);
}
compound.putBoolean("publishedPosition", publishedPosition);
compound.put("teleportationTarget", teleportationTarget.toNBT()); compound.put("teleportationTarget", teleportationTarget.toNBT());
compound.putFloat("pitch", pitch); compound.putFloat("pitch", getPitch());
compound.putFloat("yaw", yaw); compound.putFloat("yaw", getYaw());
compound.putFloat("targetPortalPitch", targetPortalPitch); compound.putFloat("targetPortalPitch", getTargetPitch());
compound.putFloat("targetPortalYaw", targetPortalYaw); compound.putFloat("targetPortalYaw", getTargetYaw());
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
targetPortalId = compound.containsUuid("targetPortalId") ? compound.getUuid("targetPortalId") : null; targetPortalId.set(compound.containsUuid("targetPortalId") ? compound.getUuid("targetPortalId") : Util.NIL_UUID);
publishedPosition = compound.getBoolean("publishedPosition");
teleportationTarget.fromNBT(compound.getCompound("teleportationTarget")); teleportationTarget.fromNBT(compound.getCompound("teleportationTarget"));
pitch = compound.getFloat("pitch"); pitch.set(compound.getFloat("pitch"));
yaw = compound.getFloat("yaw"); yaw.set(compound.getFloat("yaw"));
targetPortalPitch = compound.getFloat("targetPortalPitch"); targetPortalPitch.set(compound.getFloat("targetPortalPitch"));
targetPortalYaw = compound.getFloat("targetPortalYaw"); targetPortalYaw.set(compound.getFloat("targetPortalYaw"));
particleArea = PARTICLE_AREA.rotate( particleArea = PARTICLE_AREA.rotate(
pitch * MathHelper.RADIANS_PER_DEGREE, pitch.get() * MathHelper.RADIANS_PER_DEGREE,
(180 - yaw) * MathHelper.RADIANS_PER_DEGREE (180 - yaw.get()) * MathHelper.RADIANS_PER_DEGREE
); );
} }
} }

View file

@ -36,7 +36,7 @@ public class ScorchSpell extends FireSpell implements ProjectileDelegate.Configu
BlockPos pos = PosHelper.findSolidGroundAt(source.asWorld(), source.getOrigin(), source.getPhysics().getGravitySignum()); BlockPos pos = PosHelper.findSolidGroundAt(source.asWorld(), source.getOrigin(), source.getPhysics().getGravitySignum());
if (source.canModifyAt(pos) && StateMaps.FIRE_AFFECTED.convert(source.asWorld(), pos)) { 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, RANGE.get(getTraits())), 5, p -> {
source.addParticle(ParticleTypes.SMOKE, PosHelper.offset(p, pos), Vec3d.ZERO); source.addParticle(ParticleTypes.SMOKE, PosHelper.offset(p, pos), Vec3d.ZERO);
}); });
} }

View file

@ -9,6 +9,11 @@ import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.CastOn;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute;
import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType;
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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate; import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate;
@ -17,6 +22,7 @@ import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.projectile.ProjectileUtil; import com.minelittlepony.unicopia.projectile.ProjectileUtil;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.ColorHelper; import com.minelittlepony.unicopia.util.ColorHelper;
import com.minelittlepony.unicopia.util.Lerp; import com.minelittlepony.unicopia.util.Lerp;
import com.minelittlepony.unicopia.util.shape.Sphere; import com.minelittlepony.unicopia.util.shape.Sphere;
@ -24,6 +30,7 @@ import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EyeOfEnderEntity; import net.minecraft.entity.EyeOfEnderEntity;
import net.minecraft.entity.FallingBlockEntity; import net.minecraft.entity.FallingBlockEntity;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.TntEntity; import net.minecraft.entity.TntEntity;
import net.minecraft.entity.Entity.RemovalReason; import net.minecraft.entity.Entity.RemovalReason;
@ -44,6 +51,19 @@ public class ShieldSpell extends AbstractSpell {
.with(Trait.AIR, 9) .with(Trait.AIR, 9)
.build(); .build();
static final SpellAttribute<Float> RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, power -> Math.max(0, 4 + power));
protected static final SpellAttribute<CastOn> CAST_ON = SpellAttribute.createEnumerated(SpellAttributeType.CAST_ON, Trait.GENEROSITY, generosity -> generosity > 0 ? CastOn.LOCATION : CastOn.SELF);
static final SpellAttribute<Boolean> TARGET_ITEMS = SpellAttribute.createConditional(SpellAttributeType.PERMIT_ITEMS, Trait.KNOWLEDGE, knowledge -> knowledge > 10);
static final SpellAttribute<Boolean> PERMIT_PASSIVE = SpellAttribute.createConditional(SpellAttributeType.PERMIT_PASSIVE, Trait.LIFE, l -> l > 0);
static final SpellAttribute<Boolean> PERMIT_HOSTILE = SpellAttribute.createConditional(SpellAttributeType.PERMIT_HOSTILE, Trait.BLOOD, l -> l > 0);
static final SpellAttribute<Boolean> PERMIT_PLAYER = SpellAttribute.createConditional(SpellAttributeType.PERMIT_PLAYER, Trait.ICE, l -> l > 0);
static final TooltipFactory PERMIT_ENTITY = TooltipFactory.of(PERMIT_PASSIVE, PERMIT_HOSTILE, PERMIT_PLAYER);
static final TooltipFactory TARGET = (type, tooltip) -> (TARGET_ITEMS.get(type.traits()) ? TARGET_ITEMS : PERMIT_ENTITY).appendTooltip(type, tooltip);
static final TooltipFactory TOOLTIP = TooltipFactory.of(RANGE, TARGET, CAST_ON);
protected final TargetSelecter targetSelecter = new TargetSelecter(this).setFilter(this::isValidTarget); protected final TargetSelecter targetSelecter = new TargetSelecter(this).setFilter(this::isValidTarget);
private final Lerp radius = new Lerp(0); private final Lerp radius = new Lerp(0);
@ -58,7 +78,7 @@ public class ShieldSpell extends AbstractSpell {
@Override @Override
public Spell prepareForCast(Caster<?> caster, CastingMethod method) { public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
return method == CastingMethod.STAFF || getTraits().get(Trait.GENEROSITY) > 0 ? toPlaceable() : this; return method == CastingMethod.STAFF || CAST_ON.get(getTraits()) == CastOn.LOCATION ? toPlaceable() : this;
} }
@Override @Override
@ -90,6 +110,8 @@ public class ShieldSpell extends AbstractSpell {
if (source.isClient()) { if (source.isClient()) {
generateParticles(source); generateParticles(source);
} else {
Ether.get(source.asWorld()).getOrCreate(this, source).setRadius(radius.getValue());
} }
if (situation == Situation.PROJECTILE) { if (situation == Situation.PROJECTILE) {
@ -142,13 +164,18 @@ public class ShieldSpell extends AbstractSpell {
* Calculates the maximum radius of the shield. aka The area of effect. * Calculates the maximum radius of the shield. aka The area of effect.
*/ */
public double getDrawDropOffRange(Caster<?> source) { public double getDrawDropOffRange(Caster<?> source) {
float min = (source instanceof Pony ? 4 : 6) + getTraits().get(Trait.POWER); float min = (source instanceof Pony ? 0 : 2) + RANGE.get(getTraits());
double range = (min + (source.getLevel().getScaled(source instanceof Pony ? 4 : 40) * (source instanceof Pony ? 2 : 10))) / rangeMultiplier.getValue(); double range = (min + (source.getLevel().getScaled(source instanceof Pony ? 4 : 40) * (source instanceof Pony ? 2 : 10))) / rangeMultiplier.getValue();
return range; return range;
} }
protected boolean isValidTarget(Caster<?> source, Entity entity) { protected boolean isValidTarget(Caster<?> source, Entity entity) {
if (TARGET_ITEMS.get(getTraits())) {
return entity instanceof ItemEntity;
}
boolean valid = (entity instanceof LivingEntity boolean valid = (entity instanceof LivingEntity
|| entity instanceof TntEntity || entity instanceof TntEntity
|| entity instanceof FallingBlockEntity || entity instanceof FallingBlockEntity
@ -159,13 +186,13 @@ public class ShieldSpell extends AbstractSpell {
|| entity instanceof BoatEntity || entity instanceof BoatEntity
); );
if (getTraits().get(Trait.LIFE) > 0) { if (PERMIT_PASSIVE.get(getTraits())) {
valid &= !(entity instanceof PassiveEntity); valid &= !(entity instanceof PassiveEntity);
} }
if (getTraits().get(Trait.BLOOD) > 0) { if (PERMIT_HOSTILE.get(getTraits())) {
valid &= !(entity instanceof HostileEntity); valid &= !(entity instanceof HostileEntity);
} }
if (getTraits().get(Trait.ICE) > 0) { if (PERMIT_PLAYER.get(getTraits())) {
valid &= !(entity instanceof PlayerEntity); valid &= !(entity instanceof PlayerEntity);
} }
return valid; return valid;

View file

@ -15,6 +15,8 @@ import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.FollowingParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
@ -37,6 +39,7 @@ import net.minecraft.util.math.Vec3d;
public class SiphoningSpell extends AbstractAreaEffectSpell { public class SiphoningSpell extends AbstractAreaEffectSpell {
static final Predicate<Entity> TARGET_PREDICATE = EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR.and(EntityPredicates.VALID_LIVING_ENTITY); static final Predicate<Entity> TARGET_PREDICATE = EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR.and(EntityPredicates.VALID_LIVING_ENTITY);
private final DataTracker.Entry<Boolean> upset = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private int ticksUpset; private int ticksUpset;
protected SiphoningSpell(CustomisedSpellType<?> type) { protected SiphoningSpell(CustomisedSpellType<?> type) {
@ -51,12 +54,12 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
if (ticksUpset > 0) { if (ticksUpset > 0 && --ticksUpset <= 0) {
ticksUpset--; upset.set(false);
} }
if (source.isClient()) { if (source.isClient()) {
float radius = 4 + source.getLevel().getScaled(5); float radius = source.getLevel().getScaled(5) + RANGE.get(getTraits());
int direction = isFriendlyTogether(source) ? 1 : -1; int direction = isFriendlyTogether(source) ? 1 : -1;
source.spawnParticles(new Sphere(true, radius, 1, 0, 1), 1, pos -> { source.spawnParticles(new Sphere(true, radius, 1, 0, 1), 1, pos -> {
@ -102,7 +105,7 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
} else { } else {
e.damage(damage, e.getHealth() / 4); e.damage(damage, e.getHealth() / 4);
ticksUpset = 100; ticksUpset = 100;
setDirty(); upset.set(true);
} }
} else { } else {
e.heal((float)Math.min(source.getLevel().getScaled(e.getHealth()) / 2F, maxHealthGain * 0.6)); e.heal((float)Math.min(source.getLevel().getScaled(e.getHealth()) / 2F, maxHealthGain * 0.6));
@ -168,5 +171,8 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
ticksUpset = compound.getInt("upset"); ticksUpset = compound.getInt("upset");
if (ticksUpset > 0) {
upset.set(true);
}
} }
} }

View file

@ -2,18 +2,21 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.google.common.base.Suppliers;
import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell;
import com.minelittlepony.unicopia.ability.magic.spell.ChangelingFeedingSpell; import com.minelittlepony.unicopia.ability.magic.spell.ChangelingFeedingSpell;
import com.minelittlepony.unicopia.ability.magic.spell.DispersableDisguiseSpell; import com.minelittlepony.unicopia.ability.magic.spell.DispersableDisguiseSpell;
import com.minelittlepony.unicopia.ability.magic.spell.RainboomAbilitySpell; import com.minelittlepony.unicopia.ability.magic.spell.RainboomAbilitySpell;
import com.minelittlepony.unicopia.ability.magic.spell.PlaceableSpell; import com.minelittlepony.unicopia.ability.magic.spell.PlacementControlSpell;
import com.minelittlepony.unicopia.ability.magic.spell.RageAbilitySpell; import com.minelittlepony.unicopia.ability.magic.spell.RageAbilitySpell;
import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.ThrowableSpell; import com.minelittlepony.unicopia.ability.magic.spell.ThrowableSpell;
import com.minelittlepony.unicopia.ability.magic.spell.TimeControlAbilitySpell; import com.minelittlepony.unicopia.ability.magic.spell.TimeControlAbilitySpell;
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.SpellTraits;
import com.minelittlepony.unicopia.item.GemstoneItem; import com.minelittlepony.unicopia.item.GemstoneItem;
import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.UItems;
@ -34,47 +37,47 @@ import net.minecraft.server.command.ServerCommandSource;
public final class SpellType<T extends Spell> implements Affine, SpellPredicate<T> { public final class SpellType<T extends Spell> implements Affine, SpellPredicate<T> {
public static final Identifier EMPTY_ID = Unicopia.id("none"); 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 = builder(t -> null).affinity(Affinity.NEUTRAL).color(0xFFFFFF).unobtainable().build(EMPTY_ID);
public static final Registry<SpellType<?>> REGISTRY = RegistryUtils.createSimple(Unicopia.id("spells")); public static final Registry<SpellType<?>> REGISTRY = RegistryUtils.createSimple(Unicopia.id("spells"));
public static final RegistryKey<? extends Registry<SpellType<?>>> REGISTRY_KEY = REGISTRY.getKey(); 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)); 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<PlacementControlSpell> PLACE_CONTROL_SPELL = register("place_controller", SpellType.<PlacementControlSpell>builder(PlacementControlSpell::new).affinity(Affinity.NEUTRAL).unobtainable().stackable().shape(GemstoneItem.Shape.DONUT));
public static final SpellType<ThrowableSpell> THROWN_SPELL = register("thrown", Affinity.NEUTRAL, 0, false, GemstoneItem.Shape.DONUT, SpellTraits.EMPTY, ThrowableSpell::new); public static final SpellType<ThrowableSpell> THROWN_SPELL = register("thrown", SpellType.<ThrowableSpell>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<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", Affinity.BAD, 0xBDBDF9, false, GemstoneItem.Shape.ARROW, SpellTraits.EMPTY, ChangelingFeedingSpell::new); 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", Affinity.GOOD, 0xBDBDF9, false, GemstoneItem.Shape.ROCKET, SpellTraits.EMPTY, RainboomAbilitySpell::new); 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", Affinity.GOOD, 0xBDBDF9, false, GemstoneItem.Shape.FLAME, SpellTraits.EMPTY, RageAbilitySpell::new); 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", Affinity.GOOD, 0xBDBDF9, false, GemstoneItem.Shape.STAR, SpellTraits.EMPTY, TimeControlAbilitySpell::new); 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<IceSpell> FROST = register("frost", builder(IceSpell::new).color(0xEABBFF).shape(GemstoneItem.Shape.TRIANGLE).traits(IceSpell.DEFAULT_TRAITS).tooltip(IceSpell.TOOLTIP));
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<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", Affinity.BAD, 0xF8EC1F, true, GemstoneItem.Shape.FLAME, ScorchSpell.DEFAULT_TRAITS, ScorchSpell::new); public static final SpellType<ScorchSpell> SCORCH = register("scorch", builder(ScorchSpell::new).affinity(Affinity.BAD).color(0xF8EC1F).stackable().shape(GemstoneItem.Shape.FLAME).traits(ScorchSpell.DEFAULT_TRAITS).tooltip(AbstractAreaEffectSpell.TOOLTIP));
public static final SpellType<FireSpell> FLAME = register("flame", Affinity.GOOD, 0xFFBB99, true, GemstoneItem.Shape.FLAME, FireSpell.DEFAULT_TRAITS, FireSpell::new); public static final SpellType<FireSpell> FLAME = register("flame", builder(FireSpell::new).color(0xFFBB99).shape(GemstoneItem.Shape.FLAME).traits(FireSpell.DEFAULT_TRAITS).tooltip(AbstractAreaEffectSpell.TOOLTIP));
public static final SpellType<InfernoSpell> INFERNAL = register("infernal", Affinity.BAD, 0xFFAA00, true, GemstoneItem.Shape.FLAME, InfernoSpell.DEFAULT_TRAITS, InfernoSpell::new); public static final SpellType<InfernoSpell> INFERNAL = register("infernal", builder(InfernoSpell::new).affinity(Affinity.BAD).color(0xFFAA00).shape(GemstoneItem.Shape.FLAME).traits(InfernoSpell.DEFAULT_TRAITS).tooltip(AbstractAreaEffectSpell.TOOLTIP));
public static final SpellType<ShieldSpell> SHIELD = register("shield", Affinity.NEUTRAL, 0x66CDAA, true, GemstoneItem.Shape.SHIELD, ShieldSpell.DEFAULT_TRAITS, ShieldSpell::new); public static final SpellType<ShieldSpell> SHIELD = register("shield", builder(ShieldSpell::new).affinity(Affinity.NEUTRAL).color(0x66CDAA).shape(GemstoneItem.Shape.SHIELD).traits(ShieldSpell.DEFAULT_TRAITS).tooltip(ShieldSpell.TOOLTIP));
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<AreaProtectionSpell> ARCANE_PROTECTION = register("arcane_protection", builder(AreaProtectionSpell::new).affinity(Affinity.BAD).color(0x99CDAA).shape(GemstoneItem.Shape.SHIELD).traits(AreaProtectionSpell.DEFAULT_TRAITS).tooltip(AreaProtectionSpell.TOOLTIP));
public static final SpellType<AttractiveSpell> VORTEX = register("vortex", Affinity.NEUTRAL, 0xFFEA88, true, GemstoneItem.Shape.VORTEX, AttractiveSpell.DEFAULT_TRAITS, AttractiveSpell::new); public static final SpellType<AttractiveSpell> VORTEX = register("vortex", builder(AttractiveSpell::new).affinity(Affinity.NEUTRAL).color(0xFFEA88).shape(GemstoneItem.Shape.VORTEX).traits(AttractiveSpell.DEFAULT_TRAITS).tooltip(AttractiveSpell.TOOLTIP));
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<DarkVortexSpell> 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<NecromancySpell> NECROMANCY = register("necromancy", Affinity.BAD, 0xFA3A3A, true, GemstoneItem.Shape.SKULL, SpellTraits.EMPTY, NecromancySpell::new); public static final SpellType<NecromancySpell> NECROMANCY = register("necromancy", builder(NecromancySpell::new).affinity(Affinity.BAD).color(0xFA3A3A).shape(GemstoneItem.Shape.SKULL).tooltip(NecromancySpell.TOOLTIP));
public static final SpellType<SiphoningSpell> SIPHONING = register("siphoning", Affinity.NEUTRAL, 0xFFA3AA, true, GemstoneItem.Shape.LAMBDA, SpellTraits.EMPTY, SiphoningSpell::new); public static final SpellType<SiphoningSpell> SIPHONING = register("siphoning", builder(SiphoningSpell::new).affinity(Affinity.NEUTRAL).color(0xFFA3AA).shape(GemstoneItem.Shape.LAMBDA).tooltip(AbstractAreaEffectSpell.TOOLTIP));
public static final SpellType<DisperseIllusionSpell> REVEALING = register("reveal", Affinity.GOOD, 0xFFFFAF, true, GemstoneItem.Shape.CROSS, SpellTraits.EMPTY, DisperseIllusionSpell::new); public static final SpellType<DisperseIllusionSpell> REVEALING = register("reveal", builder(DisperseIllusionSpell::new).color(0xFFFFAF).shape(GemstoneItem.Shape.CROSS).tooltip(DisperseIllusionSpell.TOOLTIP));
public static final SpellType<AwkwardSpell> AWKWARD = register("awkward", Affinity.GOOD, 0x3A59FF, true, GemstoneItem.Shape.ICE, SpellTraits.EMPTY, AwkwardSpell::new); 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", Affinity.GOOD, 0x19E48E, true, GemstoneItem.Shape.BRUSH, SpellTraits.EMPTY, TransformationSpell::new); 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", Affinity.GOOD, 0x00EEFF, true, GemstoneItem.Shape.LAMBDA, FeatherFallSpell.DEFAULT_TRAITS, FeatherFallSpell::new); public static final SpellType<FeatherFallSpell> FEATHER_FALL = register("feather_fall", builder(FeatherFallSpell::new).color(0x00EEFF).shape(GemstoneItem.Shape.LAMBDA).traits(FeatherFallSpell.DEFAULT_TRAITS).tooltip(FeatherFallSpell.TOOLTIP));
public static final SpellType<CatapultSpell> CATAPULT = register("catapult", Affinity.GOOD, 0x22FF00, true, GemstoneItem.Shape.ROCKET, CatapultSpell.DEFAULT_TRAITS, CatapultSpell::new); public static final SpellType<CatapultSpell> CATAPULT = register("catapult", builder(CatapultSpell::new).color(0x22FF00).shape(GemstoneItem.Shape.ROCKET).traits(CatapultSpell.DEFAULT_TRAITS).tooltip(CatapultSpell.TOOLTIP));
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<FireBoltSpell> FIRE_BOLT = register("fire_bolt", builder(FireBoltSpell::new).color(0xFF8811).shape(GemstoneItem.Shape.FLAME).traits(FireBoltSpell.DEFAULT_TRAITS).tooltip(FireBoltSpell.TOOLTIP));
public static final SpellType<LightSpell> LIGHT = register("light", Affinity.GOOD, 0xEEFFAA, true, GemstoneItem.Shape.STAR, LightSpell.DEFAULT_TRAITS, LightSpell::new); public static final SpellType<LightSpell> LIGHT = register("light", builder(LightSpell::new).color(0xEEFFAA).shape(GemstoneItem.Shape.STAR).traits(LightSpell.DEFAULT_TRAITS).tooltip(LightSpell.TOOLTIP));
public static final SpellType<DisplacementSpell> DISPLACEMENT = register("displacement", Affinity.NEUTRAL, 0x9900FF, true, GemstoneItem.Shape.BRUSH, PortalSpell.DEFAULT_TRAITS, DisplacementSpell::new); public static final SpellType<DisplacementSpell> DISPLACEMENT = register("displacement", builder(DisplacementSpell::new).affinity(Affinity.NEUTRAL).color(0x9900FF).stackable().shape(GemstoneItem.Shape.BRUSH).traits(PortalSpell.DEFAULT_TRAITS).tooltip(DisplacementSpell.TOOLTIP));
public static final SpellType<PortalSpell> PORTAL = register("portal", Affinity.GOOD, 0x99FFFF, true, GemstoneItem.Shape.RING, PortalSpell.DEFAULT_TRAITS, PortalSpell::new); 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", Affinity.GOOD, 0xFFFF00, true, GemstoneItem.Shape.ARROW, SpellTraits.EMPTY, MimicSpell::new); public static final SpellType<MimicSpell> MIMIC = register("mimic", builder(MimicSpell::new).color(0xFFFF00).shape(GemstoneItem.Shape.ARROW).tooltip(MimicSpell.TOOLTIP));
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<MindSwapSpell> MIND_SWAP = register("mind_swap", builder(MindSwapSpell::new).affinity(Affinity.BAD).color(0xF9FF99).shape(GemstoneItem.Shape.WAVE).tooltip(MimicSpell.TOOLTIP));
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<HydrophobicSpell> HYDROPHOBIC = register("hydrophobic", SpellType.<HydrophobicSpell>builder(s -> new HydrophobicSpell(s, FluidTags.WATER)).affinity(Affinity.NEUTRAL).color(0xF999FF).stackable().shape(GemstoneItem.Shape.ROCKET).tooltip(HydrophobicSpell.TOOLTIP));
public static final SpellType<BubbleSpell> BUBBLE = register("bubble", Affinity.NEUTRAL, 0xF999FF, true, GemstoneItem.Shape.DONUT, BubbleSpell.DEFAULT_TRAITS, BubbleSpell::new); public static final SpellType<BubbleSpell> BUBBLE = register("bubble", builder(BubbleSpell::new).affinity(Affinity.NEUTRAL).color(0xF999FF).shape(GemstoneItem.Shape.DONUT).traits(BubbleSpell.DEFAULT_TRAITS).tooltip(BubbleSpell.TOOLTIP));
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<DispellEvilSpell> DISPEL_EVIL = register("dispel_evil", builder(DispellEvilSpell::new).color(0x00FF00).shape(GemstoneItem.Shape.CROSS).traits(DispellEvilSpell.DEFAULT_TRAITS).tooltip(DispellEvilSpell.TOOLTIP));
public static void bootstrap() {} public static void bootstrap() {}
@ -82,6 +85,7 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
private final Affinity affinity; private final Affinity affinity;
private final int color; private final int color;
private final boolean obtainable; private final boolean obtainable;
private final boolean stackable;
private final GemstoneItem.Shape shape; private final GemstoneItem.Shape shape;
private final Factory<T> factory; private final Factory<T> factory;
@ -94,15 +98,19 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
private final ItemStack defaultStack; private final ItemStack defaultStack;
private SpellType(Identifier id, Affinity affinity, int color, boolean obtainable, GemstoneItem.Shape shape, SpellTraits traits, Factory<T> factory) { private final TooltipFactory tooltipFunction;
private SpellType(Identifier id, Affinity affinity, int color, boolean obtainable, boolean stackable, GemstoneItem.Shape shape, SpellTraits traits, TooltipFactory tooltipFunction, Factory<T> factory) {
this.id = id; this.id = id;
this.affinity = affinity; this.affinity = affinity;
this.color = color; this.color = color;
this.obtainable = obtainable; this.obtainable = obtainable;
this.shape = shape; this.shape = shape;
this.tooltipFunction = tooltipFunction;
this.factory = factory; this.factory = factory;
this.traits = traits; this.traits = traits;
traited = new CustomisedSpellType<>(this, traits); this.stackable = stackable;
traited = new CustomisedSpellType<>(this, traits, SpellTraits::empty);
defaultStack = UItems.GEMSTONE.getDefaultStack(this); defaultStack = UItems.GEMSTONE.getDefaultStack(this);
} }
@ -110,6 +118,10 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
return obtainable; return obtainable;
} }
public boolean isStackable() {
return stackable;
}
public Identifier getId() { public Identifier getId() {
return id; return id;
} }
@ -154,16 +166,20 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
} }
public CustomisedSpellType<T> withTraits(SpellTraits traits) { public CustomisedSpellType<T> 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<T> getFactory() { public Factory<T> getFactory() {
return factory; return factory;
} }
public TooltipFactory getTooltip() {
return tooltipFunction;
}
@Override @Override
public boolean test(@Nullable Spell spell) { public boolean test(@Nullable Spell spell) {
return spell != null && spell.getType() == this; return spell != null && spell.getTypeAndTraits().type() == this;
} }
public void toNbt(NbtCompound tag) { public void toNbt(NbtCompound tag) {
@ -179,12 +195,12 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
return "SpellType[" + getTranslationKey() + "]"; 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) { public static <T extends Spell> SpellType<T> register(String name, Builder<T> builder) {
return register(Unicopia.id(name), affinity, color, obtainable, shape, traits, factory); 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) { public static <T extends Spell> SpellType<T> register(Identifier id, Builder<T> builder) {
return Registry.register(REGISTRY, id, new SpellType<>(id, affinity, color, obtainable, shape, traits, factory)); return Registry.register(REGISTRY, id, builder.build(id));
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -209,4 +225,62 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
public interface Factory<T extends Spell> { public interface Factory<T extends Spell> {
T create(CustomisedSpellType<T> type); 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;
private TooltipFactory tooltipFunction = TooltipFactory.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 Builder<T> tooltip(TooltipFactory tooltipFunction) {
this.tooltipFunction = tooltipFunction;
return this;
}
public SpellType<T> build(Identifier id) {
return new SpellType<>(id, affinity, color, obtainable, stackable, shape, traits, tooltipFunction, factory);
}
}
} }

View file

@ -67,6 +67,10 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
}); });
} }
public static SpellTraits empty() {
return EMPTY;
}
public static Map<Identifier, SpellTraits> all() { public static Map<Identifier, SpellTraits> all() {
return new HashMap<>(REGISTRY); return new HashMap<>(REGISTRY);
} }
@ -292,7 +296,8 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
return fromString(traits, " "); return fromString(traits, " ");
} }
public static Optional<SpellTraits> fromString(String traits, String delimiter) { @Deprecated
private static Optional<SpellTraits> fromString(String traits, String delimiter) {
return fromEntries(Arrays.stream(traits.split(delimiter)).map(a -> a.split(":")).map(pair -> { return fromEntries(Arrays.stream(traits.split(delimiter)).map(a -> a.split(":")).map(pair -> {
Trait key = Trait.fromName(pair[0]).orElse(null); Trait key = Trait.fromName(pair[0]).orElse(null);
if (key == null) { if (key == null) {

View file

@ -165,7 +165,11 @@ public enum Trait implements CommandArgumentEnum<Trait> {
@Deprecated @Deprecated
public static Optional<Trait> fromName(String name) { public static Optional<Trait> fromName(String name) {
return Optional.ofNullable(CODEC.byId(name)); Trait trait = CODEC.byId(name);
if (trait == null) {
Unicopia.LOGGER.error("Unknown trait: " + name);
}
return Optional.ofNullable(trait);
} }
public static EnumArgumentType<Trait> argument() { public static EnumArgumentType<Trait> argument() {

View file

@ -112,9 +112,9 @@ public interface UBlocks {
Block PALM_TRAPDOOR = register("palm_trapdoor", new TrapdoorBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).instrument(Instrument.BASS).strength(3).nonOpaque().allowsSpawning(BlockConstructionUtils::never).burnable(), UWoodTypes.PALM.setType()), ItemGroups.FUNCTIONAL); Block PALM_TRAPDOOR = register("palm_trapdoor", new TrapdoorBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).instrument(Instrument.BASS).strength(3).nonOpaque().allowsSpawning(BlockConstructionUtils::never).burnable(), UWoodTypes.PALM.setType()), ItemGroups.FUNCTIONAL);
Block PALM_PRESSURE_PLATE = register("palm_pressure_plate", new PressurePlateBlock(PressurePlateBlock.ActivationRule.EVERYTHING, Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).noCollision().strength(0.5f).sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.DESTROY), UWoodTypes.PALM.setType()), ItemGroups.BUILDING_BLOCKS); Block PALM_PRESSURE_PLATE = register("palm_pressure_plate", new PressurePlateBlock(PressurePlateBlock.ActivationRule.EVERYTHING, Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).noCollision().strength(0.5f).sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.DESTROY), UWoodTypes.PALM.setType()), ItemGroups.BUILDING_BLOCKS);
Block PALM_BUTTON = register("palm_button", BlockConstructionUtils.woodenButton(), ItemGroups.BUILDING_BLOCKS); Block PALM_BUTTON = register("palm_button", BlockConstructionUtils.woodenButton(), ItemGroups.BUILDING_BLOCKS);
Block PALM_SIGN = register("palm_sign", new SignBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1).burnable().sounds(BlockSoundGroup.WOOD), UWoodTypes.PALM), ItemGroups.FUNCTIONAL); Block PALM_SIGN = register("palm_sign", new SignBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1).burnable().sounds(BlockSoundGroup.WOOD), UWoodTypes.PALM));
Block PALM_WALL_SIGN = register("palm_wall_sign", new WallSignBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1).dropsLike(PALM_SIGN).burnable(), UWoodTypes.PALM)); Block PALM_WALL_SIGN = register("palm_wall_sign", new WallSignBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1).dropsLike(PALM_SIGN).burnable(), UWoodTypes.PALM));
Block PALM_HANGING_SIGN = register("palm_hanging_sign", new HangingSignBlock(Settings.create().mapColor(PALM_LOG.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1).burnable(), UWoodTypes.PALM), ItemGroups.FUNCTIONAL); Block PALM_HANGING_SIGN = register("palm_hanging_sign", new HangingSignBlock(Settings.create().mapColor(PALM_LOG.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1).burnable(), UWoodTypes.PALM));
Block PALM_WALL_HANGING_SIGN = register("palm_wall_hanging_sign", new WallHangingSignBlock(Settings.create().mapColor(PALM_LOG.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1.0f).burnable().dropsLike(PALM_HANGING_SIGN), UWoodTypes.PALM)); Block PALM_WALL_HANGING_SIGN = register("palm_wall_hanging_sign", new WallHangingSignBlock(Settings.create().mapColor(PALM_LOG.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1.0f).burnable().dropsLike(PALM_HANGING_SIGN), UWoodTypes.PALM));
Block PALM_LEAVES = register("palm_leaves", BlockConstructionUtils.createLeavesBlock(BlockSoundGroup.GRASS), ItemGroups.BUILDING_BLOCKS); Block PALM_LEAVES = register("palm_leaves", BlockConstructionUtils.createLeavesBlock(BlockSoundGroup.GRASS), ItemGroups.BUILDING_BLOCKS);

View file

@ -2,9 +2,9 @@ package com.minelittlepony.unicopia.block.cloud;
import java.util.Optional; import java.util.Optional;
import com.minelittlepony.unicopia.entity.mob.StormCloudEntity;
import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.util.PosHelper;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -82,7 +82,7 @@ public class UnstableCloudBlock extends CloudBlock {
); );
BlockPos shockPosition = lightningRodPos.or(() -> { BlockPos shockPosition = lightningRodPos.or(() -> {
return sw.getOtherEntities(entity, new Box(pos.down()).expand(5, 0, 5).stretch(0, -10, 0)).stream().findAny().map(Entity::getBlockPos); return sw.getOtherEntities(entity, new Box(pos.down()).expand(5, 0, 5).stretch(0, -10, 0)).stream().findAny().map(Entity::getBlockPos);
}).orElseGet(() -> StormCloudEntity.findSurfaceBelow(sw, pos.add(world.random.nextInt(10) - 5, -world.random.nextInt(10), world.random.nextInt(10) - 5)).toImmutable()); }).orElseGet(() -> PosHelper.findNearestSurface(sw, pos.add(world.random.nextInt(10) - 5, -world.random.nextInt(10), world.random.nextInt(10) - 5)).toImmutable());
ParticleUtils.spawnParticle(world, ParticleUtils.spawnParticle(world,
new LightningBoltParticleEffect(false, 10, 6, 0.3F, Optional.of(shockPosition.toCenterPos())), new LightningBoltParticleEffect(false, 10, 6, 0.3F, Optional.of(shockPosition.toCenterPos())),

View file

@ -56,7 +56,6 @@ import net.minecraft.client.particle.SpriteProvider;
import net.minecraft.client.render.*; import net.minecraft.client.render.*;
import net.minecraft.client.render.VertexConsumerProvider.Immediate; import net.minecraft.client.render.VertexConsumerProvider.Immediate;
import net.minecraft.client.render.block.entity.BlockEntityRendererFactories; import net.minecraft.client.render.block.entity.BlockEntityRendererFactories;
import net.minecraft.client.render.entity.EmptyEntityRenderer;
import net.minecraft.client.render.entity.FlyingItemEntityRenderer; import net.minecraft.client.render.entity.FlyingItemEntityRenderer;
import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.render.model.json.ModelTransformationMode; import net.minecraft.client.render.model.json.ModelTransformationMode;
@ -115,7 +114,7 @@ public interface URenderers {
EntityRendererRegistry.register(UEntities.LOOT_BUG, LootBugEntityRenderer::new); EntityRendererRegistry.register(UEntities.LOOT_BUG, LootBugEntityRenderer::new);
EntityRendererRegistry.register(UEntities.TENTACLE, TentacleEntityRenderer::new); EntityRendererRegistry.register(UEntities.TENTACLE, TentacleEntityRenderer::new);
EntityRendererRegistry.register(UEntities.IGNOMINIOUS_BULB, IgnominiousBulbEntityRenderer::new); EntityRendererRegistry.register(UEntities.IGNOMINIOUS_BULB, IgnominiousBulbEntityRenderer::new);
EntityRendererRegistry.register(UEntities.SPECTER, EmptyEntityRenderer::new); EntityRendererRegistry.register(UEntities.SPECTER, SpecterEntityRenderer::new);
EntityRendererRegistry.register(UEntities.MIMIC, MimicEntityRenderer::new); EntityRendererRegistry.register(UEntities.MIMIC, MimicEntityRenderer::new);
BlockEntityRendererFactories.register(UBlockEntities.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new); BlockEntityRendererFactories.register(UBlockEntities.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new);

View file

@ -2,14 +2,13 @@ package com.minelittlepony.unicopia.client.gui;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.joml.Vector4f; import org.joml.Vector4f;
import com.google.common.base.MoreObjects;
import com.minelittlepony.common.client.gui.GameGui; import com.minelittlepony.common.client.gui.GameGui;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.*;
import com.minelittlepony.unicopia.client.TextHelper;
import com.minelittlepony.unicopia.client.render.model.SphereModel; import com.minelittlepony.unicopia.client.render.model.SphereModel;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.UItems;
@ -41,11 +40,11 @@ public class DismissSpellScreen extends GameGui {
double azimuth = 0; double azimuth = 0;
double ring = 2; double ring = 2;
List<PlaceableSpell> placeableSpells = new ArrayList<>(); List<PlacementControlSpell> placeableSpells = new ArrayList<>();
for (Spell spell : pony.getSpellSlot().stream(true).filter(SpellPredicate.IS_VISIBLE).toList()) { for (Spell spell : pony.getSpellSlot().stream().filter(SpellPredicate.IS_VISIBLE).toList()) {
if (spell instanceof PlaceableSpell placeable) { if (spell instanceof PlacementControlSpell placeable) {
if (placeable.getPosition().isPresent()) { if (placeable.getPosition().isPresent()) {
placeableSpells.add(placeable); placeableSpells.add(placeable);
continue; continue;
@ -58,15 +57,16 @@ public class DismissSpellScreen extends GameGui {
} }
double minimalDistance = 75 * (ring - 1) - 25; double minimalDistance = 75 * (ring - 1) - 25;
Vec3d origin = pony.getOriginVector(); Vec3d origin = pony.asEntity().getPos();
placeableSpells.forEach(placeable -> { placeableSpells.forEach(placeable -> {
placeable.getPosition().ifPresent(position -> { placeable.getPosition().ifPresent(position -> {
Vec3d relativePos = position.subtract(origin); Vec3d relativePos = position.subtract(origin).multiply(1, 0, 1);
float yaw = client.gameRenderer.getCamera().getYaw();
Vec3d cartesian = relativePos Vec3d cartesian = relativePos
.normalize() .normalize()
.multiply(minimalDistance + relativePos.length()) .multiply(minimalDistance + relativePos.horizontalLength())
.rotateY((pony.asEntity().getYaw() - 180) * MathHelper.RADIANS_PER_DEGREE); .rotateY((180 + yaw) * MathHelper.RADIANS_PER_DEGREE);
addDrawableChild(new Entry(placeable).ofCartesian(cartesian)); addDrawableChild(new Entry(placeable).ofCartesian(cartesian));
}); });
}); });
@ -84,8 +84,8 @@ public class DismissSpellScreen extends GameGui {
matrices.push(); matrices.push();
matrices.translate(width - mouseX, height - mouseY, 0); matrices.translate(width - mouseX, height - mouseY, 0);
DrawableUtil.drawLine(matrices, 0, 0, relativeMouseX, relativeMouseY, 0xFFFFFF88); DrawableUtil.drawLine(matrices, 0, 0, relativeMouseX, relativeMouseY, 0xFFFFFF88);
DrawableUtil.drawArc(matrices, 40, 80, 0, DrawableUtil.TAU, 0x00000010, false); DrawableUtil.drawArc(matrices, 40, 80, 0, DrawableUtil.TAU, 0x00000010);
DrawableUtil.drawArc(matrices, 160, 1600, 0, DrawableUtil.TAU, 0x00000020, false); DrawableUtil.drawArc(matrices, 160, 1600, 0, DrawableUtil.TAU, 0x00000020);
super.render(context, mouseX, mouseY, delta); super.render(context, mouseX, mouseY, delta);
DrawableUtil.renderRaceIcon(context, pony.getObservedSpecies(), 0, 0, 16); DrawableUtil.renderRaceIcon(context, pony.getObservedSpecies(), 0, 0, 16);
@ -137,17 +137,16 @@ public class DismissSpellScreen extends GameGui {
} }
private Spell getActualSpell() { private Spell getActualSpell() {
if (spell instanceof AbstractDelegatingSpell) { return spell instanceof AbstractDelegatingSpell s ? MoreObjects.firstNonNull(s.getDelegate(), s)
return ((AbstractDelegatingSpell)spell).getDelegates().stream().findFirst().orElse(spell); : spell instanceof PlacementControlSpell s ? MoreObjects.firstNonNull(s.getDelegate(), s)
} : spell;
return spell;
} }
@Override @Override
public boolean mouseClicked(double mouseX, double mouseY, int button) { public boolean mouseClicked(double mouseX, double mouseY, int button) {
if (isMouseOver(relativeMouseX, relativeMouseY)) { if (isMouseOver(relativeMouseX, relativeMouseY)) {
remove(this); remove(this);
pony.getSpellSlot().removeIf(spell -> spell == this.spell, true); pony.getSpellSlot().removeIf(spell -> spell == this.spell);
Channel.REMOVE_SPELL.sendToServer(new MsgRemoveSpell(spell)); Channel.REMOVE_SPELL.sendToServer(new MsgRemoveSpell(spell));
playClickEffect(); playClickEffect();
return true; return true;
@ -164,38 +163,45 @@ public class DismissSpellScreen extends GameGui {
public void render(DrawContext context, int mouseX, int mouseY, float tickDelta) { public void render(DrawContext context, int mouseX, int mouseY, float tickDelta) {
MatrixStack matrices = context.getMatrices(); MatrixStack matrices = context.getMatrices();
var type = actualSpell.getType().withTraits(actualSpell.getTraits()); var type = actualSpell.getTypeAndTraits();
copy.set(mouseX - width * 0.5F - x * 0.5F, mouseY - height * 0.5F - y * 0.5F, 0, 0); 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, actualSpell.getAffinity().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.push();
matrices.translate(x, y, 0); matrices.translate(x, y, 0);
DrawableUtil.drawArc(matrices, 7, 8, 0, DrawableUtil.TAU, color | 0x00000088, false); matrices.push();
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(((MinecraftClient.getInstance().player.age + tickDelta) * 2) % 360));
DrawableUtil.renderItemIcon(context, actualSpell.isDead() ? UItems.BOTCHED_GEM.getDefaultStack() : type.getDefaultStack(),
-8,
-8,
1
);
matrices.pop();
if (isMouseOver(relativeMouseX, relativeMouseY)) { boolean hovered = isMouseOver(relativeMouseX, relativeMouseY);
DrawableUtil.drawArc(matrices, 0, 8, 0, DrawableUtil.TAU, color | 0x000000FF, false); double radius = (hovered ? 9 + MathHelper.sin((MinecraftClient.getInstance().player.age + tickDelta) / 9F) : 7);
DrawableUtil.drawArc(matrices, radius, radius + 1, 0, DrawableUtil.TAU, color | 0x00000088);
if (hovered) {
DrawableUtil.drawArc(matrices, 0, 8, 0, DrawableUtil.TAU, color | 0x000000FF);
List<Text> tooltip = new ArrayList<>(); List<Text> tooltip = new ArrayList<>();
MutableText name = actualSpell.getType().getName().copy(); MutableText name = type.type().getName().copy();
color = actualSpell.getType().getColor(); color = type.type().getColor();
name.setStyle(name.getStyle().withColor(color == 0 ? 0xFFAAAAAA : color)); name.setStyle(name.getStyle().withColor(color == 0 ? 0xFFAAAAAA : color));
tooltip.add(Text.translatable("gui.unicopia.dispell_screen.spell_type", name)); 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(ScreenTexts.EMPTY);
tooltip.add(Text.translatable("gui.unicopia.dispell_screen.affinity", actualSpell.getAffinity().name()).formatted(actualSpell.getAffinity().getColor())); type.appendTooltip(tooltip);
tooltip.add(ScreenTexts.EMPTY); tooltip.add(ScreenTexts.EMPTY);
tooltip.addAll(TextHelper.wrap(Text.translatable(actualSpell.getType().getTranslationKey() + ".lore").formatted(actualSpell.getAffinity().getColor()), 180).toList());
if (spell instanceof TimedSpell timed) { if (spell instanceof TimedSpell timed) {
tooltip.add(ScreenTexts.EMPTY); tooltip.add(ScreenTexts.EMPTY);
tooltip.add(Text.translatable("gui.unicopia.dispell_screen.time_left", StringHelper.formatTicks(timed.getTimer().getTicksRemaining()))); tooltip.add(Text.translatable("gui.unicopia.dispell_screen.time_left", StringHelper.formatTicks(timed.getTimer().getTicksRemaining())));

View file

@ -71,27 +71,41 @@ public interface DrawableUtil {
RenderSystem.disableBlend(); RenderSystem.disableBlend();
} }
/**
* Renders a colored arc with notches.
*
* @param mirrorHorizontally Whether or not the arc must be mirrored across the horizontal plane. Will produce a bar that grows from the middle filling both sides.
*/
static void drawNotchedArc(MatrixStack matrices, double innerRadius, double outerRadius, double startAngle, double arcAngle, double notchAngle, double notchSpacing, int color) {
double notchBegin = startAngle;
double endAngle = startAngle + arcAngle;
while (notchBegin < endAngle) {
double notchEnd = Math.min(notchBegin + notchAngle, endAngle);
if (notchEnd <= notchBegin) {
return;
}
drawArc(matrices, innerRadius, outerRadius, notchBegin, notchEnd - notchBegin, color);
notchBegin += notchAngle + notchSpacing;
}
}
/** /**
* Renders a colored arc. * Renders a colored arc.
* *
* @param mirrorHorizontally Whether or not the arc must be mirrored across the horizontal plane. Will produce a bar that grows from the middle filling both sides. * @param mirrorHorizontally Whether or not the arc must be mirrored across the horizontal plane. Will produce a bar that grows from the middle filling both sides.
*/ */
static void drawArc(MatrixStack matrices, double innerRadius, double outerRadius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally) { static void drawArc(MatrixStack matrices, double innerRadius, double outerRadius, double startAngle, double arcAngle, int color) {
if (arcAngle < INCREMENT) {
return;
}
float r = (color >> 24 & 255) / 255F; float r = (color >> 24 & 255) / 255F;
float g = (color >> 16 & 255) / 255F; float g = (color >> 16 & 255) / 255F;
float b = (color >> 8 & 255) / 255F; float b = (color >> 8 & 255) / 255F;
float k = (color & 255) / 255F; float k = (color & 255) / 255F;
if (arcAngle < INCREMENT) {
return;
}
final double maxAngle = MathHelper.clamp(startAngle + arcAngle, 0, TAU - INCREMENT); final double maxAngle = MathHelper.clamp(startAngle + arcAngle, 0, TAU - INCREMENT);
if (!mirrorHorizontally) {
startAngle = -startAngle;
}
RenderSystem.setShaderColor(1, 1, 1, 1); RenderSystem.setShaderColor(1, 1, 1, 1);
RenderSystem.setShader(GameRenderer::getPositionColorProgram); RenderSystem.setShader(GameRenderer::getPositionColorProgram);
RenderSystem.enableBlend(); RenderSystem.enableBlend();
@ -102,7 +116,7 @@ public interface DrawableUtil {
BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer(); BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer();
bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR);
for (double angle = startAngle; angle >= -maxAngle; angle -= INCREMENT) { for (double angle = -startAngle; angle >= -maxAngle; angle -= INCREMENT) {
// center // center
cylendricalVertex(bufferBuilder, model, innerRadius, angle, r, g, b, k); cylendricalVertex(bufferBuilder, model, innerRadius, angle, r, g, b, k);
// point one // point one
@ -116,70 +130,6 @@ public interface DrawableUtil {
BufferRenderer.drawWithGlobalProgram(bufferBuilder.end()); BufferRenderer.drawWithGlobalProgram(bufferBuilder.end());
} }
/**
* Renders hollow circle
*
* @param mirrorHorizontally Whether or not the arc must be mirrored across the horizontal plane. Will produce a bar that grows from the middle filling both sides.
*/
static void drawArc(MatrixStack matrices, double radius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally) {
drawCircle(matrices, radius, startAngle, arcAngle, color, mirrorHorizontally, VertexFormat.DrawMode.DEBUG_LINES);
}
/**
* Renders a filled circle.
*
* @param mirrorHorizontally Whether or not the arc must be mirrored across the horizontal plane. Will produce a bar that grows from the middle filling both sides.
*/
static void drawCircle(MatrixStack matrices, double radius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally) {
drawCircle(matrices, radius, startAngle, arcAngle, color, mirrorHorizontally, VertexFormat.DrawMode.QUADS);
}
private static void drawCircle(MatrixStack matrices, double radius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally, VertexFormat.DrawMode mode) {
float r = (color >> 24 & 255) / 255F;
float g = (color >> 16 & 255) / 255F;
float b = (color >> 8 & 255) / 255F;
float k = (color & 255) / 255F;
if (arcAngle < INCREMENT) {
return;
}
final double maxAngle = MathHelper.clamp(startAngle + arcAngle, 0, TAU - INCREMENT);
if (!mirrorHorizontally) {
startAngle = -startAngle;
}
RenderSystem.setShaderColor(1, 1, 1, 1);
RenderSystem.setShader(GameRenderer::getPositionColorProgram);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
Matrix4f model = matrices.peek().getPositionMatrix();
BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer();
bufferBuilder.begin(mode, VertexFormats.POSITION_COLOR);
boolean joinEnds = mode == VertexFormat.DrawMode.QUADS;
// center
for (double angle = startAngle; angle >= -maxAngle; angle -= INCREMENT) {
if (joinEnds) {
bufferBuilder.vertex(model, 0, 0, 0).color(r, g, b, k).next();
}
// point one
cylendricalVertex(bufferBuilder, model, radius, angle, r, g, b, k);
// point two
cylendricalVertex(bufferBuilder, model, radius, angle + INCREMENT, r, g, b, k);
if (joinEnds) {
bufferBuilder.vertex(model, 0, 0, 0).color(r, g, b, k).next();
}
}
BufferRenderer.drawWithGlobalProgram(bufferBuilder.end());
}
private static void cylendricalVertex(BufferBuilder bufferBuilder, Matrix4f model, double radius, double angle, float r, float g, float b, float k) { private static void cylendricalVertex(BufferBuilder bufferBuilder, Matrix4f model, double radius, double angle, float r, float g, float b, float k) {
bufferBuilder.vertex(model, bufferBuilder.vertex(model,
(float)(radius * MathHelper.sin((float)angle)), (float)(radius * MathHelper.sin((float)angle)),

View file

@ -7,7 +7,6 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.entity.duck.EntityDuck; import com.minelittlepony.unicopia.entity.duck.EntityDuck;
import com.minelittlepony.unicopia.entity.effect.EffectUtils;
import com.minelittlepony.unicopia.entity.effect.UEffects; import com.minelittlepony.unicopia.entity.effect.UEffects;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
@ -31,7 +30,7 @@ public class HudEffects {
private static void apply(Pony pony, float tickDelta, boolean on) { private static void apply(Pony pony, float tickDelta, boolean on) {
if (on) { if (on) {
if (!pony.asEntity().hasStatusEffect(StatusEffects.HUNGER) && EffectUtils.getAmplifier(pony.asEntity(), UEffects.FOOD_POISONING) > 0) { if (!pony.asEntity().hasStatusEffect(StatusEffects.HUNGER) && pony.asEntity().hasStatusEffect(UEffects.FOOD_POISONING)) {
addedHunger = true; addedHunger = true;
pony.asEntity().addStatusEffect(new StatusEffectInstance(StatusEffects.HUNGER, 1, 1, false, false)); pony.asEntity().addStatusEffect(new StatusEffectInstance(StatusEffects.HUNGER, 1, 1, false, false));
} }

View file

@ -1,6 +1,7 @@
package com.minelittlepony.unicopia.client.gui; package com.minelittlepony.unicopia.client.gui;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.Abilities;
import com.minelittlepony.unicopia.ability.AbilityDispatcher; import com.minelittlepony.unicopia.ability.AbilityDispatcher;
import com.minelittlepony.unicopia.ability.AbilitySlot; import com.minelittlepony.unicopia.ability.AbilitySlot;
import com.minelittlepony.unicopia.entity.player.MagicReserves; import com.minelittlepony.unicopia.entity.player.MagicReserves;
@ -21,19 +22,42 @@ class ManaRingSlot extends Slot {
@Override @Override
protected void renderContents(DrawContext context, AbilityDispatcher abilities, boolean bSwap, float tickDelta) { protected void renderContents(DrawContext context, AbilityDispatcher abilities, boolean bSwap, float tickDelta) {
MatrixStack matrices = context.getMatrices(); MatrixStack matrices = context.getMatrices();
matrices.push(); matrices.push();
matrices.translate(24.5, 25.5, 0); matrices.translate(24.125, 24.75, 0);
Pony pony = Pony.of(uHud.client.player); Pony pony = Pony.of(uHud.client.player);
MagicReserves mana = pony.getMagicalReserves(); MagicReserves mana = pony.getMagicalReserves();
double arcBegin = 0; boolean canUseSuper = Abilities.RAGE.canUse(pony.getCompositeRace()) || Abilities.RAINBOOM.canUse(pony.getCompositeRace());
arcBegin = renderRing(matrices, 17, 13, 0, mana.getMana(), 0xFF88FF99, tickDelta); double maxManaBarSize = canUseSuper ? DrawableUtil.PI : DrawableUtil.TAU;
double arcBegin = renderRing(matrices, 17, 13, 0, maxManaBarSize, mana.getMana(), 0xFF88FF99, tickDelta);
renderRing(matrices, 17, 13, 0, maxManaBarSize, mana.getExhaustion(), 0xFF002299, tickDelta);
if (!uHud.client.player.isCreative()) { if (!uHud.client.player.isCreative()) {
renderRing(matrices, 13, 9, 0, mana.getXp(), 0x88880099, tickDelta);
renderRing(matrices, 9, 0, 0, mana.getCharge(), 0x0000FF99, tickDelta); int level = pony.getLevel().get();
int seconds = level % 16;
int minutes = (level / 16) % 16;
int hours = level / 32;
DrawableUtil.drawNotchedArc(matrices, 10, 13, 0, hours * 0.2, 0.1, 0.1, 0x00AA88FF);
DrawableUtil.drawNotchedArc(matrices, 10, 13, hours * 0.2, minutes * 0.2, 0.1, 0.1, 0x008888AA);
DrawableUtil.drawNotchedArc(matrices, 10, 13, (hours + minutes) * 0.2, seconds * 0.2, 0.1, 0.1, 0x88880099);
level = pony.getCorruption().get();
seconds = level % 16;
minutes = (level / 16) % 16;
hours = level / 32;
DrawableUtil.drawNotchedArc(matrices, 7, 10, DrawableUtil.PI, hours * 0.2, 0.1, 0.1, 0x000088FF);
DrawableUtil.drawNotchedArc(matrices, 7, 10, hours * 0.2 + DrawableUtil.PI, minutes * 0.2, 0.1, 0.1, 0x000088AA);
DrawableUtil.drawNotchedArc(matrices, 7, 10, (hours + minutes) * 0.2 + DrawableUtil.PI, seconds * 0.2, 0.1, 0.1, 0x00008899);
if (canUseSuper) {
renderRing(matrices, 17, 13, Math.min(arcBegin, DrawableUtil.PI), Math.max(DrawableUtil.PI, DrawableUtil.TAU - arcBegin), mana.getCharge(), 0x88FF9999, tickDelta);
}
double cost = abilities.getStats().stream() double cost = abilities.getStats().stream()
.mapToDouble(s -> s.getCost(Unicopia.getConfig().hudPage.get())) .mapToDouble(s -> s.getCost(Unicopia.getConfig().hudPage.get()))
@ -53,28 +77,25 @@ class ManaRingSlot extends Slot {
double angle = cost * Math.PI * 2; double angle = cost * Math.PI * 2;
DrawableUtil.drawArc(matrices, 13, 17, arcBegin - angle, angle, color, false); DrawableUtil.drawArc(matrices, 13, 17, arcBegin - angle, angle, color);
} }
} }
arcBegin = renderRing(matrices, 17, 13, arcBegin, mana.getExhaustion(), 0xFF002299, tickDelta);
matrices.pop(); matrices.pop();
super.renderContents(context, abilities, bSwap, tickDelta); super.renderContents(context, abilities, bSwap, tickDelta);
} }
private double renderRing(MatrixStack matrices, double outerRadius, double innerRadius, double offsetAngle, Bar bar, int color, float tickDelta) { private double renderRing(MatrixStack matrices, double outerRadius, double innerRadius, double offsetAngle, double maxAngle, Bar bar, int color, float tickDelta) {
double fill = bar.getPercentFill(tickDelta) * DrawableUtil.TAU; double fill = bar.getPercentFill(tickDelta) * maxAngle;
double shadow = bar.getShadowFill(tickDelta) * DrawableUtil.TAU; double shadow = bar.getShadowFill(tickDelta) * maxAngle;
DrawableUtil.drawArc(matrices, innerRadius, outerRadius, offsetAngle, fill, color, true); DrawableUtil.drawArc(matrices, innerRadius, outerRadius, offsetAngle, fill, color);
if (shadow > fill) { if (shadow > fill) {
color = (color & 0xFFFFFF00) color = (color & 0xFFFFFF00)
| ((color & 0x000000FF) / 2); | ((color & 0x000000FF) / 2);
DrawableUtil.drawArc(matrices, innerRadius, outerRadius, offsetAngle + fill, shadow - fill, color, false); DrawableUtil.drawArc(matrices, innerRadius, outerRadius, offsetAngle + fill, shadow - fill, color);
} }
return offsetAngle + fill; return offsetAngle + fill;
} }

View file

@ -89,11 +89,16 @@ class Slot {
bSwap &= abilities.isFilled(bSlot); bSwap &= abilities.isFilled(bSlot);
} }
int page = Unicopia.getConfig().hudPage.get();
AbilityDispatcher.Stat stat = abilities.getStat(bSwap ? bSlot : aSlot); AbilityDispatcher.Stat stat = abilities.getStat(bSwap ? bSlot : aSlot);
if (stat.getAbility(Unicopia.getConfig().hudPage.get()).isEmpty()) { if (stat.getAbility(page).isEmpty()) {
if (aSlot != AbilitySlot.PRIMARY
|| (!abilities.getStat(AbilitySlot.SECONDARY).getAbility(page).isEmpty()
&& !abilities.getStat(AbilitySlot.TERTIARY).getAbility(page).isEmpty())) {
return; return;
} }
}
RenderSystem.setShaderColor(1, 1, 1, 1); RenderSystem.setShaderColor(1, 1, 1, 1);
RenderSystem.enableBlend(); RenderSystem.enableBlend();
@ -104,8 +109,6 @@ class Slot {
// background // background
context.drawTexture(UHud.HUD_TEXTURE, 0, 0, backgroundU, backgroundV, size, size, 128, 128); context.drawTexture(UHud.HUD_TEXTURE, 0, 0, backgroundU, backgroundV, size, size, 128, 128);
int iconPosition = ((size - iconSize + slotPadding + 1) / 2); int iconPosition = ((size - iconSize + slotPadding + 1) / 2);
int sz = iconSize - slotPadding; int sz = iconSize - slotPadding;
uHud.renderAbilityIcon(context, stat, iconPosition, iconPosition, sz, sz, sz, sz); uHud.renderAbilityIcon(context, stat, iconPosition, iconPosition, sz, sz, sz, sz);

View file

@ -33,13 +33,13 @@ public interface SpellIconRenderer {
int color = spell.type().getColor() | 0x000000FF; int color = spell.type().getColor() | 0x000000FF;
double radius = (1.5F + Math.sin(client.player.age / 9D) / 4) * ringScale; double radius = (1.5F + Math.sin(client.player.age / 9D) / 4) * ringScale;
DrawableUtil.drawArc(modelStack, radius, radius + 3, 0, DrawableUtil.TAU, color & 0xFFFFFF2F, false); DrawableUtil.drawArc(modelStack, radius, radius + 3, 0, DrawableUtil.TAU, color & 0xFFFFFF2F);
DrawableUtil.drawArc(modelStack, radius + 3, radius + 4, 0, DrawableUtil.TAU, color & 0xFFFFFFAF, false); DrawableUtil.drawArc(modelStack, radius + 3, radius + 4, 0, DrawableUtil.TAU, color & 0xFFFFFFAF);
pony.getSpellSlot().get(spell.and(SpellPredicate.IS_TIMED), false).map(TimedSpell::getTimer).ifPresent(timer -> { pony.getSpellSlot().get(spell.and(SpellPredicate.IS_TIMED)).map(TimedSpell::getTimer).ifPresent(timer -> {
DrawableUtil.drawArc(modelStack, radius, radius + 3, 0, DrawableUtil.TAU * timer.getPercentTimeRemaining(client.getTickDelta()), 0xFFFFFFFF, false); DrawableUtil.drawArc(modelStack, radius, radius + 3, 0, DrawableUtil.TAU * timer.getPercentTimeRemaining(client.getTickDelta()), 0xFFFFFFFF);
}); });
long count = pony.getSpellSlot().stream(spell, false).count(); long count = pony.getSpellSlot().stream(spell).count();
if (count > 1) { if (count > 1) {
modelStack.push(); modelStack.push();
modelStack.translate(1, 1, 900); modelStack.translate(1, 1, 900);

View file

@ -101,7 +101,7 @@ public class UHud {
float flapCooldown = pony.getPhysics().getFlapCooldown(tickDelta); float flapCooldown = pony.getPhysics().getFlapCooldown(tickDelta);
if (flapCooldown > 0) { if (flapCooldown > 0) {
float angle = MathHelper.TAU * flapCooldown; float angle = MathHelper.TAU * flapCooldown;
DrawableUtil.drawArc(context.getMatrices(), 3, 6, -angle / 2F, angle, 0x888888AF, false); DrawableUtil.drawArc(context.getMatrices(), 3, 6, -angle / 2F, angle, 0x888888AF);
} }
matrices.pop(); matrices.pop();
@ -205,7 +205,7 @@ public class UHud {
context.fill(RenderLayers.getEndPortal(), 0, 0, scaledWidth, scaledHeight, 0); context.fill(RenderLayers.getEndPortal(), 0, 0, scaledWidth, scaledHeight, 0);
context.getMatrices().push(); context.getMatrices().push();
context.getMatrices().translate(scaledWidth / 2, scaledHeight / 2, 0); context.getMatrices().translate(scaledWidth / 2, scaledHeight / 2, 0);
DrawableUtil.drawArc(context.getMatrices(), 0, 20, 0, MathHelper.TAU, 0x000000FF, false); DrawableUtil.drawArc(context.getMatrices(), 0, 20, 0, MathHelper.TAU, 0x000000FF);
context.getMatrices().pop(); context.getMatrices().pop();
return; return;
} else if (vortexDistortion > 0) { } else if (vortexDistortion > 0) {
@ -214,8 +214,8 @@ public class UHud {
boolean hasEffect = client.player.hasStatusEffect(UEffects.SUN_BLINDNESS); boolean hasEffect = client.player.hasStatusEffect(UEffects.SUN_BLINDNESS);
ItemStack glasses = GlassesItem.getForEntity(client.player); ItemStack glasses = GlassesItem.getForEntity(client.player).stack();
boolean hasSunglasses = glasses.getItem() == UItems.SUNGLASSES; boolean hasSunglasses = glasses.isOf(UItems.SUNGLASSES);
if (hasEffect || (!hasSunglasses && pony.getObservedSpecies() == Race.BAT && SunBlindnessStatusEffect.hasSunExposure(client.player))) { if (hasEffect || (!hasSunglasses && pony.getObservedSpecies() == Race.BAT && SunBlindnessStatusEffect.hasSunExposure(client.player))) {
float i = hasEffect ? (client.player.getStatusEffect(UEffects.SUN_BLINDNESS).getDuration() - tickDelta) / SunBlindnessStatusEffect.MAX_DURATION : 0; float i = hasEffect ? (client.player.getStatusEffect(UEffects.SUN_BLINDNESS).getDuration() - tickDelta) / SunBlindnessStatusEffect.MAX_DURATION : 0;

View file

@ -18,6 +18,8 @@ import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.item.TooltipContext; import net.minecraft.client.item.TooltipContext;
import net.minecraft.client.render.*; import net.minecraft.client.render.*;
import com.minelittlepony.unicopia.client.render.RenderLayers; import com.minelittlepony.unicopia.client.render.RenderLayers;
import com.minelittlepony.unicopia.container.SpellbookState;
import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.render.model.json.ModelTransformationMode; import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
@ -324,7 +326,7 @@ public class IngredientTree implements SpellbookRecipe.CraftingTreeBuilder {
@Override @Override
public void onClick() { public void onClick() {
if (MinecraftClient.getInstance().currentScreen instanceof SpellbookScreen spellbook) { if (MinecraftClient.getInstance().currentScreen instanceof SpellbookScreen spellbook) {
spellbook.getState().setCurrentPageId(SpellbookChapterList.TRAIT_DEX_ID); spellbook.getState().setCurrentPageId(SpellbookState.TRAIT_DEX_ID);
spellbook.getTraitDex().pageTo(spellbook, trait); spellbook.getTraitDex().pageTo(spellbook, trait);
} }
} }

View file

@ -6,16 +6,10 @@ import java.util.stream.Stream;
import com.minelittlepony.common.client.gui.IViewRoot; import com.minelittlepony.common.client.gui.IViewRoot;
import com.minelittlepony.unicopia.Debug; import com.minelittlepony.unicopia.Debug;
import com.minelittlepony.unicopia.Unicopia;
import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.DrawContext;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public class SpellbookChapterList { public class SpellbookChapterList {
public static final Identifier CRAFTING_ID = Unicopia.id("crafting");
public static final Identifier PROFILE_ID = Unicopia.id("profile");
public static final Identifier TRAIT_DEX_ID = Unicopia.id("traits");
private final SpellbookScreen screen; private final SpellbookScreen screen;
private final Chapter craftingChapter; private final Chapter craftingChapter;
@ -73,10 +67,6 @@ public class SpellbookChapterList {
default void copyStateFrom(Content old) {} default void copyStateFrom(Content old) {}
default boolean showInventory() {
return false;
}
default Identifier getIcon(Chapter chapter, Identifier icon) { default Identifier getIcon(Chapter chapter, Identifier icon) {
return icon; return icon;
} }

View file

@ -59,11 +59,6 @@ public class SpellbookCraftingPageContent extends ScrollContainer implements Spe
init(this::initPageContent); init(this::initPageContent);
} }
@Override
public boolean showInventory() {
return state.getOffset() == 0;
}
private void initPageContent() { private void initPageContent() {
getContentPadding().setVertical(10); getContentPadding().setVertical(10);
getContentPadding().bottom = 30; getContentPadding().bottom = 30;

View file

@ -58,11 +58,6 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content
} }
} }
@Override
public boolean showInventory() {
return true;
}
@Override @Override
public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) { public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) {
@ -110,13 +105,13 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content
int color = 0x10404000 | alpha; int color = 0x10404000 | alpha;
int xpColor = 0xAA0040FF | ((int)((0.3F + 0.7F * xpPercentage) * 0xFF) & 0xFF) << 16; int xpColor = 0xAA0040FF | ((int)((0.3F + 0.7F * xpPercentage) * 0xFF) & 0xFF) << 16;
int manaColor = 0xFF00F040; int manaColor = 0xFF00F040;
if (pony.getSpellSlot().get(SpellPredicate.IS_CORRUPTING, false).isPresent()) { if (pony.getSpellSlot().get(SpellPredicate.IS_CORRUPTING).isPresent()) {
manaColor = ColorHelper.lerp(Math.abs(MathHelper.sin(pony.asEntity().age / 15F)), manaColor, 0xFF0030F0); manaColor = ColorHelper.lerp(Math.abs(MathHelper.sin(pony.asEntity().age / 15F)), manaColor, 0xFF0030F0);
} }
manaColor |= (int)((0.3F + 0.7F * alphaF) * 0x40) << 16; manaColor |= (int)((0.3F + 0.7F * alphaF) * 0x40) << 16;
DrawableUtil.drawArc(matrices, 0, radius + 24, 0, DrawableUtil.TAU, color, false); DrawableUtil.drawArc(matrices, 0, radius + 24, 0, DrawableUtil.TAU, color);
DrawableUtil.drawArc(matrices, radius / 3, radius + 6, 0, DrawableUtil.TAU, color, false); DrawableUtil.drawArc(matrices, radius / 3, radius + 6, 0, DrawableUtil.TAU, color);
if (currentLevel >= pony.getLevel().getMax()) { if (currentLevel >= pony.getLevel().getMax()) {
int rayCount = 6; int rayCount = 6;
@ -134,14 +129,14 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content
double rad = (radius + glowSize) * 0.8F + growth - (i % 2) * 5; double rad = (radius + glowSize) * 0.8F + growth - (i % 2) * 5;
float rot = (rotate + raySeparation * i) % MathHelper.TAU; float rot = (rotate + raySeparation * i) % MathHelper.TAU;
DrawableUtil.drawArc(matrices, 0, rad, rot, 0.2F, bandAColor, false); DrawableUtil.drawArc(matrices, 0, rad, rot, 0.2F, bandAColor);
DrawableUtil.drawArc(matrices, 0, rad + 0.3F, rot + 0.37F, 0.25F, bandBColor, false); DrawableUtil.drawArc(matrices, 0, rad + 0.3F, rot + 0.37F, 0.25F, bandBColor);
} }
} }
DrawableUtil.drawArc(matrices, radius / 3, radius + 6, 0, xpPercentage * DrawableUtil.TAU, xpColor, false); DrawableUtil.drawArc(matrices, radius / 3, radius + 6, 0, xpPercentage * DrawableUtil.TAU, xpColor);
radius += 8; radius += 8;
DrawableUtil.drawArc(matrices, radius, radius + 6 + growth, 0, manaPercentage * DrawableUtil.TAU, manaColor, false); DrawableUtil.drawArc(matrices, radius, radius + 6 + growth, 0, manaPercentage * DrawableUtil.TAU, manaColor);
String manaString = (int)reserves.getMana().get() + "/" + (int)reserves.getMana().getMax(); String manaString = (int)reserves.getMana().get() + "/" + (int)reserves.getMana().getMax();

View file

@ -14,7 +14,6 @@ import com.minelittlepony.unicopia.Debug;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.client.TextHelper;
import com.minelittlepony.unicopia.client.gui.*; import com.minelittlepony.unicopia.client.gui.*;
import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.*; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.*;
import com.minelittlepony.unicopia.compat.trinkets.TrinketSlotBackSprites; import com.minelittlepony.unicopia.compat.trinkets.TrinketSlotBackSprites;
@ -52,12 +51,11 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
private final RecipeBookWidget recipeBook = new RecipeBookWidget(); private final RecipeBookWidget recipeBook = new RecipeBookWidget();
private final Chapter craftingChapter;
private final SpellbookTraitDexPageContent traitDex = new SpellbookTraitDexPageContent(this); private final SpellbookTraitDexPageContent traitDex = new SpellbookTraitDexPageContent(this);
private final SpellbookChapterList chapters = new SpellbookChapterList(this, private final SpellbookChapterList chapters = new SpellbookChapterList(this,
craftingChapter = new Chapter(SpellbookChapterList.CRAFTING_ID, TabSide.LEFT, 0, 0, Optional.of(new SpellbookCraftingPageContent(this))), new Chapter(SpellbookState.CRAFTING_ID, TabSide.LEFT, 0, 0, Optional.of(new SpellbookCraftingPageContent(this))),
new Chapter(SpellbookChapterList.PROFILE_ID, TabSide.LEFT, 1, 0, Optional.of(new SpellbookProfilePageContent(this))), new Chapter(SpellbookState.PROFILE_ID, TabSide.LEFT, 1, 0, Optional.of(new SpellbookProfilePageContent(this))),
new Chapter(SpellbookChapterList.TRAIT_DEX_ID, TabSide.LEFT, 3, 0, Optional.of(traitDex)) new Chapter(SpellbookState.TRAIT_DEX_ID, TabSide.LEFT, 3, 0, Optional.of(traitDex))
); );
private final SpellbookTabBar tabs = new SpellbookTabBar(this, chapters); private final SpellbookTabBar tabs = new SpellbookTabBar(this, chapters);
@ -68,13 +66,6 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
backgroundWidth = 405; backgroundWidth = 405;
backgroundHeight = 219; backgroundHeight = 219;
contentBounds = new Bounds(CONTENT_PADDING, CONTENT_PADDING, backgroundWidth - CONTENT_PADDING * 2, backgroundHeight - CONTENT_PADDING * 3 - 2); contentBounds = new Bounds(CONTENT_PADDING, CONTENT_PADDING, backgroundWidth - CONTENT_PADDING * 2, backgroundHeight - CONTENT_PADDING * 3 - 2);
handler.addSlotShowingCondition(slotType -> {
if (slotType == SlotType.INVENTORY) {
return chapters.getCurrentChapter().content().filter(Content::showInventory).isPresent();
}
return chapters.getCurrentChapter() == craftingChapter;
});
handler.getSpellbookState().setSynchronizer(state -> { handler.getSpellbookState().setSynchronizer(state -> {
Channel.CLIENT_SPELLBOOK_UPDATE.sendToServer(MsgSpellbookStateChanged.create(handler, state)); Channel.CLIENT_SPELLBOOK_UPDATE.sendToServer(MsgSpellbookStateChanged.create(handler, state));
}); });
@ -218,8 +209,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
List<Text> tooltip = new ArrayList<>(); List<Text> tooltip = new ArrayList<>();
tooltip.add(spell.type().getName()); tooltip.add(spell.type().getName());
tooltip.addAll(TextHelper.wrap(Text.translatable(spell.type().getTranslationKey() + ".lore").formatted(spell.type().getAffinity().getColor()), 180).toList()); spell.appendTooltip(tooltip);
context.drawTooltip(textRenderer, tooltip, x, y); context.drawTooltip(textRenderer, tooltip, x, y);
context.getMatrices().pop(); context.getMatrices().pop();

View file

@ -73,7 +73,7 @@ public class SpellbookTraitDexPageContent implements SpellbookChapterList.Conten
return; return;
} }
page /= 2; page /= 2;
state = screen.getState().getState(SpellbookChapterList.TRAIT_DEX_ID); state = screen.getState().getState(SpellbookState.TRAIT_DEX_ID);
state.setOffset(page); state.setOffset(page);
leftPage.verticalScrollbar.scrollBy(leftPage.verticalScrollbar.getScrubber().getPosition()); leftPage.verticalScrollbar.scrollBy(leftPage.verticalScrollbar.getScrubber().getPosition());
rightPage.verticalScrollbar.scrollBy(rightPage.verticalScrollbar.getScrubber().getPosition()); rightPage.verticalScrollbar.scrollBy(rightPage.verticalScrollbar.getScrubber().getPosition());
@ -84,7 +84,7 @@ public class SpellbookTraitDexPageContent implements SpellbookChapterList.Conten
@Override @Override
public void onRecipesChanged() { public void onRecipesChanged() {
init(screen, SpellbookChapterList.TRAIT_DEX_ID); init(screen, SpellbookState.TRAIT_DEX_ID);
} }
private final class DexPage extends ScrollContainer { private final class DexPage extends ScrollContainer {

View file

@ -30,7 +30,7 @@ class AmuletGear extends AmuletModel implements Gear {
@Override @Override
public boolean canRender(PonyModel<?> model, Entity entity) { public boolean canRender(PonyModel<?> model, Entity entity) {
return entity instanceof LivingEntity living && !AmuletItem.getForEntity(living).isEmpty(); return entity instanceof LivingEntity living && !AmuletItem.get(living).stack().isEmpty();
} }
@Override @Override
@ -40,7 +40,7 @@ class AmuletGear extends AmuletModel implements Gear {
@Override @Override
public <T extends Entity> Identifier getTexture(T entity, Context<T, ?> context) { public <T extends Entity> Identifier getTexture(T entity, Context<T, ?> context) {
return textures.computeIfAbsent(Registries.ITEM.getId(AmuletItem.getForEntity((LivingEntity)entity).getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png")); return textures.computeIfAbsent(Registries.ITEM.getId(AmuletItem.get((LivingEntity)entity).stack().getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png"));
} }
@Override @Override

View file

@ -67,8 +67,8 @@ class BangleGear implements Gear {
public void pose(PonyModel<?> model, Entity entity, boolean rainboom, UUID interpolatorId, float move, float swing, float bodySwing, float ticks) { public void pose(PonyModel<?> model, Entity entity, boolean rainboom, UUID interpolatorId, float move, float swing, float bodySwing, float ticks) {
alex = entity instanceof ClientPlayerEntity && ((ClientPlayerEntity)entity).getSkinTextures().model() == Model.SLIM; alex = entity instanceof ClientPlayerEntity && ((ClientPlayerEntity)entity).getSkinTextures().model() == Model.SLIM;
FriendshipBraceletItem.getWornBangles((LivingEntity)entity, slot).findFirst().ifPresent(bracelet -> { FriendshipBraceletItem.getWornBangles((LivingEntity)entity, slot).findFirst().ifPresent(bracelet -> {
color = ((DyeableItem)bracelet.getItem()).getColor(bracelet); color = ((DyeableItem)bracelet.stack().getItem()).getColor(bracelet.stack());
glowing = ((GlowableItem)bracelet.getItem()).isGlowing(bracelet); glowing = ((GlowableItem)bracelet.stack().getItem()).isGlowing(bracelet.stack());
}); });
BraceletModel m = alex ? alexModel : steveModel; BraceletModel m = alex ? alexModel : steveModel;
@ -76,7 +76,7 @@ class BangleGear implements Gear {
m.setAngles(biped); m.setAngles(biped);
} }
Arm mainArm = ((LivingEntity)entity).getMainArm(); Arm mainArm = ((LivingEntity)entity).getMainArm();
m.setVisible(slot == TrinketsDelegate.MAINHAND ? mainArm : mainArm.getOpposite()); m.setVisible(slot == TrinketsDelegate.MAIN_GLOVE ? mainArm : mainArm.getOpposite());
} }
@Override @Override

View file

@ -28,7 +28,7 @@ class GlassesGear extends GlassesModel implements Gear {
@Override @Override
public boolean canRender(PonyModel<?> model, Entity entity) { public boolean canRender(PonyModel<?> model, Entity entity) {
return entity instanceof LivingEntity living && !GlassesItem.getForEntity(living).isEmpty(); return entity instanceof LivingEntity living && !GlassesItem.getForEntity(living).stack().isEmpty();
} }
@Override @Override
@ -38,7 +38,7 @@ class GlassesGear extends GlassesModel implements Gear {
@Override @Override
public <T extends Entity> Identifier getTexture(T entity, Context<T, ?> context) { public <T extends Entity> Identifier getTexture(T entity, Context<T, ?> context) {
return textures.computeIfAbsent(Registries.ITEM.getId(GlassesItem.getForEntity((LivingEntity)entity).getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png")); return textures.computeIfAbsent(Registries.ITEM.getId(GlassesItem.getForEntity((LivingEntity)entity).stack().getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png"));
} }
@Override @Override

View file

@ -43,8 +43,8 @@ public class Main extends MineLPDelegate implements ClientModInitializer {
public void onInitializeClient() { public void onInitializeClient() {
INSTANCE = this; INSTANCE = this;
PonyModelPrepareCallback.EVENT.register(this::onPonyModelPrepared); PonyModelPrepareCallback.EVENT.register(this::onPonyModelPrepared);
Gear.register(() -> new BangleGear(TrinketsDelegate.MAINHAND)); Gear.register(() -> new BangleGear(TrinketsDelegate.MAIN_GLOVE));
Gear.register(() -> new BangleGear(TrinketsDelegate.OFFHAND)); Gear.register(() -> new BangleGear(TrinketsDelegate.SECONDARY_GLOVE));
Gear.register(HeldEntityGear::new); Gear.register(HeldEntityGear::new);
Gear.register(BodyPartGear::pegasusWings); Gear.register(BodyPartGear::pegasusWings);
Gear.register(BodyPartGear::batWings); Gear.register(BodyPartGear::batWings);

View file

@ -89,7 +89,8 @@ public class WindParticle extends AbstractBillboardParticle {
trail.update(new Vec3d(x + cos, y + sin, z - cos)); trail.update(new Vec3d(x + cos, y + sin, z - cos));
} else { } else {
if (target != null && target.isAlive()) { if (target != null && target.isAlive()) {
trail.update(target.getPos().add(offset).add(cos, sin, -cos));
trail.update(target.getPos().add(target.getRotationVecClient().multiply(-7)).add(offset).add(cos, sin, -cos));
if (attachmentTicks > 0 && --attachmentTicks <= 0) { if (attachmentTicks > 0 && --attachmentTicks <= 0) {
target = null; target = null;

View file

@ -42,7 +42,7 @@ public class AmuletFeatureRenderer<E extends LivingEntity> implements AccessoryF
@Override @Override
public void render(MatrixStack matrices, VertexConsumerProvider renderContext, int lightUv, E entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch) { public void render(MatrixStack matrices, VertexConsumerProvider renderContext, int lightUv, E entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch) {
ItemStack stack = AmuletItem.getForEntity(entity); ItemStack stack = AmuletItem.get(entity).stack();
if (!stack.isEmpty()) { if (!stack.isEmpty()) {
Identifier texture = textures.computeIfAbsent(Registries.ITEM.getId(stack.getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png")); Identifier texture = textures.computeIfAbsent(Registries.ITEM.getId(stack.getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png"));

View file

@ -49,11 +49,11 @@ public class BraceletFeatureRenderer<E extends LivingEntity> implements Accessor
@Override @Override
public void render(MatrixStack stack, VertexConsumerProvider renderContext, int lightUv, E entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch) { public void render(MatrixStack stack, VertexConsumerProvider renderContext, int lightUv, E entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch) {
FriendshipBraceletItem.getWornBangles(entity, TrinketsDelegate.MAINHAND).findFirst().ifPresent(bangle -> { FriendshipBraceletItem.getWornBangles(entity, TrinketsDelegate.MAIN_GLOVE).findFirst().ifPresent(bangle -> {
renderBangleThirdPerson(bangle, stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch, entity.getMainArm()); renderBangleThirdPerson(bangle.stack(), stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch, entity.getMainArm());
}); });
FriendshipBraceletItem.getWornBangles(entity, TrinketsDelegate.OFFHAND).findFirst().ifPresent(bangle -> { FriendshipBraceletItem.getWornBangles(entity, TrinketsDelegate.SECONDARY_GLOVE).findFirst().ifPresent(bangle -> {
renderBangleThirdPerson(bangle, stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch, entity.getMainArm().getOpposite()); renderBangleThirdPerson(bangle.stack(), stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch, entity.getMainArm().getOpposite());
}); });
} }
@ -83,14 +83,14 @@ public class BraceletFeatureRenderer<E extends LivingEntity> implements Accessor
@Override @Override
public void renderArm(MatrixStack stack, VertexConsumerProvider renderContext, int lightUv, E entity, ModelPart armModel, Arm side) { public void renderArm(MatrixStack stack, VertexConsumerProvider renderContext, int lightUv, E entity, ModelPart armModel, Arm side) {
FriendshipBraceletItem.getWornBangles(entity, side == entity.getMainArm() ? TrinketsDelegate.MAINHAND : TrinketsDelegate.OFFHAND).findFirst().ifPresent(item -> { FriendshipBraceletItem.getWornBangles(entity, side == entity.getMainArm() ? TrinketsDelegate.MAIN_GLOVE : TrinketsDelegate.SECONDARY_GLOVE).findFirst().ifPresent(item -> {
int j = ((DyeableItem)item.getItem()).getColor(item); int j = ((DyeableItem)item.stack().getItem()).getColor(item.stack());
boolean alex = entity instanceof ClientPlayerEntity && ((ClientPlayerEntity)entity).getSkinTextures().model() == SkinTextures.Model.SLIM; boolean alex = entity instanceof ClientPlayerEntity && ((ClientPlayerEntity)entity).getSkinTextures().model() == SkinTextures.Model.SLIM;
BraceletModel model = alex ? alexModel : steveModel; BraceletModel model = alex ? alexModel : steveModel;
boolean glowing = ((GlowableItem)item.getItem()).isGlowing(item); boolean glowing = ((GlowableItem)item.stack().getItem()).isGlowing(item.stack());
if (MineLPDelegate.getInstance().getPlayerPonyRace((ClientPlayerEntity)entity).isEquine()) { if (MineLPDelegate.getInstance().getPlayerPonyRace((ClientPlayerEntity)entity).isEquine()) {
stack.translate(side == Arm.LEFT ? 0.06 : -0.06, 0.3, 0); stack.translate(side == Arm.LEFT ? 0.06 : -0.06, 0.3, 0);

View file

@ -114,7 +114,7 @@ public class DisguisedArmsFeatureRenderer<E extends LivingEntity> implements Acc
} }
private Entity getAppearance(E entity) { private Entity getAppearance(E entity) {
return Caster.of(entity).flatMap(caster -> caster.getSpellSlot().get(SpellPredicate.IS_DISGUISE, false)).map(Disguise.class::cast) return Caster.of(entity).flatMap(caster -> caster.getSpellSlot().get(SpellPredicate.IS_DISGUISE)).map(Disguise.class::cast)
.flatMap(Disguise::getAppearance) .flatMap(Disguise::getAppearance)
.map(EntityAppearance::getAppearance) .map(EntityAppearance::getAppearance)
.orElse(null); .orElse(null);

View file

@ -9,20 +9,19 @@ import com.minelittlepony.unicopia.entity.behaviour.Disguise;
import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance;
import com.minelittlepony.unicopia.mixin.MixinBlockEntity; import com.minelittlepony.unicopia.mixin.MixinBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockRenderType;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.block.entity.BlockEntityRenderer; import net.minecraft.client.render.block.entity.BlockEntityRenderer;
import net.minecraft.client.render.entity.EntityRenderDispatcher;
import net.minecraft.client.render.entity.LivingEntityRenderer; import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.model.BipedEntityModel; import net.minecraft.client.render.entity.model.BipedEntityModel;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.state.property.Properties; import net.minecraft.entity.FallingBlockEntity;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RotationAxis;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
class EntityDisguiseRenderer { class EntityDisguiseRenderer {
@ -37,7 +36,9 @@ class EntityDisguiseRenderer {
double x, double y, double z, double x, double y, double z,
float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) { float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) {
int fireTicks = pony.asEntity().doesRenderOnFire() ? 1 : 0; int fireTicks = pony.asEntity().doesRenderOnFire() ? 1 : 0;
if (!delegate.client.isPaused()) {
disguise.update(pony, false); disguise.update(pony, false);
}
EntityAppearance ve = disguise.getDisguise(); EntityAppearance ve = disguise.getDisguise();
Entity e = ve.getAppearance(); Entity e = ve.getAppearance();
@ -53,11 +54,11 @@ class EntityDisguiseRenderer {
} }
render(ve, e, x, y, z, fireTicks, tickDelta, matrices, vertices, light); render(ve, e, x, y, z, fireTicks, tickDelta, matrices, vertices, light);
ve.getAttachments().forEach(ee -> { ve.getAttachments().forEach(attachment -> {
PehkUtil.copyScale(pony.asEntity(), ee); PehkUtil.copyScale(pony.asEntity(), attachment.entity());
Vec3d difference = ee.getPos().subtract(e.getPos()); Vec3d difference = attachment.entity().getPos().subtract(e.getPos());
render(ve, ee, x + difference.x, y + difference.y, z + difference.z, fireTicks, tickDelta, matrices, vertices, light); render(ve, attachment.entity(), x + difference.x, y + difference.y, z + difference.z, fireTicks, tickDelta, matrices, vertices, light);
PehkUtil.clearScale(ee); PehkUtil.clearScale(attachment.entity());
}); });
matrices.push(); matrices.push();
@ -70,6 +71,7 @@ class EntityDisguiseRenderer {
return true; return true;
} }
@SuppressWarnings("deprecation")
private void render(EntityAppearance ve, Entity e, private void render(EntityAppearance ve, Entity e,
double x, double y, double z, double x, double y, double z,
int fireTicks, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light) { int fireTicks, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light) {
@ -88,26 +90,24 @@ class EntityDisguiseRenderer {
BlockEntityRenderer<BlockEntity> r = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(blockEntity); BlockEntityRenderer<BlockEntity> r = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(blockEntity);
if (r != null) { if (r != null) {
((MixinBlockEntity)blockEntity).setPos(e.getBlockPos()); ((MixinBlockEntity)blockEntity).setPos(e.getBlockPos());
if (e instanceof FallingBlockEntity fbe) {
blockEntity.setCachedState(fbe.getBlockState());
}
blockEntity.setWorld(e.getWorld()); blockEntity.setWorld(e.getWorld());
matrices.push(); matrices.push();
BlockState state = blockEntity.getCachedState();
Direction direction = state.contains(Properties.HORIZONTAL_FACING) ? state.get(Properties.HORIZONTAL_FACING) : Direction.UP;
matrices.translate(x, y, z); matrices.translate(x, y, z);
matrices.multiply(direction.getRotationQuaternion());
matrices.multiply(RotationAxis.NEGATIVE_X.rotationDegrees(90));
matrices.translate(-0.5, 0, -0.5); matrices.translate(-0.5, 0, -0.5);
r.render(blockEntity, 1, matrices, vertexConsumers, light, OverlayTexture.DEFAULT_UV); r.render(blockEntity, 1, matrices, vertexConsumers, light, OverlayTexture.DEFAULT_UV);
matrices.pop(); matrices.pop();
blockEntity.setWorld(null);
BlockRenderType type = blockEntity.getCachedState().getRenderType();
if (type == BlockRenderType.ENTITYBLOCK_ANIMATED) {
return; return;
} }
} }
}
BipedEntityModel<?> model = getBipedModel(e); BipedEntityModel<?> model = getBipedModel(e);
@ -116,7 +116,15 @@ class EntityDisguiseRenderer {
} }
e.setFireTicks(fireTicks); e.setFireTicks(fireTicks);
delegate.client.getEntityRenderDispatcher().render(e, x, y, z, e.getYaw(), tickDelta, matrices, vertexConsumers, light);
EntityRenderDispatcher dispatcher = delegate.client.getEntityRenderDispatcher();
if (e instanceof FallingBlockEntity) {
dispatcher.setRenderShadows(false);
}
dispatcher.render(e, x, y, z, e.getYaw(), tickDelta, matrices, vertexConsumers, light);
if (e instanceof FallingBlockEntity) {
dispatcher.setRenderShadows(true);
}
e.setFireTicks(0); e.setFireTicks(0);
if (model != null) { if (model != null) {

View file

@ -51,7 +51,7 @@ class EntityReplacementManager implements Disguise {
return Optional.of(this); return Optional.of(this);
} }
return caster.getSpellSlot().get(SpellPredicate.IS_DISGUISE, false).map(Disguise.class::cast); return caster.getSpellSlot().get(SpellPredicate.IS_DISGUISE).map(Disguise.class::cast);
} }
private List<EntityType<?>> getMobTypePool(EntityType<?> type) { private List<EntityType<?>> getMobTypePool(EntityType<?> type) {

View file

@ -36,7 +36,7 @@ public class GlassesFeatureRenderer<E extends LivingEntity> implements Accessory
@Override @Override
public void render(MatrixStack matrices, VertexConsumerProvider renderContext, int lightUv, E entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch) { public void render(MatrixStack matrices, VertexConsumerProvider renderContext, int lightUv, E entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch) {
ItemStack stack = GlassesItem.getForEntity(entity); ItemStack stack = GlassesItem.getForEntity(entity).stack();
if (!stack.isEmpty()) { if (!stack.isEmpty()) {
Identifier texture = textures.computeIfAbsent(Registries.ITEM.getId(stack.getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png")); Identifier texture = textures.computeIfAbsent(Registries.ITEM.getId(stack.getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png"));

View file

@ -3,7 +3,7 @@ package com.minelittlepony.unicopia.client.render;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.ability.AbilityDispatcher.Stat; import com.minelittlepony.unicopia.ability.AbilityDispatcher.Stat;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import net.minecraft.client.model.Dilation; import net.minecraft.client.model.Dilation;
import net.minecraft.client.model.Model; import net.minecraft.client.model.Model;
@ -55,7 +55,9 @@ public class HornFeatureRenderer<E extends LivingEntity> implements AccessoryFea
return pony.getAbilities().getActiveStat() return pony.getAbilities().getActiveStat()
.flatMap(Stat::getActiveAbility) .flatMap(Stat::getActiveAbility)
.map(ability -> ability.getColor(pony)) .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(SpellType.PLACE_CONTROL_SPELL.negate())
.map(spell -> spell.getTypeAndTraits().type().getColor()));
}).ifPresent(color -> { }).ifPresent(color -> {
model.setState(true); model.setState(true);
model.render(stack, ItemRenderer.getArmorGlintConsumer(renderContext, RenderLayers.getMagicColored((0x99 << 24) | color), false, false), lightUv, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); model.render(stack, ItemRenderer.getArmorGlintConsumer(renderContext, RenderLayers.getMagicColored((0x99 << 24) | color), false, false), lightUv, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1);

View file

@ -43,7 +43,7 @@ public class PlayerPoser {
boolean liftLeftArm = mainArm == Arm.LEFT || !ponyRace.isEquine(); boolean liftLeftArm = mainArm == Arm.LEFT || !ponyRace.isEquine();
boolean liftRightArm = mainArm == Arm.RIGHT || !ponyRace.isEquine(); boolean liftRightArm = mainArm == Arm.RIGHT || !ponyRace.isEquine();
ItemStack glasses = GlassesItem.getForEntity(player); ItemStack glasses = GlassesItem.getForEntity(player).stack();
ModelPart head = model.getHead(); ModelPart head = model.getHead();
if (context == Context.THIRD_PERSON && !player.isSneaking()) { if (context == Context.THIRD_PERSON && !player.isSneaking()) {

View file

@ -1,16 +1,35 @@
package com.minelittlepony.unicopia.client.render.entity; package com.minelittlepony.unicopia.client.render.entity;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.common.util.Color;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.client.render.model.PlaneModel;
import com.minelittlepony.unicopia.client.render.spell.SpellEffectsRenderDispatcher; import com.minelittlepony.unicopia.client.render.spell.SpellEffectsRenderDispatcher;
import com.minelittlepony.unicopia.client.render.spell.SpellRenderer;
import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; import com.minelittlepony.unicopia.entity.mob.CastSpellEntity;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.EntityRenderer; import net.minecraft.client.render.entity.EntityRenderer;
import net.minecraft.client.render.entity.EntityRendererFactory; import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.screen.PlayerScreenHandler; import net.minecraft.screen.PlayerScreenHandler;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.RotationAxis;
public class CastSpellEntityRenderer extends EntityRenderer<CastSpellEntity> { public class CastSpellEntityRenderer extends EntityRenderer<CastSpellEntity> {
private static final Identifier[] TEXTURES = new Identifier[] {
Unicopia.id("textures/particles/runes_0.png"),
Unicopia.id("textures/particles/runes_1.png"),
Unicopia.id("textures/particles/runes_2.png"),
Unicopia.id("textures/particles/runes_3.png"),
Unicopia.id("textures/particles/runes_4.png"),
Unicopia.id("textures/particles/runes_5.png")
};
public CastSpellEntityRenderer(EntityRendererFactory.Context ctx) { public CastSpellEntityRenderer(EntityRendererFactory.Context ctx) {
super(ctx); super(ctx);
} }
@ -21,11 +40,62 @@ public class CastSpellEntityRenderer extends EntityRenderer<CastSpellEntity> {
} }
@Override @Override
public void render(CastSpellEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light) { public void render(CastSpellEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) {
SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertexConsumers, light, entity, 0, 0, tickDelta, getAnimationProgress(entity, tickDelta), yaw, 0); matrices.push();
matrices.translate(0, 0.001, 0);
final float height = entity.getHeight();
final float pitch = entity.getPitch(tickDelta);
matrices.translate(0, (-pitch / 90F) * height * 0.5F, 0);
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-pitch));
float animationProgress = getAnimationProgress(entity, tickDelta);
renderAmbientEffects(matrices, vertices, entity, entity.getSpellSlot().get().orElse(null), light, animationProgress, tickDelta);
SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertices, light, entity, entity.getScale(tickDelta), 0, tickDelta, animationProgress, yaw, pitch);
matrices.pop();
} }
protected float getAnimationProgress(CastSpellEntity entity, float tickDelta) { protected float getAnimationProgress(CastSpellEntity entity, float tickDelta) {
return entity.age + tickDelta; return entity.age + tickDelta;
} }
protected void renderAmbientEffects(MatrixStack matrices, VertexConsumerProvider vertices, CastSpellEntity entity, @Nullable Spell spell, int light, float animationProgress, float tickDelta) {
matrices.push();
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90));
float scale = entity.getScale(tickDelta) * 3;
matrices.scale(scale, scale, scale);
float angle = (animationProgress / 9F) % 360;
int color = spell == null ? 0 : spell.getTypeAndTraits().type().getColor();
float red = Color.r(color);
float green = Color.g(color);
float blue = Color.b(color);
@Nullable
SpellRenderer<?> renderer = spell == null ? null : SpellEffectsRenderDispatcher.INSTANCE.getRenderer(spell);
for (int i = 0; i < TEXTURES.length; i++) {
if (renderer != null && !renderer.shouldRenderEffectPass(i)) {
continue;
}
VertexConsumer buffer = vertices.getBuffer(RenderLayer.getEntityTranslucent(TEXTURES[i]));
for (int dim = 0; dim < 3; dim++) {
float ringSpeed = (i % 2 == 0 ? i : -1) * i;
matrices.push();
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(angle * ringSpeed));
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(angle * ringSpeed * dim));
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(angle * ringSpeed * dim));
PlaneModel.INSTANCE.render(matrices, buffer, light, 0, 1, red, green, blue, scale / ((float)(dim * 3) + 1));
matrices.pop();
}
}
matrices.pop();
}
} }

View file

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

Some files were not shown because too many files have changed in this diff Show more