Move placed spell logic into the cast spell entity and fix portals mislinking/multilinking

This commit is contained in:
Sollace 2024-05-23 17:02:29 +01:00
parent 8a093a8a8b
commit 1ecb28e511
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
19 changed files with 326 additions and 400 deletions

View file

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

View file

@ -50,31 +50,31 @@ public abstract class AbstractDelegatingSpell implements Spell {
@Override @Override
public void setDead() { public void setDead() {
if (delegate.get() instanceof Spell p) { if (delegate.get() != null) {
p.setDead(); delegate.get().setDead();
} }
} }
@Override @Override
public void tickDying(Caster<?> caster) { public void tickDying(Caster<?> caster) {
if (delegate.get() instanceof Spell p) { if (delegate.get() != null) {
p.tickDying(caster); delegate.get().tickDying(caster);
} }
} }
@Override @Override
public boolean isDead() { public boolean isDead() {
return !(delegate.get() instanceof Spell p) || p.isDead(); return delegate.get() == null;
} }
@Override @Override
public boolean isDying() { public boolean isDying() {
return delegate.get() instanceof Spell p && p.isDying(); return delegate.get() != null && delegate.get().isDying();
} }
@Override @Override
public boolean isDirty() { public boolean isDirty() {
return dirty || (delegate.get() instanceof Spell p && p.isDirty()); return dirty || delegate.hasDirtySpell();
} }
@Override @Override
@ -84,7 +84,7 @@ public abstract class AbstractDelegatingSpell implements Spell {
@Override @Override
public boolean isHidden() { public boolean isHidden() {
return hidden || (delegate.get() instanceof Spell p && p.isHidden()); return hidden || (delegate.get() != null && delegate.get().isHidden());
} }
@Override @Override

View file

@ -1,162 +0,0 @@
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.PlacementControlSpell.PlacementDelegate;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.server.world.Ether;
import net.minecraft.entity.Entity;
import net.minecraft.nbt.*;
import net.minecraft.util.math.MathHelper;
/**
* 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.
* <p>
* When cast two copies of this spell are created. One is attached to the player and is the controlling spell,
* the other is attached to a cast spell entity and placed in the world.
*
*/
public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedSpell {
private int prevAge;
private int age;
private boolean dead;
private int prevDeathTicks;
private int deathTicks;
private UUID controllingEntityUuid;
private UUID controllingSpellUuid;
public float pitch;
public float yaw;
public PlaceableSpell(CustomisedSpellType<?> type) {
super(type);
}
PlaceableSpell(Caster<?> caster, PlacementControlSpell control, Spell delegate) {
this(SpellType.PLACED_SPELL.withTraits());
this.controllingEntityUuid = caster.asEntity().getUuid();
this.controllingSpellUuid = control.getUuid();
this.delegate.set(delegate);
if (delegate instanceof PlacementDelegate s) {
s.onPlaced(caster, control);
}
}
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 || super.isDying();
}
@Override
public void setDead() {
super.setDead();
dead = true;
deathTicks = 20;
setDirty();
}
@Override
public boolean isDead() {
return dead && deathTicks <= 0 && super.isDead();
}
@Override
public boolean tick(Caster<?> source, Situation situation) {
if (!source.isClient()) {
if (!checkConnection(source)) {
setDead();
return true;
}
var entry = Ether.get(source.asWorld()).get(this, source);
if (entry != null && entry.hasChanged()) {
setOrientation(source, entry.getPitch(), entry.getYaw());
}
}
prevAge = age;
if (age < 25) {
age++;
}
return super.tick(source, Situation.GROUND);
}
private boolean checkConnection(Caster<?> source) {
return Ether.get(source.asWorld()).get(SpellType.PLACE_CONTROL_SPELL, controllingEntityUuid, controllingSpellUuid) != null;
}
@Override
public void tickDying(Caster<?> caster) {
super.tickDying(caster);
prevDeathTicks = deathTicks;
deathTicks--;
}
@Override
public void setOrientation(Caster<?> caster, float pitch, float yaw) {
this.pitch = -pitch - 90;
this.yaw = -yaw;
Entity entity = caster.asEntity();
entity.updatePositionAndAngles(entity.getX(), entity.getY(), entity.getZ(), this.yaw, this.pitch);
entity.setYaw(this.yaw);
entity.setPitch(this.pitch);
if (!caster.isClient()) {
var entry = Ether.get(caster.asWorld()).get(this, caster);
if (entry != null) {
entry.setPitch(pitch);
entry.setYaw(yaw);
}
}
setDirty();
}
@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);
if (controllingEntityUuid != null) {
compound.putUuid("owningEntity", controllingEntityUuid);
}
if (controllingSpellUuid != null) {
compound.putUuid("owningSpell", controllingSpellUuid);
}
}
@Override
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
dead = compound.getBoolean("dead");
deathTicks = compound.getInt("deathTicks");
age = compound.getInt("age");
controllingEntityUuid = compound.containsUuid("owningEntity") ? compound.getUuid("owningEntity") : null;
controllingSpellUuid = compound.containsUuid("owningSpell") ? compound.getUuid("owningSpell") : null;
pitch = compound.getFloat("pitch");
yaw = compound.getFloat("yaw");
}
}

View file

