From bc4f6c033f7387105682dedfc40c8e76712b03c7 Mon Sep 17 00:00:00 2001 From: Paul Reilly Date: Tue, 18 Jul 2023 17:55:55 -0500 Subject: [PATCH] Wonderful API! Now for implementations :anguished: --- .../api/spell/AbstractSpell.java | 85 +++++++++++ .../spells/soul/SoulPebble.java | 53 ++----- .../arcanumocculta/spells/soul/SoulShard.java | 32 ++++- .../arcanumocculta/util/SpellUtils.java | 134 ++++++++++++++---- 4 files changed, 239 insertions(+), 65 deletions(-) diff --git a/src/main/java/app/simplexdev/arcanumocculta/api/spell/AbstractSpell.java b/src/main/java/app/simplexdev/arcanumocculta/api/spell/AbstractSpell.java index b3ef6bf..3bc3e4f 100644 --- a/src/main/java/app/simplexdev/arcanumocculta/api/spell/AbstractSpell.java +++ b/src/main/java/app/simplexdev/arcanumocculta/api/spell/AbstractSpell.java @@ -5,8 +5,22 @@ import app.simplexdev.arcanumocculta.api.caster.CasterLevel; import app.simplexdev.arcanumocculta.api.spell.enums.Damages; import app.simplexdev.arcanumocculta.api.spell.enums.Durations; import app.simplexdev.arcanumocculta.api.spell.enums.ManaCosts; +import app.simplexdev.arcanumocculta.util.SpellUtils; +import java.util.List; import java.util.SplittableRandom; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; public abstract class AbstractSpell implements Spell { @@ -110,4 +124,75 @@ public abstract class AbstractSpell implements Spell { return random; } + + /** + * Checks if the caster has enough mana to cast this spell. + * + * @param caster The caster who is casting the spell + * @return True if the caster has enough mana, false otherwise + */ + protected boolean checkManaCosts(final Caster caster) + { + boolean isValid = !manaCost().isMoreThan(caster.getCurrentMana()); + if (!isValid) + caster.bukkit().sendMessage(ChatColor.RED + "You don't have enough mana to cast this spell!"); + + return isValid; + } + + public void applyEffects(final List targets, final Caster caster) + { + targets.stream() + .filter(LivingEntity.class::isInstance) + .map(LivingEntity.class::cast) + .forEach(entity -> applyEffectsIndividually( + getSpellEffects(), + entity, + caster)); + } + + public Entity prepareProjectile(final Caster caster, final Material visual) + { + final double expMod = getLevelRequirement().getExperienceMarker(); + + final Player player = caster.bukkit(); + final Location location = player.getLocation().clone().add(0, player.getEyeHeight(), 0); + final Vector velocity = player.getLocation().getDirection().multiply(2); + final Entity projectile = createProjectile(visual, player.getWorld(), location, + velocity); + caster.removeMana(manaCost().getManaCost()); + caster.addExperience(random().nextDouble(expMod * 0.25)); + return projectile; + } + + public void tracer(final World world, final Location location, final Particle particle) + { + world.spawnParticle(particle, + location, + 0, + random().nextDouble(-2, 2), + random().nextDouble(-2, 2), + random().nextDouble(-2, 2)); + } + + private void applyEffectsIndividually(final SpellEffect[] effects, final LivingEntity target, + final Caster caster) + { + for (final SpellEffect effect : effects) + effect.apply(target, caster); + } + + private static ItemDisplay createProjectile(final Material visual, final World world, final Location location, + final Vector velocity) + { + final ItemDisplay display = (ItemDisplay) world.spawnEntity(location, EntityType.ITEM_DISPLAY); + display.setItemStack(new ItemStack(visual)); + display.setGravity(true); + display.setPersistent(false); + display.setSilent(true); + display.setShadowRadius(0); + display.setShadowStrength(0); + display.setVelocity(velocity); + return display; + } } diff --git a/src/main/java/app/simplexdev/arcanumocculta/spells/soul/SoulPebble.java b/src/main/java/app/simplexdev/arcanumocculta/spells/soul/SoulPebble.java index d2cb298..7374475 100644 --- a/src/main/java/app/simplexdev/arcanumocculta/spells/soul/SoulPebble.java +++ b/src/main/java/app/simplexdev/arcanumocculta/spells/soul/SoulPebble.java @@ -3,19 +3,15 @@ package app.simplexdev.arcanumocculta.spells.soul; import app.simplexdev.arcanumocculta.api.caster.Caster; import app.simplexdev.arcanumocculta.api.caster.CasterLevel; import app.simplexdev.arcanumocculta.api.spell.AbstractSpell; +import app.simplexdev.arcanumocculta.api.spell.SpellEffect; import app.simplexdev.arcanumocculta.api.spell.enums.Damages; import app.simplexdev.arcanumocculta.api.spell.enums.Durations; -import app.simplexdev.arcanumocculta.api.spell.SpellEffect; import app.simplexdev.arcanumocculta.api.spell.enums.ManaCosts; import app.simplexdev.arcanumocculta.api.wand.Wand; import app.simplexdev.arcanumocculta.util.SpellUtils; -import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Particle; -import org.bukkit.entity.ItemDisplay; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.util.Vector; +import org.bukkit.entity.Entity; public final class SoulPebble extends AbstractSpell { @@ -35,56 +31,33 @@ public final class SoulPebble extends AbstractSpell public SpellEffect[] getSpellEffects() { final SpellEffect[] effects = new SpellEffect[1]; - effects[0] = (target, caster) -> - { - final Wand wand = caster.getWand(); - final double damage = baseDamage().multiply(wand.getSpellBonus()); - - SpellUtils.damage(target, caster.bukkit(), damage); - }; + effects[0] = SpellUtils.soulEffectBase(baseDamage()); return effects; } @Override public void cast(Caster caster, Wand wand) { - if (this.manaCost().isMoreThan(caster.getCurrentMana())) + if (!this.checkManaCosts(caster)) { - caster.bukkit().sendMessage("You do not have enough mana to cast this spell!"); return; } - final double expMod = this.getLevelRequirement().getExperienceMarker(); - - final Player player = caster.bukkit(); - final Location location = player.getLocation().clone().add(0, player.getEyeHeight(), 0); - final Vector velocity = player.getLocation().getDirection().multiply(2); - final ItemDisplay projectile = SpellUtils.createProjectile(Material.AIR, player.getWorld(), location, - velocity); - caster.removeMana(this.manaCost().getManaCost()); - caster.addExperience(random().nextDouble(expMod * 0.25)); + final Entity projectile = prepareProjectile(caster, Material.AIR); while (!projectile.isOnGround() || !projectile.isDead()) { - projectile.getWorld().spawnParticle(Particle.SOUL, projectile.getLocation(), 1, - random().nextGaussian() * 0.2, - random().nextGaussian() * 0.2, - random().nextGaussian() * 0.2); + tracer(projectile.getWorld(), projectile.getLocation(), Particle.SOUL); - if (!projectile.getNearbyEntities(1, 1, 1).isEmpty()) { - projectile.getNearbyEntities(1, 1, 1) - .stream() - .filter(LivingEntity.class::isInstance) - .map(LivingEntity.class::cast) - .forEach(entity -> SpellUtils.applyEffects( - this.getSpellEffects(), - entity, - caster)); + if (!projectile.getNearbyEntities(1, 1, 1).isEmpty()) + { + applyEffects(projectile.getNearbyEntities(1, 1, 1), + caster); projectile.remove(); - break; } - } - if (projectile.isOnGround()) projectile.remove(); + if (projectile.isOnGround()) + projectile.remove(); + } } } diff --git a/src/main/java/app/simplexdev/arcanumocculta/spells/soul/SoulShard.java b/src/main/java/app/simplexdev/arcanumocculta/spells/soul/SoulShard.java index 220b53d..1643a5f 100644 --- a/src/main/java/app/simplexdev/arcanumocculta/spells/soul/SoulShard.java +++ b/src/main/java/app/simplexdev/arcanumocculta/spells/soul/SoulShard.java @@ -30,6 +30,12 @@ import app.simplexdev.arcanumocculta.api.spell.enums.Durations; import app.simplexdev.arcanumocculta.api.spell.SpellEffect; import app.simplexdev.arcanumocculta.api.spell.enums.ManaCosts; import app.simplexdev.arcanumocculta.api.wand.Wand; +import app.simplexdev.arcanumocculta.util.SpellUtils; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffectType; public final class SoulShard extends AbstractSpell { @@ -46,12 +52,36 @@ public final class SoulShard extends AbstractSpell @Override public SpellEffect[] getSpellEffects() { - return new SpellEffect[0]; + final SpellEffect[] effects = new SpellEffect[1]; + effects[0] = SpellUtils.soulEffectBase(baseDamage()); + return effects; } @Override public void cast(Caster caster, Wand wand) { + if (!this.checkManaCosts(caster)) + { + return; + } + final Entity projectile = prepareProjectile(caster, Material.AIR); + + while (!projectile.isDead()) { + for (int i = 0; i < 3; i++) { + tracer(projectile.getWorld(), projectile.getLocation(), Particle.SOUL); + } + + if (!projectile.getNearbyEntities(1, 1, 1).isEmpty()) + { + applyEffects(projectile.getNearbyEntities(1, 1, 1), + caster); + projectile.remove(); + } + + if (projectile.isOnGround()) { + projectile.remove(); + } + } } } diff --git a/src/main/java/app/simplexdev/arcanumocculta/util/SpellUtils.java b/src/main/java/app/simplexdev/arcanumocculta/util/SpellUtils.java index 1c1cda6..8d4716c 100644 --- a/src/main/java/app/simplexdev/arcanumocculta/util/SpellUtils.java +++ b/src/main/java/app/simplexdev/arcanumocculta/util/SpellUtils.java @@ -22,25 +22,23 @@ package app.simplexdev.arcanumocculta.util; -import app.simplexdev.arcanumocculta.api.caster.Caster; import app.simplexdev.arcanumocculta.api.spell.Spell; import app.simplexdev.arcanumocculta.api.spell.SpellEffect; +import app.simplexdev.arcanumocculta.api.spell.enums.Damages; import app.simplexdev.arcanumocculta.spells.PrimarySpellList; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; +import java.util.SplittableRandom; +import org.bukkit.Particle; import org.bukkit.entity.Damageable; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.ItemDisplay; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; import org.bukkit.util.Vector; public final class SpellUtils { public static final String SPELL_PACKAGE = PrimarySpellList.class.getPackageName(); private static final PrimarySpellList primarySpellList = new PrimarySpellList(); + private static final SplittableRandom RANDOM = new SplittableRandom(); private SpellUtils() { @@ -55,28 +53,116 @@ public final class SpellUtils target.damage(damage); } - public static void applyEffects(final SpellEffect[] effects, final LivingEntity target, final Caster caster) - { - for (final SpellEffect effect : effects) - effect.apply(target, caster); - } - public static Spell copyFromPrimaryList(final String id) { return SpellUtils.primarySpellList.getSpell(id).dupe(); } - public static ItemDisplay createProjectile(final Material visual, final World world, final Location location, - final Vector velocity) + public static SpellEffect soulEffectBase(final Damages baseDamage) { - final ItemDisplay display = (ItemDisplay) world.spawnEntity(location, EntityType.ITEM_DISPLAY); - display.setItemStack(new ItemStack(visual)); - display.setGravity(true); - display.setPersistent(false); - display.setSilent(true); - display.setShadowRadius(0); - display.setShadowStrength(0); - display.setVelocity(velocity); - return display; + return (target, caster) -> + { + final var damage = baseDamage.multiply(caster.getWand().getSpellBonus()); + if (target instanceof Player player) + // Player is freezing when hit, as if they were in powdered snow for 1 minute. + player.setFreezeTicks(1200); + + damage(target, caster.bukkit(), damage); + }; + } + + public static SpellEffect flameEffectBase(final Damages baseDamage) + { + return (target, caster) -> + { + final var damage = baseDamage.multiply(caster.getWand().getSpellBonus()); + // Fire ticks for 15 seconds. + target.setFireTicks(300); + + damage(target, caster.bukkit(), damage); + }; + } + + public static SpellEffect witherEffectBase(final Damages baseDamage) + { + return (target, caster) -> + { + final var damage = baseDamage.multiply(caster.getWand().getSpellBonus()); + // Wither for 10 seconds. + target.addPotionEffect(PotionEffectType.WITHER.createEffect(200, 2)); + + damage(target, caster.bukkit(), damage); + }; + } + + public static SpellEffect lightningEffectBase(final Damages baseDamage) + { + return (target, caster) -> + { + final var damage = baseDamage.multiply(caster.getWand().getSpellBonus()); + // Lightning on target + target.getWorld().strikeLightning(target.getLocation()); + + damage(target, caster.bukkit(), damage); + }; + } + + public static SpellEffect healEffectBase(final Damages baseDamage) + { + return (target, caster) -> + { + final var damage = baseDamage.multiply(caster.getWand().getSpellBonus()); + // Heal target + target.setHealth(target.getHealth() + damage); + }; + } + + public static SpellEffect regenEffectBase(final Damages baseDamage) + { + return (target, caster) -> + { + final var damage = baseDamage.multiply(caster.getWand().getSpellBonus()); + // Regen target + target.addPotionEffect(PotionEffectType.REGENERATION.createEffect(300, 3)); + target.setHealth(target.getHealth() + ((target.getHealth() + damage) / 2)); + }; + } + + public static SpellEffect meteorLikeEffectBase(final Damages baseDamage, + final Vector incomingVelocity, + final float size) + { + return (target, caster) -> + { + final var damage = baseDamage.multiply(caster.getWand().getSpellBonus()); + // Meteor like effect + // TODO: Make a configuration option to enable/disable explosions destroying blocks. + target.getWorld().createExplosion(target.getLocation(), size, true, false); + target.setVelocity(incomingVelocity.normalize().multiply(2).multiply(-1)); + damage(target, caster.bukkit(), damage); + }; + } + + public static SpellEffect arsLikeEffectBase(final Damages baseDamage) + { + return (target, caster) -> + { + final var damage = baseDamage.multiply(caster.getWand().getSpellBonus()); + // Ars Magica like effect + target.setVelocity(caster.bukkit().getLocation().clone().getDirection().multiply(2)); + if (target instanceof Player player) + player.setFreezeTicks(1200); + for (int i = 0; i < 50; i++) + { + target.getWorld().spawnParticle(Particle.SOUL_FIRE_FLAME, + target.getLocation(), + 0, + RANDOM.nextDouble(-2, 2), + RANDOM.nextDouble(-2, 2), + RANDOM.nextDouble(-2, 2)); + } + + damage(target, caster.bukkit(), damage); + }; } }