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 net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.StringIdentifiable;
import net.minecraft.util.Util;
public enum Affinity implements StringIdentifiable {
GOOD(Formatting.BLUE, -1, 0),
@ -20,10 +22,13 @@ public enum Affinity implements StringIdentifiable {
public static final Affinity[] VALUES = values();
private final String translationKey;
Affinity(Formatting color, int corruption, float alignment) {
this.color = color;
this.corruption = corruption;
this.alignment = alignment;
this.translationKey = Util.createTranslationKey("affinity", Unicopia.id(name().toLowerCase(Locale.ROOT)));
}
@Override
@ -36,7 +41,11 @@ public enum Affinity implements StringIdentifiable {
}
public String getTranslationKey() {
return this == BAD ? "curse" : "spell";
return translationKey;
}
public Text getDisplayName() {
return Text.translatable(getTranslationKey()).formatted(getColor());
}
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.Ability;
import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.util.RegistryUtils;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@ -44,6 +45,7 @@ public record Race (
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> 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();
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));

View file

@ -107,6 +107,7 @@ public interface UTags {
TagKey<Block> MIMIC_CHESTS = block("mimic_chests");
TagKey<Block> BUTTERFLIES_SPAWNABLE_ON = block("butterflies_spawn_on");
TagKey<Block> ANGERS_GUARDIANS = block("angers_guardians");
private static TagKey<Block> block(String 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.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.minecraft.resource.ResourceType;
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.affliction.AfflictionType;
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.mob.UEntities;
import com.minelittlepony.unicopia.item.UItems;
@ -76,6 +78,7 @@ public class Unicopia implements ModInitializer {
Debug.runTests(w);
}
});
PlayerBlockBreakEvents.AFTER.register(SeaponyGraceStatusEffect::processBlockChange);
NocturnalSleepManager.bootstrap();
registerServerDataReloaders(ResourceManagerHelper.get(ResourceType.SERVER_DATA));

View file

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

View file

@ -10,8 +10,10 @@ import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.FriendshipBraceletItem;
import com.minelittlepony.unicopia.item.UItems;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.sound.SoundCategory;
@ -73,6 +75,11 @@ public class ChangeFormAbility implements Ability<Hit> {
List<Pony> targets = getTargets(player).toList();
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();
targets.forEach(target -> {
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);
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));
if (currentDisguise.isOf(looked)) {
@ -64,7 +64,6 @@ public class ChangelingDisguiseAbility extends ChangelingFeedAbility {
}
player.calculateDimensions();
iplayer.setDirty();
return true;
}

View file

@ -6,9 +6,11 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.AwaitTickQueue;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.UTags;
import com.minelittlepony.unicopia.ability.data.Hit;
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.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.player.Pony;
@ -80,7 +82,9 @@ public class EarthPonyStompAbility implements Ability<Hit> {
@Override
public Optional<Hit> prepare(Pony player) {
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);
return Hit.INSTANCE;
}
@ -104,9 +108,18 @@ public class EarthPonyStompAbility implements Ability<Hit> {
@Override
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());
float heavyness = 1 + EnchantmentHelper.getEquipmentLevel(UEnchantments.HEAVY, player);
@ -163,13 +176,20 @@ public class EarthPonyStompAbility implements Ability<Hit> {
iplayer.subtractEnergyCost(rad);
iplayer.asEntity().addExhaustion(3);
return 0F;
}
@Override
public void onCancelled() {
iplayer.playSound(USounds.GUI_ABILITY_FAIL, 1F);
}
};
if (iplayer.asEntity().isOnGround()) {
iplayer.setAnimation(Animation.STOMP, Animation.Recipient.ANYONE, 10);
iplayer.asEntity().jump();
iplayer.updateVelocity();
AwaitTickQueue.scheduleTask(iplayer.asWorld(), w -> r.run(), 5);
AwaitTickQueue.scheduleTask(iplayer.asWorld(), w -> r.dispatch(0F), 5);
} else {
thrustDownwards(iplayer);
iplayer.waitForFall(r);

View file

@ -57,9 +57,7 @@ public class TimeChangeAbility implements Ability<Rot> {
return false;
}
if (player.getSpellSlot().contains(SpellType.TIME_CONTROL)) {
player.getSpellSlot().removeWhere(SpellType.TIME_CONTROL, true);
} else {
if (!player.getSpellSlot().removeWhere(SpellType.TIME_CONTROL)) {
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
@Override
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
@ -65,7 +65,6 @@ public class ToggleFlightAbility implements Ability<Hit> {
} else {
player.getPhysics().cancelFlight(true);
}
player.setDirty();
player.setAnimation(Animation.SPREAD_WINGS, Animation.Recipient.ANYONE);
return true;
}

View file

@ -100,12 +100,21 @@ public class UnicornCastingAbility extends AbstractSpellCastingAbility {
if (newSpell.getResult() != ActionResult.FAIL && canCast(newSpell.getValue().type())) {
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 -> {
return s.findMatches(spell).findAny().isPresent() && (spell.isEmpty() || !SpellType.PLACED_SPELL.test(s));
}, true);
if (spell.isEmpty()) {
return false;
}
boolean has = !spell.isStackable() && player.getSpellSlot().contains(spell);
boolean removed = !spell.isStackable() && player.getSpellSlot().removeWhere(spell.type());
player.subtractEnergyCost(removed ? 2 : 4);
if (!removed) {
if (!has) {
Spell s = spell.apply(player, CastingMethod.DIRECT);
if (s == null) {
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.ability.data.Pos;
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.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.player.Pony;
@ -93,11 +92,7 @@ public class UnicornDispellAbility implements Ability<Pos> {
public boolean apply(Pony player, Pos data) {
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 -> {
target.getSpellSlot().forEach(spell -> {
spell.setDead();
spell.tickDying(target);
return Operation.ofBoolean(!spell.isDead());
}, true);
target.getSpellSlot().clear(false);
});
return true;
}

View file

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

View file

@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.*;
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.entity.*;
import com.minelittlepony.unicopia.entity.damage.UDamageSources;
@ -40,7 +41,7 @@ public interface Caster<E extends Entity> extends
Physics getPhysics();
SpellContainer getSpellSlot();
SpellSlots getSpellSlot();
/**
* 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) {
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) {

View file

@ -1,33 +1,32 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.math.MathHelper;
/**
* Object with levelling capabilities.
*/
public interface Levelled {
LevelStore EMPTY = fixed(0);
LevelStore ZERO = of(0, 1);
static LevelStore fixed(int level) {
return of(() -> level);
}
static LevelStore of(IntSupplier supplier) {
static LevelStore of(IntSupplier getter, IntConsumer setter, IntSupplier max) {
return new LevelStore() {
@Override
public int get() {
return supplier.getAsInt();
return getter.getAsInt();
}
@Override
public void set(int level) {
setter.accept(level);
}
@Override
public int getMax() {
return get();
return max.getAsInt();
}
};
}
@ -37,7 +36,9 @@ public interface Levelled {
}
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) {
@ -70,6 +71,9 @@ public interface Levelled {
void set(int level);
default float getScaled(float max) {
if (getMax() == 0) {
return 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.effect.MimicSpell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.ShieldSpell;
import net.minecraft.entity.Entity;
public interface SpellPredicate<T extends Spell> extends Predicate<Spell> {
SpellPredicate<?> ALL = spell -> true;
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<MimicSpell> IS_MIMIC = s -> s instanceof MimicSpell;
SpellPredicate<ShieldSpell> IS_SHIELD_LIKE = spell -> spell instanceof ShieldSpell;
SpellPredicate<TimedSpell> IS_TIMED = spell -> spell instanceof TimedSpell;
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_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;
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.trait.Trait;
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) {
super(type);
}

View file

@ -1,64 +1,60 @@
package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
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.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.trait.SpellTraits;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.server.world.Ether;
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 final SpellType<?> type;
private final CustomisedSpellType<?> type;
protected final SpellReference<Spell> delegate = new SpellReference<>();
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
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
public Stream<Spell> findMatches(Predicate<Spell> predicate) {
return Stream.concat(Spell.super.findMatches(predicate), getDelegates().stream().flatMap(s -> s.findMatches(predicate)));
public <T extends Spell> Stream<T> findMatches(SpellPredicate<T> predicate) {
return Stream.concat(Spell.super.findMatches(predicate), delegate.findMatches(predicate));
}
@Override
public Affinity getAffinity() {
return Affinity.NEUTRAL;
}
@Override
public SpellType<?> getType() {
public CustomisedSpellType<?> getTypeAndTraits() {
return type;
}
@Override
public SpellTraits getTraits() {
return getDelegates().stream().map(Spell::getTraits).reduce(SpellTraits.EMPTY, SpellTraits::union);
}
@Override
public final UUID getUuid() {
return uuid;
@ -66,109 +62,80 @@ public abstract class AbstractDelegatingSpell implements Spell,
@Override
public void setDead() {
getDelegates().forEach(Spell::setDead);
getOrEmpty().setDead();
}
@Override
public void tickDying(Caster<?> caster) {
getOrEmpty().tickDying(caster);
}
@Override
public boolean isDead() {
return getDelegates().isEmpty() || getDelegates().stream().allMatch(Spell::isDead);
return getOrEmpty().isDead();
}
@Override
public boolean isDying() {
return false;
return getOrEmpty().isDying();
}
@Deprecated
@Override
public boolean isDirty() {
return dirty || getDelegates().stream().anyMatch(Spell::isDirty);
return delegate.hasDirtySpell();
}
@Deprecated
@Override
public void setDirty() {
dirty = true;
getOrEmpty().setDirty();
}
@Override
public boolean isHidden() {
return hidden || getDelegates().stream().allMatch(Spell::isHidden);
return getOrEmpty().isHidden();
}
@Override
public void setHidden(boolean hidden) {
this.hidden = hidden;
getOrEmpty().setHidden(hidden);
}
@Override
public final void destroy(Caster<?> caster) {
if (destroyed) {
return;
if (!caster.isClient()) {
Ether.get(caster.asWorld()).remove(this, caster);
}
destroyed = true;
setDead();
onDestroyed(caster);
}
protected void onDestroyed(Caster<?> caster) {
getDelegates().forEach(a -> a.destroy(caster));
getOrEmpty().destroy(caster);
}
@Override
public boolean tick(Caster<?> source, Situation situation) {
return execute(getDelegates().stream(), a -> {
if (a.isDying()) {
a.tickDying(source);
return !a.isDead();
Spell s = getOrEmpty();
if (s.isDying()) {
s.tickDying(source);
return !s.isDead();
}
return a.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));
return s.tick(source, situation);
}
@Override
public void toNBT(NbtCompound compound) {
compound.putUuid("uuid", uuid);
compound.putBoolean("hidden", hidden);
saveDelegates(compound);
compound.put("spell", delegate.toNBT());
}
@Override
public void fromNBT(NbtCompound compound) {
dirty = false;
hidden = compound.getBoolean("hidden");
if (compound.contains("uuid")) {
uuid = compound.getUuid("uuid");
}
loadDelegates(compound);
delegate.fromNBT(compound.getCompound("spell"));
}
protected abstract void loadDelegates(NbtCompound compound);
protected abstract void saveDelegates(NbtCompound compound);
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);
@Override
public final String toString() {
return "Delegate{" + getTypeAndTraits() + "}[uuid=" + uuid + "][spell=" + delegate.get() + "]";
}
}

View file

@ -28,10 +28,11 @@ public abstract class AbstractDisguiseSpell extends AbstractSpell implements Dis
@Override
protected void onDestroyed(Caster<?> caster) {
super.onDestroyed(caster);
caster.asEntity().calculateDimensions();
caster.asEntity().setInvisible(false);
if (caster instanceof Pony) {
((Pony) caster).setInvisible(false);
if (caster instanceof Pony pony) {
pony.setInvisible(false);
}
disguise.remove();
}
@ -71,7 +72,7 @@ public abstract class AbstractDisguiseSpell extends AbstractSpell implements Dis
public static Entity getAppearance(Entity e) {
return e instanceof PlayerEntity ? Pony.of((PlayerEntity)e)
.getSpellSlot()
.get(SpellPredicate.IS_DISGUISE, true)
.get(SpellPredicate.IS_DISGUISE)
.map(AbstractDisguiseSpell::getDisguise)
.map(EntityAppearance::getAppearance)
.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.entity.behaviour.EntityAppearance;
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.UParticles;
import net.minecraft.entity.Entity;
@ -21,6 +23,7 @@ import net.minecraft.nbt.NbtCompound;
*/
public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements IllusionarySpell {
private final DataTracker.Entry<Boolean> suppressed = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private int suppressionCounter;
public DispersableDisguiseSpell(CustomisedSpellType<?> type) {
@ -36,8 +39,8 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I
@Override
public void onSuppressed(Caster<?> otherSource, float time) {
time /= getTraits().getOrDefault(Trait.STRENGTH, 1);
suppressionCounter = (int)(100 * time);
setDirty();
suppressionCounter = (int)time;
suppressed.set(true);
}
@Override
@ -64,7 +67,9 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I
Entity appearance = getDisguise().getAppearance();
if (isSuppressed()) {
suppressionCounter--;
if (--suppressionCounter <= 0) {
suppressed.set(false);
}
owner.setInvisible(false);
if (source instanceof Pony) {
@ -92,6 +97,9 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
suppressionCounter = compound.getInt("suppressionCounter");
if (suppressionCounter > 0) {
suppressed.set(true);
}
}
@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;
import com.minelittlepony.unicopia.ability.data.Rot;
import com.minelittlepony.unicopia.ability.magic.Caster;
public interface OrientedSpell extends Spell {
void setOrientation(float pitch, float yaw);
void setOrientation(Caster<?> caster, float pitch, float yaw);
default void setOrientation(Rot rotation) {
setOrientation(rotation.pitch(), rotation.yaw());
default void setOrientation(Caster<?> caster, Rot rotation) {
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++;
source.playSound(USounds.Vanilla.ENTITY_GENERIC_EXTINGUISH_FIRE, 1);
source.spawnParticles(ParticleTypes.CLOUD, 12);
setDirty();
} else {
ticksExtenguishing = 0;
}
@ -127,9 +126,6 @@ public class RageAbilitySpell extends AbstractSpell {
age++;
source.asEntity().setInvulnerable(age < 25);
setDirty();
return true;
}

View file

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

View file

@ -1,32 +1,28 @@
package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import com.minelittlepony.unicopia.USounds;
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.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;
public final class ThrowableSpell extends AbstractDelegatingSpell {
private final SpellReference<Spell> spell = new SpellReference<>();
public final class ThrowableSpell extends AbstractDelegatingSpell implements
ProjectileDelegate.ConfigurationListener, ProjectileDelegate.BlockHitListener, ProjectileDelegate.EntityHitListener {
public ThrowableSpell(CustomisedSpellType<?> type) {
super(type);
}
public ThrowableSpell setSpell(Spell spell) {
this.spell.set(spell);
return this;
}
@Override
public Collection<Spell> getDelegates() {
return List.of(spell.get());
public ThrowableSpell(Spell delegate) {
super(SpellType.THROWN_SPELL.withTraits(delegate.getTypeAndTraits().traits()), delegate);
}
@Override
@ -57,7 +53,7 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
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);
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
public ThrowableSpell toThrowable() {
return this;
@ -90,4 +76,25 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
@Override
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;
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.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.
*/
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();
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 com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
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;
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 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) {
this.type = type;
}
@Override
public final DataTracker getDataTracker() {
return dataTracker;
}
@Override
public final UUID getUuid() {
return uuid;
}
@Override
public final SpellType<?> getType() {
protected final SpellType<?> getType() {
return type.type();
}
@Override
public final CustomisedSpellType<?> getTypeAndTraits() {
return type;
}
@Override
public final SpellTraits getTraits() {
protected final SpellTraits getTraits() {
return type.traits();
}
@Override
public final void setDead() {
dying = true;
setDirty();
dying.set(true);
}
@Override
public final boolean isDead() {
return dead;
return dead.get();
}
@Override
public final boolean isDying() {
return dying;
return dying.get();
}
@Deprecated
@Override
public final boolean isDirty() {
return dirty;
}
@Deprecated
@Override
public final void setDirty() {
dirty = true;
@ -72,25 +80,17 @@ public abstract class AbstractSpell implements Spell {
@Override
public final boolean isHidden() {
return hidden;
return hidden.get();
}
@Override
public final void setHidden(boolean hidden) {
this.hidden = hidden;
}
@Override
public Affinity getAffinity() {
return getType().getAffinity();
}
protected void onDestroyed(Caster<?> caster) {
this.hidden.set(hidden);
}
@Override
public void tickDying(Caster<?> caster) {
dead = true;
dead.set(true);
}
@Override
@ -103,11 +103,17 @@ public abstract class AbstractSpell implements Spell {
onDestroyed(caster);
}
protected void onDestroyed(Caster<?> caster) {
if (!caster.isClient()) {
Ether.get(caster.asWorld()).remove(this, caster);
}
}
@Override
public void toNBT(NbtCompound compound) {
compound.putBoolean("dying", dying);
compound.putBoolean("dead", dead);
compound.putBoolean("hidden", hidden);
compound.putBoolean("dying", dying.get());
compound.putBoolean("dead", dead.get());
compound.putBoolean("hidden", hidden.get());
compound.putUuid("uuid", uuid);
compound.put("traits", getTraits().toNbt());
}
@ -118,12 +124,9 @@ public abstract class AbstractSpell implements Spell {
if (compound.containsUuid("uuid")) {
uuid = compound.getUuid("uuid");
}
dying = compound.getBoolean("dying");
dead = compound.getBoolean("dead");
hidden = compound.getBoolean("hidden");
if (compound.contains("traits")) {
type = type.type().withTraits(SpellTraits.fromNbt(compound.getCompound("traits")).orElse(SpellTraits.EMPTY));
}
dying.set(compound.getBoolean("dying"));
dead.set(compound.getBoolean("dead"));
hidden.set(compound.getBoolean("hidden"));
}
@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.spell.AbstractAreaEffectSpell;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.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.Trait;
import com.minelittlepony.unicopia.entity.mob.UEntities;
@ -22,10 +28,19 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
.with(Trait.STRENGTH, 30)
.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) {
super(type);
}
@Override
public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
return method == CastingMethod.STAFF || CAST_ON.get(getTraits()) == CastOn.LOCATION ? toPlaceable() : this;
}
@Override
public boolean tick(Caster<?> source, Situation situation) {
@ -33,7 +48,7 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
return false;
}
float radius = (float)getDrawDropOffRange(source);
float radius = (float)getRange(source);
if (source.isClient()) {
Vec3d origin = source.getOriginVector();
@ -44,7 +59,7 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
}
});
} 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 -> {
@ -54,17 +69,9 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
return !isDead();
}
@Override
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) {
private double getRange(Caster<?> source) {
float multiplier = source instanceof Pony pony && pony.asEntity().isSneaking() ? 1 : 2;
float min = 4 + getTraits().get(Trait.POWER);
float min = RANGE.get(getTraits());
double range = (min + (source.getLevel().getScaled(4) * 2)) / multiplier;
if (source instanceof Pony && range > 2) {
range = Math.sqrt(range);
@ -74,7 +81,7 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
public boolean blocksMagicFor(Caster<?> source, Caster<?> other, Vec3d position) {
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) {

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.effect.EffectUtils;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.enchantment.UEnchantments;
@ -61,12 +62,10 @@ public interface AttractionUtils {
return Pony.of(entity).map(pony -> {
double force = 0.75;
if (pony.getCompositeRace().canUseEarth()) {
if (EffectUtils.hasExtraDefenses(pony.asEntity())) {
force /= 12;
} else if (pony.getCompositeRace().canUseEarth()) {
force /= 2;
if (pony.asEntity().isSneaking()) {
force /= 6;
}
} else if (pony.getCompositeRace().canFly()) {
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.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.entity.EntityReference;
import com.minelittlepony.unicopia.entity.Living;
@ -13,7 +16,6 @@ import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity;
import net.minecraft.entity.ItemEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.hit.EntityHitResult;
@ -21,6 +23,10 @@ import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
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<>();
@ -28,7 +34,8 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
protected AttractiveSpell(CustomisedSpellType<?> 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
@ -38,16 +45,12 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
@Override
public boolean tick(Caster<?> caster, Situation situation) {
if (getType() != SpellType.DARK_VORTEX) {
timer.tick();
if (timer.getTicksRemaining() <= 0) {
return false;
}
setDirty();
}
target.getOrEmpty(caster.asWorld())
.filter(entity -> entity.distanceTo(caster.asEntity()) > getDrawDropOffRange(caster))
.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
protected boolean isValidTarget(Caster<?> source, Entity entity) {
if (target.referenceEquals(entity)) {
return true;
}
return getTraits().get(Trait.KNOWLEDGE) > 10 ? entity instanceof ItemEntity : super.isValidTarget(source, entity);
return target.referenceEquals(entity) || super.isValidTarget(source, entity);
}
@Override
@ -133,7 +128,7 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
@Override
public boolean setTarget(Entity target) {
if (getTraits().get(Trait.ORDER) >= 20) {
if (TARGET_FOCUSED_ENTITY.get(getTraits())) {
this.target.set(target);
target.setGlowing(true);
return true;
@ -149,7 +144,7 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
@Override
public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) {
if (!isDead() && getTraits().get(Trait.CHAOS) > 0) {
if (!isDead() && STICK_TO_TARGET.get(getTraits())) {
setDead();
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) {
return false;
}
setDirty();
}
if (source.isClient()) {

View file

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

View file

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

View file

@ -1,26 +1,45 @@
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 com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.client.TextHelper;
import com.minelittlepony.unicopia.entity.effect.EffectUtils;
import net.minecraft.item.ItemStack;
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;
public record CustomisedSpellType<T extends Spell> (
SpellType<T> type,
SpellTraits traits
SpellTraits traits,
Supplier<SpellTraits> traitsDifferenceSupplier
) implements SpellPredicate<T> {
public boolean isEmpty() {
return type.isEmpty();
}
public boolean isStackable() {
return type().isStackable();
}
public SpellTraits relativeTraits() {
return traitsDifferenceSupplier.get();
}
public T create() {
try {
return type.getFactory().create(this);
@ -31,6 +50,14 @@ public record CustomisedSpellType<T extends Spell> (
return null;
}
@Nullable
public T create(NbtCompound compound) {
T spell = create();
if (spell != null) {
spell.fromNBT(compound);
}
return spell;
}
@Nullable
public T apply(Caster<?> caster, CastingMethod method) {
@ -50,29 +77,47 @@ public record CustomisedSpellType<T extends Spell> (
}
@Override
public boolean test(Spell spell) {
return type.test(spell) && spell.getTraits().equals(traits);
public boolean test(@Nullable Spell spell) {
return spell != null && spell.getTypeAndTraits().equals(this);
}
public ItemStack getDefaultStack() {
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() {
return isEmpty() ? TypedActionResult.fail(this) : TypedActionResult.pass(this);
}
public NbtCompound toNBT() {
NbtCompound tag = new NbtCompound();
type.toNbt(tag);
tag.put("traits", traits.toNbt());
return tag;
public NbtCompound toNbt(NbtCompound compound) {
type.toNbt(compound);
compound.put("traits", traits.toNbt());
return compound;
}
public static CustomisedSpellType<?> fromNBT(NbtCompound compound) {
SpellType<?> type = SpellType.getKey(compound);
SpellTraits traits = SpellTraits.fromNbt(compound.getCompound("traits")).orElse(type.getTraits());
return type.withTraits(traits);
public static <T extends Spell> CustomisedSpellType<T> fromNBT(NbtCompound compound) {
SpellType<T> type = SpellType.getKey(compound);
return type.withTraits(SpellTraits.fromNbt(compound.getCompound("traits")).orElse(type.getTraits()));
}
}

View file

@ -17,6 +17,8 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
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.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils;
@ -56,7 +58,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
.with(Trait.DARKNESS, 100)
.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);
@ -70,7 +72,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
// 3. force reaches 0 at distance of drawDropOffRange
private double getMass() {
return 0.1F + accumulatedMass / 10F;
return 0.1F + accumulatedMass.get() / 10F;
}
public double getEventHorizonRadius() {
@ -174,11 +176,12 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
@Override
public void tickDying(Caster<?> source) {
accumulatedMass -= 0.8F;
float m = accumulatedMass.get() - 0.8F;
accumulatedMass.set(m);
double mass = getMass() * 0.1;
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);
if (accumulatedMass < 1) {
if (m < 1) {
super.tickDying(source);
}
@ -202,7 +205,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
@Override
public boolean isFriendlyTogether(Affine other) {
return accumulatedMass < 4;
return accumulatedMass.get() < 4;
}
private boolean isValidTarget(Caster<?> source, Entity entity) {
@ -274,8 +277,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
double massOfTarget = AttractionUtils.getMass(target);
if (!source.isClient() && massOfTarget != 0) {
accumulatedMass += massOfTarget;
setDirty();
accumulatedMass.set((float)(accumulatedMass.get() + massOfTarget));
}
target.damage(source.damageOf(UDamageTypes.GAVITY_WELL_RECOIL, source), Integer.MAX_VALUE);
@ -303,12 +305,12 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
compound.putFloat("accumulatedMass", accumulatedMass);
compound.putFloat("accumulatedMass", accumulatedMass.get());
}
@Override
public void fromNBT(NbtCompound 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.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.Trait;
import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect;
@ -18,6 +22,10 @@ public class DispellEvilSpell extends AbstractSpell implements ProjectileDelegat
.with(Trait.POWER, 1)
.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) {
super(type);
}
@ -28,7 +36,7 @@ public class DispellEvilSpell extends AbstractSpell implements ProjectileDelegat
return !isDead();
}
source.findAllEntitiesInRange(getTraits().get(Trait.POWER) * 10, e -> e.getType() == EntityType.PHANTOM).forEach(entity -> {
source.findAllEntitiesInRange(RANGE.get(getTraits()), e -> e.getType() == EntityType.PHANTOM).forEach(entity -> {
entity.damage(entity.getDamageSources().magic(), 50);
if (entity instanceof LivingEntity l) {
double d = source.getOriginVector().getX() - entity.getX();

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.spell.AbstractAreaEffectSpell;
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.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.util.shape.Sphere;
@ -15,6 +19,10 @@ import net.minecraft.util.math.Vec3d;
* An area-effect spell that disperses illusions.
*/
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) {
super(type);
}
@ -22,7 +30,7 @@ public class DisperseIllusionSpell extends AbstractAreaEffectSpell {
@Override
public boolean tick(Caster<?> source, Situation situation) {
float range = Math.max(0, 15 + getTraits().get(Trait.POWER));
float range = RANGE.get(getTraits());
if (range == 0) {
return false;
@ -38,10 +46,10 @@ public class DisperseIllusionSpell extends AbstractAreaEffectSpell {
}
source.findAllSpellsInRange(range).forEach(e -> {
e.getSpellSlot().get(SpellPredicate.CAN_SUPPRESS, false)
e.getSpellSlot().get(SpellPredicate.CAN_SUPPRESS)
.filter(spell -> spell.isVulnerable(source, this))
.ifPresent(spell -> {
spell.onSuppressed(source, 1 + getTraits().get(Trait.STRENGTH));
spell.onSuppressed(source, DURATION.get(getTraits()));
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.ability.magic.Caster;
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.entity.EntityReference;
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 {
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 int ticks = 10;
@ -71,7 +79,7 @@ public class DisplacementSpell extends AbstractSpell implements HomingSpell, Pro
entity.setGlowing(false);
entity.playSound(USounds.SPELL_DISPLACEMENT_TELEPORT, 1, 1);
float damage = getTraits().get(Trait.BLOOD);
float damage = DAMAGE_TO_TARGET.get(getTraits());
if (damage > 0) {
entity.damage(source.damageOf(UDamageTypes.EXHAUSTION, source), damage);
}
@ -90,6 +98,7 @@ public class DisplacementSpell extends AbstractSpell implements HomingSpell, Pro
@Override
protected void onDestroyed(Caster<?> caster) {
super.onDestroyed(caster);
caster.getOriginatingCaster().asEntity().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.spell.Situation;
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.Trait;
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 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()
.with(Trait.FOCUS, 80)
.with(Trait.POWER, 10)
@ -40,7 +63,7 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell {
protected FeatherFallSpell(CustomisedSpellType<?> type) {
super(type);
timer = new Timer(10 + (int)(getTraits().get(Trait.FOCUS, 0, 160)));
timer = new Timer(DURATION.get(getTraits()));
}
@Override
@ -56,26 +79,22 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell {
return false;
}
setDirty();
List<Entity> targets = getTargets(caster).toList();
if (targets.isEmpty()) {
return true;
}
final float strength = 1F / (getTraits().get(Trait.STRENGTH, 2, 9) / targets.size());
final float generosity = getTraits().get(Trait.GENEROSITY, 1, MAX_GENEROSITY_FACTOR) / MAX_GENEROSITY_FACTOR;
final float strength = 1F / (STRENGTH.get(getTraits()) / targets.size());
final float targetPreference = TARGET_PREFERENCE.get(getTraits());
final float casterPreference = 1 - targetPreference;
final boolean negateFallDamage = NEGATES_FALL_DAMAGE.get(getTraits());
Entity entity = caster.asEntity();
Vec3d masterVelocity = entity.getVelocity().multiply(0.1);
targets.forEach(target -> {
if (target.getVelocity().y < 0) {
boolean isSelf = caster.isOwnedBy(target) || target == entity;
float delta = strength * (isSelf ? (1F - generosity) : generosity);
if (!isSelf || generosity < 0.5F) {
if (negateFallDamage) {
target.verticalCollision = true;
target.setOnGround(true);
target.fallDistance = 0;
@ -83,6 +102,8 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell {
if (target instanceof PlayerEntity) {
((PlayerEntity)target).getAbilities().flying = false;
}
float delta = strength * ((caster.isOwnedBy(target) || target == entity) ? casterPreference : targetPreference);
target.setVelocity(target.getVelocity().multiply(1, delta, 1));
if (situation == Situation.PROJECTILE && target != entity) {
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);
});
return caster.subtractEnergyCost(timer.getTicksRemaining() % 50 == 0 ? getCostPerEntity() * targets.size() : 0);
}
protected double getCostPerEntity() {
float focus = Math.max(getTraits().get(Trait.FOCUS), 80) - 80;
float power = Math.max(getTraits().get(Trait.POWER), 10) - 10;
return MathHelper.clamp((power * POWERS_RANGE_WEIGHT) - (focus * FOCUS_RANGE_WEIGHT), 1, 7);
}
protected double getEffectRange() {
float power = getTraits().get(Trait.POWER) - 10;
return MathHelper.clamp(power * POWERS_RANGE_WEIGHT, MIN_RANGE, MAX_RANGE);
}
protected long getMaxTargets() {
long generosity = (long)getTraits().get(Trait.GENEROSITY) * 2L;
long focus = (long)getTraits().get(Trait.FOCUS, MIN_TARGETS, MAX_TARGETS) * 2L;
return generosity + focus;
return caster.subtractEnergyCost(timer.getTicksRemaining() % 50 == 0 ? COST_PER_INDIVIDUAL.get(getTraits()) * targets.size() : 0);
}
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(
FriendshipBraceletItem.isComrade(caster, a) ? 1 : 0,
FriendshipBraceletItem.isComrade(caster, b) ? 1 : 0
);
}).distinct()).limit(getMaxTargets());
}).distinct()).limit(SIMULTANIOUS_TARGETS.get(getTraits()));
}
@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.spell.HomingSpell;
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.Trait;
import com.minelittlepony.unicopia.entity.EntityReference;
@ -15,6 +19,7 @@ import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.math.MathHelper;
public class FireBoltSpell extends AbstractSpell implements HomingSpell,
ProjectileDelegate.ConfigurationListener, ProjectileDelegate.EntityHitListener {
@ -31,6 +36,15 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
.with(Trait.FIRE, 60)
.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<>();
protected FireBoltSpell(CustomisedSpellType<?> type) {
@ -44,10 +58,12 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
@Override
public boolean tick(Caster<?> caster, Situation situation) {
boolean followTarget = FOLLOWS_TARGET.get(getTraits());
float followRage = FOLLOW_RANGE.get(getTraits());
if (situation == Situation.PROJECTILE) {
if (caster instanceof MagicProjectileEntity projectile && getTraits().get(Trait.FOCUS) >= 50) {
if (caster instanceof MagicProjectileEntity projectile && followTarget) {
caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49,
followRage,
EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.validTarget(this, caster))
).findFirst().ifPresent(target -> projectile.setHomingTarget(target));
}
@ -55,9 +71,9 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
return true;
}
if (getTraits().get(Trait.FOCUS) >= 50 && target.getOrEmpty(caster.asWorld()).isEmpty()) {
if (followTarget && target.getOrEmpty(caster.asWorld()).isEmpty()) {
target.set(caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49,
followRage,
EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.validTarget(this, caster))
).findFirst().orElse(null));
}
@ -75,18 +91,18 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
@Override
public void configureProjectile(MagicProjectileEntity projectile, Caster<?> caster) {
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.setVelocity(projectile.getVelocity().multiply(1.3 + getTraits().get(Trait.STRENGTH) / 11F));
projectile.setVelocity(projectile.getVelocity().multiply(VELOCITY.get(getTraits())));
}
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
public boolean setTarget(Entity target) {
if (getTraits().get(Trait.FOCUS) >= 50) {
if (FOLLOWS_TARGET.get(getTraits())) {
this.target.set(target);
return true;
}

View file

@ -67,14 +67,14 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileDele
generateParticles(source);
}
return new Sphere(false, Math.max(0, 4 + getTraits().get(Trait.POWER))).translate(source.getOrigin()).getBlockPositions().reduce(false,
return new Sphere(false, RANGE.get(getTraits())).translate(source.getOrigin()).getBlockPositions().reduce(false,
(r, i) -> source.canModifyAt(i) && applyBlocks(source.asWorld(), i),
(a, b) -> a || b)
|| applyEntities(source, source.getOriginVector());
}
protected void generateParticles(Caster<?> source) {
source.spawnParticles(new Sphere(false, Math.max(0, 4 + getTraits().get(Trait.POWER))), (int)(1 + source.getLevel().getScaled(8)) * 6, pos -> {
source.spawnParticles(new Sphere(false, RANGE.get(getTraits())), (int)(1 + source.getLevel().getScaled(8)) * 6, pos -> {
source.addParticle(ParticleTypes.LARGE_SMOKE, pos, Vec3d.ZERO);
});
}
@ -121,8 +121,12 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileDele
return false;
}
protected float getEntityEffectRange() {
return Math.max(0, RANGE.get(getTraits()) - 1);
}
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();
return (!(e.equals(source.asEntity()) || e.equals(master)) ||
(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.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.TooltipFactory;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.advancement.UCriteria;
@ -35,6 +37,8 @@ public class HydrophobicSpell extends AbstractSpell {
.with(Trait.KNOWLEDGE, 1)
.build();
static final TooltipFactory TOOLTIP = TooltipFactory.of(ShieldSpell.CAST_ON, ShieldSpell.RANGE);
private final TagKey<Fluid> affectedFluid;
private final Set<Entry> storedFluidPositions = new HashSet<>();
@ -46,7 +50,7 @@ public class HydrophobicSpell extends AbstractSpell {
@Override
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 this;
@ -92,8 +96,7 @@ public class HydrophobicSpell extends AbstractSpell {
}
double range = getRange(source);
var entry = Ether.get(source.asWorld()).getOrCreate(this, source);
entry.radius = (float)range;
Ether.get(source.asWorld()).getOrCreate(this, source).setRadius((float)range);
source.spawnParticles(new Sphere(true, range), 10, pos -> {
BlockPos bp = BlockPos.ofFloored(pos);
@ -116,7 +119,7 @@ public class HydrophobicSpell extends AbstractSpell {
@Override
protected void onDestroyed(Caster<?> caster) {
Ether.get(caster.asWorld()).remove(this, caster);
super.onDestroyed(caster);
storedFluidPositions.removeIf(entry -> {
if (caster.canModifyAt(entry.pos())) {
entry.restore(caster.asWorld());
@ -142,7 +145,7 @@ public class HydrophobicSpell extends AbstractSpell {
*/
public double getRange(Caster<?> source) {
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;
double range = (min + (source.getLevel().getScaled(isLimitedRange ? 4 : 40) * (isLimitedRange ? 2 : 10))) / multiplier;
return range;
@ -175,19 +178,21 @@ public class HydrophobicSpell extends AbstractSpell {
}
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) {
if (world instanceof ServerWorld sw) {
if (!(world instanceof ServerWorld sw)) {
return false;
}
return Ether.get(sw).anyMatch(SpellType.HYDROPHOBIC, entry -> {
var spell = entry.getSpell();
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 false;
}
}

View file

@ -5,6 +5,10 @@ import java.util.List;
import com.minelittlepony.unicopia.Owned;
import com.minelittlepony.unicopia.ability.magic.Caster;
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.Trait;
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.util.PosHelper;
import com.minelittlepony.unicopia.util.VecHelper;
import com.minelittlepony.unicopia.util.shape.Shape;
import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.block.*;
@ -33,8 +36,9 @@ public class IceSpell extends AbstractSpell {
.with(Trait.ICE, 15)
.build();
private static final int RADIUS = 3;
private static final Shape OUTER_RANGE = new Sphere(false, RADIUS);
private static final SpellAttribute<Float> RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, power -> Math.max(0, 3 + power));
static final TooltipFactory TOOLTIP = RANGE;
protected IceSpell(CustomisedSpellType<?> type) {
super(type);
@ -43,11 +47,12 @@ public class IceSpell extends AbstractSpell {
@Override
public boolean tick(Caster<?> source, Situation situation) {
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 (submerged & source.getOrigin().isWithinDistance(i, RADIUS - 1)) {
if (submerged & source.getOrigin().isWithinDistance(i, RANGE.get(getTraits()) - 1)) {
BlockState state = source.asWorld().getBlockState(i);
if (state.isIn(BlockTags.ICE) || state.isOf(Blocks.OBSIDIAN)) {
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.Situation;
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.Trait;
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.NbtElement;
import net.minecraft.nbt.NbtList;
import net.minecraft.util.math.MathHelper;
public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileDelegate.HitListener {
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)
.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 List<EntityReference<FairyEntity>> lights = new ArrayList<>();
protected LightSpell(CustomisedSpellType<?> type) {
super(type);
timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20);
timer = new Timer(TIME.get(getTraits()));
}
@Override
@ -56,11 +65,9 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD
return false;
}
setDirty();
if (!caster.isClient()) {
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) {
lights.add(new EntityReference<>());
}
@ -76,7 +83,6 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD
entity.getWorld().spawnEntity(entity);
ref.set(entity);
setDirty();
}
});
}
@ -91,6 +97,7 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD
@Override
protected void onDestroyed(Caster<?> caster) {
super.onDestroyed(caster);
if (caster.isClient()) {
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.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.nbt.NbtCompound;
public class MimicSpell extends AbstractDisguiseSpell implements HomingSpell, TimedSpell {
static final TooltipFactory TOOLTIP = TimedSpell.TIME;
private final Timer timer;
protected MimicSpell(CustomisedSpellType<?> type) {
super(type);
timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20);
timer = new Timer(TIME.get(getTraits()));
}
@Override
@ -28,8 +31,6 @@ public class MimicSpell extends AbstractDisguiseSpell implements HomingSpell, Ti
return false;
}
setDirty();
return super.tick(caster, situation);
}

View file

@ -60,8 +60,8 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti
LivingEntity master = caster.getMaster();
Caster<?> other = Caster.of(e).get();
other.getSpellSlot().removeIf(SpellType.MIMIC, true);
caster.getSpellSlot().removeIf(getType(), true);
other.getSpellSlot().removeIf(SpellType.MIMIC);
caster.getSpellSlot().removeIf(getType());
if (!isValidTarget(master) || !isValidTarget(e)) {
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);
});
initialized = true;
setDirty();
}
if (counterpart.isSet()) {
@ -143,13 +142,12 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti
if (other == null) {
caster.getOriginatingCaster().asEntity().damage(caster.asWorld().getDamageSources().magic(), Float.MAX_VALUE);
setDead();
destroy(caster);
return false;
}
if (!Caster.of(other).get().getSpellSlot().contains(SpellType.MIMIC)) {
onDestroyed(caster);
setDead();
destroy(caster);
return false;
}
}
@ -158,8 +156,7 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti
counterpart.ifPresent(caster.asWorld(), e -> {
e.damage(e.getDamageSources().magic(), Float.MAX_VALUE);
});
onDestroyed(caster);
setDead();
destroy(caster);
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.spell.AbstractAreaEffectSpell;
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.entity.Creature;
import com.minelittlepony.unicopia.entity.EntityReference;
@ -79,6 +83,9 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
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 int spawnCountdown;
@ -90,7 +97,7 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
@Override
public boolean tick(Caster<?> source, Situation situation) {
float radius = 4 + source.getLevel().getScaled(4) * 4 + getTraits().get(Trait.POWER);
float radius = source.getLevel().getScaled(4) * 4 + RANGE.get(getTraits());
if (radius <= 0) {
return false;
@ -122,15 +129,16 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
return true;
}).isEmpty());
float additional = source.asWorld().getLocalDifficulty(source.getOrigin()).getLocalDifficulty() + getTraits().get(Trait.CHAOS, 0, 10);
setDirty();
if (--spawnCountdown > 0 && !summonedEntities.isEmpty()) {
return true;
}
// TODO: refactory speed attribute
// TODO: weather resistant attribute
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;
}
@ -153,6 +161,7 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
@Override
protected void onDestroyed(Caster<?> caster) {
super.onDestroyed(caster);
if (caster.isClient()) {
return;
}
@ -205,7 +214,6 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
source.asWorld().spawnEntity(minion);
summonedEntities.add(new EntityReference<>(minion));
setDirty();
}
@Override

View file

@ -1,19 +1,21 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.Optional;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.*;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.EntityReference;
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.server.world.Ether;
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.Blocks;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.packet.s2c.play.PositionFlag;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Util;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
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()
.with(Trait.LIFE, 10)
.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);
@Nullable
private UUID targetPortalId;
private float targetPortalPitch;
private float targetPortalYaw;
private final DataTracker.Entry<UUID> targetPortalId = dataTracker.startTracking(TrackableDataType.UUID, Util.NIL_UUID);
private final DataTracker.Entry<Float> targetPortalPitch = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private final DataTracker.Entry<Float> targetPortalYaw = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private final EntityReference<Entity> teleportationTarget = new EntityReference<>();
private boolean publishedPosition;
private float pitch;
private float yaw;
private final DataTracker.Entry<Float> pitch = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private final DataTracker.Entry<Float> yaw = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private Shape particleArea = PARTICLE_AREA;
@ -56,45 +57,56 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
super(type);
}
public boolean isLinked() {
return teleportationTarget.isSet();
}
public Optional<EntityReference.EntityValues<Entity>> getTarget() {
return teleportationTarget.getTarget();
public EntityReference<Entity> getDestinationReference() {
return teleportationTarget;
}
public float getPitch() {
return pitch;
return pitch.get();
}
public float getYaw() {
return yaw;
return yaw.get();
}
public float getTargetPitch() {
return targetPortalPitch;
return targetPortalPitch.get();
}
public float getTargetYaw() {
return targetPortalYaw;
return targetPortalYaw.get();
}
public float getYawDifference() {
return MathHelper.wrapDegrees(180 + targetPortalYaw - yaw);
return MathHelper.wrapDegrees(180 + getTargetYaw() - getYaw());
}
@SuppressWarnings("unchecked")
private Optional<Ether.Entry<PortalSpell>> getDestination(Caster<?> source) {
return getTarget().map(target -> Ether.get(source.asWorld()).get((SpellType<PortalSpell>)getType(), target, targetPortalId));
private Ether.Entry<PortalSpell> getDestination(Caster<?> source) {
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
public boolean apply(Caster<?> caster) {
setOrientation(caster.asEntity().getPitch(), caster.asEntity().getYaw());
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
public boolean tick(Caster<?> source, Situation situation) {
if (situation == Situation.GROUND) {
@ -103,53 +115,56 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
source.addParticle(ParticleTypes.ELECTRIC_SPARK, pos, Vec3d.ZERO);
});
} else {
teleportationTarget.getTarget().ifPresent(target -> {
if (Ether.get(source.asWorld()).get(getType(), target, targetPortalId) == null) {
Unicopia.LOGGER.debug("Lost sibling, breaking connection to " + target.uuid());
teleportationTarget.set(null);
setDirty();
var ownEntry = Ether.get(source.asWorld()).get(this, source);
synchronized (ownEntry) {
var targetEntry = getDestination(source);
if (targetEntry == null) {
if (teleportationTarget.isSet()) {
setDestination(null);
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(
entry -> tickWithTargetLink(source, entry),
() -> findLink(source)
);
}
} else {
tickActive(source, targetEntry);
}
}
}
Ether ether = Ether.get(source.asWorld());
var entry = ether.getOrCreate(this, source);
entry.pitch = pitch;
entry.yaw = yaw;
ether.markDirty();
var entry = Ether.get(source.asWorld()).getOrCreate(this, source);
entry.setPitch(pitch.get());
entry.setYaw(yaw.get());
}
return !isDead();
}
private void tickWithTargetLink(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();
}
private void tickActive(Caster<?> source, Ether.Entry<?> destination) {
destination.entity.getTarget().ifPresent(target -> {
source.findAllEntitiesInRange(1).forEach(entity -> {
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) {
return;
}
Vec3d offset = entity.getPos().subtract(source.getOriginVector());
float yawDifference = pitch < 15 ? getYawDifference() : 0;
Vec3d offset = entity.getPos().subtract(source.asEntity().getPos())
.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);
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.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);
setDirty();
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
public void setOrientation(float pitch, float yaw) {
this.pitch = pitch;
this.yaw = yaw;
public void setOrientation(Caster<?> caster, float pitch, float yaw) {
this.pitch.set(90 - pitch);
this.yaw.set(-yaw);
particleArea = PARTICLE_AREA.rotate(
pitch * MathHelper.RADIANS_PER_DEGREE,
(180 - yaw) * MathHelper.RADIANS_PER_DEGREE
this.pitch.get() * MathHelper.RADIANS_PER_DEGREE,
(180 - this.yaw.get()) * MathHelper.RADIANS_PER_DEGREE
);
setDirty();
}
@Override
public void onPlaced(Caster<?> source, PlaceableSpell parent, CastSpellEntity entity) {
LivingEntity caster = source.getMaster();
public void onPlaced(Caster<?> source, PlacementControlSpell parent) {
Entity caster = source.asEntity();
Vec3d targetPos = caster.getRotationVector().multiply(3).add(caster.getEyePos());
parent.setOrientation(pitch, yaw);
entity.setPos(targetPos.x, Math.abs(pitch) > 15 ? targetPos.y : caster.getPos().y, targetPos.z);
parent.setOrientation(source, -90 - source.asEntity().getPitch(), -source.asEntity().getYaw());
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
protected void onDestroyed(Caster<?> caster) {
Ether.get(caster.asWorld()).remove(getType(), caster);
getDestination(caster).ifPresent(Ether.Entry::release);
super.onDestroyed(caster);
if (!caster.isClient()) {
var destination = getDestination(caster);
if (destination != null) {
destination.release(getUuid());
}
}
}
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
if (targetPortalId != null) {
compound.putUuid("targetPortalId", targetPortalId);
}
compound.putBoolean("publishedPosition", publishedPosition);
compound.putUuid("targetPortalId", targetPortalId.get());
compound.put("teleportationTarget", teleportationTarget.toNBT());
compound.putFloat("pitch", pitch);
compound.putFloat("yaw", yaw);
compound.putFloat("targetPortalPitch", targetPortalPitch);
compound.putFloat("targetPortalYaw", targetPortalYaw);
compound.putFloat("pitch", getPitch());
compound.putFloat("yaw", getYaw());
compound.putFloat("targetPortalPitch", getTargetPitch());
compound.putFloat("targetPortalYaw", getTargetYaw());
}
@Override
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
targetPortalId = compound.containsUuid("targetPortalId") ? compound.getUuid("targetPortalId") : null;
publishedPosition = compound.getBoolean("publishedPosition");
targetPortalId.set(compound.containsUuid("targetPortalId") ? compound.getUuid("targetPortalId") : Util.NIL_UUID);
teleportationTarget.fromNBT(compound.getCompound("teleportationTarget"));
pitch = compound.getFloat("pitch");
yaw = compound.getFloat("yaw");
targetPortalPitch = compound.getFloat("targetPortalPitch");
targetPortalYaw = compound.getFloat("targetPortalYaw");
pitch.set(compound.getFloat("pitch"));
yaw.set(compound.getFloat("yaw"));
targetPortalPitch.set(compound.getFloat("targetPortalPitch"));
targetPortalYaw.set(compound.getFloat("targetPortalYaw"));
particleArea = PARTICLE_AREA.rotate(
pitch * MathHelper.RADIANS_PER_DEGREE,
(180 - yaw) * MathHelper.RADIANS_PER_DEGREE
pitch.get() * 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());
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);
});
}

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.Situation;
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.Trait;
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.ParticleUtils;
import com.minelittlepony.unicopia.projectile.ProjectileUtil;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.ColorHelper;
import com.minelittlepony.unicopia.util.Lerp;
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.EyeOfEnderEntity;
import net.minecraft.entity.FallingBlockEntity;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.TntEntity;
import net.minecraft.entity.Entity.RemovalReason;
@ -44,6 +51,19 @@ public class ShieldSpell extends AbstractSpell {
.with(Trait.AIR, 9)
.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);
private final Lerp radius = new Lerp(0);
@ -58,7 +78,7 @@ public class ShieldSpell extends AbstractSpell {
@Override
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
@ -90,6 +110,8 @@ public class ShieldSpell extends AbstractSpell {
if (source.isClient()) {
generateParticles(source);
} else {
Ether.get(source.asWorld()).getOrCreate(this, source).setRadius(radius.getValue());
}
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.
*/
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();
return range;
}
protected boolean isValidTarget(Caster<?> source, Entity entity) {
if (TARGET_ITEMS.get(getTraits())) {
return entity instanceof ItemEntity;
}
boolean valid = (entity instanceof LivingEntity
|| entity instanceof TntEntity
|| entity instanceof FallingBlockEntity
@ -159,13 +186,13 @@ public class ShieldSpell extends AbstractSpell {
|| entity instanceof BoatEntity
);
if (getTraits().get(Trait.LIFE) > 0) {
if (PERMIT_PASSIVE.get(getTraits())) {
valid &= !(entity instanceof PassiveEntity);
}
if (getTraits().get(Trait.BLOOD) > 0) {
if (PERMIT_HOSTILE.get(getTraits())) {
valid &= !(entity instanceof HostileEntity);
}
if (getTraits().get(Trait.ICE) > 0) {
if (PERMIT_PLAYER.get(getTraits())) {
valid &= !(entity instanceof PlayerEntity);
}
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.entity.damage.UDamageTypes;
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.ParticleUtils;
import com.minelittlepony.unicopia.particle.UParticles;
@ -37,6 +39,7 @@ import net.minecraft.util.math.Vec3d;
public class SiphoningSpell extends AbstractAreaEffectSpell {
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;
protected SiphoningSpell(CustomisedSpellType<?> type) {
@ -51,12 +54,12 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
@Override
public boolean tick(Caster<?> source, Situation situation) {
if (ticksUpset > 0) {
ticksUpset--;
if (ticksUpset > 0 && --ticksUpset <= 0) {
upset.set(false);
}
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;
source.spawnParticles(new Sphere(true, radius, 1, 0, 1), 1, pos -> {
@ -102,7 +105,7 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
} else {
e.damage(damage, e.getHealth() / 4);
ticksUpset = 100;
setDirty();
upset.set(true);
}
} else {
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) {
super.fromNBT(compound);
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 com.google.common.base.Suppliers;
import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Affine;
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.DispersableDisguiseSpell;
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.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.ThrowableSpell;
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.item.GemstoneItem;
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 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 RegistryKey<? extends Registry<SpellType<?>>> REGISTRY_KEY = REGISTRY.getKey();
private static final DynamicCommandExceptionType UNKNOWN_SPELL_TYPE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("spell_type.unknown", id));
public static final SpellType<PlaceableSpell> PLACED_SPELL = register("placed", Affinity.NEUTRAL, 0, false, GemstoneItem.Shape.DONUT, SpellTraits.EMPTY, PlaceableSpell::new);
public static final SpellType<ThrowableSpell> THROWN_SPELL = register("thrown", Affinity.NEUTRAL, 0, false, GemstoneItem.Shape.DONUT, SpellTraits.EMPTY, ThrowableSpell::new);
public static final SpellType<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", 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<ChangelingFeedingSpell> FEED = register("feed", Affinity.BAD, 0xBDBDF9, false, GemstoneItem.Shape.ARROW, SpellTraits.EMPTY, ChangelingFeedingSpell::new);
public static final SpellType<RainboomAbilitySpell> RAINBOOM = register("rainboom", Affinity.GOOD, 0xBDBDF9, false, GemstoneItem.Shape.ROCKET, SpellTraits.EMPTY, RainboomAbilitySpell::new);
public static final SpellType<RageAbilitySpell> RAGE = register("rage", Affinity.GOOD, 0xBDBDF9, false, GemstoneItem.Shape.FLAME, SpellTraits.EMPTY, RageAbilitySpell::new);
public static final SpellType<TimeControlAbilitySpell> TIME_CONTROL = register("time_control", Affinity.GOOD, 0xBDBDF9, false, GemstoneItem.Shape.STAR, SpellTraits.EMPTY, TimeControlAbilitySpell::new);
public static final SpellType<DispersableDisguiseSpell> CHANGELING_DISGUISE = register("disguise", builder(DispersableDisguiseSpell::new).affinity(Affinity.BAD).color(0x19E48E).unobtainable().shape(GemstoneItem.Shape.ARROW));
public static final SpellType<ChangelingFeedingSpell> FEED = register("feed", SpellType.<ChangelingFeedingSpell>builder(ChangelingFeedingSpell::new).affinity(Affinity.BAD).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.ARROW));
public static final SpellType<RainboomAbilitySpell> RAINBOOM = register("rainboom", builder(RainboomAbilitySpell::new).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.ROCKET));
public static final SpellType<RageAbilitySpell> RAGE = register("rage", builder(RageAbilitySpell::new).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.FLAME));
public static final SpellType<TimeControlAbilitySpell> TIME_CONTROL = register("time_control", builder(TimeControlAbilitySpell::new).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.STAR));
public static final SpellType<IceSpell> FROST = register("frost", Affinity.GOOD, 0xEABBFF, true, GemstoneItem.Shape.TRIANGLE, IceSpell.DEFAULT_TRAITS, IceSpell::new);
public static final SpellType<ChillingBreathSpell> CHILLING_BREATH = register("chilling_breath", Affinity.NEUTRAL, 0xFFEAFF, true, GemstoneItem.Shape.TRIANGLE, ChillingBreathSpell.DEFAULT_TRAITS, ChillingBreathSpell::new);
public static final SpellType<ScorchSpell> SCORCH = register("scorch", Affinity.BAD, 0xF8EC1F, true, GemstoneItem.Shape.FLAME, ScorchSpell.DEFAULT_TRAITS, ScorchSpell::new);
public static final SpellType<FireSpell> FLAME = register("flame", Affinity.GOOD, 0xFFBB99, true, GemstoneItem.Shape.FLAME, FireSpell.DEFAULT_TRAITS, FireSpell::new);
public static final SpellType<InfernoSpell> INFERNAL = register("infernal", Affinity.BAD, 0xFFAA00, true, GemstoneItem.Shape.FLAME, InfernoSpell.DEFAULT_TRAITS, InfernoSpell::new);
public static final SpellType<ShieldSpell> SHIELD = register("shield", Affinity.NEUTRAL, 0x66CDAA, true, GemstoneItem.Shape.SHIELD, ShieldSpell.DEFAULT_TRAITS, ShieldSpell::new);
public static final SpellType<AreaProtectionSpell> ARCANE_PROTECTION = register("arcane_protection", Affinity.BAD, 0x99CDAA, true, GemstoneItem.Shape.SHIELD, AreaProtectionSpell.DEFAULT_TRAITS, AreaProtectionSpell::new);
public static final SpellType<AttractiveSpell> VORTEX = register("vortex", Affinity.NEUTRAL, 0xFFEA88, true, GemstoneItem.Shape.VORTEX, AttractiveSpell.DEFAULT_TRAITS, AttractiveSpell::new);
public static final SpellType<DarkVortexSpell> DARK_VORTEX = register("dark_vortex", Affinity.BAD, 0xA33333, true, GemstoneItem.Shape.VORTEX, DarkVortexSpell.DEFAULT_TRAITS, DarkVortexSpell::new);
public static final SpellType<NecromancySpell> NECROMANCY = register("necromancy", Affinity.BAD, 0xFA3A3A, true, GemstoneItem.Shape.SKULL, SpellTraits.EMPTY, NecromancySpell::new);
public static final SpellType<SiphoningSpell> SIPHONING = register("siphoning", Affinity.NEUTRAL, 0xFFA3AA, true, GemstoneItem.Shape.LAMBDA, SpellTraits.EMPTY, SiphoningSpell::new);
public static final SpellType<DisperseIllusionSpell> REVEALING = register("reveal", Affinity.GOOD, 0xFFFFAF, true, GemstoneItem.Shape.CROSS, SpellTraits.EMPTY, DisperseIllusionSpell::new);
public static final SpellType<AwkwardSpell> AWKWARD = register("awkward", Affinity.GOOD, 0x3A59FF, true, GemstoneItem.Shape.ICE, SpellTraits.EMPTY, AwkwardSpell::new);
public static final SpellType<TransformationSpell> TRANSFORMATION = register("transformation", Affinity.GOOD, 0x19E48E, true, GemstoneItem.Shape.BRUSH, SpellTraits.EMPTY, TransformationSpell::new);
public static final SpellType<FeatherFallSpell> FEATHER_FALL = register("feather_fall", Affinity.GOOD, 0x00EEFF, true, GemstoneItem.Shape.LAMBDA, FeatherFallSpell.DEFAULT_TRAITS, FeatherFallSpell::new);
public static final SpellType<CatapultSpell> CATAPULT = register("catapult", Affinity.GOOD, 0x22FF00, true, GemstoneItem.Shape.ROCKET, CatapultSpell.DEFAULT_TRAITS, CatapultSpell::new);
public static final SpellType<FireBoltSpell> FIRE_BOLT = register("fire_bolt", Affinity.GOOD, 0xFF8811, true, GemstoneItem.Shape.FLAME, FireBoltSpell.DEFAULT_TRAITS, FireBoltSpell::new);
public static final SpellType<LightSpell> LIGHT = register("light", Affinity.GOOD, 0xEEFFAA, true, GemstoneItem.Shape.STAR, LightSpell.DEFAULT_TRAITS, LightSpell::new);
public static final SpellType<DisplacementSpell> DISPLACEMENT = register("displacement", Affinity.NEUTRAL, 0x9900FF, true, GemstoneItem.Shape.BRUSH, PortalSpell.DEFAULT_TRAITS, DisplacementSpell::new);
public static final SpellType<PortalSpell> PORTAL = register("portal", Affinity.GOOD, 0x99FFFF, true, GemstoneItem.Shape.RING, PortalSpell.DEFAULT_TRAITS, PortalSpell::new);
public static final SpellType<MimicSpell> MIMIC = register("mimic", Affinity.GOOD, 0xFFFF00, true, GemstoneItem.Shape.ARROW, SpellTraits.EMPTY, MimicSpell::new);
public static final SpellType<MindSwapSpell> MIND_SWAP = register("mind_swap", Affinity.BAD, 0xF9FF99, true, GemstoneItem.Shape.WAVE, SpellTraits.EMPTY, MindSwapSpell::new);
public static final SpellType<HydrophobicSpell> HYDROPHOBIC = register("hydrophobic", Affinity.NEUTRAL, 0xF999FF, true, GemstoneItem.Shape.ROCKET, SpellTraits.EMPTY, s -> new HydrophobicSpell(s, FluidTags.WATER));
public static final SpellType<BubbleSpell> BUBBLE = register("bubble", Affinity.NEUTRAL, 0xF999FF, true, GemstoneItem.Shape.DONUT, BubbleSpell.DEFAULT_TRAITS, BubbleSpell::new);
public static final SpellType<DispellEvilSpell> DISPEL_EVIL = register("dispel_evil", Affinity.GOOD, 0x00FF00, true, GemstoneItem.Shape.CROSS, DispellEvilSpell.DEFAULT_TRAITS, DispellEvilSpell::new);
public static final SpellType<IceSpell> FROST = register("frost", builder(IceSpell::new).color(0xEABBFF).shape(GemstoneItem.Shape.TRIANGLE).traits(IceSpell.DEFAULT_TRAITS).tooltip(IceSpell.TOOLTIP));
public static final SpellType<ChillingBreathSpell> CHILLING_BREATH = register("chilling_breath", builder(ChillingBreathSpell::new).affinity(Affinity.NEUTRAL).color(0xFFEAFF).shape(GemstoneItem.Shape.TRIANGLE).traits(ChillingBreathSpell.DEFAULT_TRAITS));
public static final SpellType<ScorchSpell> SCORCH = register("scorch", builder(ScorchSpell::new).affinity(Affinity.BAD).color(0xF8EC1F).stackable().shape(GemstoneItem.Shape.FLAME).traits(ScorchSpell.DEFAULT_TRAITS).tooltip(AbstractAreaEffectSpell.TOOLTIP));
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", 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", 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", 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", 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", 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", builder(NecromancySpell::new).affinity(Affinity.BAD).color(0xFA3A3A).shape(GemstoneItem.Shape.SKULL).tooltip(NecromancySpell.TOOLTIP));
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", builder(DisperseIllusionSpell::new).color(0xFFFFAF).shape(GemstoneItem.Shape.CROSS).tooltip(DisperseIllusionSpell.TOOLTIP));
public static final SpellType<AwkwardSpell> AWKWARD = register("awkward", builder(AwkwardSpell::new).affinity(Affinity.NEUTRAL).color(0x3A59FF).shape(GemstoneItem.Shape.ICE));
public static final SpellType<TransformationSpell> TRANSFORMATION = register("transformation", builder(TransformationSpell::new).color(0x19E48E).shape(GemstoneItem.Shape.BRUSH));
public static final SpellType<FeatherFallSpell> FEATHER_FALL = register("feather_fall", builder(FeatherFallSpell::new).color(0x00EEFF).shape(GemstoneItem.Shape.LAMBDA).traits(FeatherFallSpell.DEFAULT_TRAITS).tooltip(FeatherFallSpell.TOOLTIP));
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", builder(FireBoltSpell::new).color(0xFF8811).shape(GemstoneItem.Shape.FLAME).traits(FireBoltSpell.DEFAULT_TRAITS).tooltip(FireBoltSpell.TOOLTIP));
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", 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", builder(PortalSpell::new).color(0x99FFFF).shape(GemstoneItem.Shape.RING).traits(PortalSpell.DEFAULT_TRAITS));
public static final SpellType<MimicSpell> MIMIC = register("mimic", builder(MimicSpell::new).color(0xFFFF00).shape(GemstoneItem.Shape.ARROW).tooltip(MimicSpell.TOOLTIP));
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", 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", 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", builder(DispellEvilSpell::new).color(0x00FF00).shape(GemstoneItem.Shape.CROSS).traits(DispellEvilSpell.DEFAULT_TRAITS).tooltip(DispellEvilSpell.TOOLTIP));
public static void bootstrap() {}
@ -82,6 +85,7 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
private final Affinity affinity;
private final int color;
private final boolean obtainable;
private final boolean stackable;
private final GemstoneItem.Shape shape;
private final Factory<T> factory;
@ -94,15 +98,19 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
private final ItemStack defaultStack;
private SpellType(Identifier id, Affinity affinity, int color, boolean obtainable, GemstoneItem.Shape shape, SpellTraits traits, Factory<T> factory) {
private 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.affinity = affinity;
this.color = color;
this.obtainable = obtainable;
this.shape = shape;
this.tooltipFunction = tooltipFunction;
this.factory = factory;
this.traits = traits;
traited = new CustomisedSpellType<>(this, traits);
this.stackable = stackable;
traited = new CustomisedSpellType<>(this, traits, SpellTraits::empty);
defaultStack = UItems.GEMSTONE.getDefaultStack(this);
}
@ -110,6 +118,10 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
return obtainable;
}
public boolean isStackable() {
return stackable;
}
public Identifier getId() {
return id;
}
@ -154,16 +166,20 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
}
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() {
return factory;
}
public TooltipFactory getTooltip() {
return tooltipFunction;
}
@Override
public boolean test(@Nullable Spell spell) {
return spell != null && spell.getType() == this;
return spell != null && spell.getTypeAndTraits().type() == this;
}
public void toNbt(NbtCompound tag) {
@ -179,12 +195,12 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
return "SpellType[" + getTranslationKey() + "]";
}
public static <T extends Spell> SpellType<T> register(String name, Affinity affinity, int color, boolean obtainable, GemstoneItem.Shape shape, SpellTraits traits, Factory<T> factory) {
return register(Unicopia.id(name), affinity, color, obtainable, shape, traits, factory);
public static <T extends Spell> SpellType<T> register(String name, Builder<T> builder) {
return register(Unicopia.id(name), builder);
}
public static <T extends Spell> SpellType<T> register(Identifier id, Affinity affinity, int color, boolean obtainable, GemstoneItem.Shape shape, SpellTraits traits, Factory<T> factory) {
return Registry.register(REGISTRY, id, new SpellType<>(id, affinity, color, obtainable, shape, traits, factory));
public static <T extends Spell> SpellType<T> register(Identifier id, Builder<T> builder) {
return Registry.register(REGISTRY, id, builder.build(id));
}
@SuppressWarnings("unchecked")
@ -209,4 +225,62 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
public interface Factory<T extends Spell> {
T create(CustomisedSpellType<T> type);
}
public static <T extends Spell> Builder<T> builder(Factory<T> factory) {
return new Builder<>(factory);
}
static class Builder<T extends Spell> {
private final Factory<T> factory;
private Affinity affinity = Affinity.GOOD;
private int color;
private boolean obtainable = true;
private boolean stackable = false;
private GemstoneItem.Shape shape = GemstoneItem.Shape.ROUND;
private SpellTraits traits = SpellTraits.EMPTY;
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() {
return new HashMap<>(REGISTRY);
}
@ -292,7 +296,8 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
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 -> {
Trait key = Trait.fromName(pair[0]).orElse(null);
if (key == null) {

View file

@ -165,7 +165,11 @@ public enum Trait implements CommandArgumentEnum<Trait> {
@Deprecated
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() {

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_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_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_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_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 com.minelittlepony.unicopia.entity.mob.StormCloudEntity;
import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.util.PosHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
@ -82,7 +82,7 @@ public class UnstableCloudBlock extends CloudBlock {
);
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);
}).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,
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.VertexConsumerProvider.Immediate;
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.item.ItemRenderer;
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.TENTACLE, TentacleEntityRenderer::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);
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.List;
import org.joml.Vector4f;
import com.google.common.base.MoreObjects;
import com.minelittlepony.common.client.gui.GameGui;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
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.entity.player.Pony;
import com.minelittlepony.unicopia.item.UItems;
@ -41,11 +40,11 @@ public class DismissSpellScreen extends GameGui {
double azimuth = 0;
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()) {
placeableSpells.add(placeable);
continue;
@ -58,15 +57,16 @@ public class DismissSpellScreen extends GameGui {
}
double minimalDistance = 75 * (ring - 1) - 25;
Vec3d origin = pony.getOriginVector();
Vec3d origin = pony.asEntity().getPos();
placeableSpells.forEach(placeable -> {
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
.normalize()
.multiply(minimalDistance + relativePos.length())
.rotateY((pony.asEntity().getYaw() - 180) * MathHelper.RADIANS_PER_DEGREE);
.multiply(minimalDistance + relativePos.horizontalLength())
.rotateY((180 + yaw) * MathHelper.RADIANS_PER_DEGREE);
addDrawableChild(new Entry(placeable).ofCartesian(cartesian));
});
});
@ -84,8 +84,8 @@ public class DismissSpellScreen extends GameGui {
matrices.push();
matrices.translate(width - mouseX, height - mouseY, 0);
DrawableUtil.drawLine(matrices, 0, 0, relativeMouseX, relativeMouseY, 0xFFFFFF88);
DrawableUtil.drawArc(matrices, 40, 80, 0, DrawableUtil.TAU, 0x00000010, false);
DrawableUtil.drawArc(matrices, 160, 1600, 0, DrawableUtil.TAU, 0x00000020, false);
DrawableUtil.drawArc(matrices, 40, 80, 0, DrawableUtil.TAU, 0x00000010);
DrawableUtil.drawArc(matrices, 160, 1600, 0, DrawableUtil.TAU, 0x00000020);
super.render(context, mouseX, mouseY, delta);
DrawableUtil.renderRaceIcon(context, pony.getObservedSpecies(), 0, 0, 16);
@ -137,17 +137,16 @@ public class DismissSpellScreen extends GameGui {
}
private Spell getActualSpell() {
if (spell instanceof AbstractDelegatingSpell) {
return ((AbstractDelegatingSpell)spell).getDelegates().stream().findFirst().orElse(spell);
}
return spell;
return spell instanceof AbstractDelegatingSpell s ? MoreObjects.firstNonNull(s.getDelegate(), s)
: spell instanceof PlacementControlSpell s ? MoreObjects.firstNonNull(s.getDelegate(), s)
: spell;
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
if (isMouseOver(relativeMouseX, relativeMouseY)) {
remove(this);
pony.getSpellSlot().removeIf(spell -> spell == this.spell, true);
pony.getSpellSlot().removeIf(spell -> spell == this.spell);
Channel.REMOVE_SPELL.sendToServer(new MsgRemoveSpell(spell));
playClickEffect();
return true;
@ -164,38 +163,45 @@ public class DismissSpellScreen extends GameGui {
public void render(DrawContext context, int mouseX, int mouseY, float tickDelta) {
MatrixStack matrices = context.getMatrices();
var type = actualSpell.getType().withTraits(actualSpell.getTraits());
var type = actualSpell.getTypeAndTraits();
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.renderItemIcon(context, actualSpell.isDead() ? UItems.BOTCHED_GEM.getDefaultStack() : type.getDefaultStack(),
x - 8 - copy.x * 0.2F,
y - 8 - copy.y * 0.2F,
1
);
int color = actualSpell.getType().getColor() << 2;
int color = type.type().getColor() << 2;
matrices.push();
matrices.translate(x, y, 0);
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)) {
DrawableUtil.drawArc(matrices, 0, 8, 0, DrawableUtil.TAU, color | 0x000000FF, false);
boolean hovered = isMouseOver(relativeMouseX, relativeMouseY);
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<>();
MutableText name = actualSpell.getType().getName().copy();
color = actualSpell.getType().getColor();
MutableText name = type.type().getName().copy();
color = type.type().getColor();
name.setStyle(name.getStyle().withColor(color == 0 ? 0xFFAAAAAA : color));
tooltip.add(Text.translatable("gui.unicopia.dispell_screen.spell_type", name));
actualSpell.getType().getTraits().appendTooltip(tooltip);
type.traits().appendTooltip(tooltip);
tooltip.add(ScreenTexts.EMPTY);
tooltip.add(Text.translatable("gui.unicopia.dispell_screen.affinity", actualSpell.getAffinity().name()).formatted(actualSpell.getAffinity().getColor()));
type.appendTooltip(tooltip);
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) {
tooltip.add(ScreenTexts.EMPTY);
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();
}
/**
* 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.
*
* @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 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();
@ -102,7 +116,7 @@ public interface DrawableUtil {
BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer();
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
cylendricalVertex(bufferBuilder, model, innerRadius, angle, r, g, b, k);
// point one
@ -116,70 +130,6 @@ public interface DrawableUtil {
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) {
bufferBuilder.vertex(model,
(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.entity.duck.EntityDuck;
import com.minelittlepony.unicopia.entity.effect.EffectUtils;
import com.minelittlepony.unicopia.entity.effect.UEffects;
import com.minelittlepony.unicopia.entity.player.Pony;
@ -31,7 +30,7 @@ public class HudEffects {
private static void apply(Pony pony, float tickDelta, boolean 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;
pony.asEntity().addStatusEffect(new StatusEffectInstance(StatusEffects.HUNGER, 1, 1, false, false));
}

View file

@ -1,6 +1,7 @@
package com.minelittlepony.unicopia.client.gui;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.Abilities;
import com.minelittlepony.unicopia.ability.AbilityDispatcher;
import com.minelittlepony.unicopia.ability.AbilitySlot;
import com.minelittlepony.unicopia.entity.player.MagicReserves;
@ -21,19 +22,42 @@ class ManaRingSlot extends Slot {
@Override
protected void renderContents(DrawContext context, AbilityDispatcher abilities, boolean bSwap, float tickDelta) {
MatrixStack matrices = context.getMatrices();
matrices.push();
matrices.translate(24.5, 25.5, 0);
matrices.translate(24.125, 24.75, 0);
Pony pony = Pony.of(uHud.client.player);
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()) {
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()
.mapToDouble(s -> s.getCost(Unicopia.getConfig().hudPage.get()))
@ -53,28 +77,25 @@ class ManaRingSlot extends Slot {
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();
super.renderContents(context, abilities, bSwap, tickDelta);
}
private double renderRing(MatrixStack matrices, double outerRadius, double innerRadius, double offsetAngle, Bar bar, int color, float tickDelta) {
double fill = bar.getPercentFill(tickDelta) * DrawableUtil.TAU;
double shadow = bar.getShadowFill(tickDelta) * DrawableUtil.TAU;
private double renderRing(MatrixStack matrices, double outerRadius, double innerRadius, double offsetAngle, double maxAngle, Bar bar, int color, float tickDelta) {
double fill = bar.getPercentFill(tickDelta) * maxAngle;
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) {
color = (color & 0xFFFFFF00)
| ((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;
}

View file

@ -89,11 +89,16 @@ class Slot {
bSwap &= abilities.isFilled(bSlot);
}
int page = Unicopia.getConfig().hudPage.get();
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;
}
}
RenderSystem.setShaderColor(1, 1, 1, 1);
RenderSystem.enableBlend();
@ -104,8 +109,6 @@ class Slot {
// background
context.drawTexture(UHud.HUD_TEXTURE, 0, 0, backgroundU, backgroundV, size, size, 128, 128);
int iconPosition = ((size - iconSize + slotPadding + 1) / 2);
int sz = iconSize - slotPadding;
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;
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 + 3, radius + 4, 0, DrawableUtil.TAU, color & 0xFFFFFFAF, false);
pony.getSpellSlot().get(spell.and(SpellPredicate.IS_TIMED), false).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, color & 0xFFFFFF2F);
DrawableUtil.drawArc(modelStack, radius + 3, radius + 4, 0, DrawableUtil.TAU, color & 0xFFFFFFAF);
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);
});
long count = pony.getSpellSlot().stream(spell, false).count();
long count = pony.getSpellSlot().stream(spell).count();
if (count > 1) {
modelStack.push();
modelStack.translate(1, 1, 900);

View file

@ -101,7 +101,7 @@ public class UHud {
float flapCooldown = pony.getPhysics().getFlapCooldown(tickDelta);
if (flapCooldown > 0) {
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();
@ -205,7 +205,7 @@ public class UHud {
context.fill(RenderLayers.getEndPortal(), 0, 0, scaledWidth, scaledHeight, 0);
context.getMatrices().push();
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();
return;
} else if (vortexDistortion > 0) {
@ -214,8 +214,8 @@ public class UHud {
boolean hasEffect = client.player.hasStatusEffect(UEffects.SUN_BLINDNESS);
ItemStack glasses = GlassesItem.getForEntity(client.player);
boolean hasSunglasses = glasses.getItem() == UItems.SUNGLASSES;
ItemStack glasses = GlassesItem.getForEntity(client.player).stack();
boolean hasSunglasses = glasses.isOf(UItems.SUNGLASSES);
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;

View file

@ -18,6 +18,8 @@ import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.item.TooltipContext;
import net.minecraft.client.render.*;
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.model.json.ModelTransformationMode;
import net.minecraft.client.util.math.MatrixStack;
@ -324,7 +326,7 @@ public class IngredientTree implements SpellbookRecipe.CraftingTreeBuilder {
@Override
public void onClick() {
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);
}
}

View file

@ -6,16 +6,10 @@ import java.util.stream.Stream;
import com.minelittlepony.common.client.gui.IViewRoot;
import com.minelittlepony.unicopia.Debug;
import com.minelittlepony.unicopia.Unicopia;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.util.Identifier;
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 Chapter craftingChapter;
@ -73,10 +67,6 @@ public class SpellbookChapterList {
default void copyStateFrom(Content old) {}
default boolean showInventory() {
return false;
}
default Identifier getIcon(Chapter chapter, Identifier icon) {
return icon;
}

View file

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

View file

@ -58,11 +58,6 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content
}
}
@Override
public boolean showInventory() {
return true;
}
@Override
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 xpColor = 0xAA0040FF | ((int)((0.3F + 0.7F * xpPercentage) * 0xFF) & 0xFF) << 16;
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 |= (int)((0.3F + 0.7F * alphaF) * 0x40) << 16;
DrawableUtil.drawArc(matrices, 0, radius + 24, 0, DrawableUtil.TAU, color, false);
DrawableUtil.drawArc(matrices, radius / 3, radius + 6, 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);
if (currentLevel >= pony.getLevel().getMax()) {
int rayCount = 6;
@ -134,14 +129,14 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content
double rad = (radius + glowSize) * 0.8F + growth - (i % 2) * 5;
float rot = (rotate + raySeparation * i) % MathHelper.TAU;
DrawableUtil.drawArc(matrices, 0, rad, rot, 0.2F, bandAColor, false);
DrawableUtil.drawArc(matrices, 0, rad + 0.3F, rot + 0.37F, 0.25F, bandBColor, false);
DrawableUtil.drawArc(matrices, 0, rad, rot, 0.2F, bandAColor);
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;
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();

View file

@ -14,7 +14,6 @@ import com.minelittlepony.unicopia.Debug;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia;
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.spellbook.SpellbookChapterList.*;
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 Chapter craftingChapter;
private final SpellbookTraitDexPageContent traitDex = new SpellbookTraitDexPageContent(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(SpellbookChapterList.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.CRAFTING_ID, TabSide.LEFT, 0, 0, Optional.of(new SpellbookCraftingPageContent(this))),
new Chapter(SpellbookState.PROFILE_ID, TabSide.LEFT, 1, 0, Optional.of(new SpellbookProfilePageContent(this))),
new Chapter(SpellbookState.TRAIT_DEX_ID, TabSide.LEFT, 3, 0, Optional.of(traitDex))
);
private final SpellbookTabBar tabs = new SpellbookTabBar(this, chapters);
@ -68,13 +66,6 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
backgroundWidth = 405;
backgroundHeight = 219;
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 -> {
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<>();
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.getMatrices().pop();

View file

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

View file

@ -30,7 +30,7 @@ class AmuletGear extends AmuletModel implements Gear {
@Override
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
@ -40,7 +40,7 @@ class AmuletGear extends AmuletModel implements Gear {
@Override
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

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) {
alex = entity instanceof ClientPlayerEntity && ((ClientPlayerEntity)entity).getSkinTextures().model() == Model.SLIM;
FriendshipBraceletItem.getWornBangles((LivingEntity)entity, slot).findFirst().ifPresent(bracelet -> {
color = ((DyeableItem)bracelet.getItem()).getColor(bracelet);
glowing = ((GlowableItem)bracelet.getItem()).isGlowing(bracelet);
color = ((DyeableItem)bracelet.stack().getItem()).getColor(bracelet.stack());
glowing = ((GlowableItem)bracelet.stack().getItem()).isGlowing(bracelet.stack());
});
BraceletModel m = alex ? alexModel : steveModel;
@ -76,7 +76,7 @@ class BangleGear implements Gear {
m.setAngles(biped);
}
Arm mainArm = ((LivingEntity)entity).getMainArm();
m.setVisible(slot == TrinketsDelegate.MAINHAND ? mainArm : mainArm.getOpposite());
m.setVisible(slot == TrinketsDelegate.MAIN_GLOVE ? mainArm : mainArm.getOpposite());
}
@Override

View file

@ -28,7 +28,7 @@ class GlassesGear extends GlassesModel implements Gear {
@Override
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
@ -38,7 +38,7 @@ class GlassesGear extends GlassesModel implements Gear {
@Override
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

View file

@ -43,8 +43,8 @@ public class Main extends MineLPDelegate implements ClientModInitializer {
public void onInitializeClient() {
INSTANCE = this;
PonyModelPrepareCallback.EVENT.register(this::onPonyModelPrepared);
Gear.register(() -> new BangleGear(TrinketsDelegate.MAINHAND));
Gear.register(() -> new BangleGear(TrinketsDelegate.OFFHAND));
Gear.register(() -> new BangleGear(TrinketsDelegate.MAIN_GLOVE));
Gear.register(() -> new BangleGear(TrinketsDelegate.SECONDARY_GLOVE));
Gear.register(HeldEntityGear::new);
Gear.register(BodyPartGear::pegasusWings);
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));
} else {
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) {
target = null;

View file

@ -42,7 +42,7 @@ public class AmuletFeatureRenderer<E extends LivingEntity> implements AccessoryF
@Override
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()) {
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
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 -> {
renderBangleThirdPerson(bangle, stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch, entity.getMainArm());
FriendshipBraceletItem.getWornBangles(entity, TrinketsDelegate.MAIN_GLOVE).findFirst().ifPresent(bangle -> {
renderBangleThirdPerson(bangle.stack(), stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch, entity.getMainArm());
});
FriendshipBraceletItem.getWornBangles(entity, TrinketsDelegate.OFFHAND).findFirst().ifPresent(bangle -> {
renderBangleThirdPerson(bangle, stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch, entity.getMainArm().getOpposite());
FriendshipBraceletItem.getWornBangles(entity, TrinketsDelegate.SECONDARY_GLOVE).findFirst().ifPresent(bangle -> {
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
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 -> {
int j = ((DyeableItem)item.getItem()).getColor(item);
FriendshipBraceletItem.getWornBangles(entity, side == entity.getMainArm() ? TrinketsDelegate.MAIN_GLOVE : TrinketsDelegate.SECONDARY_GLOVE).findFirst().ifPresent(item -> {
int j = ((DyeableItem)item.stack().getItem()).getColor(item.stack());
boolean alex = entity instanceof ClientPlayerEntity && ((ClientPlayerEntity)entity).getSkinTextures().model() == SkinTextures.Model.SLIM;
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()) {
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) {
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)
.map(EntityAppearance::getAppearance)
.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.mixin.MixinBlockEntity;
import net.minecraft.block.BlockState;
import net.minecraft.block.BlockRenderType;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.VertexConsumerProvider;
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.model.BipedEntityModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.Entity;
import net.minecraft.state.property.Properties;
import net.minecraft.util.math.Direction;
import net.minecraft.entity.FallingBlockEntity;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RotationAxis;
import net.minecraft.util.math.Vec3d;
class EntityDisguiseRenderer {
@ -37,7 +36,9 @@ class EntityDisguiseRenderer {
double x, double y, double z,
float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) {
int fireTicks = pony.asEntity().doesRenderOnFire() ? 1 : 0;
if (!delegate.client.isPaused()) {
disguise.update(pony, false);
}
EntityAppearance ve = disguise.getDisguise();
Entity e = ve.getAppearance();
@ -53,11 +54,11 @@ class EntityDisguiseRenderer {
}
render(ve, e, x, y, z, fireTicks, tickDelta, matrices, vertices, light);
ve.getAttachments().forEach(ee -> {
PehkUtil.copyScale(pony.asEntity(), ee);
Vec3d difference = ee.getPos().subtract(e.getPos());
render(ve, ee, x + difference.x, y + difference.y, z + difference.z, fireTicks, tickDelta, matrices, vertices, light);
PehkUtil.clearScale(ee);
ve.getAttachments().forEach(attachment -> {
PehkUtil.copyScale(pony.asEntity(), attachment.entity());
Vec3d difference = attachment.entity().getPos().subtract(e.getPos());
render(ve, attachment.entity(), x + difference.x, y + difference.y, z + difference.z, fireTicks, tickDelta, matrices, vertices, light);
PehkUtil.clearScale(attachment.entity());
});
matrices.push();
@ -70,6 +71,7 @@ class EntityDisguiseRenderer {
return true;
}
@SuppressWarnings("deprecation")
private void render(EntityAppearance ve, Entity e,
double x, double y, double z,
int fireTicks, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light) {
@ -88,26 +90,24 @@ class EntityDisguiseRenderer {
BlockEntityRenderer<BlockEntity> r = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(blockEntity);
if (r != null) {
((MixinBlockEntity)blockEntity).setPos(e.getBlockPos());
if (e instanceof FallingBlockEntity fbe) {
blockEntity.setCachedState(fbe.getBlockState());
}
blockEntity.setWorld(e.getWorld());
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.multiply(direction.getRotationQuaternion());
matrices.multiply(RotationAxis.NEGATIVE_X.rotationDegrees(90));
matrices.translate(-0.5, 0, -0.5);
r.render(blockEntity, 1, matrices, vertexConsumers, light, OverlayTexture.DEFAULT_UV);
matrices.pop();
blockEntity.setWorld(null);
BlockRenderType type = blockEntity.getCachedState().getRenderType();
if (type == BlockRenderType.ENTITYBLOCK_ANIMATED) {
return;
}
}
}
BipedEntityModel<?> model = getBipedModel(e);
@ -116,7 +116,15 @@ class EntityDisguiseRenderer {
}
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);
if (model != null) {

View file

@ -51,7 +51,7 @@ class EntityReplacementManager implements Disguise {
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) {

View file

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

View file

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

View file

@ -1,16 +1,35 @@
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.SpellRenderer;
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.entity.EntityRenderer;
import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.screen.PlayerScreenHandler;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.RotationAxis;
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) {
super(ctx);
}
@ -21,11 +40,62 @@ public class CastSpellEntityRenderer extends EntityRenderer<CastSpellEntity> {
}
@Override
public void render(CastSpellEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light) {
SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertexConsumers, light, entity, 0, 0, tickDelta, getAnimationProgress(entity, tickDelta), yaw, 0);
public void render(CastSpellEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) {
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) {
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
);
RenderLayer layer = entity.getSpellSlot().get(true)
.map(spell -> (0x99 << 24) | spell.getType().getColor())
RenderLayer layer = entity.getSpellSlot().get()
.map(spell -> (0x99 << 24) | spell.getTypeAndTraits().type().getColor())
.map(RenderLayers::getMagicColored)
.orElseGet(RenderLayers::getMagicColored);

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