From c05f4001fdc8d0c90484349fba1dc2a64d756c4d Mon Sep 17 00:00:00 2001 From: Electroblob77 <35599699+Electroblob77@users.noreply.github.com> Date: Sun, 27 Sep 2020 17:48:37 +0100 Subject: [PATCH] Add frost barrier spell --- .../wizardry/client/ClientProxy.java | 1 + .../client/model/ModelIceBarrier.java | 35 ++++ .../renderer/entity/RenderIceBarrier.java | 50 ++++++ .../entity/construct/EntityIceBarrier.java | 157 ++++++++++++++++++ .../electroblob/wizardry/registry/Spells.java | 4 + .../wizardry/registry/WizardryEntities.java | 1 + .../wizardry/registry/WizardrySounds.java | 2 + .../electroblob/wizardry/spell/Boulder.java | 3 +- .../electroblob/wizardry/spell/Fangs.java | 4 +- .../wizardry/spell/FrostBarrier.java | 129 ++++++++++++++ .../wizardry/util/GeometryUtils.java | 7 + .../wizardry/util/SpellModifiers.java | 23 +-- .../assets/ebwizardry/lang/en_gb.lang | 2 + .../assets/ebwizardry/lang/en_us.lang | 2 + .../resources/assets/ebwizardry/sounds.json | 3 + .../ebwizardry/spells/frost_barrier.json | 22 +++ .../textures/entity/ice_barrier.png | Bin 0 -> 12245 bytes .../textures/spells/frost_barrier.png | Bin 0 -> 5379 bytes 18 files changed, 426 insertions(+), 19 deletions(-) create mode 100644 src/main/java/electroblob/wizardry/client/model/ModelIceBarrier.java create mode 100644 src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceBarrier.java create mode 100644 src/main/java/electroblob/wizardry/entity/construct/EntityIceBarrier.java create mode 100644 src/main/java/electroblob/wizardry/spell/FrostBarrier.java create mode 100644 src/main/resources/assets/ebwizardry/spells/frost_barrier.json create mode 100644 src/main/resources/assets/ebwizardry/textures/entity/ice_barrier.png create mode 100644 src/main/resources/assets/ebwizardry/textures/spells/frost_barrier.png diff --git a/src/main/java/electroblob/wizardry/client/ClientProxy.java b/src/main/java/electroblob/wizardry/client/ClientProxy.java index 8683ed84..4ad38117 100644 --- a/src/main/java/electroblob/wizardry/client/ClientProxy.java +++ b/src/main/java/electroblob/wizardry/client/ClientProxy.java @@ -817,6 +817,7 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityRadiantTotem.class, RenderRadiantTotem::new); RenderingRegistry.registerEntityRenderingHandler(EntityBoulder.class, RenderBoulder::new); RenderingRegistry.registerEntityRenderingHandler(EntityWitheringTotem.class, RenderWitheringTotem::new); + RenderingRegistry.registerEntityRenderingHandler(EntityIceBarrier.class, RenderIceBarrier::new); //RenderingRegistry.registerEntityRenderingHandler(EntityContainmentField.class, RenderContainmentField::new); // Stuff that doesn't render diff --git a/src/main/java/electroblob/wizardry/client/model/ModelIceBarrier.java b/src/main/java/electroblob/wizardry/client/model/ModelIceBarrier.java new file mode 100644 index 00000000..0a2137e5 --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/model/ModelIceBarrier.java @@ -0,0 +1,35 @@ +package electroblob.wizardry.client.model; + +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.model.ModelBox; +import net.minecraft.client.model.ModelRenderer; +import net.minecraft.entity.Entity; + +// Made with Blockbench 3.5.4 +public class ModelIceBarrier extends ModelBase { + + private final ModelRenderer mainGroup; + + public ModelIceBarrier(){ + + textureWidth = 64; + textureHeight = 64; + + mainGroup = new ModelRenderer(this); + mainGroup.setRotationPoint(0.0F, 24.0F, 0.0F); + setRotationAngle(mainGroup, 0.0F, 0.0F, 0.7854F); + mainGroup.cubeList.add(new ModelBox(mainGroup, 0, 0, -14.5F, -14.5F, -1.5F, 29, 29, 3, 0.0F, false)); + mainGroup.cubeList.add(new ModelBox(mainGroup, 0, 32, -9.0F, -9.0F, -4.0F, 18, 18, 8, 0.0F, false)); + } + + @Override + public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5) { + mainGroup.render(f5); + } + + public void setRotationAngle(ModelRenderer modelRenderer, float x, float y, float z) { + modelRenderer.rotateAngleX = x; + modelRenderer.rotateAngleY = y; + modelRenderer.rotateAngleZ = z; + } +} \ No newline at end of file diff --git a/src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceBarrier.java b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceBarrier.java new file mode 100644 index 00000000..5ee0edac --- /dev/null +++ b/src/main/java/electroblob/wizardry/client/renderer/entity/RenderIceBarrier.java @@ -0,0 +1,50 @@ +package electroblob.wizardry.client.renderer.entity; + +import electroblob.wizardry.Wizardry; +import electroblob.wizardry.client.model.ModelIceBarrier; +import electroblob.wizardry.entity.construct.EntityIceBarrier; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.util.ResourceLocation; + +public class RenderIceBarrier extends Render { + + private static final ResourceLocation TEXTURE = new ResourceLocation(Wizardry.MODID, "textures/entity/ice_barrier.png"); + + private ModelIceBarrier model = new ModelIceBarrier(); + + public RenderIceBarrier(RenderManager manager){ + super(manager); + } + + @Override + public void doRender(EntityIceBarrier entity, double x, double y, double z, float yaw, float partialTicks){ + + GlStateManager.pushMatrix(); + GlStateManager.translate(x, y + entity.height/2, z); + GlStateManager.rotate(180, 0F, 0F, 1F); + GlStateManager.rotate(yaw, 0, 1, 0); + //GlStateManager.rotate(entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * partialTicks, 0, 0, 1); + GlStateManager.translate(0, -entity.height/2 - 0.3, 0); + + // Pass in -1 for the lifetime as the boulder crumbles when the time expires +// float s = DrawingUtils.smoothScaleFactor(-1, entity.ticksExisted, partialTicks, 10, 10); + float s = entity.getSizeMultiplier(); + GlStateManager.scale(s, s, s); + +// GlStateManager.translate(0, 0.875, 0); // No idea why it starts 7/8 of a block too low, but it does + + this.bindTexture(TEXTURE); + + model.render(entity, 0, 0, 0, 0, 0, 0.0625f); + + GlStateManager.popMatrix(); + } + + @Override + protected ResourceLocation getEntityTexture(EntityIceBarrier entity){ + return TEXTURE; + } + +} diff --git a/src/main/java/electroblob/wizardry/entity/construct/EntityIceBarrier.java b/src/main/java/electroblob/wizardry/entity/construct/EntityIceBarrier.java new file mode 100644 index 00000000..7b8f6757 --- /dev/null +++ b/src/main/java/electroblob/wizardry/entity/construct/EntityIceBarrier.java @@ -0,0 +1,157 @@ +package electroblob.wizardry.entity.construct; + +import electroblob.wizardry.entity.ICustomHitbox; +import electroblob.wizardry.registry.WizardrySounds; +import net.minecraft.entity.Entity; +import net.minecraft.entity.MoverType; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.DamageSource; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class EntityIceBarrier extends EntityScaledConstruct implements ICustomHitbox { + + private static final double THICKNESS = 0.4; + + private int delay = 0; + + public EntityIceBarrier(World world){ + super(world); + this.setSize(1.8f, 1.05f); + } + + public void setDelay(int delay){ + this.delay = delay; + this.lifetime += delay; + } + + @Override + public void setRotation(float yaw, float pitch){ + super.setRotation(yaw, pitch); + float a = MathHelper.cos((float)Math.toRadians(rotationYaw)); + float b = MathHelper.sin((float)Math.toRadians(rotationYaw)); + double x = width/2 * a + THICKNESS/2 * b; + double z = width/2 * b + THICKNESS/2 * a; + setEntityBoundingBox(new AxisAlignedBB(this.posX - x, this.posY, this.posZ - z, this.posX + x, this.posY + height, this.posZ + z)); + } + + @Override + public boolean canBeCollidedWith(){ + return true; + } + + @Override + public void onUpdate(){ + + // Bit of a cheat but it's easier than trying to sync FrostBarrier#addConstructExtras + if(world.isRemote && firstUpdate){ + setRotation(rotationYaw, rotationPitch); + setSizeMultiplier(sizeMultiplier); + } + + this.prevPosX = posX; + this.prevPosY = posY; + this.prevPosZ = posZ; + + if(!world.isRemote){ + + double extensionSpeed = 0; + + if(lifetime - this.ticksExisted < 20){ + extensionSpeed = -0.01 * (this.ticksExisted - (lifetime - 20)) * sizeMultiplier; + }else if(ticksExisted > 3 + delay){ + extensionSpeed = 0; + }else if(ticksExisted > delay){ + extensionSpeed = 0.5 * sizeMultiplier; + } + + this.move(MoverType.SELF, 0, extensionSpeed, 0); + } + + if(ticksExisted == delay + 1) this.playSound(WizardrySounds.ENTITY_ICE_BARRIER_EXTEND, 1, 1.5f); + + super.onUpdate(); + + Vec3d look = this.getLookVec(); + + for(Entity entity : world.getEntitiesWithinAABBExcludingEntity(this, getEntityBoundingBox().grow(2))){ + + if(entity instanceof EntityMagicConstruct) continue; + + if(!entity.getEntityBoundingBox().intersects(this.getEntityBoundingBox())) continue; + + double perpendicularDist = getSignedPerpendicularDistance(entity.getPositionVector()); + + if(Math.abs(perpendicularDist) < entity.width/2 + THICKNESS/2){ + double velocity = 0.25 * Math.signum(perpendicularDist); + entity.addVelocity(velocity * look.x, 0, velocity * look.z); + } + } + + } + + @Override + public boolean isBurning(){ + return false; + } + + @Override + public boolean attackEntityFrom(DamageSource source, float amount){ + this.playSound(WizardrySounds.ENTITY_ICE_BARRIER_DEFLECT, 0.7f, 2.5f); + return super.attackEntityFrom(source, amount); + } + +// @Override +// public int getBrightnessForRender(){ +// return 15728880; +// } + + @Override + protected void readEntityFromNBT(NBTTagCompound nbt){ + super.readEntityFromNBT(nbt); + delay = nbt.getInteger("delay"); + } + + @Override + protected void writeEntityToNBT(NBTTagCompound nbt){ + super.writeEntityToNBT(nbt); + nbt.setInteger("delay", delay); + } + + @Override + public Vec3d calculateIntercept(Vec3d origin, Vec3d endpoint, float fuzziness){ + // Calculate the point at which the line intersects the barrier plane + Vec3d vec = endpoint.subtract(origin); + + double perpendicularDist = getPerpendicularDistance(origin); + double perpendicularDist2 = getPerpendicularDistance(endpoint); + + Vec3d intercept = origin.add(vec.scale(perpendicularDist / (perpendicularDist + perpendicularDist2))); + + // This seems to be all over the palce, but the calculation MUST be right because it works for entity collisions! +// world.spawnParticle(EnumParticleTypes.END_ROD, intercept.x, intercept.y, intercept.z, 0, 0, 0); + + // If the point is within the hitbox (expanded by the fuzziness), it was a hit + return getEntityBoundingBox().grow(fuzziness).contains(intercept) ? intercept : null; + } + + @Override + public boolean contains(Vec3d point){ + return this.getEntityBoundingBox().contains(point) && getPerpendicularDistance(point) < THICKNESS/2; + } + + private double getPerpendicularDistance(Vec3d point){ + return Math.abs(getSignedPerpendicularDistance(point)); + } + + private double getSignedPerpendicularDistance(Vec3d point){ + Vec3d look = this.getLookVec(); + Vec3d delta = new Vec3d(point.x - this.posX, 0, point.z - this.posZ); + double dist = delta.length(); + float angle = (float)(delta.dotProduct(look) / dist); + return dist * MathHelper.sin(angle); + } + +} diff --git a/src/main/java/electroblob/wizardry/registry/Spells.java b/src/main/java/electroblob/wizardry/registry/Spells.java index 5dce3c2f..286a9ab2 100644 --- a/src/main/java/electroblob/wizardry/registry/Spells.java +++ b/src/main/java/electroblob/wizardry/registry/Spells.java @@ -240,6 +240,8 @@ public final class Spells { // Wizardry 4.3 spells + public static final Spell frost_barrier = placeholder(); + public static final Spell blinding_flash = placeholder(); public static final Spell mark_sacrifice = placeholder(); @@ -453,6 +455,8 @@ public final class Spells { // Wizardry 4.3 spells + registry.register(new FrostBarrier()); + registry.register(new BlindingFlash()); registry.register(new MarkSacrifice()); diff --git a/src/main/java/electroblob/wizardry/registry/WizardryEntities.java b/src/main/java/electroblob/wizardry/registry/WizardryEntities.java index fe7de88e..12b3df17 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardryEntities.java +++ b/src/main/java/electroblob/wizardry/registry/WizardryEntities.java @@ -162,6 +162,7 @@ public class WizardryEntities { registry.register(createEntry(EntityTornado.class, "tornado") .tracker(160, 3, false).build()); registry.register(createEntry(EntityIceSpike.class, "ice_spike") .tracker(160, 1, true).build()); registry.register(createEntry(EntityBoulder.class, "boulder") .tracker(160, 1, true).build()); // Vertical velocity is not constant + registry.register(createEntry(EntityIceBarrier.class, "ice_barrier") .tracker(160, 1, true).build()); } diff --git a/src/main/java/electroblob/wizardry/registry/WizardrySounds.java b/src/main/java/electroblob/wizardry/registry/WizardrySounds.java index 4842151a..e1610825 100644 --- a/src/main/java/electroblob/wizardry/registry/WizardrySounds.java +++ b/src/main/java/electroblob/wizardry/registry/WizardrySounds.java @@ -66,6 +66,8 @@ public final class WizardrySounds { public static final SoundEvent ENTITY_HAMMER_THROW = createSound("entity.hammer.throw"); public static final SoundEvent ENTITY_HAMMER_LAND = createSound("entity.hammer.land"); public static final SoundEvent ENTITY_HEAL_AURA_AMBIENT = createSound("entity.heal_aura.ambient"); + public static final SoundEvent ENTITY_ICE_BARRIER_DEFLECT = createSound("entity.ice_barrier.deflect"); + public static final SoundEvent ENTITY_ICE_BARRIER_EXTEND = createSound("entity.ice_barrier.extend"); public static final SoundEvent ENTITY_ICE_SPIKE_EXTEND = createSound("entity.ice_spike.extend"); public static final SoundEvent ENTITY_LIGHTNING_SIGIL_TRIGGER = createSound("entity.lightning_sigil.trigger"); public static final SoundEvent ENTITY_METEOR_FALLING = createSound("entity.meteor.falling"); diff --git a/src/main/java/electroblob/wizardry/spell/Boulder.java b/src/main/java/electroblob/wizardry/spell/Boulder.java index b4debe62..700ad4e1 100644 --- a/src/main/java/electroblob/wizardry/spell/Boulder.java +++ b/src/main/java/electroblob/wizardry/spell/Boulder.java @@ -23,8 +23,7 @@ public class Boulder extends SpellConstruct { protected void addConstructExtras(EntityBoulder construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){ float speed = getProperty(SPEED).floatValue(); // Unlike tornado, boulder always has the same speed - Vec3d direction = caster == null ? new Vec3d(side.getDirectionVec()) - : GeometryUtils.replaceComponent(caster.getLookVec(), Axis.Y, 0).normalize(); + Vec3d direction = caster == null ? new Vec3d(side.getDirectionVec()) : GeometryUtils.horizontalise(caster.getLookVec()); construct.setHorizontalVelocity(direction.x * speed, direction.z * speed); construct.rotationYaw = caster == null ? side.getHorizontalAngle() : caster.rotationYaw; double yOffset = caster == null ? 0 : 1.6; diff --git a/src/main/java/electroblob/wizardry/spell/Fangs.java b/src/main/java/electroblob/wizardry/spell/Fangs.java index 607ab398..1a6390f5 100644 --- a/src/main/java/electroblob/wizardry/spell/Fangs.java +++ b/src/main/java/electroblob/wizardry/spell/Fangs.java @@ -11,7 +11,6 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.projectile.EntityEvokerFangs; import net.minecraft.tileentity.TileEntityDispenser; import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumFacing.Axis; import net.minecraft.util.EnumHand; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; @@ -42,8 +41,7 @@ public class Fangs extends Spell { @Override public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ - if(!spawnFangs(world, caster.getPositionVector(), - GeometryUtils.replaceComponent(caster.getLookVec(), Axis.Y, 0).normalize(), caster, modifiers)) return false; + if(!spawnFangs(world, caster.getPositionVector(), GeometryUtils.horizontalise(caster.getLookVec()), caster, modifiers)) return false; this.playSound(world, caster, ticksInUse, -1, modifiers); return true; } diff --git a/src/main/java/electroblob/wizardry/spell/FrostBarrier.java b/src/main/java/electroblob/wizardry/spell/FrostBarrier.java new file mode 100644 index 00000000..16c56f91 --- /dev/null +++ b/src/main/java/electroblob/wizardry/spell/FrostBarrier.java @@ -0,0 +1,129 @@ +package electroblob.wizardry.spell; + +import electroblob.wizardry.constants.Constants; +import electroblob.wizardry.entity.construct.EntityIceBarrier; +import electroblob.wizardry.item.SpellActions; +import electroblob.wizardry.registry.WizardryItems; +import electroblob.wizardry.util.BlockUtils; +import electroblob.wizardry.util.GeometryUtils; +import electroblob.wizardry.util.SpellModifiers; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumFacing.Axis; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class FrostBarrier extends Spell { + + private static final double BARRIER_DISTANCE = 2; + private static final double BARRIER_ARC_RADIUS = 10; + private static final double BARRIER_SPACING = 1.4; + + public FrostBarrier(){ + super("frost_barrier", SpellActions.SUMMON, false); + this.npcSelector((e, o) -> true); + addProperties(DURATION); + } + + @Override + public boolean requiresPacket(){ + return false; + } + + @Override + public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){ + + if(caster.onGround){ + if(!createBarriers(world, caster.getPositionVector(), caster.getLookVec(), caster, modifiers)) return false; + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + return false; + } + + @Override + public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){ + + if(caster.onGround){ + if(!createBarriers(world, caster.getPositionVector(), target.getPositionVector().subtract(caster.getPositionVector()), + caster, modifiers)) return false; + this.playSound(world, caster, ticksInUse, -1, modifiers); + return true; + } + + return false; + } + + @Override + public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){ + if(!createBarriers(world, new Vec3d(x, y, z), new Vec3d(direction.getDirectionVec()), null, modifiers)) return false; + // This MUST be the coordinates of the actual dispenser, so we need to offset it + this.playSound(world, x - direction.getXOffset(), y - direction.getYOffset(), z - direction.getZOffset(), ticksInUse, duration, modifiers); + return true; + } + + private boolean createBarriers(World world, Vec3d origin, Vec3d direction, @Nullable EntityLivingBase caster, SpellModifiers modifiers){ + + if(!world.isRemote){ + + direction = GeometryUtils.horizontalise(direction); + Vec3d centre = origin.add(direction.scale(BARRIER_DISTANCE - BARRIER_ARC_RADIUS)); // Arc centred behind caster + + // Don't spawn them yet or the anti-overlap will prevent the rest from spawning + List barriers = new ArrayList<>(); + + int barrierCount = 1 + Math.max(1, (int)((modifiers.get(SpellModifiers.POTENCY) - 1) / Constants.POTENCY_INCREASE_PER_TIER + 0.5f)); + + for(int i = 0; i < barrierCount; i++){ + + EntityIceBarrier barrier = createBarrier(world, centre, direction.rotateYaw((float)(BARRIER_SPACING / BARRIER_ARC_RADIUS) * i), caster, modifiers, barrierCount, i); + if(barrier != null) barriers.add(barrier); + + if(i == 0) continue; // Only one in the middle + barrier = createBarrier(world, centre, direction.rotateYaw(-(float)(BARRIER_SPACING / BARRIER_ARC_RADIUS) * i), caster, modifiers, barrierCount, i); + if(barrier != null) barriers.add(barrier); + } + + if(barriers.isEmpty()) return false; + + barriers.forEach(world::spawnEntity); // Finally spawn them all + } + + return true; + } + + private EntityIceBarrier createBarrier(World world, Vec3d centre, Vec3d direction, @Nullable EntityLivingBase caster, SpellModifiers modifiers, int barrierCount, int index){ + + Vec3d position = centre.add(direction.scale(BARRIER_ARC_RADIUS)); + Integer floor = BlockUtils.getNearestFloor(world, new BlockPos(position), 3); + if(floor == null) return null; + position = GeometryUtils.replaceComponent(position, Axis.Y, floor); + + float scale = 1.5f - (float)index/barrierCount * 0.5f; + double yOffset = 1.5 * scale; + + EntityIceBarrier barrier = new EntityIceBarrier(world); + barrier.setPosition(position.x, position.y - yOffset, position.z); + barrier.setCaster(caster); + barrier.lifetime = (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)); + barrier.damageMultiplier = modifiers.get(SpellModifiers.POTENCY); + barrier.setRotation((float)Math.toDegrees(MathHelper.atan2(-direction.x, direction.z)), barrier.rotationPitch); + barrier.setSizeMultiplier(scale); + barrier.setDelay(1 + 3 * index); // Delay 0 seems to move it down 1 block, no idea why + + if(!world.getEntitiesWithinAABB(barrier.getClass(), barrier.getEntityBoundingBox().offset(0, yOffset, 0)).isEmpty()) return null; + + return barrier; + } + +} diff --git a/src/main/java/electroblob/wizardry/util/GeometryUtils.java b/src/main/java/electroblob/wizardry/util/GeometryUtils.java index d13d2c7b..0b14fb76 100644 --- a/src/main/java/electroblob/wizardry/util/GeometryUtils.java +++ b/src/main/java/electroblob/wizardry/util/GeometryUtils.java @@ -100,6 +100,13 @@ public final class GeometryUtils { return new Vec3i(components[0], components[1], components[2]); } + /** + * Returns a normalised {@link Vec3d} with the same yaw angle as the given vector, but with a y component of zero. + */ + public static Vec3d horizontalise(Vec3d vec){ + return replaceComponent(vec, Axis.Y, 0).normalize(); + } + /** * Returns an array of {@code Vec3d} objects representing the vertices of the given bounding box. * @param box The bounding box whose vertices are to be returned. diff --git a/src/main/java/electroblob/wizardry/util/SpellModifiers.java b/src/main/java/electroblob/wizardry/util/SpellModifiers.java index 53f4a535..a0c30416 100644 --- a/src/main/java/electroblob/wizardry/util/SpellModifiers.java +++ b/src/main/java/electroblob/wizardry/util/SpellModifiers.java @@ -58,20 +58,15 @@ public final class SpellModifiers { syncedMultiplierMap = new HashMap<>(); } -// /** Returns a deep copy of this {@code SpellModifiers} object. */ -// @Override -// public SpellModifiers clone(){ -// SpellModifiers clone; -// try { -// clone = (SpellModifiers)super.clone(); -// }catch(CloneNotSupportedException e){ -// Wizardry.logger.error("Whaaaaat?!", e); -// return null; -// } -// clone.multiplierMap = new HashMap<>(this.multiplierMap); -// clone.syncedMultiplierMap = new HashMap<>(this.syncedMultiplierMap); -// return clone; -// } + private SpellModifiers(Map multiplierMap, Map syncedMultiplierMap){ + this.multiplierMap = multiplierMap; + this.syncedMultiplierMap = syncedMultiplierMap; + } + + /** Returns a deep copy (with copies of the underlying maps) of this {@code SpellModifiers} object. */ + public SpellModifiers copy(){ + return new SpellModifiers(new HashMap<>(this.multiplierMap), new HashMap<>(this.syncedMultiplierMap)); + } /** * Adds the given multiplier to this SpellModifiers object, using the string identifier that the given wand upgrade diff --git a/src/main/resources/assets/ebwizardry/lang/en_gb.lang b/src/main/resources/assets/ebwizardry/lang/en_gb.lang index 24b20d21..fb498fa7 100644 --- a/src/main/resources/assets/ebwizardry/lang/en_gb.lang +++ b/src/main/resources/assets/ebwizardry/lang/en_gb.lang @@ -829,6 +829,7 @@ spell.ebwizardry\:forest_of_thorns=Forest of Thorns spell.ebwizardry\:freeze=Freeze spell.ebwizardry\:freezing_weapon=Freezing Weapon spell.ebwizardry\:frost_axe=Frost Axe +spell.ebwizardry\:frost_barrier=Frost Barrier spell.ebwizardry\:frost_ray=Frost Ray spell.ebwizardry\:frost_sigil=Frost Sigil spell.ebwizardry\:frost_step=Frost Step @@ -1012,6 +1013,7 @@ spell.ebwizardry\:forest_of_thorns.desc=Amidst the wood lies a hidden glade,\nIn spell.ebwizardry\:freeze.desc=Freezes the target for 10 seconds. Will also freeze water and create snow on the ground. spell.ebwizardry\:freezing_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with the power of frost, causing it to freeze its victims. The magic wears off after 45 seconds. spell.ebwizardry\:frost_axe.desc=Creates a frozen axe which freezes enemies when hit. Lasts for 30 seconds. +spell.ebwizardry\:frost_barrier.desc=Creates a barricade of ice in front of the caster that shields them from incoming attacks. The barrier lasts for 20 seconds. spell.ebwizardry\:frost_ray.desc=Creates a stream of frost in the direction you are pointing which slows and continually damages targets. spell.ebwizardry\:frost_sigil.desc=Places a magical ice trap on the ground which damages and freezes the creature that triggers it. spell.ebwizardry\:frost_step.desc=Allows the caster to freeze water as they walk for 30 seconds. diff --git a/src/main/resources/assets/ebwizardry/lang/en_us.lang b/src/main/resources/assets/ebwizardry/lang/en_us.lang index 3980aed6..82e12104 100644 --- a/src/main/resources/assets/ebwizardry/lang/en_us.lang +++ b/src/main/resources/assets/ebwizardry/lang/en_us.lang @@ -829,6 +829,7 @@ spell.ebwizardry\:forest_of_thorns=Forest of Thorns spell.ebwizardry\:freeze=Freeze spell.ebwizardry\:freezing_weapon=Freezing Weapon spell.ebwizardry\:frost_axe=Frost Axe +spell.ebwizardry\:frost_barrier=Frost Barrier spell.ebwizardry\:frost_ray=Frost Ray spell.ebwizardry\:frost_sigil=Frost Sigil spell.ebwizardry\:frost_step=Frost Step @@ -1012,6 +1013,7 @@ spell.ebwizardry\:forest_of_thorns.desc=Amidst the wood lies a hidden glade,\nIn spell.ebwizardry\:freeze.desc=Freezes the target for 10 seconds. Will also freeze water and create snow on the ground. spell.ebwizardry\:freezing_weapon.desc=Temporarily imbues the first weapon on the caster's hotbar with the power of frost, causing it to freeze its victims. The magic wears off after 45 seconds. spell.ebwizardry\:frost_axe.desc=Creates a frozen axe which freezes enemies when hit. Lasts for 30 seconds. +spell.ebwizardry\:frost_barrier.desc=Creates a barricade of ice in front of the caster that shields them from incoming attacks. The barrier lasts for 20 seconds. spell.ebwizardry\:frost_ray.desc=Creates a stream of frost in the direction you are pointing which slows and continually damages targets. spell.ebwizardry\:frost_sigil.desc=Places a magical ice trap on the ground which damages and freezes the creature that triggers it. spell.ebwizardry\:frost_step.desc=Allows the caster to freeze water as they walk for 30 seconds. diff --git a/src/main/resources/assets/ebwizardry/sounds.json b/src/main/resources/assets/ebwizardry/sounds.json index 19ab10ad..f74362cf 100644 --- a/src/main/resources/assets/ebwizardry/sounds.json +++ b/src/main/resources/assets/ebwizardry/sounds.json @@ -37,6 +37,8 @@ "entity.hammer.throw": {"category": "spells", "sounds": ["random/bow"]}, "entity.hammer.land": {"category": "spells", "sounds": ["random/anvil_land"]}, "entity.heal_aura.ambient": {"category": "spells", "sounds": ["ebwizardry:sparkle"]}, + "entity.ice_barrier.deflect": {"category": "spells", "sounds": ["random/anvil_land"]}, + "entity.ice_barrier.extend": {"category": "spells", "sounds": ["ebwizardry:slice"]}, "entity.ice_spike.extend": {"category": "spells", "sounds": ["ebwizardry:slice"]}, "entity.lightning_sigil.trigger": {"category": "spells", "sounds": ["ebwizardry:arc1", "ebwizardry:arc2", "ebwizardry:arc3"]}, "entity.meteor.falling": {"category": "spells", "sounds": ["ebwizardry:flames_loop"]}, @@ -196,6 +198,7 @@ "spell.freeze": {"category": "spells", "sounds": ["ebwizardry:ice"]}, "spell.freezing_weapon": {"category": "spells", "sounds": ["ebwizardry:buff"]}, "spell.frost_axe": {"category": "spells", "sounds": ["ebwizardry:buff"]}, + "spell.frost_barrier": {"category": "spells", "sounds": []}, "spell.frost_ray.start": {"category": "spells", "sounds": ["ebwizardry:frost_start"]}, "spell.frost_ray.loop": {"category": "spells", "sounds": ["ebwizardry:frost_loop"]}, "spell.frost_ray.end": {"category": "spells", "sounds": ["ebwizardry:frost_end"]}, diff --git a/src/main/resources/assets/ebwizardry/spells/frost_barrier.json b/src/main/resources/assets/ebwizardry/spells/frost_barrier.json new file mode 100644 index 00000000..6b7fc5b9 --- /dev/null +++ b/src/main/resources/assets/ebwizardry/spells/frost_barrier.json @@ -0,0 +1,22 @@ +{ + "enabled": { + "book": true, + "scroll": true, + "wands": true, + "npcs": true, + "dispensers": true, + "commands": true, + "treasure": true, + "trades": true, + "looting": true + }, + "tier": "apprentice", + "element": "ice", + "type": "defence", + "cost": 20, + "chargeup": 0, + "cooldown": 50, + "base_properties": { + "duration": 400 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ebwizardry/textures/entity/ice_barrier.png b/src/main/resources/assets/ebwizardry/textures/entity/ice_barrier.png new file mode 100644 index 0000000000000000000000000000000000000000..9b6e1229f1e65b3067663fe8d9f3b7cca2074bb0 GIT binary patch literal 12245 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/ebwizardry/textures/spells/frost_barrier.png b/src/main/resources/assets/ebwizardry/textures/spells/frost_barrier.png new file mode 100644 index 0000000000000000000000000000000000000000..b53ac71dc8323170be4fb473d0fbb8563f453211 GIT binary patch literal 5379 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 literal 0 HcmV?d00001