@ -7,9 +7,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.AbstractSpell; 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.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; import com.minelittlepony.unicopia.entity.mob.CastSpellEntity;
import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.NbtSerialisable;
@ -22,8 +20,6 @@ import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
public class PlacementControlSpell extends AbstractSpell implements OrientedSpell { public class PlacementControlSpell extends AbstractSpell implements OrientedSpell {
@Nullable
private UUID placedSpellId;
@Nullable @Nullable
private UUID placedEntityId; private UUID placedEntityId;
@ -31,20 +27,21 @@ public class PlacementControlSpell extends AbstractSpell implements OrientedSpel
private Optional<Vec3d> position = Optional.empty(); private Optional<Vec3d> position = Optional.empty();
private Optional<Vec3d> orientation = Optional.empty(); private Optional<Vec3d> orientation = Optional.empty();
private SpellReference<Spell> delegate = new SpellReference<>(); @Nullable
private Spell delegate;
public PlacementControlSpell(CustomisedSpellType<?> type) { public PlacementControlSpell(CustomisedSpellType<?> type) {
super(type); super(type);
} }
PlacementControlSpell(CustomisedSpellType<?> type, Spell delegate) { PlacementControlSpell(CustomisedSpellType<?> type, Spell delegate) {
super(type); this(type);
this.delegate.set(delegate); this.delegate = delegate;
} }
@Nullable @Nullable
public Spell getDelegate() { public Spell getDelegate() {
return delegate.get(); return delegate;
} }
public Optional<Vec3d> getPosition() { public Optional<Vec3d> getPosition() {
@ -62,20 +59,24 @@ public class PlacementControlSpell extends AbstractSpell implements OrientedSpel
@Override @Override
public void setOrientation(Caster<?> caster, float pitch, float yaw) { public void setOrientation(Caster<?> caster, float pitch, float yaw) {
this.orientation = Optional.of(new Vec3d(pitch, yaw, 0)); this.orientation = Optional.of(new Vec3d(pitch, yaw, 0));
if (delegate instanceof OrientedSpell o) {
o.setOrientation(caster, pitch, yaw);
}
setDirty(); setDirty();
if (!caster.isClient() && placedEntityId != null) { if (!caster.isClient()) {
getWorld(caster).ifPresent(world -> { var entry = getConnection(caster);
var entry = Ether.get(world).get(SpellType.PLACED_SPELL, placedEntityId, placedSpellId); if (entry != null) {
if (entry != null) { entry.setPitch(pitch);
entry.setPitch(pitch); entry.setYaw(yaw);
entry.setYaw(yaw); }
}
});
} }
} }
@Override @Override
public boolean apply(Caster<?> caster) { public boolean apply(Caster<?> caster) {
if (delegate == null) {
return false;
}
boolean result = super.apply(caster); boolean result = super.apply(caster);
if (result) { if (result) {
if (dimension.isEmpty()) { if (dimension.isEmpty()) {
@ -84,21 +85,18 @@ public class PlacementControlSpell extends AbstractSpell implements OrientedSpel
if (position.isEmpty()) { if (position.isEmpty()) {
setPosition(caster.asEntity().getPos()); setPosition(caster.asEntity().getPos());
} }
if (delegate instanceof PlacementDelegate) {
((PlacementDelegate)delegate).onPlaced(caster, this);
}
PlaceableSpell copy = new PlaceableSpell(caster, this, delegate.get()); CastSpellEntity entity = new CastSpellEntity(caster.asWorld(), caster, this);
Vec3d pos = position.orElse(caster.asEntity().getPos()); Vec3d pos = position.get();
Vec3d rot = orientation.orElse(Vec3d.ZERO); Vec3d rot = orientation.orElse(Vec3d.ZERO);
CastSpellEntity entity = UEntities.CAST_SPELL.create(caster.asWorld());
entity.setCaster(caster);
entity.updatePositionAndAngles(pos.x, pos.y, pos.z, (float)rot.y, (float)rot.x); entity.updatePositionAndAngles(pos.x, pos.y, pos.z, (float)rot.y, (float)rot.x);
entity.setYaw((float)rot.y);
entity.setPitch((float)rot.x);
copy.apply(entity);
entity.getWorld().spawnEntity(entity); entity.getWorld().spawnEntity(entity);
placedSpellId = copy.getUuid();
placedEntityId = entity.getUuid(); placedEntityId = entity.getUuid();
setDirty(); setDirty();
} }
@ -107,14 +105,17 @@ public class PlacementControlSpell extends AbstractSpell implements OrientedSpel
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
if (!source.isClient() && !checkConnection(source)) { if (!source.isClient() && getConnection(source) == null) {
setDead(); setDead();
} }
return !isDead(); return !isDead();
} }
private boolean checkConnection(Caster<?> source) { @Nullable
return getWorld(source).map(world -> Ether.get(world).get(SpellType.PLACED_SPELL, placedEntityId, placedSpellId)).isPresent(); 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) { private Optional<World> getWorld(Caster<?> source) {
@ -124,13 +125,10 @@ public class PlacementControlSpell extends AbstractSpell implements OrientedSpel
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
super.toNBT(compound); super.toNBT(compound);
compound.put("spell", delegate.toNBT()); compound.put("spell", Spell.writeNbt(delegate));
position.ifPresent(pos -> compound.put("position", NbtSerialisable.writeVector(pos))); position.ifPresent(pos -> compound.put("position", NbtSerialisable.writeVector(pos)));
orientation.ifPresent(o -> compound.put("orientation", NbtSerialisable.writeVector(o))); orientation.ifPresent(o -> compound.put("orientation", NbtSerialisable.writeVector(o)));
dimension.ifPresent(d -> compound.putString("dimension", d.getValue().toString())); dimension.ifPresent(d -> compound.putString("dimension", d.getValue().toString()));
if (placedSpellId != null) {
compound.putUuid("placedSpellId", placedSpellId);
}
if (placedEntityId != null) { if (placedEntityId != null) {
compound.putUuid("placedEntityId", placedEntityId); compound.putUuid("placedEntityId", placedEntityId);
} }
@ -139,8 +137,7 @@ public class PlacementControlSpell extends AbstractSpell implements OrientedSpel
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
delegate.fromNBT(compound.getCompound("spell")); delegate = Spell.readNbt(compound.getCompound("spell"));
placedSpellId = compound.containsUuid("placedSpellId") ? compound.getUuid("placedSpellId") : null;
placedEntityId = compound.containsUuid("placedEntityId") ? compound.getUuid("placedEntityId") : null; placedEntityId = compound.containsUuid("placedEntityId") ? compound.getUuid("placedEntityId") : null;
position = compound.contains("position") ? Optional.of(NbtSerialisable.readVector(compound.getList("position", NbtElement.FLOAT_TYPE))) : Optional.empty(); position = compound.contains("position") ? Optional.of(NbtSerialisable.readVector(compound.getList("position", NbtElement.FLOAT_TYPE))) : Optional.empty();
orientation = compound.contains("orientation") ? Optional.of(NbtSerialisable.readVector(compound.getList("orientation", NbtElement.FLOAT_TYPE))) : Optional.empty(); orientation = compound.contains("orientation") ? Optional.of(NbtSerialisable.readVector(compound.getList("orientation", NbtElement.FLOAT_TYPE))) : Optional.empty();

View file

@ -156,9 +156,16 @@ public interface Spell extends NbtSerialisable, Affine {
return compound == null || !compound.containsUuid("uuid") ? Util.NIL_UUID : compound.getUuid("uuid"); return compound == null || !compound.containsUuid("uuid") ? Util.NIL_UUID : compound.getUuid("uuid");
} }
static NbtCompound writeNbt(Spell effect) { static NbtCompound writeNbt(@Nullable Spell effect) {
if (effect == null) {
return new NbtCompound();
}
NbtCompound compound = effect.toNBT(); NbtCompound compound = effect.toNBT();
effect.getTypeAndTraits().toNbt(compound); effect.getTypeAndTraits().toNbt(compound);
return compound; return compound;
} }
static <T extends Spell> Spell copy(T spell) {
return readNbt(writeNbt(spell));
}
} }

View file

@ -1,12 +1,10 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect; package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.*;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
@ -23,7 +21,6 @@ import com.minelittlepony.unicopia.util.shape.*;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.packet.s2c.play.PositionFlag; import net.minecraft.network.packet.s2c.play.PositionFlag;
import net.minecraft.particle.ParticleTypes; import net.minecraft.particle.ParticleTypes;
@ -59,12 +56,8 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
super(type); super(type);
} }
public boolean isLinked() { public EntityReference<Entity> getDestinationReference() {
return teleportationTarget.isSet(); return teleportationTarget;
}
public Optional<EntityReference.EntityValues<Entity>> getTarget() {
return teleportationTarget.getTarget();
} }
public float getPitch() { public float getPitch() {
@ -88,13 +81,16 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Optional<Ether.Entry<PortalSpell>> getDestination(Caster<?> source) { private Ether.Entry<PortalSpell> getDestination(Caster<?> source) {
return getTarget().map(target -> Ether.get(source.asWorld()).get((SpellType<PortalSpell>)getType(), target, targetPortalId)); return targetPortalId == null ? null : getDestinationReference()
.getTarget()
.map(target -> Ether.get(source.asWorld()).get((SpellType<PortalSpell>)getType(), target.uuid(), targetPortalId))
.filter(destination -> destination.isClaimedBy(getUuid()))
.orElse(null);
} }
@Override @Override
public boolean apply(Caster<?> caster) { public boolean apply(Caster<?> caster) {
setOrientation(caster, caster.asEntity().getPitch(), caster.asEntity().getYaw());
return toPlaceable().apply(caster); return toPlaceable().apply(caster);
} }
@ -106,19 +102,43 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
source.addParticle(ParticleTypes.ELECTRIC_SPARK, pos, Vec3d.ZERO); source.addParticle(ParticleTypes.ELECTRIC_SPARK, pos, Vec3d.ZERO);
}); });
} else { } else {
teleportationTarget.getTarget().ifPresent(target -> { var ownEntry = Ether.get(source.asWorld()).get(this, source);
if (Ether.get(source.asWorld()).get(getType(), target, targetPortalId) == null) { synchronized (ownEntry) {
Unicopia.LOGGER.debug("Lost sibling, breaking connection to " + target.uuid()); var targetEntry = getDestination(source);
teleportationTarget.set(null);
setDirty();
source.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, source.getOrigin(), Block.getRawIdFromState(Blocks.GLASS.getDefaultState()));
}
});
getDestination(source).ifPresentOrElse( if (targetEntry == null) {
entry -> tickWithTargetLink(source, entry), if (teleportationTarget.isSet()) {
() -> findLink(source) teleportationTarget.set(null);
); targetPortalId = null;
setDirty();
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.teleportationTarget.copyFrom(ownEntry.entity);
portal.targetPortalId = getUuid();
portal.targetPortalPitch = pitch;
portal.targetPortalYaw = yaw;
portal.setDirty();
}
}
teleportationTarget.copyFrom(entry.entity);
targetPortalId = entry.getSpellId();
targetPortalPitch = entry.getPitch();
targetPortalYaw = entry.getYaw();
setDirty();
}
return false;
});
}
} else {
tickActive(source, targetEntry);
}
}
} }
var entry = Ether.get(source.asWorld()).getOrCreate(this, source); var entry = Ether.get(source.asWorld()).getOrCreate(this, source);
@ -129,13 +149,7 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
return !isDead(); return !isDead();
} }
private void tickWithTargetLink(Caster<?> source, Ether.Entry<?> destination) { private void tickActive(Caster<?> source, Ether.Entry<?> destination) {
if (destination.hasChanged()) {
targetPortalPitch = destination.getPitch();
targetPortalYaw = destination.getYaw();
}
destination.entity.getTarget().ifPresent(target -> { destination.entity.getTarget().ifPresent(target -> {
source.findAllEntitiesInRange(1).forEach(entity -> { source.findAllEntitiesInRange(1).forEach(entity -> {
if (!entity.hasPortalCooldown()) { if (!entity.hasPortalCooldown()) {
@ -145,8 +159,9 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
return; return;
} }
Vec3d offset = entity.getPos().subtract(source.getOriginVector()); Vec3d offset = entity.getPos().subtract(source.asEntity().getPos())
float yawDifference = pitch < 15 ? getYawDifference() : 0; .add(new Vec3d(0, 0, -0.7F).rotateY(-getTargetYaw() * MathHelper.RADIANS_PER_DEGREE));
float yawDifference = getYawDifference();
Vec3d dest = target.pos().add(offset.rotateY(yawDifference * MathHelper.RADIANS_PER_DEGREE)).add(0, 0.1, 0); Vec3d dest = target.pos().add(offset.rotateY(yawDifference * MathHelper.RADIANS_PER_DEGREE)).add(0, 0.1, 0);
if (entity.getWorld().isTopSolid(BlockPos.ofFloored(dest).up(), entity)) { if (entity.getWorld().isTopSolid(BlockPos.ofFloored(dest).up(), entity)) {
@ -176,37 +191,22 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
}); });
} }
private void findLink(Caster<?> source) {
if (source.isClient()) {
return;
}
Ether.get(source.asWorld()).anyMatch(getType(), entry -> {
if (!entry.entity.referenceEquals(source.asEntity()) && entry.claim()) {
teleportationTarget.copyFrom(entry.entity);
targetPortalId = entry.getSpellId();
setDirty();
}
return false;
});
}
@Override @Override
public void setOrientation(Caster<?> caster, float pitch, float yaw) { public void setOrientation(Caster<?> caster, float pitch, float yaw) {
this.pitch = pitch; this.pitch = 90 - pitch;
this.yaw = yaw; this.yaw = -yaw;
particleArea = PARTICLE_AREA.rotate( particleArea = PARTICLE_AREA.rotate(
pitch * MathHelper.RADIANS_PER_DEGREE, this.pitch * MathHelper.RADIANS_PER_DEGREE,
yaw * MathHelper.RADIANS_PER_DEGREE this.yaw * MathHelper.RADIANS_PER_DEGREE
); );
setDirty(); setDirty();
} }
@Override @Override
public void onPlaced(Caster<?> source, PlacementControlSpell parent) { public void onPlaced(Caster<?> source, PlacementControlSpell parent) {
parent.setOrientation(source, source.asEntity().getPitch(), source.asEntity().getYaw()); Entity caster = source.asEntity();
LivingEntity caster = source.getMaster();
Vec3d targetPos = caster.getRotationVector().multiply(3).add(caster.getEyePos()); Vec3d targetPos = caster.getRotationVector().multiply(3).add(caster.getEyePos());
parent.setOrientation(source, -90 - source.asEntity().getPitch(), -source.asEntity().getYaw());
parent.setPosition(new Vec3d(targetPos.x, caster.getPos().y, targetPos.z)); parent.setPosition(new Vec3d(targetPos.x, caster.getPos().y, targetPos.z));
if (source instanceof Pony pony) { if (source instanceof Pony pony) {
Channel.SERVER_REQUEST_PLAYER_LOOK.sendToPlayer(new MsgCasterLookRequest(parent.getUuid()), (ServerPlayerEntity)pony.asEntity()); Channel.SERVER_REQUEST_PLAYER_LOOK.sendToPlayer(new MsgCasterLookRequest(parent.getUuid()), (ServerPlayerEntity)pony.asEntity());
@ -217,7 +217,10 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
protected void onDestroyed(Caster<?> caster) { protected void onDestroyed(Caster<?> caster) {
super.onDestroyed(caster); super.onDestroyed(caster);
if (!caster.isClient()) { if (!caster.isClient()) {
getDestination(caster).ifPresent(Ether.Entry::release); var destination = getDestination(caster);
if (destination != null) {
destination.release(getUuid());
}
} }
} }

View file

@ -11,7 +11,6 @@ import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.ChangelingFeedingSpell; import com.minelittlepony.unicopia.ability.magic.spell.ChangelingFeedingSpell;
import com.minelittlepony.unicopia.ability.magic.spell.DispersableDisguiseSpell; import com.minelittlepony.unicopia.ability.magic.spell.DispersableDisguiseSpell;
import com.minelittlepony.unicopia.ability.magic.spell.RainboomAbilitySpell; import com.minelittlepony.unicopia.ability.magic.spell.RainboomAbilitySpell;
import com.minelittlepony.unicopia.ability.magic.spell.PlaceableSpell;
import com.minelittlepony.unicopia.ability.magic.spell.PlacementControlSpell; import com.minelittlepony.unicopia.ability.magic.spell.PlacementControlSpell;
import com.minelittlepony.unicopia.ability.magic.spell.RageAbilitySpell; import com.minelittlepony.unicopia.ability.magic.spell.RageAbilitySpell;
import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.Spell;
@ -45,7 +44,6 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
private static final DynamicCommandExceptionType UNKNOWN_SPELL_TYPE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("spell_type.unknown", id)); private static final DynamicCommandExceptionType UNKNOWN_SPELL_TYPE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("spell_type.unknown", id));
public static final SpellType<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<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<PlaceableSpell> PLACED_SPELL = register("placed", builder(PlaceableSpell::new).affinity(Affinity.NEUTRAL).unobtainable().stackable().shape(GemstoneItem.Shape.DONUT));
public static final SpellType<ThrowableSpell> THROWN_SPELL = register("thrown", builder(ThrowableSpell::new).affinity(Affinity.NEUTRAL).unobtainable().shape(GemstoneItem.Shape.DONUT)); public static final SpellType<ThrowableSpell> THROWN_SPELL = register("thrown", builder(ThrowableSpell::new).affinity(Affinity.NEUTRAL).unobtainable().shape(GemstoneItem.Shape.DONUT));
public static final SpellType<DispersableDisguiseSpell> CHANGELING_DISGUISE = register("disguise", builder(DispersableDisguiseSpell::new).affinity(Affinity.BAD).color(0x19E48E).unobtainable().shape(GemstoneItem.Shape.ARROW)); public static final SpellType<DispersableDisguiseSpell> CHANGELING_DISGUISE = register("disguise", builder(DispersableDisguiseSpell::new).affinity(Affinity.BAD).color(0x19E48E).unobtainable().shape(GemstoneItem.Shape.ARROW));

View file

@ -2,9 +2,9 @@ package com.minelittlepony.unicopia.client.gui;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.joml.Vector4f; import org.joml.Vector4f;
import com.google.common.base.MoreObjects;
import com.minelittlepony.common.client.gui.GameGui; import com.minelittlepony.common.client.gui.GameGui;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
@ -136,8 +136,8 @@ public class DismissSpellScreen extends GameGui {
} }
private Spell getActualSpell() { private Spell getActualSpell() {
return spell instanceof AbstractDelegatingSpell s && s.getDelegate() instanceof Spell p ? p return spell instanceof AbstractDelegatingSpell s ? MoreObjects.firstNonNull(s.getDelegate(), s)
: spell instanceof PlacementControlSpell s && s.getDelegate() instanceof Spell p ? p : spell instanceof PlacementControlSpell s ? MoreObjects.firstNonNull(s.getDelegate(), s)
: spell; : spell;
} }

View file

@ -3,7 +3,7 @@ package com.minelittlepony.unicopia.client.render;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.ability.AbilityDispatcher.Stat; import com.minelittlepony.unicopia.ability.AbilityDispatcher.Stat;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import net.minecraft.client.model.Dilation; import net.minecraft.client.model.Dilation;
import net.minecraft.client.model.Model; import net.minecraft.client.model.Model;
@ -56,7 +56,7 @@ public class HornFeatureRenderer<E extends LivingEntity> implements AccessoryFea
.flatMap(Stat::getActiveAbility) .flatMap(Stat::getActiveAbility)
.map(ability -> ability.getColor(pony)) .map(ability -> ability.getColor(pony))
.filter(i -> i != -1).or(() -> pony.getSpellSlot() .filter(i -> i != -1).or(() -> pony.getSpellSlot()
.get(SpellPredicate.IS_NOT_PLACED) .get(SpellType.PLACE_CONTROL_SPELL.negate())
.map(spell -> spell.getTypeAndTraits().type().getColor())); .map(spell -> spell.getTypeAndTraits().type().getColor()));
}).ifPresent(color -> { }).ifPresent(color -> {
model.setState(true); model.setState(true);

View file

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

View file

@ -1,92 +0,0 @@
package com.minelittlepony.unicopia.client.render.spell;
import com.minelittlepony.common.util.Color;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.PlaceableSpell;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.client.render.model.PlaneModel;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.RotationAxis;
public class PlacedSpellRenderer extends SpellRenderer<PlaceableSpell> {
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")
};
@Override
public void render(MatrixStack matrices, VertexConsumerProvider vertices, PlaceableSpell spell, Caster<?> caster, int light, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) {
matrices.push();
Spell delegate = spell.getDelegate();
if (delegate != null) {
renderAmbientEffects(matrices, vertices, spell, delegate, caster, light, animationProgress, tickDelta);
matrices.push();
float height = caster.asEntity().getHeight();
matrices.translate(0, (-spell.pitch / 90F) * height * 0.5F, 0);
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(spell.yaw));
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-spell.pitch));
SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertices, delegate, caster, light, spell.getScale(tickDelta), limbDistance, tickDelta, animationProgress, headYaw, headPitch);
matrices.pop();
}
matrices.pop();
}
protected void renderAmbientEffects(MatrixStack matrices, VertexConsumerProvider vertices, PlaceableSpell spell, Spell delegate, Caster<?> caster, int light, float animationProgress, float tickDelta) {
matrices.push();
matrices.translate(0, 0.001, 0);
float height = caster.asEntity().getHeight();
matrices.translate(0, (-spell.pitch / 90F) * height * 0.5F, 0);
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(spell.yaw));
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-spell.pitch));
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90));
float scale = spell.getScale(tickDelta) * 3;
matrices.scale(scale, scale, scale);
float angle = (animationProgress / 9F) % 360;
int color = delegate.getTypeAndTraits().type().getColor();
float red = Color.r(color);
float green = Color.g(color);
float blue = Color.b(color);
SpellRenderer<?> renderer = SpellEffectsRenderDispatcher.INSTANCE.getRenderer(delegate);
for (int i = 0; i < TEXTURES.length; i++) {
if (!renderer.shouldRenderEffectPass(i)) {
continue;
}
VertexConsumer buffer = vertices.getBuffer(RenderLayer.getEntityTranslucent(TEXTURES[i]));
for (int dim = 0; dim < 3; dim++) {
float ringSpeed = (i % 2 == 0 ? i : -1) * i;
matrices.push();
matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(angle * ringSpeed));
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(angle * ringSpeed * dim));
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(angle * ringSpeed * dim));
PlaneModel.INSTANCE.render(matrices, buffer, light, 0, 1, red, green, blue, scale / ((float)(dim * 3) + 1));
matrices.pop();
}
}
matrices.pop();
}
}

