mirror of
https://github.com/Sollace/Unicopia.git
synced 2024-11-23 21:38:00 +01:00
Merge branch '1.20.1' into 1.20.2
This commit is contained in:
commit
74a08b4820
224 changed files with 4953 additions and 2679 deletions
|
@ -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() {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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--;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package com.minelittlepony.unicopia.ability.magic.spell.attribute;
|
||||
|
||||
public enum CastOn {
|
||||
LOCATION,
|
||||
SELF
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.minelittlepony.unicopia.ability.magic.spell.attribute;
|
||||
|
||||
public enum Permits {
|
||||
ITEMS,
|
||||
PASSIVE,
|
||||
HOSTILE,
|
||||
PLAYER
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -40,8 +40,6 @@ public class AwkwardSpell extends AbstractSpell implements TimedSpell {
|
|||
if (timer.getTicksRemaining() <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setDirty();
|
||||
}
|
||||
|
||||
if (source.isClient()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())));
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue