Fixed unable to place the necromancy spell

This commit is contained in:
Sollace 2023-08-06 18:26:17 +01:00
parent 82daf178df
commit 9a0041e295
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
28 changed files with 234 additions and 153 deletions

View file

@ -6,6 +6,7 @@ import java.util.UUID;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.EntityReference.EntityValues;
import net.minecraft.entity.Entity;
@ -26,7 +27,7 @@ public interface WeaklyOwned<E extends Entity> extends Owned<E>, WorldConvertabl
@Override
default Optional<UUID> getMasterId() {
return getMasterReference().getId();
return getMasterReference().getTarget().map(EntityValues::uuid);
}
interface Mutable<E extends Entity> extends WeaklyOwned<E>, Owned.Mutable<E> {

View file

@ -7,6 +7,7 @@ import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.mixin.MixinFallingBlockEntity;
@ -51,7 +52,7 @@ public class ChangelingDisguiseAbility extends ChangelingFeedAbility {
player.getEntityWorld().playSound(null, player.getBlockPos(), USounds.ENTITY_PLAYER_CHANGELING_TRANSFORM, SoundCategory.PLAYERS, 1.4F, 0.4F);
iplayer.getSpellSlot().get(SpellType.CHANGELING_DISGUISE, true)
.orElseGet(() -> SpellType.CHANGELING_DISGUISE.withTraits().apply(iplayer))
.orElseGet(() -> SpellType.CHANGELING_DISGUISE.withTraits().apply(iplayer, CastingMethod.INNATE))
.setDisguise(looked);
if (!player.isCreative()) {

View file

@ -4,6 +4,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
@ -78,7 +79,7 @@ public class PegasusRainboomAbility implements Ability<Hit> {
player.subtractEnergyCost(9);
player.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, player.getPhysics().getMotionAngle()), player.getOriginVector(), Vec3d.ZERO);
SpellType.RAINBOOM.withTraits().apply(player);
SpellType.RAINBOOM.withTraits().apply(player, CastingMethod.INNATE);
}
@Override

View file

@ -4,6 +4,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.*;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.HomingSpell;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
@ -106,7 +107,7 @@ public class UnicornCastingAbility extends AbstractSpellCastingAbility {
}, true);
player.subtractEnergyCost(removed ? 2 : 4);
if (!removed) {
Spell s = spell.apply(player);
Spell s = spell.apply(player, CastingMethod.GEM);
if (s == null) {
player.spawnParticles(ParticleTypes.LARGE_SMOKE, 6);
player.playSound(USounds.SPELL_CAST_FAIL, 1, 0.5F);

View file

@ -45,13 +45,6 @@ public interface Caster<E extends Entity> extends
*/
boolean subtractEnergyCost(double amount);
/**
* Gets the entity who originally cast the currently active spell.
* @return
*/
@Override
LivingEntity getMaster();
/**
* Gets the original caster responsible for this spell.
* If none is found, will return itself.

View file

@ -9,7 +9,7 @@ public abstract class AbstractAreaEffectSpell extends AbstractSpell {
}
@Override
public boolean apply(Caster<?> source) {
return toPlaceable().apply(source);
public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
return toPlaceable();
}
}

View file

@ -0,0 +1,24 @@
package com.minelittlepony.unicopia.ability.magic.spell;
public enum CastingMethod {
/**
* Casting from a gem or a unicorn's equipped spell.
*/
GEM,
/**
* Casting a projectile form from a gem or unicorn's equipped spell
*/
GEM_PROJECTILE,
/**
* Result of a projectile impact
*/
PROJECTILE,
/**
* Casting from a magic staff
*/
STAFF,
/**
* Result of an entities innate ability
*/
INNATE
}

View file

@ -8,6 +8,7 @@ import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.entity.CastSpellEntity;
import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.EntityReference.EntityValues;
import com.minelittlepony.unicopia.entity.UEntities;
import com.minelittlepony.unicopia.particle.OrientedBillboardParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleHandle;
@ -86,8 +87,8 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
setDirty();
}
castEntity.getId().ifPresentOrElse(
id -> checkDetachment(source, id),
castEntity.getTarget().ifPresentOrElse(
target -> checkDetachment(source, target),
() -> spawnPlacedEntity(source)
);
}
@ -96,9 +97,12 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
}
if (situation == Situation.GROUND_ENTITY) {
if (!source.isClient() && Ether.get(source.asWorld()).getEntry(getType(), source).isEmpty()) {
setDead();
return false;
if (!source.isClient()) {
Ether ether = Ether.get(source.asWorld());
if (ether.getEntry(getType(), source).isEmpty()) {
setDead();
return false;
}
}
if (spell instanceof PlacementDelegate delegate) {
@ -115,15 +119,15 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
return !isDead();
}
private void checkDetachment(Caster<?> source, UUID id) {
if (getWorld(source).map(Ether::get).flatMap(ether -> ether.getEntry(getType(), id)).isEmpty()) {
private void checkDetachment(Caster<?> source, EntityValues<?> target) {
if (getWorld(source).map(Ether::get).flatMap(ether -> ether.getEntry(getType(), target.uuid())).isEmpty()) {
setDead();
}
}
private void spawnPlacedEntity(Caster<?> source) {
CastSpellEntity entity = UEntities.CAST_SPELL.create(source.asWorld());
Vec3d pos = castEntity.getPosition().orElse(position.orElse(source.getOriginVector()));
Vec3d pos = getPosition().orElse(position.orElse(source.getOriginVector()));
entity.updatePositionAndAngles(pos.x, pos.y, pos.z, source.asEntity().getYaw(), source.asEntity().getPitch());
PlaceableSpell copy = spell.toPlaceable();
if (spell instanceof PlacementDelegate delegate) {
@ -162,9 +166,9 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
@Override
public void onDestroyed(Caster<?> source) {
if (!source.isClient()) {
castEntity.getId().ifPresent(id -> {
castEntity.getTarget().ifPresent(target -> {
getWorld(source).map(Ether::get)
.flatMap(ether -> ether.getEntry(getType(), id))
.flatMap(ether -> ether.getEntry(getType(), target.uuid()))
.ifPresent(Ether.Entry::markDead);
});
castEntity.set(null);
@ -184,7 +188,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
}
public Optional<Vec3d> getPosition() {
return castEntity.getPosition();
return castEntity.getTarget().map(EntityValues::pos);
}
public Optional<Attachment> getParticleEffectAttachment(Caster<?> source) {

View file

@ -74,6 +74,14 @@ public interface Spell extends NbtSerialisable, Affine {
return true;
}
/**
* Gets the default form of this spell used to apply to a caster.
* @param caster
*/
default Spell prepareForCast(Caster<?> caster, CastingMethod method) {
return this;
}
/**
* Called to generate this spell's effects.
* @param caster The caster currently fueling this spell

View file

@ -33,6 +33,11 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
return List.of(spell);
}
@Override
public boolean apply(Caster<?> source) {
return throwProjectile(source).isPresent();
}
/**
* Projects this spell.
*
@ -59,7 +64,7 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
projectile.setPosition(entity.getX(), entity.getEyeY() - 0.1F, entity.getZ());
projectile.setOwner(entity);
projectile.setItem(UItems.GEMSTONE.getDefaultStack(spell.getType()));
projectile.getSpellSlot().put(spell);
spell.prepareForCast(caster, CastingMethod.PROJECTILE).apply(projectile);
projectile.setVelocity(entity, entity.getPitch(), entity.getYaw(), 0, 1.5F, divergance);
projectile.setNoGravity(true);
configureProjectile(projectile, caster);

View file

@ -45,11 +45,12 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
}
setDirty();
Vec3d pos = caster.getOriginVector();
if (target.isPresent(caster.asWorld()) && target.get(caster.asWorld()).distanceTo(caster.asEntity()) > getDrawDropOffRange(caster)) {
target.get(caster.asWorld()).requestTeleport(pos.x, pos.y, pos.z);
}
target.getOrEmpty(caster.asWorld())
.filter(entity -> entity.distanceTo(caster.asEntity()) > getDrawDropOffRange(caster))
.ifPresent(entity -> {
Vec3d pos = caster.getOriginVector();
entity.requestTeleport(pos.x, pos.y, pos.z);
});
return super.tick(caster, situation);
}
@ -145,7 +146,7 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) {
if (!isDead() && getTraits().get(Trait.CHAOS) > 0) {
setDead();
Caster.of(hit.getEntity()).ifPresent(getTypeAndTraits()::apply);
Caster.of(hit.getEntity()).ifPresent(caster -> getTypeAndTraits().apply(caster, CastingMethod.PROJECTILE));
}
}

View file

@ -154,7 +154,7 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
@Override
public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) {
Caster.of(hit.getEntity()).ifPresent(caster -> {
getTypeAndTraits().apply(caster);
getTypeAndTraits().apply(caster, CastingMethod.PROJECTILE);
});
}

View file

@ -4,6 +4,7 @@ import org.jetbrains.annotations.Nullable;
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;
@ -32,13 +33,13 @@ public record CustomisedSpellType<T extends Spell> (
@Nullable
public T apply(Caster<?> caster) {
public T apply(Caster<?> caster, CastingMethod method) {
if (isEmpty()) {
return null;
}
T spell = create();
if (spell != null && spell.apply(caster)) {
if (spell != null && spell.prepareForCast(caster, method).apply(caster)) {
return spell;
}

View file

@ -5,7 +5,9 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Affine;
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.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
@ -66,8 +68,8 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega
}
@Override
public boolean apply(Caster<?> source) {
return toPlaceable().apply(source);
public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
return method == CastingMethod.STAFF ? toThrowable() : toPlaceable();
}
@Override

View file

@ -27,8 +27,8 @@ public class DisplacementSpell extends AbstractSpell implements HomingSpell, Pla
}
@Override
public boolean apply(Caster<?> caster) {
return toPlaceable().apply(caster);
public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
return toPlaceable();
}
@Override

View file

@ -57,7 +57,7 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell,
return true;
}
if (getTraits().get(Trait.FOCUS) >= 50 && !target.isPresent(caster.asWorld())) {
if (getTraits().get(Trait.FOCUS) >= 50 && target.getOrEmpty(caster.asWorld()).isEmpty()) {
target.set(caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49,
EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.notOwnerOrFriend(this, caster))

View file

@ -4,11 +4,13 @@ import java.util.ArrayList;
import java.util.List;
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.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.EntityReference.EntityValues;
import com.minelittlepony.unicopia.entity.FairyEntity;
import com.minelittlepony.unicopia.entity.UEntities;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
@ -60,14 +62,14 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD
if (lights.isEmpty()) {
int size = 2 + caster.asWorld().random.nextInt(2) + (int)(getTraits().get(Trait.LIFE, 10, 20) - 10)/10;
while (lights.size() < size) {
lights.add(new EntityReference<FairyEntity>());
lights.add(new EntityReference<>());
}
}
lights.forEach(ref -> {
if (!ref.isPresent(caster.asWorld())) {
if (ref.getOrEmpty(caster.asWorld()).isEmpty()) {
FairyEntity entity = UEntities.TWITTERMITE.create(caster.asWorld());
entity.setPosition(ref.getPosition().orElseGet(() -> {
entity.setPosition(ref.getTarget().map(EntityValues::pos).orElseGet(() -> {
return caster.getOriginVector().add(VecHelper.supply(() -> caster.asWorld().random.nextInt(3) - 1));
}));
entity.setMaster(caster);
@ -84,7 +86,7 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD
@Override
public void onImpact(MagicProjectileEntity projectile) {
Caster.of(projectile.getMaster()).ifPresent(getTypeAndTraits()::apply);
Caster.of(projectile.getMaster()).ifPresent(caster -> getTypeAndTraits().apply(caster, CastingMethod.PROJECTILE));
}
@Override

View file

@ -4,6 +4,7 @@ import java.util.Optional;
import com.minelittlepony.unicopia.USounds;
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.entity.EntityReference;
import com.minelittlepony.unicopia.entity.behaviour.EntitySwap;
@ -65,7 +66,7 @@ public class MindSwapSpell extends MimicSpell {
setDisguise(e);
Caster<?> other = Caster.of(e).get();
SpellType.MIMIC.withTraits().apply(other).setDisguise(master);
SpellType.MIMIC.withTraits().apply(other, CastingMethod.PROJECTILE).setDisguise(master);
EntitySwap.ALL.accept(master, e);
Inventory.swapInventories(
@ -80,7 +81,7 @@ public class MindSwapSpell extends MimicSpell {
});
}
if (counterpart.getId().isPresent() && counterpart.get(caster.asWorld()) == null) {
if (counterpart.isSet() && counterpart.get(caster.asWorld()) == null) {
caster.getOriginatingCaster().asEntity().damage(caster.asWorld().getDamageSources().magic(), Float.MAX_VALUE);
setDead();
return false;

View file

@ -72,9 +72,15 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
super(type);
}
@Override
public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
return method == CastingMethod.GEM ? toPlaceable() : super.prepareForCast(caster, method);
}
@Override
public boolean tick(Caster<?> source, Situation situation) {
float radius = source.getLevel().getScaled(4) * 4 + getTraits().get(Trait.POWER);
float radius = 4 + source.getLevel().getScaled(4) * 4 + getTraits().get(Trait.POWER);
if (radius <= 0) {
return false;
@ -82,22 +88,22 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
boolean rainy = source.asWorld().hasRain(source.getOrigin());
if (source.isClient()) {
source.spawnParticles(new Sphere(true, radius * 2), rainy ? 98 : 125, pos -> {
BlockPos bpos = BlockPos.ofFloored(pos);
source.spawnParticles(new Sphere(true, radius * 2), rainy ? 98 : 125, pos -> {
BlockPos bpos = BlockPos.ofFloored(pos);
if (!source.asWorld().isAir(bpos.down())) {
source.addParticle(source.asWorld().hasRain(bpos) ? ParticleTypes.SMOKE : ParticleTypes.FLAME, pos, Vec3d.ZERO);
}
});
return true;
}
if (source.asWorld().isAir(bpos) && !source.asWorld().isAir(bpos.down())) {
source.addParticle(source.asWorld().hasRain(bpos) ? ParticleTypes.SMOKE : ParticleTypes.FLAME, pos, Vec3d.ZERO);
}
});
if (source.asWorld().getDifficulty() == Difficulty.PEACEFUL) {
if (source.isClient() || source.asWorld().getDifficulty() == Difficulty.PEACEFUL) {
return true;
}
summonedEntities.removeIf(ref -> ref.getOrEmpty(source.asWorld()).filter(e -> {
if (e.isRemoved()) {
return false;
}
if (e.getPos().distanceTo(source.getOriginVector()) > radius * 2) {
e.getWorld().sendEntityStatus(e, (byte)60);
e.discard();

View file

@ -62,10 +62,10 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
if (source.isClient()) {
Vec3d origin = source.getOriginVector();
ParticleEffect effect = teleportationTarget.getPosition()
ParticleEffect effect = teleportationTarget.getTarget()
.map(target -> {
getType();
return new FollowingParticleEffect(UParticles.HEALTH_DRAIN, target, 0.2F).withChild(ParticleTypes.ELECTRIC_SPARK);
return new FollowingParticleEffect(UParticles.HEALTH_DRAIN, target.pos(), 0.2F).withChild(ParticleTypes.ELECTRIC_SPARK);
})
.orElse(ParticleTypes.ELECTRIC_SPARK);
@ -73,7 +73,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
source.addParticle(effect, pos, Vec3d.ZERO);
});
teleportationTarget.getPosition().ifPresentOrElse(position -> {
teleportationTarget.getTarget().ifPresentOrElse(target -> {
particleEffect.update(getUuid(), source, spawner -> {
spawner.addParticle(new SphereParticleEffect(UParticles.DISK, getType().getColor(), 0.8F, 1.8F, new Vec3d(pitch, yaw, 0)), source.getOriginVector(), Vec3d.ZERO);
});
@ -81,9 +81,9 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
particleEffect.destroy();
});
} else {
teleportationTarget.getId().ifPresent(id -> {
if (Ether.get(source.asWorld()).getEntry(getType(), id).isEmpty()) {
Unicopia.LOGGER.debug("Lost sibling, breaking connection to " + id);
teleportationTarget.getTarget().ifPresent(target -> {
if (Ether.get(source.asWorld()).getEntry(getType(), target.uuid()).isEmpty()) {
Unicopia.LOGGER.debug("Lost sibling, breaking connection to " + target.uuid());
teleportationTarget.set(null);
setDirty();
source.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, source.getOrigin(), Block.getRawIdFromState(Blocks.GLASS.getDefaultState()));
@ -109,12 +109,12 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
private void tickWithTargetLink(Caster<?> source, Ether.Entry destination) {
destination.entity.getPosition().ifPresent(targetPos -> {
destination.entity.getTarget().ifPresent(target -> {
source.findAllEntitiesInRange(1).forEach(entity -> {
if (!entity.hasPortalCooldown() && entity.timeUntilRegen <= 0) {
Vec3d offset = entity.getPos().subtract(source.getOriginVector());
float yawDifference = pitch < 15 ? (180 - yaw + destination.yaw) : 0;
Vec3d dest = targetPos.add(offset.rotateY(yawDifference * MathHelper.RADIANS_PER_DEGREE)).add(0, 0.05, 0);
Vec3d dest = target.pos().add(offset.rotateY(yawDifference * MathHelper.RADIANS_PER_DEGREE)).add(0, 0.05, 0);
entity.resetPortalCooldown();
entity.timeUntilRegen = 100;
@ -145,7 +145,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
Ether ether = Ether.get(source.asWorld());
ether.getEntries(getType())
.stream()
.filter(entry -> entry.isAvailable() && !entry.entity.referenceEquals(source.asEntity()) && entry.entity.getId().isPresent())
.filter(entry -> entry.isAvailable() && !entry.entity.referenceEquals(source.asEntity()) && entry.entity.isSet())
.findAny()
.ifPresent(entry -> {
entry.setTaken(true);
@ -155,7 +155,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
}
private Optional<Ether.Entry> getTarget(Caster<?> source) {
return teleportationTarget.getId().flatMap(id -> Ether.get(source.asWorld()).getEntry(getType(), id));
return teleportationTarget.getTarget().flatMap(target -> Ether.get(source.asWorld()).getEntry(getType(), target.uuid()));
}
@Override

View file

@ -1,7 +1,9 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect;
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.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.block.state.StateMaps;
@ -25,8 +27,8 @@ public class ScorchSpell extends FireSpell implements ProjectileDelegate.Configu
}
@Override
public boolean apply(Caster<?> source) {
return toPlaceable().apply(source);
public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
return method == CastingMethod.STAFF ? this : toPlaceable();
}
@Override

View file

@ -4,7 +4,9 @@ import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia;
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.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.player.Pony;
@ -47,11 +49,8 @@ public class ShieldSpell extends AbstractSpell {
}
@Override
public boolean apply(Caster<?> source) {
if (getTraits().get(Trait.GENEROSITY) > 0) {
return toPlaceable().apply(source);
}
return super.apply(source);
public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
return method == CastingMethod.STAFF || getTraits().get(Trait.GENEROSITY) > 0 ? toPlaceable() : this;
}
@Override

View file

@ -43,22 +43,22 @@ public class TargetSelecter {
return targets.values().stream().filter(Target::canHurt).count();
}
public static <T extends Entity> Predicate<T> notOwnerOrFriend(Affine spell, Caster<?> source) {
return target -> notOwnerOrFriend(spell, source, target);
public static <T extends Entity> Predicate<T> notOwnerOrFriend(Affine affine, Caster<?> source) {
return target -> notOwnerOrFriend(affine, source, target);
}
public static <T extends Entity> Predicate<T> isOwnerOrFriend(Affine spell, Caster<?> source) {
return target -> isOwnerOrFriend(spell, source, target);
public static <T extends Entity> Predicate<T> isOwnerOrFriend(Affine affine, Caster<?> source) {
return target -> isOwnerOrFriend(affine, source, target);
}
public static <T extends Entity> boolean notOwnerOrFriend(Affine spell, Caster<?> source, Entity target) {
return !isOwnerOrFriend(spell, source, target);
public static <T extends Entity> boolean notOwnerOrFriend(Affine affine, Caster<?> source, Entity target) {
return !isOwnerOrFriend(affine, source, target);
}
public static <T extends Entity> boolean isOwnerOrFriend(Affine spell, Caster<?> source, Entity target) {
public static <T extends Entity> boolean isOwnerOrFriend(Affine affine, Caster<?> source, Entity target) {
Entity owner = source.getMaster();
if (!(spell.isFriendlyTogether(source) && EquinePredicates.PLAYER_UNICORN.test(owner))) {
if (affine.isEnemy(source) || !EquinePredicates.PLAYER_UNICORN.test(owner)) {
return FriendshipBraceletItem.isComrade(source, target);
}

View file

@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.command;
import java.util.Optional;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.PlaceableSpell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
@ -112,7 +113,7 @@ public class CastCommand {
private static int apply(CommandContext<ServerCommandSource> source, TraitsFunc traits) throws CommandSyntaxException {
CustomisedSpellType<?> spellType = getSpell(source, traits);
EntityArgumentType.getEntities(source, "targets").forEach(target -> {
Caster.of(target).ifPresent(caster -> spellType.apply(caster));
Caster.of(target).ifPresent(caster -> spellType.apply(caster, CastingMethod.INNATE));
});
return 0;

View file

@ -5,6 +5,7 @@ import java.util.function.Function;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.mojang.authlib.GameProfile;
@ -83,7 +84,7 @@ public class DisguiseCommand {
Pony iplayer = Pony.of(player);
iplayer.getSpellSlot().get(SpellType.CHANGELING_DISGUISE, true)
.orElseGet(() -> SpellType.CHANGELING_DISGUISE.withTraits().apply(iplayer))
.orElseGet(() -> SpellType.CHANGELING_DISGUISE.withTraits().apply(iplayer, CastingMethod.INNATE))
.setDisguise(entity);
if (source.getEntity() == player) {

View file

@ -88,11 +88,6 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster<CastS
setMaster(caster);
}
@Override
public LivingEntity getMaster() {
return WeaklyOwned.Mutable.super.getMaster();
}
@Override
public LevelStore getLevel() {
return level;

View file

@ -1,11 +1,15 @@
package com.minelittlepony.unicopia.entity;
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.Levelled;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.entity.Entity;
@ -13,6 +17,7 @@ import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Util;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
@ -21,22 +26,15 @@ import net.minecraft.world.World;
* Used to store the 'owner' reference for certain objects that allows them to\
* remember who they belong to even when the entity has been unloaded.
*
* Will also remember the position and certain attributes of the owner.
*
* @param <T> The type of the entity this reference points to.
*/
public class EntityReference<T extends Entity> implements NbtSerialisable {
@Nullable
private EntityValues<T> reference;
/**
* Server UUID of the entity used to look up the entity instance on the server.
*/
private Optional<UUID> uuid = Optional.empty();
/**
* Corresponding client id used to look up the entity instance on the client.
*/
private int clientId;
/**
* The last-known position of the entity.
*/
private Optional<Vec3d> pos = Optional.empty();
private WeakReference<T> directReference = new WeakReference<>(null);
public EntityReference() {}
@ -48,48 +46,42 @@ public class EntityReference<T extends Entity> implements NbtSerialisable {
fromNBT(nbt);
}
@SuppressWarnings("unchecked")
public void copyFrom(EntityReference<? extends T> other) {
uuid = other.uuid;
clientId = other.clientId;
pos = other.pos;
this.reference = ((EntityReference<T>)other).reference;
this.directReference = new WeakReference<>(other.directReference.get());
}
public void set(@Nullable T entity) {
if (entity != null) {
uuid = Optional.of(entity.getUuid());
clientId = entity.getId();
pos = Optional.of(entity.getPos());
} else {
uuid = Optional.empty();
clientId = 0;
pos = Optional.empty();
public boolean set(@Nullable T entity) {
this.directReference = new WeakReference<>(entity);
this.reference = entity == null ? null : new EntityValues<>(entity);
return entity != null;
}
public Optional<EntityValues<T>> getTarget() {
T value = directReference.get();
if (value != null) {
set(value);
}
}
/**
* Gets the assigned entity's UUID
*/
public Optional<UUID> getId() {
return uuid;
}
/**
* Gets the last known position of the assigned entity.
*/
public Optional<Vec3d> getPosition() {
return pos;
return Optional.ofNullable(reference);
}
public boolean isSet() {
return getId().isPresent();
return reference != null;
}
public boolean referenceEquals(Entity entity) {
return entity != null && entity.getUuid().equals(uuid.orElse(null));
return entity != null && referenceEquals(entity.getUuid());
}
public boolean isPresent(World world) {
return getOrEmpty(world).isPresent();
public boolean referenceEquals(UUID uuid) {
return (reference == null ? Util.NIL_UUID : reference.uuid()).equals(uuid);
}
public boolean referenceEquals(@Nullable EntityReference<?> other) {
final EntityValues<?> st = reference;
final EntityValues<?> ot = other == null ? null : other.reference;
return st == ot || (st != null && ot != null && Objects.equals(st.uuid(), ot.uuid()));
}
public void ifPresent(World world, Consumer<T> consumer) {
@ -101,35 +93,74 @@ public class EntityReference<T extends Entity> implements NbtSerialisable {
return getOrEmpty(world).orElse(null);
}
@SuppressWarnings("unchecked")
public Optional<T> getOrEmpty(World world) {
if (uuid.isPresent() && world instanceof ServerWorld serverWorld) {
return uuid.map(serverWorld::getEntity).map(e -> (T)e).filter(this::checkReference);
}
if (clientId != 0) {
return Optional.ofNullable((T)world.getEntityById(clientId)).filter(this::checkReference);
}
return Optional.empty();
}
private boolean checkReference(Entity e) {
pos = Optional.of(e.getPos());
return e instanceof PlayerEntity || !e.isRemoved();
return Optional.ofNullable(directReference.get())
.or(() -> reference == null ? Optional.empty() : reference.resolve(world))
.filter(this::set);
}
@Override
public void toNBT(NbtCompound tag) {
uuid.ifPresent(uuid -> tag.putUuid("uuid", uuid));
pos.ifPresent(p -> tag.put("pos", NbtSerialisable.writeVector(p)));
tag.putInt("clientId", clientId);
getTarget().ifPresent(ref -> ref.toNBT(tag));
}
@Override
public void fromNBT(NbtCompound tag) {
uuid = tag.containsUuid("uuid") ? Optional.of(tag.getUuid("uuid")) : Optional.empty();
pos = tag.contains("pos") ? Optional.ofNullable(NbtSerialisable.readVector(tag.getList("pos", NbtElement.DOUBLE_TYPE))) : Optional.empty();
clientId = tag.getInt("clientId");
this.reference = tag.contains("uuid") ? new EntityValues<>(tag) : null;
}
@Override
public int hashCode() {
return getTarget().map(EntityValues::uuid).orElse(Util.NIL_UUID).hashCode();
}
public record EntityValues<T extends Entity>(
UUID uuid,
Vec3d pos,
int clientId,
boolean isPlayer,
boolean isDead,
Levelled.LevelStore level,
Levelled.LevelStore corruption) {
public EntityValues(Entity entity) {
this(
entity.getUuid(),
entity.getPos(),
entity.getId(), entity instanceof PlayerEntity,
!entity.isAlive(),
Caster.of(entity).map(Caster::getLevel).map(Levelled::copyOf).orElse(Levelled.EMPTY),
Caster.of(entity).map(Caster::getCorruption).map(Levelled::copyOf).orElse(Levelled.EMPTY)
);
}
public EntityValues(NbtCompound tag) {
this(
tag.getUuid("uuid"),
NbtSerialisable.readVector(tag.getList("pos", NbtElement.DOUBLE_TYPE)),
tag.getInt("clientId"),
tag.getBoolean("isPlayer"),
tag.getBoolean("isDead"),
Levelled.fromNbt(tag.getCompound("level")),
Levelled.fromNbt(tag.getCompound("corruption"))
);
}
@SuppressWarnings("unchecked")
public Optional<T> resolve(World world) {
if (world instanceof ServerWorld serverWorld) {
return Optional.ofNullable((T)serverWorld.getEntity(uuid));
}
return Optional.ofNullable((T)world.getEntityById(clientId()));
}
public void toNBT(NbtCompound tag) {
tag.putUuid("uuid", uuid);
tag.put("pos", NbtSerialisable.writeVector(pos));
tag.putInt("clientId", clientId);
tag.putBoolean("isPlayer", isPlayer);
tag.putBoolean("isDead", isDead);
tag.put("level", level.toNbt());
tag.put("corruption", corruption.toNbt());
}
}
}

View file

@ -9,6 +9,7 @@ import javax.annotation.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
@ -152,7 +153,7 @@ public class EnchantedStaffItem extends StaffItem implements EnchantableItem, Ch
if (attacker.isSneaking() && hasCharge(stack)) {
stack.damage(50, attacker, p -> p.sendEquipmentBreakStatus(EquipmentSlot.MAINHAND));
Caster.of(attacker).ifPresent(c -> c.subtractEnergyCost(4));
Caster.of(target).ifPresent(c -> getSpellEffect(stack).create().apply(c));
Caster.of(target).ifPresent(c -> getSpellEffect(stack).apply(c, CastingMethod.STAFF));
ChargeableItem.consumeEnergy(stack, 1);
return true;
@ -182,7 +183,7 @@ public class EnchantedStaffItem extends StaffItem implements EnchantableItem, Ch
living.clearActiveItem();
living.damage(entity.getDamageSources().magic(), 1);
if (EnchantableItem.isEnchanted(stack) && hasCharge(stack)) {
Caster.of(entity).ifPresent(c -> getSpellEffect(stack).create().apply(c));
Caster.of(entity).ifPresent(c -> getSpellEffect(stack).apply(c, CastingMethod.STAFF));
ChargeableItem.consumeEnergy(stack, 1);
}
}