View file

@ -159,13 +159,13 @@ class PortalFrameBuffer implements AutoCloseable {
Camera camera = client.gameRenderer.getCamera(); Camera camera = client.gameRenderer.getCamera();
Entity cameraEntity = UEntities.CAST_SPELL.create(caster.asWorld()); Entity cameraEntity = UEntities.CAST_SPELL.create(caster.asWorld());
Vec3d offset = new Vec3d(0, -0.2F, -0.2F).rotateY(-spell.getTargetYaw() * MathHelper.RADIANS_PER_DEGREE); Vec3d offset = new Vec3d(0, 0, -0.1F).rotateY(-spell.getTargetYaw() * MathHelper.RADIANS_PER_DEGREE);
float yaw = spell.getTargetYaw() + camera.getYaw() - spell.getYaw() + 180; float yaw = spell.getTargetYaw() + camera.getYaw() - spell.getYaw() + 180;
float pitch = spell.getTargetPitch() + (camera.getPitch() - spell.getPitch()) * 1.65F; float pitch = spell.getTargetPitch() + (camera.getPitch() - spell.getPitch()) * 1.65F;
cameraEntity.setPosition(target.pos().add(offset)); cameraEntity.setPosition(target.pos().add(offset));
cameraEntity.setPitch(pitch); cameraEntity.setPitch(90 + pitch);
cameraEntity.setYaw(yaw); cameraEntity.setYaw(yaw);
drawWorld(cameraEntity, 400, 400); drawWorld(cameraEntity, 400, 400);

View file

@ -10,6 +10,7 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.RotationAxis;
public class PortalSpellRenderer extends SpellRenderer<PortalSpell> { public class PortalSpellRenderer extends SpellRenderer<PortalSpell> {
@ -29,7 +30,9 @@ public class PortalSpellRenderer extends SpellRenderer<PortalSpell> {
SphereModel.DISK.render(matrices, buff, light, 0, 2F * strength, 1, 1, 1, 1); SphereModel.DISK.render(matrices, buff, light, 0, 2F * strength, 1, 1, 1, 1);
matrices.pop(); matrices.pop();
if (Unicopia.getConfig().simplifiedPortals.get() || !spell.isLinked()) { EntityReference<Entity> destination = spell.getDestinationReference();
if (Unicopia.getConfig().simplifiedPortals.get() || !destination.isSet()) {
matrices.push(); matrices.push();
matrices.translate(0, -0.02, 0); matrices.translate(0, -0.02, 0);
matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180)); matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180));
@ -52,7 +55,7 @@ public class PortalSpellRenderer extends SpellRenderer<PortalSpell> {
matrices.push(); matrices.push();
matrices.scale(strength, strength, strength); matrices.scale(strength, strength, strength);
spell.getTarget().ifPresent(target -> { destination.getTarget().ifPresent(target -> {
float grown = Math.min(caster.asEntity().age, 20) / 20F; float grown = Math.min(caster.asEntity().age, 20) / 20F;
matrices.push(); matrices.push();
matrices.translate(0, -0.01, 0); matrices.translate(0, -0.01, 0);

View file

@ -47,7 +47,6 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader
} }
static { static {
register(SpellType.PLACED_SPELL, PlacedSpellRenderer::new);
register(SpellType.SHIELD, ShieldSpellRenderer::new); register(SpellType.SHIELD, ShieldSpellRenderer::new);
register(SpellType.DARK_VORTEX, DarkVortexSpellRenderer::new); register(SpellType.DARK_VORTEX, DarkVortexSpellRenderer::new);
register(SpellType.BUBBLE, BubbleSpellRenderer::new); register(SpellType.BUBBLE, BubbleSpellRenderer::new);

View file

@ -1,10 +1,14 @@
package com.minelittlepony.unicopia.entity.mob; package com.minelittlepony.unicopia.entity.mob;
import java.util.UUID;
import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.*;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.Levelled; import com.minelittlepony.unicopia.ability.magic.Levelled;
import com.minelittlepony.unicopia.ability.magic.SpellInventory; import com.minelittlepony.unicopia.ability.magic.SpellInventory;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.SpellSlots; import com.minelittlepony.unicopia.ability.magic.SpellSlots;
import com.minelittlepony.unicopia.ability.magic.spell.PlacementControlSpell;
import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
@ -13,6 +17,7 @@ import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.MagicImmune; import com.minelittlepony.unicopia.entity.MagicImmune;
import com.minelittlepony.unicopia.entity.Physics; import com.minelittlepony.unicopia.entity.Physics;
import com.minelittlepony.unicopia.network.track.Trackable; import com.minelittlepony.unicopia.network.track.Trackable;
import com.minelittlepony.unicopia.server.world.Ether;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityDimensions; import net.minecraft.entity.EntityDimensions;
@ -24,6 +29,7 @@ import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.entity.data.TrackedDataHandlerRegistry;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World; import net.minecraft.world.World;
public class CastSpellEntity extends LightEmittingEntity implements Caster<CastSpellEntity>, WeaklyOwned.Mutable<LivingEntity>, MagicImmune { public class CastSpellEntity extends LightEmittingEntity implements Caster<CastSpellEntity>, WeaklyOwned.Mutable<LivingEntity>, MagicImmune {
@ -32,6 +38,8 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster<CastS
private static final TrackedData<Integer> CORRUPTION = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.INTEGER); private static final TrackedData<Integer> CORRUPTION = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.INTEGER);
private static final TrackedData<Integer> MAX_CORRUPTION = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.INTEGER); private static final TrackedData<Integer> MAX_CORRUPTION = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.INTEGER);
private static final TrackedData<Boolean> DEAD = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
private final EntityPhysics<CastSpellEntity> physics = new EntityPhysics<>(this); private final EntityPhysics<CastSpellEntity> physics = new EntityPhysics<>(this);
private final SpellInventory spells = SpellSlots.ofSingle(this); private final SpellInventory spells = SpellSlots.ofSingle(this);
@ -49,6 +57,23 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster<CastS
() -> dataTracker.get(MAX_CORRUPTION) () -> dataTracker.get(MAX_CORRUPTION)
); );
private UUID controllingEntityUuid;
private UUID controllingSpellUuid;
private int prevAge;
private int prevDeathTicks;
private int deathTicks;
public CastSpellEntity(World world, Caster<?> caster, PlacementControlSpell control) {
this(UEntities.CAST_SPELL, world);
this.controllingEntityUuid = caster.asEntity().getUuid();
this.controllingSpellUuid = control.getUuid();
setCaster(caster);
Spell spell = Spell.copy(control.getDelegate());
spells.getSlots().put(spell);
}
public CastSpellEntity(EntityType<?> type, World world) { public CastSpellEntity(EntityType<?> type, World world) {
super(type, world); super(type, world);
ignoreCameraFrustum = true; ignoreCameraFrustum = true;
@ -61,6 +86,27 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster<CastS
dataTracker.startTracking(CORRUPTION, 0); dataTracker.startTracking(CORRUPTION, 0);
dataTracker.startTracking(MAX_LEVEL, 1); dataTracker.startTracking(MAX_LEVEL, 1);
dataTracker.startTracking(MAX_CORRUPTION, 1); dataTracker.startTracking(MAX_CORRUPTION, 1);
dataTracker.startTracking(DEAD, false);
}
@Override
public void updatePositionAndAngles(double x, double y, double z, float yaw, float pitch) {
super.updatePositionAndAngles(x, y, z, yaw, pitch);
spells.getSlots().stream(SpellPredicate.IS_ORIENTED).forEach(spell -> spell.setOrientation(this, pitch, yaw));
}
private boolean checkConnection() {
return Ether.get(getWorld()).get(SpellType.PLACE_CONTROL_SPELL, controllingEntityUuid, controllingSpellUuid) != null;
}
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 = MathHelper.clamp(MathHelper.lerp(tickDelta, prevDeathTicks, deathTicks) / 20F, 0, 1);
return MathHelper.clamp(add - subtract, 0, 1);
} }
@Override @Override
@ -78,19 +124,51 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster<CastS
} }
@Override @Override
public void tick() { public void baseTick() {
super.tick(); prevAge = age;
age++;
if (!isRemoved() && !spells.tick(Situation.GROUND_ENTITY)) { super.baseTick();
if (!isClient()) {
discard(); if (!isClient()) {
if (!checkConnection()) {
kill();
} }
spells.getSlots().get().ifPresent(spell -> {
var entry = Ether.get(getWorld()).getOrCreate(spell, this);
if (entry.hasChanged()) {
//updatePositionAndAngles(getX(), getY(), getZ(), entry.getYaw(), entry.getPitch());
}
});
}
prevDeathTicks = deathTicks;
if (!spells.tick(Situation.GROUND) && deathTicks++ > 40) {
remove(Entity.RemovalReason.KILLED);
}
}
@Override
public void kill() {
spells.getSlots().clear();
}
public boolean isDead() {
return dataTracker.get(DEAD);
}
public void setDead(boolean dead) {
dataTracker.set(DEAD, dead);
if (dead) {
spells.getSlots().clear();
} }
} }
@Override @Override
public EntityDimensions getDimensions(EntityPose pose) { public EntityDimensions getDimensions(EntityPose pose) {
return super.getDimensions(pose).scaled(getSpellSlot().get(SpellType.IS_PLACED).map(spell -> spell.getScale(1)).orElse(1F)); return super.getDimensions(pose).scaled(getScale(1));
} }
@Override @Override
@ -154,23 +232,42 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster<CastS
@Override @Override
protected void writeCustomDataToNbt(NbtCompound tag) { protected void writeCustomDataToNbt(NbtCompound tag) {
tag.put("owner", owner.toNBT());
tag.put("level", level.toNbt()); tag.put("level", level.toNbt());
tag.put("corruption", corruption.toNbt()); tag.put("corruption", corruption.toNbt());
if (controllingEntityUuid != null) {
tag.putUuid("owningEntity", controllingEntityUuid);
}
if (controllingSpellUuid != null) {
tag.putUuid("owningSpell", controllingSpellUuid);
}
spells.getSlots().toNBT(tag); spells.getSlots().toNBT(tag);
tag.putInt("age", age);
tag.putInt("prevAge", prevAge);
tag.putBoolean("dead", isDead());
tag.put("owner", owner.toNBT());
} }
@Override @Override
protected void readCustomDataFromNbt(NbtCompound tag) { protected void readCustomDataFromNbt(NbtCompound tag) {
if (tag.contains("owner")) {
owner.fromNBT(tag.getCompound("owner"));
}
spells.getSlots().fromNBT(tag);
var level = Levelled.fromNbt(tag.getCompound("level")); var level = Levelled.fromNbt(tag.getCompound("level"));
dataTracker.set(MAX_LEVEL, level.getMax()); dataTracker.set(MAX_LEVEL, level.getMax());
dataTracker.set(LEVEL, level.get()); dataTracker.set(LEVEL, level.get());
var corruption = Levelled.fromNbt(tag.getCompound("corruption")); var corruption = Levelled.fromNbt(tag.getCompound("corruption"));
dataTracker.set(MAX_CORRUPTION, corruption.getMax()); dataTracker.set(MAX_CORRUPTION, corruption.getMax());
dataTracker.set(CORRUPTION, corruption.get()); dataTracker.set(CORRUPTION, corruption.get());
controllingEntityUuid = tag.containsUuid("owningEntity") ? tag.getUuid("owningEntity") : null;
controllingSpellUuid = tag.containsUuid("owningSpell") ? tag.getUuid("owningSpell") : null;
spells.getSlots().fromNBT(tag);
age = tag.getInt("age");
prevAge = tag.getInt("prevAge");
setDead(tag.getBoolean("dead"));
if (tag.contains("owner")) {
owner.fromNBT(tag.getCompound("owner"));
}
} }
} }

