diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java index 81179daf..1c8ce90a 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java @@ -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 { 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); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornProjectileAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornProjectileAbility.java index ecb4a486..324bbd8d 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornProjectileAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornProjectileAbility.java @@ -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 { 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); + }); + } + }); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/HomingSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/HomingSpell.java new file mode 100644 index 00000000..2e7908fa --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/HomingSpell.java @@ -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); +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java index c4bf6684..26600ee3 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java @@ -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 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 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); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java index 87be47e5..d7736427 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java @@ -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; } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java index 7c45adeb..00f47cef 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java @@ -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 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; diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/ChangelingMagicParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/ChangelingMagicParticle.java index c0c5d2f6..988dea78 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/ChangelingMagicParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/ChangelingMagicParticle.java @@ -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 diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/MagicParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/MagicParticle.java index 6becb08c..612b6ee4 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/MagicParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/MagicParticle.java @@ -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; diff --git a/src/main/java/com/minelittlepony/unicopia/particle/MagicParticleEffect.java b/src/main/java/com/minelittlepony/unicopia/particle/MagicParticleEffect.java index c1264571..aac8668b 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/MagicParticleEffect.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/MagicParticleEffect.java @@ -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 diff --git a/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java b/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java index 392cc76a..3ba442c0 100644 --- a/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java @@ -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
  • physics = new EntityPhysics<>(this, GRAVITY, false); + private final EntityReference homingTarget = new EntityReference<>(); + public MagicProjectileEntity(EntityType type, World world) { super(type, world); } @@ -94,6 +99,10 @@ public class MagicProjectileEntity extends ThrownItemEntity implements Caster
  • { + 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
  • { compound.put("effect", SpellType.toNBT(effect)); }); diff --git a/src/main/java/com/minelittlepony/unicopia/util/RayTraceHelper.java b/src/main/java/com/minelittlepony/unicopia/util/RayTraceHelper.java index 3d874b4a..c8555554 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/RayTraceHelper.java +++ b/src/main/java/com/minelittlepony/unicopia/util/RayTraceHelper.java @@ -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 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 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 {