Added a homing ability to the firebolt spell

This commit is contained in:
Sollace 2021-12-28 18:22:47 +02:00
parent de6f9a92d4
commit fb6031b89f
11 changed files with 170 additions and 107 deletions

View file

@ -6,14 +6,18 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Hit;
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;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.AmuletItem;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.util.RayTraceHelper;
import com.minelittlepony.unicopia.util.VecHelper;
import net.minecraft.item.ItemStack;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.ActionResult;
@ -97,10 +101,14 @@ public class UnicornCastingAbility implements Ability<Hit> {
boolean remove = player.getSpellSlot().removeIf(spell, true);
player.subtractEnergyCost(remove ? 2 : 4);
if (!remove) {
if (spell.apply(player) == null) {
Spell s = spell.apply(player);
if (s == null) {
player.spawnParticles(ParticleTypes.LARGE_SMOKE, 6);
player.playSound(SoundEvents.ENTITY_ITEM_BREAK, 1, 0.5F);
} else {
if (s instanceof HomingSpell) {
RayTraceHelper.doTrace(player.getMaster(), 600, 1, EntityPredicates.EXCEPT_SPECTATOR).getEntity().ifPresent(((HomingSpell)s)::setTarget);
}
player.playSound(SoundEvents.BLOCK_BEACON_POWER_SELECT, 0.05F, 2.2F);
}
}

View file

@ -2,10 +2,14 @@ package com.minelittlepony.unicopia.ability;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Hit;
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;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.util.RayTraceHelper;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
@ -61,7 +65,16 @@ public class UnicornProjectileAbility implements Ability<Hit> {
if (thrown.getResult() != ActionResult.FAIL) {
player.subtractEnergyCost(getCostEstimate(player));
thrown.getValue().create().toThrowable().throwProjectile(player);
Spell spell = thrown.getValue().create();
spell.toThrowable().throwProjectile(player).ifPresent(projectile -> {
if (spell instanceof HomingSpell) {
RayTraceHelper.doTrace(player.getMaster(), 600, 1, EntityPredicates.EXCEPT_SPECTATOR).getEntity().filter(((HomingSpell)spell)::setTarget).ifPresent(target -> {
projectile.setHomingTarget(target);
});
}
});
}
}

View file

@ -0,0 +1,10 @@
package com.minelittlepony.unicopia.ability.magic.spell;
import net.minecraft.entity.Entity;
/**
* A spell that's capable of homing in on a pre-defined target.
*/
public interface HomingSpell extends Spell {
boolean setTarget(Entity target);
}

View file

@ -40,6 +40,15 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
* Returns the resulting projectile entity for customization (or null if on the client).
*/
public Optional<MagicProjectileEntity> throwProjectile(Caster<?> caster) {
return throwProjectile(caster, 1);
}
/**
* Projects this spell.
*
* Returns the resulting projectile entity for customization (or null if on the client).
*/
public Optional<MagicProjectileEntity> throwProjectile(Caster<?> caster, float divergance) {
World world = caster.getWorld();
LivingEntity entity = caster.getMaster();
@ -51,7 +60,7 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
projectile.setItem(GemstoneItem.enchant(UItems.GEMSTONE.getDefaultStack(), spell.getType()));
projectile.getSpellSlot().put(this);
projectile.setVelocity(entity, entity.getPitch(), entity.getYaw(), 0, 1.5F, 1);
projectile.setVelocity(entity, entity.getPitch(), entity.getYaw(), 0, 1.5F, divergance);
projectile.setHydrophobic();
configureProjectile(projectile, caster);
world.spawnEntity(projectile);

View file

@ -1,21 +1,35 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.HomingSpell;
import com.minelittlepony.unicopia.ability.magic.spell.ProjectileSpell;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import net.minecraft.entity.Entity;
import net.minecraft.item.Items;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.sound.SoundEvents;
public class FireBoltSpell extends AbstractSpell implements ProjectileSpell {
public class FireBoltSpell extends AbstractSpell implements ProjectileSpell, HomingSpell {
public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder()
.with(Trait.FIRE, 90)
.with(Trait.AIR, 60)
.with(Trait.FOCUS, 10)
.with(Trait.CHAOS, 1)
.with(Trait.STRENGTH, 11)
.with(Trait.FIRE, 60)
.build();
public static final SpellTraits HOMING_TRAITS = new SpellTraits.Builder()
.with(Trait.FOCUS, 50)
.with(Trait.CHAOS, 10)
.with(Trait.STRENGTH, 11)
.with(Trait.FIRE, 60)
.build();
@Nullable
private Entity target;
protected FireBoltSpell(SpellType<?> type, SpellTraits traits) {
super(type, traits);
@ -29,11 +43,30 @@ public class FireBoltSpell extends AbstractSpell implements ProjectileSpell {
@Override
public boolean tick(Caster<?> caster, Situation situation) {
if (situation == Situation.PROJECTILE) {
if (caster instanceof MagicProjectileEntity && getTraits().get(Trait.FOCUS) >= 50) {
caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49,
EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.notOwnerOrFriend(this, caster))
).findFirst().ifPresent(target -> {
((MagicProjectileEntity)caster).setHomingTarget(target);
});
}
return true;
}
if (getTraits().get(Trait.FOCUS) >= 50 && target == null) {
target = caster.findAllEntitiesInRange(
getTraits().get(Trait.FOCUS) - 49,
EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.notOwnerOrFriend(this, caster))
).findFirst().orElse(null);
}
for (int i = 0; i < getNumberOfBalls(caster); i++) {
getType().create(getTraits()).toThrowable().throwProjectile(caster);
getType().create(getTraits()).toThrowable().throwProjectile(caster, 2).ifPresent(c -> {
c.setHomingTarget(target);
});
caster.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 0.7F, 0.4F / (caster.getWorld().random.nextFloat() * 0.4F + 0.8F));
}
return false;
@ -42,12 +75,21 @@ public class FireBoltSpell extends AbstractSpell implements ProjectileSpell {
@Override
public void configureProjectile(MagicProjectileEntity projectile, Caster<?> caster) {
projectile.setItem(Items.FIRE_CHARGE.getDefaultStack());
projectile.addThrowDamage(getTraits().get(Trait.FIRE) / 10F);
projectile.addThrowDamage(getTraits().get(Trait.POWER, 0, getTraits().get(Trait.FOCUS) >= 50 ? 500 : 50) / 10F);
projectile.setFireTicks(900000);
projectile.setVelocity(projectile.getVelocity().multiply(1.3 + getTraits().get(Trait.STRENGTH)));
projectile.setVelocity(projectile.getVelocity().multiply(1.3 + getTraits().get(Trait.STRENGTH) / 11F));
}
protected int getNumberOfBalls(Caster<?> caster) {
return 1 + caster.getWorld().random.nextInt(3) + (int)getTraits().get(Trait.POWER) * 3;
return 1 + caster.getWorld().random.nextInt(3) + (int)getTraits().get(Trait.EARTH) * 3;
}
@Override
public boolean setTarget(Entity target) {
if (getTraits().get(Trait.FOCUS) >= 50) {
this.target = target;
return true;
}
return false;
}
}

View file

@ -4,6 +4,7 @@ import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.stream.Stream;
import com.minelittlepony.unicopia.EquinePredicates;
@ -54,6 +55,20 @@ public class TargetSelecter {
return targets.values().stream().filter(Target::canHurt).count();
}
public static Predicate<Entity> notOwnerOrFriend(Spell spell, Caster<?> source) {
Entity owner = source.getMaster();
boolean ownerIsValid = spell.isFriendlyTogether(source) && (EquinePredicates.PLAYER_UNICORN.test(owner));
if (!ownerIsValid) {
return e -> true;
}
return entity -> {
return !ownerIsValid || !(Pony.equal(entity, owner) || owner.isConnectedThroughVehicle(entity) || FriendshipBraceletItem.isComrade(source, entity));
};
}
static final class Target {
private int cooldown = 20;

View file

@ -3,20 +3,19 @@ package com.minelittlepony.unicopia.client.particle;
import net.minecraft.client.particle.SpriteProvider;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.particle.ParticleEffect;
import net.minecraft.util.math.Vec3f;
public class ChangelingMagicParticle extends MagicParticle {
private final SpriteProvider provider;
public ChangelingMagicParticle(ParticleEffect effect, SpriteProvider provider, ClientWorld world, double x, double y, double z, double dx, double dy, double dz) {
super(effect, provider, world, x, y, z, dx, dy, dz, 1, 1, 1);
super(effect, provider, world, x, y, z, dx, dy, dz, nextColor(world.random.nextFloat() * 0.6F + 0.4F), 1);
this.provider = provider;
}
float intensity = random.nextFloat() * 0.6F + 0.4F;
colorRed = intensity * 0.5F;
colorGreen = intensity;
colorBlue = intensity * 0.4f;
static Vec3f nextColor(float intensity) {
return new Vec3f(intensity * 0.5F, intensity, intensity * 0.4F);
}
@Override

View file

@ -7,13 +7,14 @@ import net.minecraft.client.particle.SpriteBillboardParticle;
import net.minecraft.client.particle.SpriteProvider;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.particle.ParticleEffect;
import net.minecraft.util.math.Vec3f;
public class MagicParticle extends SpriteBillboardParticle {
private double startX;
private double startY;
private double startZ;
private final double startX;
private final double startY;
private final double startZ;
MagicParticle(ParticleEffect effect, SpriteProvider provider, ClientWorld w, double x, double y, double z, double vX, double vY, double vZ, float r, float g, float b) {
MagicParticle(ParticleEffect effect, SpriteProvider provider, ClientWorld w, double x, double y, double z, double vX, double vY, double vZ, Vec3f color, float alpha) {
super(w, x, y, z);
setSprite(provider);
@ -26,41 +27,14 @@ public class MagicParticle extends SpriteBillboardParticle {
scale = random.nextFloat() * 0.12F;
maxAge = (int)(Math.random() * 10) + 20;
colorRed = r;
colorGreen = g;
colorBlue = b;
colorRed = color.getX();
colorGreen = color.getY();
colorBlue = color.getZ();
colorAlpha = alpha;
}
public MagicParticle(ParticleEffect effect, SpriteProvider provider, ClientWorld w, double x, double y, double z, double vX, double vY, double vZ) {
this(effect, provider, w, x, y, z, vX, vY, vZ, 1, 1, 1);
colorAlpha = 0.7F;
colorGreen *= 0.3F;
if (effect instanceof MagicParticleEffect && ((MagicParticleEffect)effect).hasTint()) {
MagicParticleEffect parameters = (MagicParticleEffect)effect;
colorRed = parameters.getColor().getX();
colorGreen = parameters.getColor().getY();
colorBlue = parameters.getColor().getZ();
} else {
if (random.nextBoolean()) {
colorBlue *= 0.4F;
}
if (random.nextBoolean()) {
colorRed *= 0.9F;
}
if (random.nextBoolean()) {
colorGreen += 0.5F;
}
if (random.nextBoolean()) {
colorGreen *= 2F;
} else if (random.nextBoolean()) {
colorRed *= 3.9F;
}
}
public MagicParticle(MagicParticleEffect effect, SpriteProvider provider, ClientWorld w, double x, double y, double z, double vX, double vY, double vZ) {
this(effect, provider, w, x, y, z, vX, vY, vZ, effect.getColor(w.random), 0.7F);
}
@Override
@ -80,7 +54,7 @@ public class MagicParticle extends SpriteBillboardParticle {
float timer = (float)age / (float)maxAge;
int v = light >> 16 & 255;
v = (int)Math.min(v + Math.pow(timer, 4) * 240, 240);
v = (int)Math.min(v + (timer * timer * timer * timer) * 240, 240);
return (light & 255) | v << 16;
}
@ -95,7 +69,8 @@ public class MagicParticle extends SpriteBillboardParticle {
markDead();
} else {
float timer = (float)age / (float)maxAge;
timer = 1 + timer - timer * timer * 2;
timer += 1 - (timer * timer) * 2;
x = startX + velocityX * timer;
y = startY + velocityY;

View file

@ -1,6 +1,7 @@
package com.minelittlepony.unicopia.particle;
import java.util.Locale;
import java.util.Random;
import com.minelittlepony.common.util.Color;
import com.mojang.brigadier.StringReader;
@ -46,8 +47,22 @@ public class MagicParticleEffect implements ParticleEffect {
return tinted;
}
public Vec3f getColor() {
return color;
public Vec3f getColor(Random random) {
if (hasTint()) {
return color;
}
float r = random.nextBoolean() ? 0.9F : 1;
float g = 0.3F;
float b = random.nextBoolean() ? 0.4F : 1;
if (random.nextBoolean()) {
g *= 2F;
} else if (random.nextBoolean()) {
r *= 3.9F;
}
return new Vec3f(r, g, b);
}
@Override

View file

@ -2,6 +2,8 @@ package com.minelittlepony.unicopia.projectile;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.ability.magic.Caster;
@ -12,6 +14,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.EntityPhysics;
import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.Physics;
import com.minelittlepony.unicopia.entity.UEntities;
import com.minelittlepony.unicopia.item.UItems;
@ -58,6 +61,8 @@ public class MagicProjectileEntity extends ThrownItemEntity implements Caster<Li
private final EntityPhysics<MagicProjectileEntity> physics = new EntityPhysics<>(this, GRAVITY, false);
private final EntityReference<Entity> homingTarget = new EntityReference<>();
public MagicProjectileEntity(EntityType<MagicProjectileEntity> type, World world) {
super(type, world);
}
@ -94,6 +99,10 @@ public class MagicProjectileEntity extends ThrownItemEntity implements Caster<Li
setOwner(owner);
}
public void setHomingTarget(@Nullable Entity target) {
homingTarget.set(target);
}
@Override
public LivingEntity getMaster() {
return (LivingEntity)getOwner();
@ -146,8 +155,8 @@ public class MagicProjectileEntity extends ThrownItemEntity implements Caster<Li
@Override
public void tick() {
if (!world.isClient()) {
if (Math.abs(getVelocity().x) < 0.01 && Math.abs(getVelocity().x) < 0.01 && Math.abs(getVelocity().y) < 0.01) {
if (!world.isClient() && !homingTarget.isPresent(world)) {
if (getVelocity().length() < 0.01) {
discard();
}
}
@ -175,6 +184,12 @@ public class MagicProjectileEntity extends ThrownItemEntity implements Caster<Li
setVelocity(new Vec3d(vel.x, velY, vel.z));
}
}
homingTarget.ifPresent(world, e -> {
setNoGravity(true);
noClip = true;
setVelocity(getVelocity().add(e.getPos().subtract(getPos()).normalize().multiply(0.2)).multiply(0.6, 0.6, 0.6));
});
}
private ParticleEffect getParticleParameters() {
@ -200,13 +215,13 @@ public class MagicProjectileEntity extends ThrownItemEntity implements Caster<Li
world.addParticle(effect, getX(), getY(), getZ(), 0, 0, 0);
}
}
}
@Override
public void readCustomDataFromNbt(NbtCompound compound) {
super.readCustomDataFromNbt(compound);
physics.fromNBT(compound);
homingTarget.fromNBT(compound.getCompound("homingTarget"));
if (compound.contains("effect")) {
getSpellSlot().put(SpellType.fromNBT(compound.getCompound("effect")));
}
@ -216,6 +231,7 @@ public class MagicProjectileEntity extends ThrownItemEntity implements Caster<Li
public void writeCustomDataToNbt(NbtCompound compound) {
super.writeCustomDataToNbt(compound);
physics.toNBT(compound);
compound.put("homingTarget", homingTarget.toNBT());
getSpellSlot().get(true).ifPresent(effect -> {
compound.put("effect", SpellType.toNBT(effect));
});

View file

@ -7,6 +7,7 @@ import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import net.minecraft.entity.Entity;
import net.minecraft.entity.projectile.ProjectileUtil;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
@ -49,58 +50,18 @@ public class RayTraceHelper {
* @return A Trace describing what was found.
*/
public static Trace doTrace(Entity e, double distance, float tickDelta, Predicate<Entity> predicate) {
HitResult tracedBlock = e.raycast(distance, tickDelta, false);
final Vec3d ray = e.getRotationVec(tickDelta).multiply(distance);
final Vec3d start = e.getCameraPosVec(tickDelta);
final double totalTraceDistance = tracedBlock == null ? distance : tracedBlock.getPos().distanceTo(start);
final Box box = e.getBoundingBox().stretch(ray).expand(1);
final Vec3d ray = e.getRotationVec(tickDelta).multiply(distance);
final Vec3d end = start.add(ray);
EntityHitResult pointedEntity = ProjectileUtil.raycast(e, start, start.add(ray), box, predicate.and(Entity::collides), distance);
Vec3d hit = null;
Entity pointedEntity = null;
double traceDistance = totalTraceDistance;
for (Entity entity : e.world.getOtherEntities(e,
e.getBoundingBox().expand(ray.x + 1, ray.y + 1, ray.z + 1),
predicate.and(Entity::collides)
)) {
Box entityAABB = entity.getBoundingBox().expand(entity.getTargetingMargin());
Optional<Vec3d> intercept = entityAABB.raycast(start, end);
if (entityAABB.contains(start)) {
if (traceDistance <= 0) {
pointedEntity = entity;
hit = intercept.orElse(null);
traceDistance = 0;
}
} else if (intercept.isPresent()) {
Vec3d inter = intercept.get();
double distanceToHit = start.distanceTo(inter);
if (distanceToHit < traceDistance || traceDistance == 0) {
if (entity.getRootVehicle() == e.getRootVehicle()) {
if (traceDistance == 0) {
pointedEntity = entity;
hit = inter;
}
} else {
pointedEntity = entity;
hit = inter;
traceDistance = distanceToHit;
}
}
}
if (pointedEntity != null) {
return new Trace(pointedEntity);
}
if (pointedEntity != null && (traceDistance < totalTraceDistance || tracedBlock == null)) {
return new Trace(new EntityHitResult(pointedEntity, hit));
}
return new Trace(tracedBlock);
return new Trace(e.raycast(distance, tickDelta, false));
}
public static class Trace {