View file

@ -50,7 +50,7 @@ public interface UEntities {
.trackRangeBlocks(200) .trackRangeBlocks(200)
.disableSummon() .disableSummon()
.dimensions(EntityDimensions.fixed(1, 1))); .dimensions(EntityDimensions.fixed(1, 1)));
EntityType<CastSpellEntity> CAST_SPELL = register("cast_spell", FabricEntityTypeBuilder.create(SpawnGroup.MISC, CastSpellEntity::new) EntityType<CastSpellEntity> CAST_SPELL = register("cast_spell", FabricEntityTypeBuilder.<CastSpellEntity>create(SpawnGroup.MISC, CastSpellEntity::new)
.trackRangeBlocks(200) .trackRangeBlocks(200)
.disableSummon() .disableSummon()
.dimensions(EntityDimensions.changing(4, 4))); .dimensions(EntityDimensions.changing(4, 4)));

View file

@ -892,7 +892,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
oldSuppressedRace = Race.UNSET; oldSuppressedRace = Race.UNSET;
Channel.SERVER_SELECT_TRIBE.sendToPlayer(new MsgTribeSelect(Race.allPermitted(entity), "gui.unicopia.tribe_selection.respawn"), (ServerPlayerEntity)entity); Channel.SERVER_SELECT_TRIBE.sendToPlayer(new MsgTribeSelect(Race.allPermitted(entity), "gui.unicopia.tribe_selection.respawn"), (ServerPlayerEntity)entity);
} else { } else {
oldPlayer.getSpellSlot().stream().filter(SpellPredicate.IS_PLACED).forEach(getSpellSlot()::put); oldPlayer.getSpellSlot().stream().filter(SpellType.PLACE_CONTROL_SPELL).forEach(getSpellSlot()::put);
} }
// putting it here instead of adding another injection point into ServerPlayerEntity.copyFrom() // putting it here instead of adding another injection point into ServerPlayerEntity.copyFrom()

View file

@ -9,7 +9,6 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
@ -47,7 +46,7 @@ public class EnchantedStaffItem extends StaffItem implements EnchantableItem, Ch
public static SpellType<?> getSpellType(Entity entity, boolean remove) { public static SpellType<?> getSpellType(Entity entity, boolean remove) {
if (entity instanceof CastSpellEntity cast) { if (entity instanceof CastSpellEntity cast) {
return cast.getSpellSlot().get(c -> !SpellPredicate.IS_PLACED.test(c)) return cast.getSpellSlot().get(SpellType.PLACE_CONTROL_SPELL.negate())
.map(Spell::getTypeAndTraits) .map(Spell::getTypeAndTraits)
.map(CustomisedSpellType::type) .map(CustomisedSpellType::type)
.orElse(SpellType.empty()); .orElse(SpellType.empty());

View file

@ -168,13 +168,14 @@ public class Ether extends PersistentState {
private WeakReference<T> spell; private WeakReference<T> spell;
private boolean removed; private boolean removed;
private boolean taken;
private float pitch; private float pitch;
private final AtomicBoolean changed = new AtomicBoolean(true); private final AtomicBoolean changed = new AtomicBoolean(true);
private float yaw; private float yaw;
private float radius; private float radius;
private final Set<UUID> claimants = new HashSet<>();
private Entry(NbtElement nbt) { private Entry(NbtElement nbt) {
this.entity = new EntityReference<>(); this.entity = new EntityReference<>();
this.spell = new WeakReference<>(null); this.spell = new WeakReference<>(null);
@ -227,7 +228,7 @@ public class Ether extends PersistentState {
markDirty(); markDirty();
} }
boolean isAlive() { public boolean isAlive() {
return !isDead(); return !isDead();
} }
@ -246,6 +247,7 @@ public class Ether extends PersistentState {
public void markDead() { public void markDead() {
Unicopia.LOGGER.debug("Marking " + entity.getTarget().orElse(null) + " as dead"); Unicopia.LOGGER.debug("Marking " + entity.getTarget().orElse(null) + " as dead");
removed = true; removed = true;
claimants.clear();
markDirty(); markDirty();
} }
@ -253,25 +255,22 @@ public class Ether extends PersistentState {
return entity.getTarget().filter(target -> uuid.equals(target.uuid())).isPresent(); return entity.getTarget().filter(target -> uuid.equals(target.uuid())).isPresent();
} }
public boolean isAvailable() { public void claim(UUID claimant) {
return !isDead() && !taken && entity.isSet(); claimants.add(claimant);
}
public void setTaken(boolean taken) {
this.taken = taken;
markDirty(); markDirty();
} }
public void release() { public void release(UUID claimant) {
setTaken(false); claimants.remove(claimant);
markDirty();
} }
public boolean claim() { public boolean isClaimedBy(UUID claimant) {
if (isAvailable()) { return claimants.contains(claimant);
setTaken(true); }
return true;
} public boolean hasClaimant() {
return false; return !claimants.isEmpty();
} }
@Nullable @Nullable
@ -315,24 +314,34 @@ public class Ether extends PersistentState {
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
entity.toNBT(compound); entity.toNBT(compound);
compound.putBoolean("removed", removed); compound.putBoolean("removed", removed);
compound.putBoolean("taken", taken);
compound.putFloat("pitch", pitch); compound.putFloat("pitch", pitch);
compound.putFloat("yaw", yaw); compound.putFloat("yaw", yaw);
compound.putFloat("radius", radius); compound.putFloat("radius", radius);
if (spellId != null) { if (spellId != null) {
compound.putUuid("spellId", spellId); compound.putUuid("spellId", spellId);
} }
NbtList list = new NbtList();
claimants.forEach(claimant -> {
list.add(NbtHelper.fromUuid(claimant));
});
compound.put("claimants", list);
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
entity.fromNBT(compound); entity.fromNBT(compound);
removed = compound.getBoolean("removed"); removed = compound.getBoolean("removed");
taken = compound.getBoolean("taken");
pitch = compound.getFloat("pitch"); pitch = compound.getFloat("pitch");
yaw = compound.getFloat("yaw"); yaw = compound.getFloat("yaw");
radius = compound.getFloat("radius"); radius = compound.getFloat("radius");
spellId = compound.containsUuid("spellid") ? compound.getUuid("spellId") : null; spellId = compound.containsUuid("spellid") ? compound.getUuid("spellId") : null;
claimants.clear();
if (compound.contains("claimants", NbtElement.LIST_TYPE)) {
compound.getList("claimants", NbtElement.INT_ARRAY_TYPE).forEach(el -> {
claimants.add(NbtHelper.toUuid(el));
});
}
} }
@Override @Override