That's one heck of a commit you've got there...

I may have got a bit behind with version control. A lot behind, in fact. Maybe I'll go back and split this sometime - then again, I probably won't. But hey, at least it's here!
This commit is contained in:
Electroblob77
2019-08-18 00:04:18 +01:00
parent 2680d02304
commit f37812be3e
1564 changed files with 47652 additions and 12984 deletions
@@ -1,32 +1,32 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.EntityArc;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class Arc extends SpellRay {
private static final float BASE_DAMAGE = 3;
public Arc(){
super("arc", Tier.BASIC, Element.LIGHTNING, SpellType.ATTACK, 5, 15, false, 8, null);
super("arc", false, EnumAction.NONE);
this.aimAssist(0.6f);
this.soundValues(1, 1.7f, 0.2f);
this.addProperties(DAMAGE);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
@@ -44,11 +44,9 @@ public class Arc extends SpellRay {
target.getName(), this.getNameForTranslationFormatted()), true);
}else{
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK),
BASE_DAMAGE * modifiers.get(SpellModifiers.POTENCY));
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
}
// TODO: Does this mean that players hit by the spell hear no sound?
target.playSound(WizardrySounds.SPELL_SPARK, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F);
return true;
}
@@ -56,12 +54,12 @@ public class Arc extends SpellRay {
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@@ -1,47 +1,43 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.living.EntityWizard;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.monster.EntitySpellcasterIllager;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class ArcaneJammer extends SpellRay {
public ArcaneJammer(){
super("arcane_jammer", Tier.ADVANCED, Element.HEALING, SpellType.ATTACK, 30, 50, false, 10, WizardrySounds.SPELL_DEFLECTION);
super("arcane_jammer", false, EnumAction.NONE);
this.soundValues(0.7f, 1, 0.4f);
this.addProperties(EFFECT_DURATION);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
if(target instanceof EntityWizard && caster instanceof EntityPlayer)
WizardryAdvancementTriggers.jam_wizard.triggerFor((EntityPlayer)caster);
if(!world.isRemote){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.arcane_jammer,
(int)(300 * modifiers.get(WizardryItems.duration_upgrade)), 0));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), 0));
}
}
@@ -49,12 +45,12 @@ public class ArcaneJammer extends SpellRay {
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@@ -64,10 +60,18 @@ public class ArcaneJammer extends SpellRay {
.spawn(world);
}
@SubscribeEvent
@SubscribeEvent(priority = EventPriority.HIGHEST) // Prevents all spells so it comes before everything else
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
// Arcane jammer prevents spell casting.
if(event.getCaster() != null && event.getCaster().isPotionActive(WizardryPotions.arcane_jammer)) event.setCanceled(true);
}
@SubscribeEvent
public static void onLivingUpdateEvent(LivingEvent.LivingUpdateEvent event){
if(event.getEntity() instanceof EntitySpellcasterIllager
&& event.getEntityLiving().isPotionActive(WizardryPotions.arcane_jammer)){
((EntitySpellcasterIllager)event.getEntity()).setSpellType(EntitySpellcasterIllager.SpellType.NONE);
}
}
}
@@ -0,0 +1,133 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.world.ExplosionEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class ArcaneLock extends SpellRay {
/** The NBT tag name for storing the owner's UUID in the tile entity data. */
public static final String NBT_KEY = "arcaneLockOwner";
public ArcaneLock(){
super("arcane_lock", false, EnumAction.NONE);
}
@Override public boolean requiresPacket(){ return true; }
@Override public boolean canBeCastByDispensers(){ return false; }
@Override public boolean canBeCastByNPCs(){ return false; }
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(caster instanceof EntityPlayer){
TileEntity tileentity = world.getTileEntity(pos);
if(tileentity != null){
if(tileentity.getTileData().hasUniqueId(NBT_KEY)){
// Unlocking
if(world.getPlayerEntityByUUID(tileentity.getTileData().getUniqueId(NBT_KEY)) == caster){
NBTExtras.removeUniqueId(tileentity.getTileData(), NBT_KEY);
return true;
}
}else{
// Locking
tileentity.getTileData().setUniqueId(NBT_KEY, caster.getUniqueID());
return true;
}
}
}
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@SubscribeEvent
public static void onLeftClickBlockEvent(PlayerInteractEvent.LeftClickBlock event){
if(!canBypassLocks(event.getEntityPlayer())){
TileEntity tileentity = event.getWorld().getTileEntity(event.getPos());
// Prevents arcane-locked containers from being broken
// Need to check if it has the unique id first because if it is absent getUniqueId will return the nil UUID
if(tileentity != null && tileentity.getTileData().hasUniqueId(ArcaneLock.NBT_KEY)){
// Only the player that owns the lock may break the container
// If nobody owns it (i.e. it's part of a shrine), player will be null
// Why is getUniqueId marked @Nullable? It literally creates a UUID and returns it!
if(event.getEntityPlayer().getUniqueID() != tileentity.getTileData().getUniqueId(ArcaneLock.NBT_KEY)){
event.setCanceled(true);
}
}
}
}
@SubscribeEvent
public static void onRightClickBlockEvent(PlayerInteractEvent.RightClickBlock event){
if(!canBypassLocks(event.getEntityPlayer())){
TileEntity tileentity = event.getWorld().getTileEntity(event.getPos());
// Prevents arcane-locked containers from being opened
// Need to check if it has the unique id first because if it is absent getUniqueId will return the nil UUID
if(tileentity != null && tileentity.getTileData().hasUniqueId(ArcaneLock.NBT_KEY)){
// Why is getUniqueId marked @Nullable? It literally creates a UUID and returns it!
EntityPlayer owner = event.getWorld().getPlayerEntityByUUID(tileentity.getTileData().getUniqueId(ArcaneLock.NBT_KEY));
// Only the player that owns the lock or an ally of that player may open the container
// If nobody owns it (i.e. it's part of a shrine, or the owner logged out), player will be null
// Unfortunately we can't get the owner's allies if the owner is offline
// Perhaps if this crops up again we can store inverted 'ally of' maps as well?
if(owner == null || (owner != event.getEntityPlayer() && !AllyDesignationSystem.isPlayerAlly(owner, event.getEntityPlayer()))){
event.setCanceled(true);
}
}
}
}
private static boolean canBypassLocks(EntityPlayer player){
if(!player.isCreative()) return false;
if(Wizardry.settings.creativeBypassesArcaneLock) return true;
MinecraftServer server = player.world.getMinecraftServer();
return server != null && WizardryUtilities.isPlayerOp(player, server);
}
@SubscribeEvent
public static void onExplosionEvent(ExplosionEvent.Detonate event){
// Prevents arcane-locked containers from being exploded
event.getAffectedBlocks().removeIf(pos -> event.getWorld().getTileEntity(pos) != null
&& event.getWorld().getTileEntity(pos).getTileData().hasUniqueId(NBT_KEY));
}
}
@@ -1,23 +1,20 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.construct.EntityArrowRain;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.EnumFacing;
import net.minecraft.world.World;
public class ArrowRain extends SpellConstructRanged<EntityArrowRain> {
public ArrowRain(){
super("arrow_rain", Tier.MASTER, Element.SORCERY, SpellType.ATTACK, 75, 300, EntityArrowRain::new, 120, 20, WizardrySounds.SPELL_SUMMONING);
super("arrow_rain", EntityArrowRain::new, false);
this.floor(true);
}
@Override
protected boolean spawnConstruct(World world, double x, double y, double z, EntityLivingBase caster, SpellModifiers modifiers){
protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){
// Moves the entity back towards the caster a bit, so the area of effect is better centred on the position.
// 3 is the distance to move the entity back towards the caster.
@@ -29,11 +26,11 @@ public class ArrowRain extends SpellConstructRanged<EntityArrowRain> {
// Moves the entity up 5 blocks so that it is above mobs' heads.
y += 5;
return super.spawnConstruct(world, x, y, z, caster, modifiers);
return super.spawnConstruct(world, x, y, z, side, caster, modifiers);
}
@Override
protected void addConstructExtras(EntityArrowRain construct, EntityLivingBase caster, SpellModifiers modifiers){
protected void addConstructExtras(EntityArrowRain construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){
// Makes the arrows shoot in the direction the caster was looking when they cast the spell.
construct.rotationYaw = caster.rotationYawHead;
}
@@ -1,8 +1,5 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
@@ -10,79 +7,48 @@ import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class Banish extends SpellRay {
public static final String MINIMUM_TELEPORT_DISTANCE = "minimum_teleport_distance";
public static final String MAXIMUM_TELEPORT_DISTANCE = "maximum_teleport_distance";
public Banish(){
super("banish", Tier.APPRENTICE, Element.NECROMANCY, SpellType.ATTACK, 15, 40, false, 10, SoundEvents.ENTITY_ENDERMEN_TELEPORT);
super("banish", false, EnumAction.NONE);
this.addProperties(MINIMUM_TELEPORT_DISTANCE, MAXIMUM_TELEPORT_DISTANCE);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(target instanceof EntityLivingBase){
EntityLivingBase entity = (EntityLivingBase)target;
double radius = (8 + world.rand.nextDouble() * 8) * modifiers.get(WizardryItems.range_upgrade);
double angle = world.rand.nextDouble() * Math.PI * 2;
double minRadius = getProperty(MINIMUM_TELEPORT_DISTANCE).doubleValue();
double maxRadius = getProperty(MAXIMUM_TELEPORT_DISTANCE).doubleValue();
double radius = (minRadius + world.rand.nextDouble() * maxRadius-minRadius) * modifiers.get(WizardryItems.blast_upgrade);
int x = MathHelper.floor(entity.posX + Math.sin(angle) * radius);
int z = MathHelper.floor(entity.posZ - Math.cos(angle) * radius);
int y = WizardryUtilities.getNearestFloorLevel(world,
new BlockPos(x, (int)caster.getEntityBoundingBox().minY, z), (int)radius);
if(world.isRemote){
for(int i=0; i<10; i++){
double dx1 = entity.posX;
double dy1 = entity.getEntityBoundingBox().minY + entity.height * world.rand.nextFloat();
double dz1 = entity.posZ;
world.spawnParticle(EnumParticleTypes.PORTAL, dx1, dy1, dz1, world.rand.nextDouble() - 0.5,
world.rand.nextDouble() - 0.5, world.rand.nextDouble() - 0.5);
}
// Can't be bothered to route this through the proxies!
if(entity == net.minecraft.client.Minecraft.getMinecraft().player)
electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect();
}
if(y > -1){
// This means stuff like snow layers is ignored, meaning when on snow-covered ground the target does
// not teleport 1 block above the ground.
if(!world.getBlockState(new BlockPos(x, y, z)).getMaterial().blocksMovement()){
y--;
}
if(world.getBlockState(new BlockPos(x, y + 1, z)).getMaterial().blocksMovement()
|| world.getBlockState(new BlockPos(x, y + 2, z)).getMaterial().blocksMovement()){
return false;
}
if(!world.isRemote){
entity.setPositionAndUpdate(x + 0.5, y + 1, z + 0.5);
}
entity.playSound(SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f);
}
teleport(entity, world, radius);
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@@ -92,4 +58,52 @@ public class Banish extends SpellRay {
ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.2f, 0, 0.2f).spawn(world);
}
// Extracted as a separate method for external use
public boolean teleport(EntityLivingBase entity, World world, double radius){
float angle = world.rand.nextFloat() * (float)Math.PI * 2;
int x = MathHelper.floor(entity.posX + MathHelper.sin(angle) * radius);
int z = MathHelper.floor(entity.posZ - MathHelper.cos(angle) * radius);
Integer y = WizardryUtilities.getNearestFloor(world,
new BlockPos(x, (int)entity.getEntityBoundingBox().minY, z), (int)radius);
if(world.isRemote){
for(int i=0; i<10; i++){
double dx1 = entity.posX;
double dy1 = entity.getEntityBoundingBox().minY + entity.height * world.rand.nextFloat();
double dz1 = entity.posZ;
world.spawnParticle(EnumParticleTypes.PORTAL, dx1, dy1, dz1, world.rand.nextDouble() - 0.5,
world.rand.nextDouble() - 0.5, world.rand.nextDouble() - 0.5);
}
// Can't be bothered to route this through the proxies!
if(entity == net.minecraft.client.Minecraft.getMinecraft().player)
electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect();
}
if(y != null){
// This means stuff like snow layers is ignored, meaning when on snow-covered ground the target does
// not teleport 1 block above the ground.
if(!world.getBlockState(new BlockPos(x, y, z)).getMaterial().blocksMovement()){
y--;
}
if(world.getBlockState(new BlockPos(x, y + 1, z)).getMaterial().blocksMovement()
|| world.getBlockState(new BlockPos(x, y + 2, z)).getMaterial().blocksMovement()){
return false;
}
if(!world.isRemote){
entity.setPositionAndUpdate(x + 0.5, y + 1, z + 0.5);
}
this.playSound(world, entity, 0, -1, new SpellModifiers());
}
return true;
}
}
@@ -1,15 +1,12 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.RayTracer;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
@@ -22,14 +19,15 @@ import net.minecraft.world.World;
public class Blink extends Spell {
public Blink(){
super("blink", Tier.APPRENTICE, Element.SORCERY, SpellType.UTILITY, 15, 25, EnumAction.NONE, false);
super("blink", EnumAction.NONE, false);
addProperties(RANGE);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
RayTraceResult rayTrace = WizardryUtilities.standardBlockRayTrace(world, caster,
25 * modifiers.get(WizardryItems.range_upgrade), false);
RayTraceResult rayTrace = RayTracer.standardBlockRayTrace(world, caster,
getProperty(RANGE).doubleValue() * modifiers.get(WizardryItems.range_upgrade), false);
// It's worth noting that on the client side, the cast() method only gets called if the server side
// cast method succeeded, so you need not check any conditions for spawning particles.
@@ -52,8 +50,8 @@ public class Blink extends Spell {
BlockPos pos = rayTrace.getBlockPos();
// Can't teleport onto the ceiling.
if(rayTrace.sideHit == EnumFacing.DOWN) return false;
// Leave space for the player's head
if(rayTrace.sideHit == EnumFacing.DOWN) pos = pos.down();
// This means stuff like snow layers is ignored, meaning when on snow-covered ground the player does
// not teleport 1 block above the ground.
@@ -61,7 +59,7 @@ public class Blink extends Spell {
pos = pos.down();
}
pos = rayTrace.getBlockPos().offset(rayTrace.sideHit);
pos = pos.offset(rayTrace.sideHit);
// Prevents the player from teleporting into blocks and suffocating.
if(world.getBlockState(pos).getMaterial().blocksMovement()
@@ -70,11 +68,11 @@ public class Blink extends Spell {
}
// Plays before and after so it is heard from both positions
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
if(!world.isRemote) caster.setPositionAndUpdate(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5);
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
caster.swingArm(hand);
return true;
}
@@ -86,14 +84,14 @@ public class Blink extends Spell {
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target,
SpellModifiers modifiers){
double angle = Math.atan2(target.posZ - caster.posZ, target.posX - caster.posX)
+ world.rand.nextDouble() * Math.PI;
float angle = (float)(Math.atan2(target.posZ - caster.posZ, target.posX - caster.posX)
+ world.rand.nextDouble() * Math.PI);
double radius = caster.getDistance(target.posX, target.getEntityBoundingBox().minY, target.posZ)
+ world.rand.nextDouble() * 3.0d;
int x = MathHelper.floor(target.posX + Math.sin(angle) * radius);
int z = MathHelper.floor(target.posZ - Math.cos(angle) * radius);
int y = WizardryUtilities.getNearestFloorLevel(world, new BlockPos(caster), (int)radius);
int x = MathHelper.floor(target.posX + MathHelper.sin(angle) * radius);
int z = MathHelper.floor(target.posZ - MathHelper.cos(angle) * radius);
Integer y = WizardryUtilities.getNearestFloor(world, new BlockPos(caster), (int)radius);
// It's worth noting that on the client side, the cast() method only gets called if the server side
// cast method succeeded, so you need not check any conditions for spawning particles.
@@ -109,7 +107,7 @@ public class Blink extends Spell {
}
}
if(y > -1){
if(y != null){
// This means stuff like snow layers is ignored, meaning when on snow-covered ground the caster does
// not teleport 1 block above the ground.
@@ -123,13 +121,13 @@ public class Blink extends Spell {
}
// Plays before and after so it is heard from both positions
caster.playSound(SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
if(!world.isRemote){
caster.setPositionAndUpdate(x + 0.5, y + 1, z + 0.5);
}
caster.playSound(SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
caster.swingArm(hand);
return true;
}
@@ -1,11 +1,7 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.construct.EntityBubble;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
@@ -13,40 +9,30 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class Bubble extends SpellRay {
public Bubble(){
super("bubble", Tier.APPRENTICE, Element.EARTH, SpellType.ATTACK, 15, 20, false, 10, WizardrySounds.SPELL_ICE);
super("bubble", false, EnumAction.NONE);
this.soundValues(0.5f, 1.1f, 0.2f);
addProperties(DURATION);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
// This spell uses more than one sound, so this is required...
boolean flag = super.cast(world, caster, hand, ticksInUse, modifiers);
if(flag) WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_GENERIC_SWIM, 1, 1 + 0.2f * world.rand.nextFloat());
return flag;
protected SoundEvent[] createSounds(){
return this.createSoundsWithSuffixes("shoot", "splash");
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){
boolean flag = super.cast(world, caster, hand, ticksInUse, target, modifiers);
if(flag) caster.playSound(SoundEvents.ENTITY_GENERIC_SWIM, 1.0F, world.rand.nextFloat() * 0.2F + 1.0F);
return flag;
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
@@ -57,7 +43,7 @@ public class Bubble extends SpellRay {
EntityBubble bubble = new EntityBubble(world);
bubble.setPosition(target.posX, target.posY, target.posZ);
bubble.setCaster(caster);
bubble.lifetime = ((int)(200 * modifiers.get(WizardryItems.duration_upgrade)));
bubble.lifetime = ((int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
bubble.isDarkOrb = false;
bubble.damageMultiplier = modifiers.get(SpellModifiers.POTENCY);
@@ -70,12 +56,12 @@ public class Bubble extends SpellRay {
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@@ -1,79 +1,81 @@
package electroblob.wizardry.spell;
import java.util.List;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.EntityArc;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import java.util.List;
public class ChainLightning extends SpellRay {
private static final float PRIMARY_DAMAGE = 10;
private static final float SECONDARY_DAMAGE = 8;
private static final float TERTIARY_DAMAGE = 6;
public static final String PRIMARY_DAMAGE = "primary_damage";
public static final String SECONDARY_DAMAGE = "secondary_damage";
public static final String TERTIARY_DAMAGE = "tertiary_damage";
private static final double PRIMARY_RANGE = 10;
private static final double SECONDARY_RANGE = 5;
private static final double TERTIARY_RANGE = 5;
public static final String SECONDARY_RANGE = "secondary_range";
public static final String TERTIARY_RANGE = "tertiary_range";
private static final int SECONDARY_MAX_TARGETS = 5;
private static final int TERTIARY_MAX_TARGETS = 2; // This is per secondary target, giving 10 in total
public static final String SECONDARY_MAX_TARGETS = "secondary_max_targets";
public static final String TERTIARY_MAX_TARGETS = "tertiary_max_targets"; // This is per secondary target
public ChainLightning(){
super("chain_lightning", Tier.ADVANCED, Element.LIGHTNING, SpellType.ATTACK, 25, 50, false, PRIMARY_RANGE, null);
super("chain_lightning", false, EnumAction.NONE);
this.aimAssist(0.6f);
this.soundValues(1, 1.7f, 0.2f);
addProperties(PRIMARY_DAMAGE, SECONDARY_DAMAGE, TERTIARY_DAMAGE, SECONDARY_RANGE, TERTIARY_RANGE,
SECONDARY_MAX_TARGETS, TERTIARY_MAX_TARGETS);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
// Anything can be attacked with the initial arc, because the player has control over where it goes. If they
// hit a minion or an ally, it's their problem!
if(WizardryUtilities.isLiving(target)){
electrocute(world, caster, caster, target, PRIMARY_DAMAGE * modifiers.get(SpellModifiers.POTENCY));
electrocute(world, caster, origin, target, getProperty(PRIMARY_DAMAGE).floatValue()
* modifiers.get(SpellModifiers.POTENCY));
// Secondary chaining effect
List<EntityLivingBase> secondaryTargets = WizardryUtilities.getEntitiesWithinRadius(SECONDARY_RANGE,
target.posX, target.posY + target.height / 2, target.posZ, world);
List<EntityLivingBase> secondaryTargets = WizardryUtilities.getEntitiesWithinRadius(
getProperty(SECONDARY_RANGE).doubleValue(), target.posX, target.posY + target.height / 2, target.posZ, world);
secondaryTargets.remove(target);
secondaryTargets.removeIf(e -> !WizardryUtilities.isLiving(e));
secondaryTargets.removeIf(e -> !WizardryUtilities.isValidTarget(caster, e));
if(secondaryTargets.size() > SECONDARY_MAX_TARGETS) secondaryTargets = secondaryTargets.subList(0, SECONDARY_MAX_TARGETS);
secondaryTargets.removeIf(e -> !AllyDesignationSystem.isValidTarget(caster, e));
if(secondaryTargets.size() > getProperty(SECONDARY_MAX_TARGETS).intValue())
secondaryTargets = secondaryTargets.subList(0, getProperty(SECONDARY_MAX_TARGETS).intValue());
for(EntityLivingBase secondaryTarget : secondaryTargets){
electrocute(world, caster, target, secondaryTarget,
SECONDARY_DAMAGE * modifiers.get(SpellModifiers.POTENCY));
electrocute(world, caster, target.getPositionVector().add(0, target.height/2, 0), secondaryTarget,
getProperty(SECONDARY_DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
// Tertiary chaining effect
List<EntityLivingBase> tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius(TERTIARY_RANGE,
secondaryTarget.posX, secondaryTarget.posY + secondaryTarget.height / 2,
secondaryTarget.posZ, world);
List<EntityLivingBase> tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius(
getProperty(TERTIARY_RANGE).doubleValue(), secondaryTarget.posX,
secondaryTarget.posY + secondaryTarget.height / 2, secondaryTarget.posZ, world);
tertiaryTargets.remove(target);
tertiaryTargets.removeAll(secondaryTargets);
tertiaryTargets.removeIf(e -> !WizardryUtilities.isLiving(e));
tertiaryTargets.removeIf(e -> !WizardryUtilities.isValidTarget(caster, e));
if(tertiaryTargets.size() > TERTIARY_MAX_TARGETS) tertiaryTargets = tertiaryTargets.subList(0, TERTIARY_MAX_TARGETS);
tertiaryTargets.removeIf(e -> !AllyDesignationSystem.isValidTarget(caster, e));
if(tertiaryTargets.size() > getProperty(TERTIARY_MAX_TARGETS).intValue())
tertiaryTargets = tertiaryTargets.subList(0, getProperty(TERTIARY_MAX_TARGETS).intValue());
for(EntityLivingBase tertiaryTarget : tertiaryTargets){
electrocute(world, caster, secondaryTarget, tertiaryTarget,
TERTIARY_DAMAGE * modifiers.get(SpellModifiers.POTENCY));
electrocute(world, caster, secondaryTarget.getPositionVector().add(0, secondaryTarget.height/2, 0),
tertiaryTarget, getProperty(TERTIARY_DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
}
}
@@ -84,16 +86,16 @@ public class ChainLightning extends SpellRay {
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
private void electrocute(World world, Entity caster, Entity origin, Entity target, float damage){
private void electrocute(World world, Entity caster, Vec3d origin, Entity target, float damage){
if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){
if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage(
@@ -111,7 +113,7 @@ public class ChainLightning extends SpellRay {
ParticleBuilder.spawnShockParticles(world, target.posX, target.getEntityBoundingBox().minY + target.height/2, target.posZ);
}
target.playSound(WizardrySounds.SPELL_SPARK, 1, 1.5f + 0.4f * world.rand.nextFloat());
//target.playSound(WizardrySounds.SPELL_SPARK, 1, 1.5f + 0.4f * world.rand.nextFloat());
}
}
@@ -0,0 +1,125 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.data.IVariable;
import electroblob.wizardry.data.Persistence;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.List;
@Mod.EventBusSubscriber
public class Charge extends Spell {
public static final IVariable<Integer> CHARGE_TIME = new IVariable.Variable<Integer>(Persistence.NEVER).withTicker(Charge::update);
public static final IVariable<SpellModifiers> CHARGE_MODIFIERS = new IVariable.Variable<>(Persistence.NEVER);
public static final String CHARGE_SPEED = "charge_speed";
public static final String KNOCKBACK_STRENGTH = "knockback_strength";
private static final double EXTRA_HIT_MARGIN = 1;
public Charge(){
super("charge", EnumAction.NONE, false);
addProperties(CHARGE_SPEED, DURATION, DAMAGE, KNOCKBACK_STRENGTH);
this.soundValues(0.6f, 1, 0);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
WizardData.get(caster).setVariable(CHARGE_TIME, (int)(getProperty(DURATION).floatValue()
* modifiers.get(WizardryItems.duration_upgrade)));
WizardData.get(caster).setVariable(CHARGE_MODIFIERS, modifiers);
if(world.isRemote) world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, caster.posX, caster.posY + caster.height/2, caster.posZ, 0, 0, 0);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
private static int update(EntityPlayer player, Integer chargeTime){
if(chargeTime == null) chargeTime = 0;
if(chargeTime > 0){
SpellModifiers modifiers = WizardData.get(player).getVariable(CHARGE_MODIFIERS);
if(modifiers == null) modifiers = new SpellModifiers();
Vec3d look = player.getLookVec();
float speed = Spells.charge.getProperty(Charge.CHARGE_SPEED).floatValue() * modifiers.get(WizardryItems.range_upgrade);
player.motionX = look.x * speed;
player.motionZ = look.z * speed;
if(player.world.isRemote){
for(int i = 0; i < 5; i++){
ParticleBuilder.create(Type.SPARK, player).spawn(player.world);
}
}
List<EntityLivingBase> collided = player.world.getEntitiesWithinAABB(EntityLivingBase.class, player.getEntityBoundingBox().grow(EXTRA_HIT_MARGIN));
collided.remove(player);
float damage = Spells.charge.getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY);
float knockback = Spells.charge.getProperty(KNOCKBACK_STRENGTH).floatValue();
collided.forEach(e -> e.attackEntityFrom(MagicDamage.causeDirectMagicDamage(player, MagicDamage.DamageType.SHOCK), damage));
collided.forEach(e -> e.addVelocity(player.motionX * knockback, player.motionY * knockback + 0.3f, player.motionZ * knockback));
if(player.world.isRemote) player.world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE,
player.posX + player.motionX, player.posY + player.height/2, player.posZ + player.motionZ, 0, 0, 0);
if(collided.isEmpty()) chargeTime--;
else{
WizardryUtilities.playSoundAtPlayer(player, SoundEvents.ENTITY_GENERIC_HURT, 1, 1);
chargeTime = 0;
}
}
return chargeTime;
}
@SubscribeEvent(priority = EventPriority.HIGH)
public static void onLivingAttackEvent(LivingAttackEvent event){
// Players are immune to melee damage while charging
if(event.getEntity() instanceof EntityPlayer && event.getSource().getTrueSource() instanceof EntityLivingBase){
EntityPlayer player = (EntityPlayer)event.getEntity();
EntityLivingBase attacker = (EntityLivingBase)event.getSource().getTrueSource();
if(WizardData.get(player) != null){
Integer chargeTime = WizardData.get(player).getVariable(CHARGE_TIME);
if(chargeTime != null && chargeTime > 0
&& player.getEntityBoundingBox().grow(EXTRA_HIT_MARGIN).intersects(attacker.getEntityBoundingBox())){
event.setCanceled(true);
}
}
}
}
}
@@ -1,21 +1,17 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.data.IStoredVariable;
import electroblob.wizardry.data.Persistence;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.misc.WizardryPathFinder;
import electroblob.wizardry.packet.PacketClairvoyance;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryPathFinder;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.monster.EntityZombie;
import net.minecraft.entity.player.EntityPlayer;
@@ -39,23 +35,30 @@ public class Clairvoyance extends Spell {
/** The number of ticks it takes each path particle to move from one path point to the next. */
public static final int PARTICLE_MOVEMENT_INTERVAL = 45;
public static final IStoredVariable<BlockPos> LOCATION_KEY = IStoredVariable.StoredVariable.ofBlockPos("clairvoyancePos", Persistence.ALWAYS);
public static final IStoredVariable<Integer> DIMENSION_KEY = IStoredVariable.StoredVariable.ofInt("clairvoyanceDimension", Persistence.ALWAYS);
public Clairvoyance(){
super("clairvoyance", Tier.APPRENTICE, Element.SORCERY, SpellType.UTILITY, 20, 100, EnumAction.BOW, false);
super("clairvoyance", EnumAction.BOW, false);
addProperties(RANGE, DURATION);
WizardData.registerStoredVariables(LOCATION_KEY, DIMENSION_KEY);
}
@Override
public boolean doesSpellRequirePacket(){
return false;
}
@Override public boolean canBeCastByNPCs() { return false; }
@Override public boolean canBeCastByDispensers() { return false; }
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
WizardData properties = WizardData.get(caster);
WizardData data = WizardData.get(caster);
if(properties != null && !caster.isSneaking()){
if(caster.dimension == properties.getClairvoyanceDimension()){
if(properties.getClairvoyanceLocation() != null){
if(data != null && !caster.isSneaking()){
Integer dimension = data.getVariable(DIMENSION_KEY);
BlockPos location = data.getVariable(LOCATION_KEY);
if(dimension != null && caster.dimension == dimension){
if(location != null){
if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".searching"), true);
@@ -66,16 +69,15 @@ public class Clairvoyance extends Spell {
}
};
arbitraryZombie.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE)
.setBaseValue(256 * modifiers.get(WizardryItems.range_upgrade));
.setBaseValue(getProperty(RANGE).doubleValue() * modifiers.get(WizardryItems.range_upgrade));
arbitraryZombie.setPosition(caster.posX, caster.posY, caster.posZ);
arbitraryZombie.setPathPriority(PathNodeType.WATER, 0.0F);
arbitraryZombie.onGround = true;
BlockPos destination = properties.getClairvoyanceLocation();
WizardryPathFinder pathfinder = new WizardryPathFinder(arbitraryZombie.getNavigator().getNodeProcessor());
Path path = pathfinder.findPath(world, arbitraryZombie, destination, 256 * modifiers.get(WizardryItems.range_upgrade));
Path path = pathfinder.findPath(world, arbitraryZombie, location,
getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade));
if(path != null && path.getFinalPathPoint() != null){
@@ -83,9 +85,9 @@ public class Clairvoyance extends Spell {
int y = path.getFinalPathPoint().y;
int z = path.getFinalPathPoint().z;
if(x == destination.getX() && y == destination.getY() && z == destination.getZ()){
if(x == location.getX() && y == location.getY() && z == location.getZ()){
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
if(!world.isRemote && caster instanceof EntityPlayerMP){
WizardryPacketHandler.net.sendTo(new PacketClairvoyance.Message(path, modifiers.get(WizardryItems.duration_upgrade)),
@@ -107,13 +109,16 @@ public class Clairvoyance extends Spell {
}
// Fixes the problem with the sound not playing for the client of the caster.
if(world.isRemote) WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f);
if(world.isRemote) this.playSound(world, caster, ticksInUse, -1, modifiers);
return false;
}
public static void spawnPathPaticles(World world, Path path, float durationMultiplier){
// A bit annoying that we have to use the reference here but there's no easy way around it
float duration = Spells.clairvoyance.getProperty(DURATION).floatValue();
PathPoint point, nextPoint;
while(!path.isFinished()){
@@ -127,7 +132,7 @@ public class Clairvoyance extends Spell {
(nextPoint.x - point.x) / (float)PARTICLE_MOVEMENT_INTERVAL,
(nextPoint.y - point.y) / (float)PARTICLE_MOVEMENT_INTERVAL,
(nextPoint.z - point.z) / (float)PARTICLE_MOVEMENT_INTERVAL)
.time((int)(1800 * durationMultiplier)).clr(0, 1, 0.3f).spawn(world);
.time((int)(duration * durationMultiplier)).clr(0, 1, 0.3f).spawn(world);
path.incrementPathIndex();
path.incrementPathIndex();
@@ -136,7 +141,7 @@ public class Clairvoyance extends Spell {
point = path.getFinalPathPoint();
ParticleBuilder.create(Type.PATH).pos(point.x + 0.5, point.y + 0.5, point.z + 0.5)
.time((int)(1800 * durationMultiplier)).clr(1, 1, 1).spawn(world);
.time((int)(duration * durationMultiplier)).clr(1f, 1f, 1f).spawn(world);
}
@SubscribeEvent
@@ -145,17 +150,19 @@ public class Clairvoyance extends Spell {
if(event.getEntityPlayer().isSneaking()){
// The event now has an ItemStack, which greatly simplifies hand-related stuff.
ItemStack wand = event.getItemStack();
ItemStack stack = event.getItemStack();
if(wand.getItem() instanceof ItemWand && WandHelper.getCurrentSpell(wand) instanceof Clairvoyance){
if(stack.getItem() instanceof ISpellCastingItem
&& ((ISpellCastingItem)stack.getItem()).getCurrentSpell(stack) instanceof Clairvoyance){
WizardData properties = WizardData.get(event.getEntityPlayer());
WizardData data = WizardData.get(event.getEntityPlayer());
if(data != null){
if(properties != null){
// THIS is why BlockPos is a thing - in 1.7.10 this requires a clumsy switch statement.
BlockPos pos = event.getPos().offset(event.getFace());
properties.setClairvoyancePoint(pos, event.getWorld().provider.getDimension());
data.setVariable(LOCATION_KEY, pos);
data.setVariable(DIMENSION_KEY, event.getWorld().provider.getDimension());
if(!event.getWorld().isRemote){
event.getEntityPlayer().sendStatusMessage(
@@ -1,63 +1,58 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.tileentity.TileEntityTimer;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import java.util.List;
public class Cobwebs extends SpellRay {
private static final int BASE_DURATION = 400;
public Cobwebs(){
super("cobwebs", Tier.ADVANCED, Element.EARTH, SpellType.ATTACK, 30, 70, false, 12, SoundEvents.BLOCK_LAVA_EXTINGUISH);
this.ignoreEntities(true);
super("cobwebs", false, EnumAction.NONE);
this.ignoreLivingEntities(true);
addProperties(EFFECT_RADIUS, DURATION);
}
@Override public boolean doesSpellRequirePacket(){ return false; }
@Override public boolean requiresPacket(){ return false; }
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
boolean flag = false;
pos = pos.offset(side);
if(world.isAirBlock(pos)){
if(!world.isRemote){
world.setBlockState(pos, WizardryBlocks.vanishing_cobweb.getDefaultState());
if(world.getTileEntity(pos) instanceof TileEntityTimer){
((TileEntityTimer)world.getTileEntity(pos))
.setLifetime((int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)));
}
}
flag = true;
}
int blastUpgradeCount = (int)((modifiers.get(WizardryItems.blast_upgrade) - 1) / Constants.RANGE_INCREASE_PER_LEVEL + 0.5f);
for(EnumFacing facing : EnumFacing.values()){
float radius = getProperty(EFFECT_RADIUS).floatValue() + 0.73f * blastUpgradeCount;
BlockPos pos1 = pos.offset(facing);
List<BlockPos> sphere = WizardryUtilities.getBlockSphere(pos, radius * modifiers.get(WizardryItems.blast_upgrade));
for(BlockPos pos1 : sphere){
if(world.isAirBlock(pos1)){
if(!world.isRemote){
world.setBlockState(pos1, WizardryBlocks.vanishing_cobweb.getDefaultState());
if(world.getTileEntity(pos1) instanceof TileEntityTimer){
((TileEntityTimer)world.getTileEntity(pos1))
.setLifetime((int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)));
.setLifetime((int)(getProperty(DURATION).doubleValue()
* modifiers.get(WizardryItems.duration_upgrade)));
}
}
flag = true;
@@ -68,7 +63,7 @@ public class Cobwebs extends SpellRay {
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@@ -1,22 +1,28 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import com.google.common.collect.ImmutableMap;
import electroblob.wizardry.item.IConjuredItem;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagList;
import java.util.Map;
public class ConjureArmour extends SpellConjuration {
private static final Map<EntityEquipmentSlot, Item> SPECTRAL_ARMOUR_MAP = ImmutableMap.of(
EntityEquipmentSlot.HEAD, WizardryItems.spectral_helmet,
EntityEquipmentSlot.CHEST, WizardryItems.spectral_chestplate,
EntityEquipmentSlot.LEGS, WizardryItems.spectral_leggings,
EntityEquipmentSlot.FEET, WizardryItems.spectral_boots);
public ConjureArmour(){
super("conjure_armour", Tier.ADVANCED, Element.HEALING, SpellType.DEFENCE, 45, 50, null, WizardrySounds.SPELL_CONJURATION);
super("conjure_armour", null);
}
@Override
@@ -29,9 +35,9 @@ public class ConjureArmour extends SpellConjuration {
for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){
if(caster.getItemStackFromSlot(slot).isEmpty() &&
!WizardryUtilities.doesPlayerHaveItem(caster, WizardryItems.SPECTRAL_ARMOUR_MAP.get(slot))){
!WizardryUtilities.doesPlayerHaveItem(caster, SPECTRAL_ARMOUR_MAP.get(slot))){
armour = new ItemStack(WizardryItems.SPECTRAL_ARMOUR_MAP.get(slot));
armour = new ItemStack(SPECTRAL_ARMOUR_MAP.get(slot));
IConjuredItem.setDurationMultiplier(armour, modifiers.get(WizardryItems.duration_upgrade));
// Sets a blank "ench" tag to trick the renderer into showing the enchantment effect on the armour model
armour.getTagCompound().setTag("ench", new NBTTagList());
@@ -0,0 +1,79 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.tileentity.TileEntityTimer;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class ConjureBlock extends SpellRay {
private static final String BLOCK_LIFETIME = "block_lifetime";
public ConjureBlock(){
super("conjure_block", false, EnumAction.NONE);
this.ignoreLivingEntities(true);
addProperties(BLOCK_LIFETIME);
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(world.getBlockState(pos).getBlock() == WizardryBlocks.spectral_block){
if(!world.isRemote){
// Dispelling of blocks
world.setBlockToAir(pos);
}else{
ParticleBuilder.create(Type.FLASH).pos(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5).scale(3)
.clr(0.75f, 1, 0.85f).spawn(world);
}
return true;
}
pos = pos.offset(side);
if(world.isRemote){
ParticleBuilder.create(Type.FLASH).pos(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5).scale(3)
.clr(0.75f, 1, 0.85f).spawn(world);
}
if(WizardryUtilities.canBlockBeReplaced(world, pos)){
if(!world.isRemote){
world.setBlockState(pos, WizardryBlocks.spectral_block.getDefaultState());
if(world.getTileEntity(pos) instanceof TileEntityTimer){
((TileEntityTimer)world.getTileEntity(pos)).setLifetime((int)(getProperty(BLOCK_LIFETIME).floatValue()
* modifiers.get(WizardryItems.duration_upgrade)));
}
}
return true;
}
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
}
@@ -0,0 +1,54 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class Containment extends SpellRay {
public Containment(){
super("containment", false, EnumAction.NONE);
this.soundValues(1, 1, 0.2f);
addProperties(EFFECT_DURATION, EFFECT_STRENGTH);
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.containment,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@Override
protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){
ParticleBuilder.create(Type.PATH).pos(x, y, z).clr(0x7988cc).time(20 + world.rand.nextInt(8)).spawn(world);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(1f, 1f, 1f).spawn(world);
}
}
@@ -1,25 +1,36 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.potion.PotionEffect;
public class CureEffects extends SpellBuff {
public CureEffects(){
super("cure_effects", Tier.APPRENTICE, Element.HEALING, SpellType.DEFENCE, 25, 40, WizardrySounds.SPELL_HEAL, 0.8f, 0.8f, 1);
super("cure_effects", 0.8f, 0.8f, 1);
this.soundValues(0.7f, 1.2f, 0.4f);
}
@Override
protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){
if(!caster.getActivePotionEffects().isEmpty()){
caster.clearActivePotions();
return true;
ItemStack milk = new ItemStack(Items.MILK_BUCKET);
boolean flag = false;
for(PotionEffect effect : caster.getActivePotionEffects()){
// The PotionEffect version (as opposed to Potion) does not call cleanup callbacks
if(effect.isCurativeItem(milk)){
caster.removePotionEffect(effect.getPotion());
flag = true;
}
}
return flag;
}
return false;
@@ -0,0 +1,60 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class CurseOfEnfeeblement extends SpellRay {
public CurseOfEnfeeblement(){
super("curse_of_enfeeblement", false, EnumAction.NONE);
this.soundValues(1, 1.1f, 0.2f);
addProperties(EFFECT_STRENGTH);
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
// This will actually run out in the end, but only if you leave Minecraft running for 3.4 years
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.curse_of_enfeeblement,
Integer.MAX_VALUE, getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
// Reduce the target's health to its new max health if necessary
if(((EntityLivingBase)target).getHealth() > ((EntityLivingBase)target).getMaxHealth()){
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, MagicDamage.DamageType.WITHER),
((EntityLivingBase)target).getHealth() - ((EntityLivingBase)target).getMaxHealth());
}
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@Override
protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){
ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.2f, 0, 0.3f).spawn(world);
ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.1f, 0, 0).spawn(world);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(0.4f, 0, 0).spawn(world);
}
}
@@ -1,54 +1,80 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.util.IElementalDamage;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.data.IStoredVariable;
import electroblob.wizardry.data.Persistence;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.integration.DamageSafetyChecker;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
@Mod.EventBusSubscriber
public class CurseOfSoulbinding extends SpellRay {
public static final IStoredVariable<Set<UUID>> TARGETS_KEY = new IStoredVariable.StoredVariable<>("soulboundCreatures",
s -> NBTExtras.listToNBT(s, NBTUtil::createUUIDTag),
// For some reason gradle screams at me unless I explicitly declare the type of t here, despite IntelliJ being fine without it
(NBTTagList t) -> new HashSet<>(NBTExtras.NBTToList(t, NBTUtil::getUUIDFromTag)),
// Curse of soulbinding is lifted when the caster dies, but not when they switch dimensions.
Persistence.DIMENSION_CHANGE);
public CurseOfSoulbinding(){
super("curse_of_soulbinding", Tier.ADVANCED, Element.NECROMANCY, SpellType.ATTACK, 35, 100, false, 10, SoundEvents.ENTITY_WITHER_SPAWN);
super("curse_of_soulbinding", false, EnumAction.NONE);
this.soundValues(1, 1.1f, 0.2f);
WizardData.registerStoredVariables(TARGETS_KEY);
}
@Override public boolean canBeCastByNPCs() { return false; }
// You can't damage a dispenser so this would be nonsense!
@Override public boolean canBeCastByDispensers() { return false; }
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target) && caster instanceof EntityPlayer
&& WizardData.get((EntityPlayer)caster) != null){
// Return false if soulbinding failed (e.g. if the target is already soulbound)
if(!WizardData.get((EntityPlayer)caster).soulbind((EntityLivingBase)target)) return false;
if(WizardryUtilities.isLiving(target) && caster instanceof EntityPlayer){
WizardData data = WizardData.get((EntityPlayer)caster);
if(data != null){
// Return false if soulbinding failed (e.g. if the target is already soulbound)
if(getSoulboundCreatures(data).add(target.getUniqueID())){
// This will actually run out in the end, but only if you leave Minecraft running for 3.4 years
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.curse_of_soulbinding, Integer.MAX_VALUE));
}else{
return false;
}
}
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@@ -65,11 +91,41 @@ public class CurseOfSoulbinding extends SpellRay {
if(!event.getEntity().world.isRemote && event.getEntityLiving() instanceof EntityPlayer
&& !event.getSource().isUnblockable() && !(event.getSource() instanceof IElementalDamage
&& ((IElementalDamage)event.getSource()).isRetaliatory())){
WizardData data = WizardData.get((EntityPlayer)event.getEntityLiving());
EntityPlayer player = (EntityPlayer)event.getEntityLiving();
WizardData data = WizardData.get(player);
if(data != null){
data.damageAllSoulboundCreatures(event.getAmount(), event.getSource().getDamageType());
for(Iterator<UUID> iterator = getSoulboundCreatures(data).iterator(); iterator.hasNext();){
Entity entity = WizardryUtilities.getEntityByUUID(player.world, iterator.next());
if(entity == null) iterator.remove();
if(entity instanceof EntityLivingBase){
// Retaliatory effect
if(DamageSafetyChecker.attackEntitySafely(entity, MagicDamage.causeDirectMagicDamage(player,
MagicDamage.DamageType.MAGIC, true), event.getAmount(), event.getSource().getDamageType(),
DamageSource.MAGIC, false)){
// Sound only plays if the damage succeeds
entity.playSound(WizardrySounds.SPELL_CURSE_OF_SOULBINDING_RETALIATE, 1.0F, player.world.rand.nextFloat() * 0.2F + 1.0F);
}
}
}
}
}
}
public static Set<UUID> getSoulboundCreatures(WizardData data){
if(data.getVariable(TARGETS_KEY) == null){
Set<UUID> result = new HashSet<>();
data.setVariable(TARGETS_KEY, result);
return result;
}else return data.getVariable(TARGETS_KEY);
}
}
@@ -0,0 +1,55 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class CurseOfUndeath extends SpellRay {
public CurseOfUndeath(){
super("curse_of_undeath", false, EnumAction.NONE);
this.soundValues(1, 1.1f, 0.2f);
addProperties(EFFECT_STRENGTH);
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
// This will actually run out in the end, but only if you leave Minecraft running for 3.4 years
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.curse_of_undeath, Integer.MAX_VALUE,
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@Override
protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){
ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0x686c00).spawn(world);
ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0x251609).spawn(world);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(0xe6e592).spawn(world);
}
}
@@ -1,43 +1,43 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.construct.EntityDecay;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.SoundEvents;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class Decay extends SpellConstructRanged<EntityDecay> {
private static final int BASE_SPAWN_COUNT = 5;
public static final String DECAY_PATCHES_SPAWNED = "decay_patches_spawned";
public Decay(){
super("decay", Tier.ADVANCED, Element.NECROMANCY, SpellType.ATTACK, 50, 200, EntityDecay::new, 400, 12, SoundEvents.ENTITY_WITHER_SHOOT);
super("decay", EntityDecay::new, false);
this.soundValues(1, 1.1f, 0.1f);
this.floor(true);
this.overlap(true);
addProperties(DECAY_PATCHES_SPAWNED, EFFECT_DURATION);
}
@Override
protected boolean spawnConstruct(World world, double x, double y, double z, EntityLivingBase caster, SpellModifiers modifiers){
protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){
if(world.getBlockState(new BlockPos(x, y, z)).isNormalCube()) return false;
super.spawnConstruct(world, x, y, z, caster, modifiers);
int quantity = (int)(BASE_SPAWN_COUNT * modifiers.get(WizardryItems.blast_upgrade));
int horizontalRange = (int)(2 * modifiers.get(WizardryItems.blast_upgrade));
super.spawnConstruct(world, x, y, z, side, caster, modifiers);
float decayCount = getProperty(DECAY_PATCHES_SPAWNED).floatValue();
int quantity = (int)(decayCount * modifiers.get(WizardryItems.blast_upgrade));
// If there are more decay patches, they need more space to spawn in
int horizontalRange = (int)(0.4 * decayCount * modifiers.get(WizardryItems.blast_upgrade));
int verticalRange = (int)(6 * modifiers.get(WizardryItems.blast_upgrade));
for(int i=0; i<quantity; i++){
BlockPos pos = WizardryUtilities.findNearbyFloorSpace(caster, horizontalRange, verticalRange);
if(pos == null) break;
super.spawnConstruct(world, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, caster, modifiers);
super.spawnConstruct(world, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, side, caster, modifiers);
}
return true;
@@ -1,10 +1,6 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.living.EntityDecoy;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLiving;
@@ -16,8 +12,13 @@ import net.minecraft.world.World;
public class Decoy extends Spell {
public static final String DECOY_LIFETIME = "decoy_lifetime";
public static final String MOB_TRICK_CHANCE = "mob_trick_chance";
public Decoy(){
super("decoy", Tier.ADVANCED, Element.SORCERY, SpellType.UTILITY, 40, 200, EnumAction.BOW, false);
super("decoy", EnumAction.BOW, false);
this.soundValues(1, 0.9f, 0.2f);
addProperties(DECOY_LIFETIME, MOB_TRICK_CHANCE);
}
@Override public boolean canBeCastByNPCs(){ return true; }
@@ -28,7 +29,7 @@ public class Decoy extends Spell {
// Uses the synchronised entity id to ensure it is consistent on client and server, but not always the same.
double splitSpeed = caster.getEntityId() % 2 == 0 ? 0.3 : -0.3;
spawnDecoy(world, caster, modifiers, splitSpeed);
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0F, 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F));
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -38,7 +39,7 @@ public class Decoy extends Spell {
// Determines whether the caster moves left and the decoy moves right, or vice versa.
double splitSpeed = world.rand.nextBoolean() ? 0.3 : -0.3;
spawnDecoy(world, caster, modifiers, splitSpeed);
caster.playSound(WizardrySounds.SPELL_CONJURATION, 1.0F, 0.4F / (world.rand.nextFloat() * 0.4F + 0.8F));
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -47,7 +48,7 @@ public class Decoy extends Spell {
if(!world.isRemote){
EntityDecoy decoy = new EntityDecoy(world);
decoy.setCaster(caster);
decoy.setLifetime(600);
decoy.setLifetime(getProperty(DECOY_LIFETIME).intValue());
decoy.setLocationAndAngles(caster.posX, caster.posY, caster.posZ, caster.rotationYaw, caster.rotationPitch);
decoy.addVelocity(-caster.getLookVec().z * splitSpeed, 0, caster.getLookVec().x * splitSpeed);
// Ignores the show names setting, since this would allow a player to easily detect a decoy
@@ -58,9 +59,11 @@ public class Decoy extends Spell {
// Tricks any mobs that are targeting the caster into targeting the decoy instead.
for(EntityLiving creature : WizardryUtilities.getEntitiesWithinRadius(16, caster.posX, caster.posY,
caster.posZ, world, EntityLiving.class)){
// More likely to trick mobs the higher the damage multiplier. Starts off at 50%.
if(world.rand.nextInt((int)(6 * modifiers.get(SpellModifiers.POTENCY))) < 3){
if(creature.getAttackTarget() == caster) creature.setAttackTarget(decoy);
// More likely to trick mobs the higher the damage multiplier
// The default base value is 0.5, so modifiers of 2 or more will guarantee mobs are tricked
if(creature.getAttackTarget() == caster && world.rand.nextFloat()
< getProperty(MOB_TRICK_CHANCE).floatValue() * modifiers.get(SpellModifiers.POTENCY)){
creature.setAttackTarget(decoy);
}
}
}
@@ -1,10 +1,5 @@
package electroblob.wizardry.spell;
import java.util.List;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
@@ -12,40 +7,45 @@ import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import java.util.List;
public class Detonate extends SpellRay {
private static final double BASE_RADIUS = 3;
// More descriptive/accurate than just "damage"
public static final String MAX_DAMAGE = "max_damage";
public Detonate(){
super("detonate", Tier.ADVANCED, Element.FIRE, SpellType.ATTACK, 45, 50, false, 16, SoundEvents.ENTITY_GENERIC_EXPLODE);
super("detonate", false, EnumAction.NONE);
this.soundValues(4, 0.7f, 0.14f);
this.ignoreEntities(true);
this.ignoreLivingEntities(true);
addProperties(MAX_DAMAGE, BLAST_RADIUS);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(!world.isRemote){
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(
BASE_RADIUS * modifiers.get(WizardryItems.blast_upgrade), pos.getX(), pos.getY(), pos.getZ(), world);
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(getProperty(BLAST_RADIUS).doubleValue()
* modifiers.get(WizardryItems.blast_upgrade), pos.getX(), pos.getY(), pos.getZ(), world);
for(EntityLivingBase target : targets){
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.BLAST),
// Damage decreases with distance but cannot be less than 0, naturally.
Math.max(12.0f - (float)target.getDistance(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5)
* 4, 0) * modifiers.get(SpellModifiers.POTENCY));
Math.max(getProperty(MAX_DAMAGE).floatValue() - (float)target.getDistance(pos.getX() + 0.5,
pos.getY() + 0.5, pos.getZ() + 0.5) * 4, 0) * modifiers.get(SpellModifiers.POTENCY));
}
}else{
@@ -56,7 +56,7 @@ public class Detonate extends SpellRay {
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@@ -0,0 +1,97 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.entity.projectile.EntityEmber;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class Disintegration extends SpellRay {
public static final String EMBER_COUNT = "ember_count";
public static final String EMBER_LIFETIME = "ember_lifetime";
public Disintegration(){
super("disintegration", false, EnumAction.NONE);
addProperties(DAMAGE, BURN_DURATION, EMBER_LIFETIME, EMBER_COUNT);
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(MagicDamage.isEntityImmune(DamageType.FIRE, target)){
if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage(
new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true);
}else{
target.setFire((int)(getProperty(BURN_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
WizardryUtilities.attackEntityWithoutKnockback(target, caster == null ? DamageSource.MAGIC :
MagicDamage.causeDirectMagicDamage(caster, DamageType.FIRE),
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
if(!world.isRemote && target instanceof EntityLivingBase && ((EntityLivingBase)target).getHealth() <= 0){
spawnEmbers(world, caster, target, getProperty(EMBER_COUNT).intValue());
}
}
return true;
}
public static void spawnEmbers(World world, EntityLivingBase caster, Entity target, int count){
for(int i = 0; i < count; i++){
EntityEmber ember = new EntityEmber(world, caster);
double x = (world.rand.nextDouble() - 0.5) * target.width;
double y = world.rand.nextDouble() * target.height;
double z = (world.rand.nextDouble() - 0.5) * target.width;
ember.setPosition(target.posX + x, target.posY + y, target.posZ + z);
float speed = 0.2f;
ember.setVelocity(x * speed, y * 0.5f * speed, z * speed);
world.spawnEntity(ember);
}
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(world.isRemote){
for(int i = 0; i < 8; i++){
world.spawnParticle(EnumParticleTypes.LAVA, hit.x, hit.y, hit.z, 0, 0, 0);
}
if(world.getBlockState(pos).getMaterial().isSolid()){
Vec3d vec = hit.add(new Vec3d(side.getDirectionVec()).scale(WizardryUtilities.ANTI_Z_FIGHTING_OFFSET));
ParticleBuilder.create(Type.SCORCH).pos(vec).face(side).clr(1, 0.2f, 0).spawn(world);
}
}
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@Override
protected void spawnParticleRay(World world, Vec3d origin, Vec3d direction, EntityLivingBase caster, double distance){
Vec3d endpoint = origin.add(direction.scale(distance));
ParticleBuilder.create(Type.BEAM).clr(1, 0.4f, 0).fade(1, 0.1f, 0).time(4).pos(origin).target(endpoint).spawn(world);
}
}
@@ -0,0 +1,149 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockCrystalOre;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.RelativeFacing;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.BlockOre;
import net.minecraft.block.BlockRedstoneOre;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.FurnaceRecipes;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class Divination extends Spell {
private static final float NUDGE_SPEED = 0.2f;
public Divination(){
super("divination", EnumAction.NONE, false);
addProperties(RANGE);
}
/** A set of constants representing the different 'signal strengths' for the divination spell. In practical
* terms, this means different chat readouts and effects. */
protected enum Strength {
NOTHING("nothing", -1),
WEAK("weak", 0),
MODERATE("moderate", 0.25f),
STRONG("strong", 0.5f),
VERY_STRONG("very_strong", 0.75f);
String key;
float minWeight;
Strength(String key, float minWeight){
this.key = key;
this.minWeight = minWeight;
}
protected static Strength forWeight(float weight){
return Arrays.stream(values()).filter(s -> s.minWeight < weight).max(Comparator.naturalOrder()).orElse(NOTHING);
}
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade);
List<BlockPos> sphere = WizardryUtilities.getBlockSphere(caster.getPosition(), range);
sphere.removeIf(b -> !(world.getBlockState(b).getBlock() instanceof BlockOre
|| world.getBlockState(b).getBlock() instanceof BlockRedstoneOre
|| world.getBlockState(b).getBlock() instanceof BlockCrystalOre
|| Arrays.asList(Wizardry.settings.divinationOreWhitelist)
.contains(world.getBlockState(b).getBlock().getRegistryName())));
Strength strength = Strength.NOTHING;
EnumFacing direction = EnumFacing.DOWN; // Doesn't matter what this is
if(!sphere.isEmpty()){
// Sorts the positions based on weight (see below), in ascending order
sphere.sort(Comparator.comparingDouble(b -> calculateWeight(world, caster, b, range, modifiers)));
// The weights are sorted in ascending order, so this must be the largest
BlockPos target = sphere.get(sphere.size() - 1);
direction = EnumFacing.getFacingFromVector((float)(target.getX() + 0.5 - caster.posX),
(float)(target.getY() + 0.5 - (caster.getEntityBoundingBox().minY + caster.getEyeHeight())),
(float)(target.getZ() + 0.5 - caster.posZ));
if(world.isRemote) ParticleBuilder.create(ParticleBuilder.Type.SPARKLE).pos(target.getX() + 0.5,
target.getY() + 1.5, target.getZ() + 0.5).spawn(world);
strength = Strength.forWeight(calculateWeight(world, caster, target, range, modifiers));
}
if(!world.isRemote){
caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + "."
+ strength.key, new TextComponentTranslation("spell." + this.getUnlocalisedName() + "."
+ RelativeFacing.relativise(direction, caster).name)), false);
}else{
switch(strength){
case NOTHING: break;
case WEAK: break;
case MODERATE:
spawnHintParticles(world, caster, 3, direction);
break;
case STRONG:
spawnHintParticles(world, caster, 8, direction);
break;
case VERY_STRONG:
spawnHintParticles(world, caster, 12, direction);
caster.addVelocity(direction.getXOffset() * NUDGE_SPEED, direction.getYOffset() * NUDGE_SPEED,
direction.getZOffset() * NUDGE_SPEED);
break;
}
}
return true;
}
private static void spawnHintParticles(World world, Entity caster, int count, EnumFacing direction){
Vec3d vec = new Vec3d(caster.getPosition().offset(EnumFacing.UP).offset(direction, 2)).add(0.5, 0.5, 0.5);
for(int i=0; i<count; i++){
ParticleBuilder.create(ParticleBuilder.Type.FLASH, world.rand, vec.x, vec.y, vec.z, 0.7, false)
.time(20 + world.rand.nextInt(5)).clr(0.6f + world.rand.nextFloat() * 0.4f,
0.6f + world.rand.nextFloat() * 0.4f, 0.6f + world.rand.nextFloat() * 0.4f).scale(0.3f).spawn(world);
}
}
protected static float calculateWeight(World world, EntityPlayer caster, BlockPos pos, double range, SpellModifiers modifiers){
Block block = world.getBlockState(pos).getBlock();
// On a non-sorcery wand, the value of the ore has no effect on its weight
float weightModifier = modifiers.get(SpellModifiers.POTENCY) - 1;
// xp is a decent way of determining the 'value' of a block
// There is a degree of randomness associated with it though...
float xp = block.getExpDrop(world.getBlockState(pos), world, pos, 0);
// For some reason smelting gives a lot less than mining, hence the multiplying by 4
if(xp == 0) xp = 4 * FurnaceRecipes.instance().getSmeltingExperience(new ItemStack(block));
// By my (rather rough) calculations, this should mean that using a master sorcerer wand, an iron ore block
// and a diamond ore block just under twice as far away as the iron ore should have about the same weight
return (float)(1 - caster.getDistance(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5)/range + 0.2 * weightModifier * xp);
}
}
@@ -1,29 +1,27 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntitySmallFireball;
import net.minecraft.init.SoundEvents;
import net.minecraft.entity.projectile.EntityDragonFireball;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class Fireball extends Spell {
public class DragonFireball extends Spell {
public Fireball(){
super("fireball", Tier.APPRENTICE, Element.FIRE, SpellType.ATTACK, 10, 15, EnumAction.NONE, false);
// Does 2.5 hearts of damage and 5 seconds of fire, for reference.
public static final String ACCELERATION = "acceleration";
public DragonFireball(){
super("dragon_fireball", EnumAction.NONE, false);
addProperties(ACCELERATION);
}
@Override
public boolean doesSpellRequirePacket(){
public boolean requiresPacket(){
return false;
}
@@ -33,15 +31,21 @@ public class Fireball extends Spell {
Vec3d look = caster.getLookVec();
if(!world.isRemote){
EntitySmallFireball fireball = new EntitySmallFireball(world, caster, 1, 1, 1);
EntityDragonFireball fireball = new EntityDragonFireball(world, caster, 1, 1, 1);
fireball.setPosition(caster.posX + look.x, caster.posY + look.y + 1.3, caster.posZ + look.z);
fireball.accelerationX = look.x * 0.1;
fireball.accelerationY = look.y * 0.1;
fireball.accelerationZ = look.z * 0.1;
double acceleration = getProperty(ACCELERATION).doubleValue() * modifiers.get(WizardryItems.range_upgrade);
fireball.accelerationX = look.x * acceleration;
fireball.accelerationY = look.y * acceleration;
fireball.accelerationZ = look.z * acceleration;
world.spawnEntity(fireball);
}
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1);
this.playSound(world, caster, ticksInUse, -1, modifiers);
caster.swingArm(hand);
return true;
}
@@ -54,23 +58,25 @@ public class Fireball extends Spell {
if(!world.isRemote){
EntitySmallFireball fireball = new EntitySmallFireball(world, caster, 1, 1, 1);
EntityDragonFireball fireball = new EntityDragonFireball(world, caster, 1, 1, 1);
double dx = target.posX - caster.posX;
double dy = target.getEntityBoundingBox().minY + (double)(target.height / 2.0F)
- (caster.posY + (double)(caster.height / 2.0F));
double dz = target.posZ - caster.posZ;
fireball.accelerationX = dx / caster.getDistance(target) * 0.1;
fireball.accelerationY = dy / caster.getDistance(target) * 0.1;
fireball.accelerationZ = dz / caster.getDistance(target) * 0.1;
double acceleration = getProperty(ACCELERATION).doubleValue();
fireball.accelerationX = dx / caster.getDistance(target) * acceleration;
fireball.accelerationY = dy / caster.getDistance(target) * acceleration;
fireball.accelerationZ = dz / caster.getDistance(target) * acceleration;
fireball.setPosition(caster.posX, caster.posY + caster.getEyeHeight(), caster.posZ);
world.spawnEntity(fireball);
}
caster.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1);
this.playSound(world, caster, ticksInUse, -1, modifiers);
caster.swingArm(hand);
return true;
}
@@ -1,41 +1,43 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.construct.EntityEarthquake;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.world.World;
import electroblob.wizardry.entity.construct.EntityEarthquake;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.world.World;
public class Earthquake extends SpellConstruct<EntityEarthquake> {
public class Earthquake extends SpellConstruct<EntityEarthquake> {
public static final String SPREAD_SPEED = "spread_speed";
public Earthquake(){
super("earthquake", Tier.MASTER, Element.EARTH, SpellType.ATTACK, 75, 250, EnumAction.NONE, EntityEarthquake::new, -1, WizardrySounds.SPELL_EARTHQUAKE);
super("earthquake", EnumAction.NONE, EntityEarthquake::new, true);
this.soundValues(2, 1, 0);
this.overlap(true);
this.floor(true);
addProperties(EFFECT_RADIUS, SPREAD_SPEED);
}
// This one spawns particles
@Override public boolean doesSpellRequirePacket(){ return true; }
@Override public boolean requiresPacket(){ return true; }
@Override
protected void addConstructExtras(EntityEarthquake construct, EntityLivingBase caster, SpellModifiers modifiers){
construct.lifetime = (int)(20 * modifiers.get(WizardryItems.blast_upgrade));
protected void addConstructExtras(EntityEarthquake construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){
// Calculates the lifetime based on the base radius and spread speed
// Also overwrites the -1 lifetime set due to permanent being true
construct.lifetime = (int)(getProperty(EFFECT_RADIUS).floatValue()/getProperty(SPREAD_SPEED).floatValue()
* modifiers.get(WizardryItems.blast_upgrade));
}
@Override
protected boolean spawnConstruct(World world, double x, double y, double z, EntityLivingBase caster, SpellModifiers modifiers){
protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){
// TODO: Couldn't this be moved to EntityEarthquake?
if(world.isRemote){
world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, caster.posX,
@@ -49,15 +51,13 @@ public class Earthquake extends SpellConstruct<EntityEarthquake> {
particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble();
IBlockState block = WizardryUtilities.getBlockEntityIsStandingOn(caster);
if(block != null){
world.spawnParticle(EnumParticleTypes.BLOCK_DUST, particleX, caster.getEntityBoundingBox().minY,
particleZ, particleX - caster.posX, 0, particleZ - caster.posZ,
Block.getStateId(block));
}
world.spawnParticle(EnumParticleTypes.BLOCK_DUST, particleX, caster.getEntityBoundingBox().minY,
particleZ, particleX - caster.posX, 0, particleZ - caster.posZ,
Block.getStateId(block));
}
}
return super.spawnConstruct(world, x, y, z, caster, modifiers);
return super.spawnConstruct(world, x, y, z, side, caster, modifiers);
}
}
@@ -0,0 +1,81 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.List;
@Mod.EventBusSubscriber
public class EmpoweringPresence extends Spell {
public EmpoweringPresence(){
super("empowering_presence", EnumAction.BOW, false);
addProperties(EFFECT_RADIUS, EFFECT_DURATION, EFFECT_STRENGTH);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
List<EntityPlayer> targets = WizardryUtilities.getEntitiesWithinRadius(getProperty(EFFECT_RADIUS).doubleValue()
* modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world, EntityPlayer.class);
for(EntityPlayer target : targets){
if(AllyDesignationSystem.isPlayerAlly(caster, target) || target == caster){
int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY));
target.addPotionEffect(new PotionEffect(WizardryPotions.empowerment,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier));
}
}
if(world.isRemote){
for(int i = 0; i < 50 * modifiers.get(WizardryItems.blast_upgrade); i++){
double radius = (1 + world.rand.nextDouble() * 4) * modifiers.get(WizardryItems.blast_upgrade);
float angle = world.rand.nextFloat() * (float)Math.PI * 2;
double x = caster.posX + radius * MathHelper.cos(angle);
double y = caster.getEntityBoundingBox().minY;
double z = caster.posZ + radius * MathHelper.sin(angle);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.03, 0).time(50).clr(0.5f, 0.4f, 0.75f).spawn(world);
}
}
//WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1, 1 + 0.2f * world.rand.nextFloat());
return true;
}
@SubscribeEvent(priority = EventPriority.LOW) // Doesn't really matter but there's no point processing it if casting is blocked
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
// Empowerment stacks extra potency on top of the existing potency.
if(event.getCaster() != null && event.getCaster().isPotionActive(WizardryPotions.empowerment)){
float potency = 1 + Constants.EMPOWERMENT_POTENCY_PER_LEVEL
* (event.getCaster().getActivePotionEffect(WizardryPotions.empowerment).getAmplifier() + 1);
event.getModifiers().set(SpellModifiers.POTENCY,
event.getModifiers().get(SpellModifiers.POTENCY) * potency, true);
}
}
}
@@ -1,8 +1,5 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.construct.EntityBubble;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.MagicDamage;
@@ -13,21 +10,25 @@ import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class Entrapment extends SpellRay {
public static final String DAMAGE_INTERVAL = "damage_interval";
public Entrapment(){
super("entrapment", Tier.ADVANCED, Element.NECROMANCY, SpellType.ATTACK, 35, 75, false, 10, SoundEvents.ENTITY_WITHER_SHOOT);
super("entrapment", false, EnumAction.NONE);
this.soundValues(1, 0.85f, 0.3f);
addProperties(EFFECT_DURATION, DAMAGE_INTERVAL);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
@@ -38,7 +39,7 @@ public class Entrapment extends SpellRay {
EntityBubble bubble = new EntityBubble(world);
bubble.setPosition(target.posX, target.posY, target.posZ);
bubble.setCaster(caster);
bubble.lifetime = ((int)(200 * modifiers.get(WizardryItems.duration_upgrade)));
bubble.lifetime = ((int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
bubble.isDarkOrb = true;
bubble.damageMultiplier = modifiers.get(SpellModifiers.POTENCY);
@@ -51,12 +52,12 @@ public class Entrapment extends SpellRay {
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@@ -0,0 +1,48 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class Evade extends Spell {
private static final String EVADE_VELOCITY = "evade_velocity";
private static final float UPWARD_VELOCITY = 0.25f;
public Evade(){
super("evade", EnumAction.NONE, false);
addProperties(EVADE_VELOCITY);
}
@Override
public boolean requiresPacket(){
return false;
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
Vec3d look = caster.getLookVec();
// We want a horizontal only vector
look = look.subtract(0, look.y, 0).normalize();
Vec3d evadeDirection;
if(caster.moveStrafing == 0){
// If the caster isn't strafing, pick a random direction
evadeDirection = look.rotateYaw(world.rand.nextBoolean() ? (float)Math.PI/2f : (float)-Math.PI/2f);
}else{
// Otherwise, evade always moves whichever direction the caster was already strafing
evadeDirection = look.rotateYaw(Math.signum(caster.moveStrafing) * (float)Math.PI/2f);
}
evadeDirection = evadeDirection.scale(getProperty(EVADE_VELOCITY).floatValue() * modifiers.get(SpellModifiers.POTENCY));
caster.addVelocity(evadeDirection.x, UPWARD_VELOCITY, evadeDirection.z);
return true;
}
}
@@ -0,0 +1,95 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class FireBreath extends SpellRay {
public FireBreath(){
super("fire_breath", true, EnumAction.NONE);
this.particleVelocity(1);
this.particleJitter(0.3);
this.particleSpacing(0.25);
addProperties(DAMAGE, BURN_DURATION);
}
@Override
protected SoundEvent[] createSounds(){
return this.createContinuousSpellSounds();
}
@Override
protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, entity, ticksInUse);
}
@Override
protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, x, y, z, ticksInUse, duration);
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
// Fire can damage armour stands
if(target instanceof EntityLivingBase){
if(MagicDamage.isEntityImmune(DamageType.FIRE, target)){
if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) ((EntityPlayer)caster)
.sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(),
this.getNameForTranslationFormatted()), true);
// This now only damages in line with the maxHurtResistantTime. Some mods don't play nicely and fiddle
// with this mechanic for their own purposes, so this line makes sure that doesn't affect wizardry.
}else if(ticksInUse % ((EntityLivingBase)target).maxHurtResistantTime == 1){
target.setFire((int)(getProperty(BURN_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
WizardryUtilities.attackEntityWithoutKnockback(target,
MagicDamage.causeDirectMagicDamage(caster, DamageType.FIRE),
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
}
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(!WizardryUtilities.canDamageBlocks(caster, world)) return false;
pos = pos.offset(side);
if(world.isAirBlock(pos)){
if(!world.isRemote) world.setBlockState(pos, Blocks.FIRE.getDefaultState());
return true;
}
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@Override
protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){
ParticleBuilder.create(Type.MAGIC_FIRE).pos(x, y, z).vel(vx, vy, vz).scale(2 + world.rand.nextFloat()).collide(true).spawn(world);
ParticleBuilder.create(Type.MAGIC_FIRE).pos(x, y, z).vel(vx, vy, vz).scale(2 + world.rand.nextFloat()).collide(true).spawn(world);
}
}
@@ -1,110 +0,0 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.SoundEvents;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class Firestorm extends SpellRay {
private static final float BASE_DAMAGE = 6;
/** The base duration for which entities are set on fire by this spell. */
private static final int BASE_DURATION = 10;
public Firestorm(){
super("firestorm", Tier.MASTER, Element.FIRE, SpellType.ATTACK, 15, 0, true, 10, null);
this.particleVelocity(1);
this.particleDither(0.3);
this.particleSpacing(0.25);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
// Temporary solution until I implement a better continuous sound system
boolean flag = super.cast(world, caster, hand, ticksInUse, modifiers);
if(flag){
if(ticksInUse % 16 == 0){
if(ticksInUse == 0) WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1);
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_FIRE, 0.5f, 1.0f);
}
}
return flag;
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){
boolean flag = super.cast(world, caster, hand, ticksInUse, target, modifiers);
if(flag){
if(ticksInUse % 16 == 0){
if(ticksInUse == 0) caster.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1);
caster.playSound(WizardrySounds.SPELL_LOOP_FIRE, 0.5f, 1.0f);
}
}
return flag;
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
// Fire can damage armour stands
if(target instanceof EntityLivingBase){
if(MagicDamage.isEntityImmune(DamageType.FIRE, target)){
if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) ((EntityPlayer)caster)
.sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(),
this.getNameForTranslationFormatted()), true);
}else{
target.setFire((int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)));
WizardryUtilities.attackEntityWithoutKnockback(target,
MagicDamage.causeDirectMagicDamage(caster, DamageType.FIRE),
BASE_DAMAGE * modifiers.get(SpellModifiers.POTENCY));
}
return true;
}
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
pos = pos.offset(side);
if(world.isAirBlock(pos)){
if(!world.isRemote) world.setBlockState(pos, Blocks.FIRE.getDefaultState());
return true;
}
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
return true;
}
@Override
protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){
ParticleBuilder.create(Type.MAGIC_FIRE).pos(x, y, z).vel(vx, vy, vz).scale(3 + world.rand.nextFloat()).spawn(world);
ParticleBuilder.create(Type.MAGIC_FIRE).pos(x, y, z).vel(vx, vy, vz).scale(3 + world.rand.nextFloat()).spawn(world);
}
}
@@ -1,10 +1,6 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
@@ -12,55 +8,45 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class FlameRay extends SpellRay {
private static final float BASE_DAMAGE = 3;
/** The base duration for which entities are set on fire by this spell. */
private static final int BASE_DURATION = 10;
public FlameRay(){
super("flame_ray", Tier.APPRENTICE, Element.FIRE, SpellType.ATTACK, 5, 0, true, 10, null);
super("flame_ray", true, EnumAction.NONE);
this.particleVelocity(1);
this.particleSpacing(0.5);
addProperties(DAMAGE, BURN_DURATION);
this.soundValues(1.5f, 1, 0);
}
// The following three methods serve as a good example of how to implement continuous spell sounds (hint: it's easy)
@Override
protected SoundEvent[] createSounds(){
return this.createContinuousSpellSounds();
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
// TODO: Temporary solution until I implement a better continuous sound system
boolean flag = super.cast(world, caster, hand, ticksInUse, modifiers);
if(flag){
if(ticksInUse % 16 == 0){
if(ticksInUse == 0) WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1);
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_FIRE, 0.5f, 1.0f);
}
}
return flag;
protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, entity, ticksInUse);
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){
boolean flag = super.cast(world, caster, hand, ticksInUse, target, modifiers);
if(flag){
if(ticksInUse % 16 == 0){
if(ticksInUse == 0) caster.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1);
caster.playSound(WizardrySounds.SPELL_LOOP_FIRE, 0.5f, 1.0f);
}
}
return flag;
protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, x, y, z, ticksInUse, duration);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
// Fire can damage armour stands
if(target instanceof EntityLivingBase){
@@ -68,33 +54,33 @@ public class FlameRay extends SpellRay {
if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) ((EntityPlayer)caster)
.sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(),
this.getNameForTranslationFormatted()), true);
}else{
target.setFire((int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)));
// This now only damages in line with the maxHurtResistantTime. Some mods don't play nicely and fiddle
// with this mechanic for their own purposes, so this line makes sure that doesn't affect wizardry.
}else if(ticksInUse % ((EntityLivingBase)target).maxHurtResistantTime == 1){
target.setFire((int)(getProperty(BURN_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
WizardryUtilities.attackEntityWithoutKnockback(target,
MagicDamage.causeDirectMagicDamage(caster, DamageType.FIRE),
BASE_DAMAGE * modifiers.get(SpellModifiers.POTENCY));
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
}
return true;
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@Override
protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){
ParticleBuilder.create(Type.MAGIC_FIRE).pos(x, y, z).vel(vx, vy, vz).spawn(world);
ParticleBuilder.create(Type.MAGIC_FIRE).pos(x, y, z).vel(vx, vy, vz).spawn(world);
ParticleBuilder.create(Type.MAGIC_FIRE).pos(x, y, z).vel(vx, vy, vz).collide(true).spawn(world);
ParticleBuilder.create(Type.MAGIC_FIRE).pos(x, y, z).vel(vx, vy, vz).collide(true).spawn(world);
}
}
@@ -1,19 +1,16 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.SoundEvents;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.world.World;
public class FlamingAxe extends SpellConjuration {
public FlamingAxe(){
super("flaming_axe", Tier.ADVANCED, Element.FIRE, SpellType.UTILITY, 45, 50, WizardryItems.flaming_axe, SoundEvents.ENTITY_BLAZE_SHOOT);
super("flaming_axe", WizardryItems.flaming_axe);
addProperties(DAMAGE, BURN_DURATION);
}
@Override
@@ -1,13 +1,9 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.registry.WizardryEnchantments;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
@@ -15,16 +11,15 @@ import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraft.util.EnumHand;
import net.minecraft.world.World;
public class FlamingWeapon extends Spell {
public FlamingWeapon(){
super("flaming_weapon", Tier.ADVANCED, Element.FIRE, SpellType.UTILITY, 35, 70, EnumAction.BOW, false);
super("flaming_weapon", EnumAction.BOW, false);
addProperties(EFFECT_DURATION);
}
@Override
@@ -36,17 +31,17 @@ public class FlamingWeapon extends Spell {
for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(caster)){
if((stack.getItem() instanceof ItemSword || stack.getItem() instanceof ItemBow)
if((ImbueWeapon.isSword(stack.getItem()) || ImbueWeapon.isBow(stack.getItem()))
&& !EnchantmentHelper.getEnchantments(stack).containsKey(WizardryEnchantments.flaming_weapon)){
// The enchantment level as determined by the damage multiplier. The + 0.5f is so that
// weird float processing doesn't incorrectly round it down.
stack.addEnchantment(WizardryEnchantments.flaming_weapon,
modifiers.get(SpellModifiers.POTENCY) == 1.0f ? 1
: (int)((modifiers.get(SpellModifiers.POTENCY) - 1.0f)
/ Constants.DAMAGE_INCREASE_PER_TIER + 0.5f));
/ Constants.POTENCY_INCREASE_PER_TIER + 0.5f));
WizardData.get(caster).setImbuementDuration(WizardryEnchantments.flaming_weapon,
(int)(900 * modifiers.get(WizardryItems.duration_upgrade)));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
if(world.isRemote){
for(int i=0; i<10; i++){
@@ -57,7 +52,7 @@ public class FlamingWeapon extends Spell {
}
}
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -1,40 +1,47 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.world.World;
public class Flight extends Spell {
public static final String SPEED = "speed";
public static final String ACCELERATION = "acceleration";
private static final double Y_NUDGE_ACCELERATION = 0.075;
public Flight(){
super("flight", Tier.MASTER, Element.EARTH, SpellType.UTILITY, 10, 0, EnumAction.NONE, true);
super("flight", EnumAction.NONE, true);
addProperties(SPEED, ACCELERATION);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
if(!caster.isInWater() && !caster.isElytraFlying()){
if(!caster.isInWater() && !caster.isInLava() && !caster.isElytraFlying()){
float speed = getProperty(SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY);
float acceleration = getProperty(ACCELERATION).floatValue() * modifiers.get(SpellModifiers.POTENCY);
// The division thingy checks if the look direction is the opposite way to the velocity. If this is the
// case then the velocity should be added regardless of the player's current speed.
if((Math.abs(caster.motionX) < 0.6 || caster.motionX / caster.getLookVec().x < 0)
&& (Math.abs(caster.motionZ) < 0.6 || caster.motionZ / caster.getLookVec().z < 0)){
caster.addVelocity(caster.getLookVec().x / 20, 0, caster.getLookVec().z / 20);
if((Math.abs(caster.motionX) < speed || caster.motionX / caster.getLookVec().x < 0)
&& (Math.abs(caster.motionZ) < speed || caster.motionZ / caster.getLookVec().z < 0)){
caster.addVelocity(caster.getLookVec().x * acceleration, 0, caster.getLookVec().z * acceleration);
}
// y velocity is handled separately to stop the player from falling from the sky when they reach maximum
// horizontal speed.
if(Math.abs(caster.motionY) < 0.6 || caster.motionY / caster.getLookVec().y < 0){
caster.motionY += caster.getLookVec().y / 20 + 0.075;
if(Math.abs(caster.motionY) < speed || caster.motionY / caster.getLookVec().y < 0){
caster.motionY += caster.getLookVec().y * acceleration + Y_NUDGE_ACCELERATION;
}
caster.fallDistance = 0.0f;
if(!Wizardry.settings.replaceVanillaFallDamage) caster.fallDistance = 0.0f;
}
if(world.isRemote){
@@ -45,12 +52,10 @@ public class Flight extends Spell {
x = caster.posX - 1 + world.rand.nextDouble() * 2;
y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble();
z = caster.posZ - 1 + world.rand.nextDouble() * 2;
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, -0.1, 0).time(15).clr(1, 1, 1).spawn(world);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, -0.1, 0).time(15).clr(1f, 1f, 1f).spawn(world);
}
if(ticksInUse % 24 == 0){
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERDRAGON_FLAP, 0.5F, 1.0f);
}
if(ticksInUse % 24 == 0) playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -1,13 +1,9 @@
package electroblob.wizardry.spell;
import java.util.List;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
@@ -16,50 +12,67 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.List;
public class FontOfMana extends Spell {
public FontOfMana(){
super("font_of_mana", Tier.MASTER, Element.HEALING, SpellType.UTILITY, 100, 250, EnumAction.BOW, false);
super("font_of_mana", EnumAction.BOW, false);
this.soundValues(0.7f, 1.2f, 0.4f);
addProperties(EFFECT_RADIUS, EFFECT_DURATION, EFFECT_STRENGTH);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
double maxRadius = getProperty(EFFECT_RADIUS).doubleValue();
List<EntityPlayer> targets = WizardryUtilities.getEntitiesWithinRadius(
5 * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world,
EntityPlayer.class);
maxRadius * modifiers.get(WizardryItems.blast_upgrade),
caster.posX, caster.posY, caster.posZ, world, EntityPlayer.class);
for(EntityPlayer target : targets){
if(WizardryUtilities.isPlayerAlly(caster, target) || target == caster){
// Damage multiplier can only ever be 1 or 1.6 for master spells, so there's little point in actually
// calculating this.
if(AllyDesignationSystem.isPlayerAlly(caster, target) || target == caster){
target.addPotionEffect(new PotionEffect(WizardryPotions.font_of_mana,
(int)(600 * modifiers.get(WizardryItems.duration_upgrade)),
modifiers.get(SpellModifiers.POTENCY) > 1 ? 1 : 0));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
(int)(getProperty(EFFECT_STRENGTH).intValue() + (modifiers.get(SpellModifiers.POTENCY) - 1) * 2)));
}
}
if(world.isRemote){
for(int i = 0; i < 100 * modifiers.get(WizardryItems.blast_upgrade); i++){
double radius = (1 + world.rand.nextDouble() * 4) * modifiers.get(WizardryItems.blast_upgrade);
double angle = world.rand.nextDouble() * Math.PI * 2;
double radius = (1 + world.rand.nextDouble() * (maxRadius - 1)) * modifiers.get(WizardryItems.blast_upgrade);
float angle = world.rand.nextFloat() * (float)Math.PI * 2;
;
float hue = world.rand.nextFloat() * 0.4f;
double x = caster.posX + radius * Math.cos(angle);
double x = caster.posX + radius * MathHelper.cos(angle);
double y = caster.getEntityBoundingBox().minY;
double z = caster.posZ + radius * Math.sin(angle);
double z = caster.posZ + radius * MathHelper.sin(angle);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.03, 0).time(50)
.clr(1, 1 - hue, 0.6f + hue).spawn(world);
.clr(1, 1 - hue, 0.6f + hue).spawn(world);
}
}
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F,
world.rand.nextFloat() * 0.4F + 1.0F);
playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
}
@SubscribeEvent(priority = EventPriority.LOW) // Doesn't really matter but there's no point processing it if casting is blocked
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
// Moved from ItemWand (quite why this wasn't done with modifiers before I don't know!)
if(event.getCaster() != null && event.getCaster().isPotionActive(WizardryPotions.font_of_mana)){
// Dividing by this rather than setting it takes upgrades and font of mana into account simultaneously
event.getModifiers().set(WizardryItems.cooldown_upgrade, event.getModifiers().get(WizardryItems.cooldown_upgrade)
/ (2 + event.getCaster().getActivePotionEffect(WizardryPotions.font_of_mana).getAmplifier()), false);
}
}
}
@@ -0,0 +1,21 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.entity.projectile.EntityForceArrow;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import javax.annotation.Nullable;
public class ForceArrow extends SpellArrow<EntityForceArrow> {
public ForceArrow(){
super("force_arrow", EntityForceArrow::new);
this.addProperties(Spell.DAMAGE);
this.soundValues(1, 1.3f, 0.2f);
}
@Override
protected void addArrowExtras(EntityForceArrow arrow, @Nullable EntityLivingBase caster, SpellModifiers modifiers){
arrow.setMana((int)(this.getCost() * modifiers.get(SpellModifiers.COST)));
}
}
@@ -0,0 +1,21 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.entity.construct.EntityForcefield;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
public class Forcefield extends SpellConstruct<EntityForcefield> {
public Forcefield(){
super("forcefield", EnumAction.BOW, EntityForcefield::new, false);
addProperties(Spell.EFFECT_RADIUS);
}
@Override
protected void addConstructExtras(EntityForcefield construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){
construct.setRadius(getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade));
}
}
@@ -0,0 +1,99 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.block.BlockThorns;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.tileentity.TileEntityPlayerSaveTimed;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class ForestOfThorns extends Spell {
public ForestOfThorns(){
super("forest_of_thorns", EnumAction.BOW, false);
addProperties(EFFECT_RADIUS, DURATION, DAMAGE);
}
@Override public boolean requiresPacket(){ return false; }
@Override public boolean canBeCastByNPCs(){ return true; }
@Override public boolean canBeCastByDispensers(){ return true; }
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
if(!summonThorns(world, caster, caster.getPosition(), modifiers)) return false;
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){
if(!summonThorns(world, caster, caster.getPosition(), modifiers)) return false;
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@Override
public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){
if(!summonThorns(world, null, new BlockPos(x, y, z).offset(direction), modifiers)) return false;
this.playSound(world, x, y, z, ticksInUse, duration, modifiers);
return true;
}
private boolean summonThorns(World world, @Nullable EntityLivingBase caster, BlockPos origin, SpellModifiers modifiers){
if(!world.isRemote){
double radius = getProperty(EFFECT_RADIUS).doubleValue() * modifiers.get(WizardryItems.blast_upgrade);
List<BlockPos> ring = new ArrayList<>((int)(7 * radius)); // 7 is a bit more than 2 pi
for(int x = -(int)radius; x <= radius; x++){
for(int z = -(int)radius; z <= radius; z++){
double distance = MathHelper.sqrt(x*x + z*z);
if(distance > radius || distance < radius - 1.5) continue;
Integer y = WizardryUtilities.getNearestSurface(world, origin.add(x, 0, z), EnumFacing.UP, (int)radius, true, WizardryUtilities.SurfaceCriteria.BUILDABLE);
if(y != null) ring.add(new BlockPos(origin.getX() + x, y, origin.getZ() + z));
}
}
if(ring.isEmpty()) return false;
// Because we're always using EnumFacing.UP in the code above, we can be sure that pos is the block above the floor
for(BlockPos pos : ring){
((BlockThorns)WizardryBlocks.thorns).placeAt(world, pos, 3);
for(int i=0; i<2; i++){
TileEntity tileentity = world.getTileEntity(pos.up(i));
if(tileentity instanceof TileEntityPlayerSaveTimed){
((TileEntityPlayerSaveTimed)tileentity).setLifetime((int)(getProperty(DURATION).floatValue()
* modifiers.get(WizardryItems.duration_upgrade)));
if(caster != null) ((TileEntityPlayerSaveTimed)tileentity).setCaster(caster);
}
}
}
}
return true;
}
}
@@ -1,8 +1,5 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
@@ -12,19 +9,16 @@ import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.MobEffects;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.world.World;
public class ForestsCurse extends SpellAreaEffect {
private static final int BASE_DAMAGE = 4;
private static final int BASE_DURATION = 140;
public ForestsCurse(){
super("forests_curse", Tier.MASTER, Element.EARTH, SpellType.ATTACK, 75, 200, EnumAction.BOW, 5, SoundEvents.ENTITY_WITHER_SPAWN);
super("forests_curse", EnumAction.BOW);
this.soundValues(1, 1.1f, 0.2f);
addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH);
}
@Override
@@ -33,13 +27,15 @@ public class ForestsCurse extends SpellAreaEffect {
if(!MagicDamage.isEntityImmune(DamageType.POISON, target) && WizardryUtilities.isLiving(target)){
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.POISON),
BASE_DAMAGE * modifiers.get(SpellModifiers.POTENCY));
int duration = (int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade));
target.addPotionEffect(new PotionEffect(MobEffects.POISON, duration, 2));
target.addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, duration, 2));
target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, duration, 2));
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY));
int duration = (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade));
int amplifier = (int)(getProperty(EFFECT_STRENGTH).floatValue() + bonusAmplifier);
target.addPotionEffect(new PotionEffect(MobEffects.POISON, duration, amplifier));
target.addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, duration, amplifier));
target.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, duration, amplifier));
}
}
@@ -1,12 +1,7 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.living.EntityBlazeMinion;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
@@ -17,33 +12,33 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.EntityBlaze;
import net.minecraft.entity.monster.EntityMagmaCube;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class Freeze extends SpellRay {
/** The base duration of the frostbite effect appled by this spell. */
private static final int BASE_DURATION = 200;
/** The base damage dealt to entities vulnerable to frost. */
private static final float BASE_DAMAGE = 3;
public Freeze(){
super("freeze", Tier.BASIC, Element.ICE, SpellType.ATTACK, 5, 10, false, 10, WizardrySounds.SPELL_ICE);
super("freeze", false, EnumAction.NONE);
this.soundValues(1, 1.4f, 0.4f);
addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH);
this.hitLiquids(true);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
if(target instanceof EntityBlaze || target instanceof EntityMagmaCube || target instanceof EntityBlazeMinion){
if(target instanceof EntityBlaze || target instanceof EntityMagmaCube){
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.FROST),
BASE_DAMAGE * modifiers.get(SpellModifiers.POTENCY));
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
}
if(MagicDamage.isEntityImmune(DamageType.FROST, target)){
@@ -51,11 +46,12 @@ public class Freeze extends SpellRay {
new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true);
}else{
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.frost,
(int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)), 1));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue()));
}
if(target.isBurning()) target.extinguish();
return true;
}
@@ -63,24 +59,27 @@ public class Freeze extends SpellRay {
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
if(world.getBlockState(pos).getBlock() == Blocks.WATER && !world.isRemote){
world.setBlockState(pos, Blocks.ICE.getDefaultState());
}else if(world.getBlockState(pos).getBlock() == Blocks.LAVA && !world.isRemote){
world.setBlockState(pos, Blocks.OBSIDIAN.getDefaultState());
}else if(world.getBlockState(pos).getBlock() == Blocks.FLOWING_LAVA && !world.isRemote){
world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState());
}else if(side == EnumFacing.UP && !world.isRemote && world.isSideSolid(pos, EnumFacing.UP)
&& WizardryUtilities.canBlockBeReplaced(world, pos.up())){
world.setBlockState(pos.up(), Blocks.SNOW_LAYER.getDefaultState());
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.canDamageBlocks(caster, world)){
if(world.getBlockState(pos).getBlock() == Blocks.WATER && !world.isRemote){
world.setBlockState(pos, Blocks.ICE.getDefaultState());
}else if(world.getBlockState(pos).getBlock() == Blocks.LAVA && !world.isRemote){
world.setBlockState(pos, Blocks.OBSIDIAN.getDefaultState());
}else if(world.getBlockState(pos).getBlock() == Blocks.FLOWING_LAVA && !world.isRemote){
world.setBlockState(pos, Blocks.COBBLESTONE.getDefaultState());
}else if(side == EnumFacing.UP && !world.isRemote && world.isSideSolid(pos, EnumFacing.UP)
&& WizardryUtilities.canBlockBeReplaced(world, pos.up())){
world.setBlockState(pos.up(), Blocks.SNOW_LAYER.getDefaultState());
}
}
return true; // Always succeeds if it hits a block
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@@ -1,13 +1,9 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.registry.WizardryEnchantments;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
@@ -15,9 +11,7 @@ import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraft.util.EnumHand;
import net.minecraft.world.World;
@@ -30,7 +24,8 @@ public class FreezingWeapon extends Spell {
public static final String FREEZING_ARROW_NBT_KEY = "frostLevel";
public FreezingWeapon(){
super("freezing_weapon", Tier.ADVANCED, Element.ICE, SpellType.UTILITY, 35, 70, EnumAction.BOW, false);
super("freezing_weapon", EnumAction.BOW, false);
addProperties(EFFECT_DURATION);
}
@Override
@@ -42,17 +37,17 @@ public class FreezingWeapon extends Spell {
for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(caster)){
if((stack.getItem() instanceof ItemSword || stack.getItem() instanceof ItemBow)
if((ImbueWeapon.isSword(stack.getItem()) || ImbueWeapon.isBow(stack.getItem()))
&& !EnchantmentHelper.getEnchantments(stack).containsKey(WizardryEnchantments.freezing_weapon)){
// The enchantment level as determined by the damage multiplier. The + 0.5f is so that
// weird float processing doesn't incorrectly round it down.
stack.addEnchantment(WizardryEnchantments.freezing_weapon,
modifiers.get(SpellModifiers.POTENCY) == 1.0f ? 1
: (int)((modifiers.get(SpellModifiers.POTENCY) - 1.0f)
/ Constants.DAMAGE_INCREASE_PER_TIER + 0.5f));
/ Constants.POTENCY_INCREASE_PER_TIER + 0.5f));
WizardData.get(caster).setImbuementDuration(WizardryEnchantments.freezing_weapon,
(int)(900 * modifiers.get(WizardryItems.duration_upgrade)));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
if(world.isRemote){
for(int i=0; i<10; i++){
@@ -63,7 +58,7 @@ public class FreezingWeapon extends Spell {
}
}
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1, 1);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -1,10 +1,6 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
@@ -14,7 +10,7 @@ import net.minecraft.world.World;
public class FrostAxe extends SpellConjuration {
public FrostAxe(){
super("frost_axe", Tier.ADVANCED, Element.ICE, SpellType.UTILITY, 45, 50, WizardryItems.frost_axe, WizardrySounds.SPELL_ICE);
super("frost_axe", WizardryItems.frost_axe);
}
@Override
@@ -1,11 +1,7 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
@@ -13,57 +9,45 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.EntityBlaze;
import net.minecraft.entity.monster.EntityMagmaCube;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class FrostRay extends SpellRay {
private static final float BASE_DAMAGE = 3;
/** The base duration of the forstbite effect applied by this spell. */
private static final int BASE_DURATION = 200;
public FrostRay(){
super("frost_ray", Tier.APPRENTICE, Element.ICE, SpellType.ATTACK, 5, 0, true, 10, null);
super("frost_ray", true, EnumAction.NONE);
this.particleVelocity(1);
this.particleSpacing(0.5);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
// TODO: Temporary solution until I implement a better continuous sound system
boolean flag = super.cast(world, caster, hand, ticksInUse, modifiers);
if(flag){
if(ticksInUse % 12 == 0){
if(ticksInUse == 0) WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 0.5f, 1);
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_ICE, 0.5f, 1);
}
}
return flag;
addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH);
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){
boolean flag = super.cast(world, caster, hand, ticksInUse, target, modifiers);
if(flag){
if(ticksInUse % 12 == 0){
if(ticksInUse == 0) caster.playSound(WizardrySounds.SPELL_ICE, 0.5f, 1);
caster.playSound(WizardrySounds.SPELL_LOOP_ICE, 0.5f, 1);
}
}
return flag;
protected SoundEvent[] createSounds(){
return this.createContinuousSpellSounds();
}
@Override
protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, entity, ticksInUse);
}
@Override
protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, x, y, z, ticksInUse, duration);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
@@ -73,32 +57,33 @@ public class FrostRay extends SpellRay {
if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) ((EntityPlayer)caster)
.sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(),
this.getNameForTranslationFormatted()), true);
}else{
// This now only damages in line with the maxHurtResistantTime. Some mods don't play nicely and fiddle
// with this mechanic for their own purposes, so this line makes sure that doesn't affect wizardry.
}else if(ticksInUse % ((EntityLivingBase)target).maxHurtResistantTime == 1){
// For frost ray the entity can move slightly, unlike freeze
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.frost,
(int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)), 0));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue()));
float damage = BASE_DAMAGE * modifiers.get(SpellModifiers.POTENCY);
float damage = getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY);
if(target instanceof EntityBlaze || target instanceof EntityMagmaCube) damage *= 2;
WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster,
DamageType.FROST), damage);
}
return true;
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@@ -106,8 +91,8 @@ public class FrostRay extends SpellRay {
protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){
float brightness = world.rand.nextFloat();
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(vx, vy, vz).time(8 + world.rand.nextInt(12))
.clr(0.4f + 0.6f * brightness, 0.6f + 0.4f*brightness, 1).spawn(world);
ParticleBuilder.create(Type.SNOW).pos(x, y, z).vel(vx, vy, vz).time(8 + world.rand.nextInt(12)).spawn(world);
.clr(0.4f + 0.6f * brightness, 0.6f + 0.4f*brightness, 1).collide(true).spawn(world);
ParticleBuilder.create(Type.SNOW).pos(x, y, z).vel(vx, vy, vz).time(8 + world.rand.nextInt(12)).collide(true).spawn(world);
}
}
@@ -1,41 +1,66 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.world.World;
public class Glide extends Spell {
public static final String SPEED = "speed";
public static final String FALL_SPEED = "fall_speed";
public static final String ACCELERATION = "acceleration";
public Glide(){
super("glide", Tier.ADVANCED, Element.EARTH, SpellType.UTILITY, 5, 0, EnumAction.NONE, true);
super("glide", EnumAction.NONE, true);
addProperties(SPEED, FALL_SPEED, ACCELERATION);
}
@Override
protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
if(ticksInUse == 0 && world.isRemote) Wizardry.proxy.playSpellSoundLoop(entity, this, this.sounds[0], this.sounds[0], this.sounds[0],
WizardrySounds.SPELLS, volume, pitch + pitchVariation * (world.rand.nextFloat() - 0.5f));
}
@Override
protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
if(ticksInUse == 0 && world.isRemote){
Wizardry.proxy.playSpellSoundLoop(world, x, y, z, this, this.sounds[0], this.sounds[0], this.sounds[0],
WizardrySounds.SPELLS, volume, pitch + pitchVariation * (world.rand.nextFloat() - 0.5f), duration);
}
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
if(caster.motionY < -0.1 && !caster.isInWater()){
caster.motionY = -0.1;
if(Math.abs(caster.motionX) < 0.4 && Math.abs(caster.motionZ) < 0.4){
caster.addVelocity(caster.getLookVec().x / 8, 0, caster.getLookVec().z / 8);
// entityplayer.moveEntity(entityplayer.motionX*10, 0, entityplayer.motionZ*10);
float speed = getProperty(SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY);
// There seems to be some sort of 'terminal velocity', presumably due to the slight slowing-down effect in
// vanilla - this means we have to apply potency modifiers to the acceleration as well as the speed or they
// appear to have no effect (a bug which had me confused for quite a while!) This also applies to flight.
float acceleration = getProperty(ACCELERATION).floatValue() * modifiers.get(SpellModifiers.POTENCY);
caster.motionY = -getProperty(FALL_SPEED).floatValue();
if(Math.abs(caster.motionX) < speed && Math.abs(caster.motionZ) < speed){
caster.addVelocity(caster.getLookVec().x * acceleration, 0, caster.getLookVec().z * acceleration);
}
caster.fallDistance = 0.0f;
if(!Wizardry.settings.replaceVanillaFallDamage) caster.fallDistance = 0.0f;
}
if(world.isRemote){
double x = caster.posX - 0.25 + world.rand.nextDouble() / 2;
double y = caster.getEntityBoundingBox().minY + world.rand.nextDouble();
double z = caster.posZ - 0.25 + world.rand.nextDouble() / 2;
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, -0.1, 0).time(15).clr(1, 1, 1).spawn(world);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, -0.1, 0).time(15).clr(1f, 1f, 1f).spawn(world);
x = caster.posX - 0.25 + world.rand.nextDouble() / 2;
y = caster.getEntityBoundingBox().minY + world.rand.nextDouble();
z = caster.posZ - 0.25 + world.rand.nextDouble() / 2;
@@ -43,7 +68,7 @@ public class Glide extends Spell {
}
if(ticksInUse % 24 == 0){
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ITEM_ELYTRA_FLYING, 0.5F, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
}
return true;
@@ -0,0 +1,419 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.data.IVariable;
import electroblob.wizardry.data.Persistence;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.RayTracer;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.EnumAction;
import net.minecraft.network.play.server.SPacketEntityVelocity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import javax.annotation.Nullable;
public class Grapple extends Spell {
/** The speed at which the vine extends/retracts from the caster, in blocks per tick. */
public static final String EXTENSION_SPEED = "extension_speed";
/** The speed at which the vine reels in the caster or target, in blocks per tick. */
public static final String REEL_SPEED = "reel_speed";
public static final IVariable<RayTraceResult> TARGET_KEY = new IVariable.Variable<RayTraceResult>(Persistence.NEVER)
.withTicker(Grapple::update);
/** The distance from the target position at which the spell will stop reeling in entities. */
private static final double MINIMUM_REEL_DISTANCE = 3;
/** The acceleration with which the vine reels in the caster or target. */
private static final double REEL_ACCELERATION = 0.3;
/** The speed at which the caster or target is lowered when the caster is sneaking. */
private static final double PAYOUT_SPEED = 0.25;
/** Once attached, the vine can stretch beyond the maximum range by this factor before breaking. */
private static final double STRETCH_LIMIT = 1.5;
/** The distance between spawned particles. */
protected static final double PARTICLE_SPACING = 1.5;
/** The maximum jitter (random position offset) for spawned particles. */
protected static final double PARTICLE_JITTER = 0.04;
public Grapple(){
super("grapple", EnumAction.NONE, true);
addProperties(RANGE, EXTENSION_SPEED, REEL_SPEED);
}
@Override
public boolean canBeCastByNPCs(){
return super.canBeCastByNPCs();
}
@Override
public boolean canBeCastByDispensers(){
return true;
}
@Override
protected SoundEvent[] createSounds(){
return this.createSoundsWithSuffixes("shoot", "attach", "pull", "release");
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
WizardData data = WizardData.get(caster);
Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight(), caster.posZ);
float extensionSpeed = getProperty(EXTENSION_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY);
RayTraceResult hit = data.getVariable(TARGET_KEY);
// Initial targeting
if(hit == null){
hit = findTarget(world, caster, origin, caster.getLookVec(), modifiers);
data.setVariable(TARGET_KEY, hit);
caster.swingArm(hand);
// This condition prevents the sound playing every tick after a missed shot has finished extending
if(hit.typeOfHit != RayTraceResult.Type.MISS
|| ticksInUse * extensionSpeed < getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade)){
this.playSound(world, caster, ticksInUse, -1, modifiers, "shoot");
}
}
Vec3d target = hit.hitVec;
if(hit.entityHit instanceof EntityLivingBase){
// If the target is an entity, we need to use the entity's centre rather than the original hit position
// because the entity will have moved!
target = new Vec3d(hit.entityHit.posX, hit.entityHit.getEntityBoundingBox().minY + hit.entityHit.height/2, hit.entityHit.posZ);
}
double distance = origin.distanceTo(target);
Vec3d direction = target.subtract(origin).normalize();
double maxLength = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade) * STRETCH_LIMIT;
// If the vine stretched too far
if(distance > maxLength){
if(world.isRemote && (ticksInUse-1) * extensionSpeed < distance){
spawnLeafParticles(world, origin.subtract(0, SpellRay.Y_OFFSET, 0), direction, distance);
}
data.setVariable(TARGET_KEY, null);
return false; // The spell is finished
}
boolean extending = ticksInUse * extensionSpeed < distance;
if(extending){
// Extension
if(world.isRemote){
// world.getTotalWorldTime() - ticksInUse generates a constant but unique seed each time the spell is cast
ParticleBuilder.create(Type.VINE).entity(caster).pos(0, caster.getEyeHeight() - SpellRay.Y_OFFSET, 0)
.target(origin.add(direction.scale(ticksInUse * extensionSpeed))).tvel(direction.scale(extensionSpeed))
.seed(world.getTotalWorldTime() - ticksInUse).spawn(world);
}
}else{
// Retraction
Vec3d velocity = direction.scale(getProperty(REEL_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY));
int retractTime = ticksInUse - (int)(distance/extensionSpeed);
switch(hit.typeOfHit){
case BLOCK:
// Payout
if(caster.isSneaking() && ItemArtefact.isArtefactActive(caster, WizardryItems.charm_abseiling))
velocity = new Vec3d(velocity.x, distance < maxLength-1 ? -PAYOUT_SPEED : distance-maxLength+1, velocity.z);
// Reel the caster towards the block hit
double ax = (velocity.x - caster.motionX) * REEL_ACCELERATION;
double ay = (velocity.y - caster.motionY) * REEL_ACCELERATION;
double az = (velocity.z - caster.motionZ) * REEL_ACCELERATION;
caster.addVelocity(ax, ay, az);
if(caster.motionY > 0 && !Wizardry.settings.replaceVanillaFallDamage) caster.fallDistance = 0; // Reset fall distance if the caster moves upwards
if(world.isRemote){
ParticleBuilder.create(Type.VINE).entity(caster).pos(0, caster.getEyeHeight() - SpellRay.Y_OFFSET, 0)
.target(target).seed(world.getTotalWorldTime() - ticksInUse).spawn(world);
}
if(retractTime == 1){ // Just hit
this.playSound(world, caster, ticksInUse, -1, modifiers, "pull");
this.playSound(world, hit.hitVec, ticksInUse, -1, modifiers, "attach");
}
break;
case ENTITY:
// Payout
if(caster.isSneaking() && ItemArtefact.isArtefactActive(caster, WizardryItems.charm_abseiling))
velocity = new Vec3d(velocity.x, distance < maxLength-1 ? PAYOUT_SPEED : maxLength-1-distance, velocity.z);
// Reel the entity hit towards the caster
Entity entity = hit.entityHit;
if(distance > MINIMUM_REEL_DISTANCE){
double ax1 = (-velocity.x - entity.motionX) * REEL_ACCELERATION;
double ay1 = (-velocity.y - entity.motionY) * REEL_ACCELERATION;
double az1 = (-velocity.z - entity.motionZ) * REEL_ACCELERATION;
entity.addVelocity(ax1, ay1, az1);
// Player motion is handled on that player's client so needs packets
if(entity instanceof EntityPlayerMP){
((EntityPlayerMP)entity).connection.sendPacket(new SPacketEntityVelocity(entity));
}
}
if(world.isRemote){
ParticleBuilder.create(Type.VINE).entity(caster).pos(0, caster.getEyeHeight() - SpellRay.Y_OFFSET, 0)
.target(entity).seed(world.getTotalWorldTime() - ticksInUse).spawn(world);
}
if(retractTime == 1){ // Just hit
this.playSound(world, caster, ticksInUse, -1, modifiers, "pull");
this.playSound(world, entity.posX, entity.posY, entity.posZ, ticksInUse, -1, modifiers, "attach");
}
break;
default:
// Missed
if(world.isRemote && (ticksInUse-1) * extensionSpeed < distance){
spawnLeafParticles(world, origin.subtract(0, SpellRay.Y_OFFSET, 0), direction, distance);
}
//caster.resetActiveHand();
data.setVariable(TARGET_KEY, null);
return false; // The spell is finished
}
}
return true;
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){
Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight(), caster.posZ);
// If the target is an entity, we need to use the entity's centre rather than the original hit position
// because the entity will have moved!
Vec3d targetVec = new Vec3d(target.posX, target.getEntityBoundingBox().minY + target.height/2, target.posZ);
double distance = origin.distanceTo(targetVec);
// Can't cast the spell at all if the target is too far away
if(ticksInUse <= 1 && distance > getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade))
return false;
Vec3d vec = targetVec.subtract(origin).normalize();
float extensionSpeed = getProperty(EXTENSION_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY);
// If the vine stretched too far
if(distance > getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade) * STRETCH_LIMIT){
if(world.isRemote && (ticksInUse-1) * extensionSpeed < distance){
spawnLeafParticles(world, origin.subtract(0, SpellRay.Y_OFFSET, 0), vec, distance);
}
return false;
}
Vec3d hookPosition;
if(ticksInUse * extensionSpeed < distance){
// Extension
hookPosition = origin.add(vec.scale(ticksInUse * extensionSpeed));
}else{
// Retraction
Vec3d velocity = vec.scale(getProperty(REEL_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY));
// Reel the entity hit towards the caster
if(distance > MINIMUM_REEL_DISTANCE){
double ax1 = (-velocity.x - target.motionX) * REEL_ACCELERATION;
double ay1 = (-velocity.y - target.motionY) * REEL_ACCELERATION;
double az1 = (-velocity.z - target.motionZ) * REEL_ACCELERATION;
target.addVelocity(ax1, ay1, az1);
// Player motion is handled on that player's client so needs packets
if(target instanceof EntityPlayerMP){
((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target));
}
}
hookPosition = targetVec;
}
if(world.isRemote){
// world.getTotalWorldTime() - ticksInUse generates a constant but unique seed each time the spell is cast
ParticleBuilder.create(Type.VINE).pos(origin).target(hookPosition).tvel(vec.scale(extensionSpeed))
.seed(world.getTotalWorldTime() - ticksInUse).spawn(world);
}
return true;
}
@Override
public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){
Vec3d origin = new Vec3d(x, y, z);
RayTraceResult result = findTarget(world, null, origin, new Vec3d(direction.getDirectionVec()), modifiers);
if(result.entityHit instanceof EntityLivingBase){
Entity entity = result.entityHit;
// If the target is an entity, we need to use the entity's centre rather than the original hit position
// because the entity will have moved!
Vec3d target = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.height/2, entity.posZ);
double distance = origin.distanceTo(target);
Vec3d vec = target.subtract(origin).normalize();
float extensionSpeed = getProperty(EXTENSION_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY);
// If the vine stretched too far
if(distance > getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade) * STRETCH_LIMIT){
if(world.isRemote && (ticksInUse-1) * extensionSpeed < distance){
spawnLeafParticles(world, origin.subtract(0, SpellRay.Y_OFFSET, 0), vec, distance);
}
return false;
}
Vec3d hookPosition;
if(ticksInUse * extensionSpeed < distance){
// Extension
hookPosition = origin.add(vec.scale(ticksInUse * extensionSpeed));
}else{
// Retraction
Vec3d velocity = vec.scale(getProperty(REEL_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY));
// Reel the entity hit towards the caster
if(distance > MINIMUM_REEL_DISTANCE){
double ax1 = (-velocity.x - entity.motionX) * REEL_ACCELERATION;
double ay1 = (-velocity.y - entity.motionY) * REEL_ACCELERATION;
double az1 = (-velocity.z - entity.motionZ) * REEL_ACCELERATION;
entity.addVelocity(ax1, ay1, az1);
// Player motion is handled on that player's client so needs packets
if(entity instanceof EntityPlayerMP){
((EntityPlayerMP)entity).connection.sendPacket(new SPacketEntityVelocity(entity));
}
}
hookPosition = target;
}
if(world.isRemote){
// world.getTotalWorldTime() - ticksInUse generates a constant but unique seed each time the spell is cast
ParticleBuilder.create(Type.VINE).pos(origin).target(hookPosition).seed(world.getTotalWorldTime() - ticksInUse).spawn(world);
}
return true;
}
return false;
}
@Override
public void finishCasting(World world, @Nullable EntityLivingBase caster, double x, double y, double z, EnumFacing facing, int duration, SpellModifiers modifiers){
Vec3d origin = null;
Vec3d direction = null;
Vec3d target = null;
if(caster != null){
origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight(), caster.posZ);
if(caster instanceof EntityPlayer){
WizardData data = WizardData.get((EntityPlayer)caster);
if(data != null){
RayTraceResult hit = data.getVariable(TARGET_KEY);
if(hit != null) target = hit.hitVec;
}
}else if(caster instanceof EntityLiving){
Entity entity = ((EntityLiving)caster).getAttackTarget();
if(entity != null) target = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.height/2, entity.posZ);
}
if(target != null) direction = target.subtract(origin).normalize();
this.playSound(world, caster, duration, duration, modifiers, "release");
}else if(!Double.isNaN(x) && !Double.isNaN(y) && !Double.isNaN(z)){
origin = new Vec3d(x, y, z);
direction = new Vec3d(facing.getDirectionVec());
RayTraceResult result = findTarget(world, null, origin, direction, modifiers);
target = result.hitVec;
this.playSound(world, origin, duration, duration, modifiers, "release");
}
if(world.isRemote && origin != null && direction != null){
float extensionSpeed = getProperty(EXTENSION_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY);
double distance = Math.min(target.subtract(origin).length(), duration * extensionSpeed);
spawnLeafParticles(world, origin, direction, distance);
}
}
private void spawnLeafParticles(World world, Vec3d origin, Vec3d direction, double distance){
// Copied from SpellRay
for(double d = PARTICLE_SPACING; d <= distance; d += PARTICLE_SPACING){
double x = origin.x + d*direction.x;// + PARTICLE_JITTER * (world.rand.nextDouble()*2 - 1);
double y = origin.y + d*direction.y;// + PARTICLE_JITTER * (world.rand.nextDouble()*2 - 1);
double z = origin.z + d*direction.z;// + PARTICLE_JITTER * (world.rand.nextDouble()*2 - 1);
ParticleBuilder.create(Type.LEAF, world.rand, x, y, z, PARTICLE_JITTER, true).time(25 + world.rand.nextInt(5)).spawn(world);
}
}
private RayTraceResult findTarget(World world, @Nullable EntityLivingBase caster, Vec3d origin, Vec3d direction, SpellModifiers modifiers){
double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade);
Vec3d endpoint = origin.add(direction.scale(range));
RayTraceResult result = RayTracer.rayTrace(world, origin, endpoint, 0, false,
true, false, Entity.class, RayTracer.ignoreEntityFilter(caster));
// Non-solid blocks (or if the result is null) count as misses
if(result == null || result.typeOfHit == RayTraceResult.Type.BLOCK
&& !world.getBlockState(result.getBlockPos()).getMaterial().isSolid()){
return new RayTraceResult(RayTraceResult.Type.MISS, endpoint, EnumFacing.DOWN, new BlockPos(endpoint));
// Immovable entities count as misses too, but the endpoint is the hit vector instead
}else if(result.entityHit != null && !result.entityHit.canBePushed()){
return new RayTraceResult(RayTraceResult.Type.MISS, result.hitVec, EnumFacing.DOWN, new BlockPos(endpoint));
}
// If the ray trace missed, result.hitVec will be the endpoint anyway - neat!
return result;
}
private static RayTraceResult update(EntityPlayer player, RayTraceResult grapplingTarget){
if(grapplingTarget != null && (!WizardryUtilities.isCasting(player, Spells.grapple)
|| (grapplingTarget.entityHit != null && !grapplingTarget.entityHit.isEntityAlive()))){
return null;
}
return grapplingTarget;
}
}
@@ -1,85 +0,0 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityLargeFireball;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class GreaterFireball extends Spell {
public GreaterFireball(){
super("greater_fireball", Tier.ADVANCED, Element.FIRE, SpellType.ATTACK, 20, 30, EnumAction.NONE, false);
}
@Override
public boolean doesSpellRequirePacket(){
return false;
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
Vec3d look = caster.getLookVec();
if(!world.isRemote){
EntityLargeFireball fireball = new EntityLargeFireball(world, caster, 1, 1, 1);
fireball.setPosition(caster.posX + look.x, caster.posY + look.y + 1.3, caster.posZ + look.z);
fireball.accelerationX = look.x * 0.1;
fireball.accelerationY = look.y * 0.1;
fireball.accelerationZ = look.z * 0.1;
world.spawnEntity(fireball);
}
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1);
caster.swingArm(hand);
return true;
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target,
SpellModifiers modifiers){
if(target != null){
if(!world.isRemote){
EntityLargeFireball fireball = new EntityLargeFireball(world, caster, 1, 1, 1);
double dx = target.posX - caster.posX;
double dy = target.getEntityBoundingBox().minY + (double)(target.height / 2.0F)
- (caster.posY + (double)(caster.height / 2.0F));
double dz = target.posZ - caster.posZ;
fireball.accelerationX = dx / caster.getDistance(target) * 0.1;
fireball.accelerationY = dy / caster.getDistance(target) * 0.1;
fireball.accelerationZ = dz / caster.getDistance(target) * 0.1;
fireball.setPosition(caster.posX, caster.posY + caster.getEyeHeight(), caster.posZ);
world.spawnEntity(fireball);
}
caster.playSound(SoundEvents.ENTITY_BLAZE_SHOOT, 1, 1);
caster.swingArm(hand);
return true;
}
return false;
}
@Override
public boolean canBeCastByNPCs(){
return true;
}
}
@@ -1,24 +1,21 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
public class GreaterHeal extends SpellBuff {
public GreaterHeal(){
super("greater_heal", Tier.ADVANCED, Element.HEALING, SpellType.DEFENCE, 15, 40, WizardrySounds.SPELL_HEAL, 1, 1, 0.3f);
super("greater_heal", 1, 1, 0.3f);
this.soundValues(0.7f, 1.2f, 0.4f);
addProperties(HEALTH);
}
@Override
protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){
if(caster.getHealth() < caster.getMaxHealth() && caster.getHealth() > 0){
caster.heal(8 * modifiers.get(SpellModifiers.POTENCY));
caster.heal(getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY));
return true;
}
@@ -0,0 +1,159 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.entity.EntityLevitatingBlock;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityTNTPrimed;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.EnumAction;
import net.minecraft.network.play.server.SPacketEntityVelocity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class GreaterTelekinesis extends SpellRay {
public static final String HOLD_RANGE = "hold_range";
public static final String THROW_VELOCITY = "throw_velocity";
/** Makes things a bit smoother-looking / 'realistic'. */
private static final float UNDERSHOOT = 0.2f;
public GreaterTelekinesis(){
super("greater_telekinesis", true, EnumAction.NONE);
this.aimAssist(0.4f);
this.particleSpacing(1);
this.particleJitter(0.05);
this.particleVelocity(0.3);
addProperties(HOLD_RANGE, THROW_VELOCITY, DAMAGE);
this.soundValues(0.8f, 1, 0.2f);
}
@Override public boolean canBeCastByNPCs() { return false; }
@Override public boolean canBeCastByDispensers() { return false; }
@Override
protected SoundEvent[] createSounds(){
return this.createContinuousSpellSounds();
}
@Override
protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, entity, ticksInUse);
}
@Override
protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, x, y, z, ticksInUse, duration);
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
// Can't be cast by dispensers so we know caster isn't null, but just in case...
if(caster != null && (target instanceof EntityLivingBase || target instanceof EntityLevitatingBlock || target instanceof EntityTNTPrimed)){
if(target instanceof EntityPlayer && ((caster instanceof EntityPlayer && !Wizardry.settings.playersMoveEachOther)
|| ItemArtefact.isArtefactActive((EntityPlayer)target, WizardryItems.amulet_anchoring))){
if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage(
new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true);
return false;
}
if(target instanceof EntityLevitatingBlock){
((EntityLevitatingBlock)target).suspend();
((EntityLevitatingBlock)target).setCaster(caster); // Yep, you can steal other players' blocks in mid-air!
}
Vec3d targetPos = target.getPositionVector().add(0, target.height/2, 0);
if(caster.isSneaking()){
Vec3d look = caster.getLookVec().scale(getProperty(THROW_VELOCITY).floatValue() * modifiers.get(WizardryItems.range_upgrade));
target.addVelocity(look.x, look.y, look.z);
if(caster instanceof EntityPlayer) caster.swingArm(caster.getActiveHand());
}else{
WizardryUtilities.undoGravity(target);
// The following code extrapolates the entity's current velocity to determine whether it will pass the
// target position in the next tick, and adds or subtracts velocity accordingly.
Vec3d vec = origin.add(caster.getLookVec().scale(getProperty(HOLD_RANGE).floatValue()));
Vec3d velocity = vec.subtract(targetPos).subtract(target.motionX, target.motionY, target.motionZ)
.scale(1 - UNDERSHOOT);
target.addVelocity(velocity.x, velocity.y, velocity.z);
}
// Player motion is handled on that player's client so needs packets
if(target instanceof EntityPlayerMP){
((EntityPlayerMP)target).connection.sendPacket(new SPacketEntityVelocity(target));
}
if(world.isRemote){
ParticleBuilder.create(Type.BEAM).entity(caster).clr(0.2f, 0.6f + 0.3f * world.rand.nextFloat(), 1)
.pos(origin.subtract(caster.getPositionVector())).target(target).time(0)
.scale(MathHelper.sin(ticksInUse * 0.3f) * 0.1f + 0.9f).spawn(world);
if(ticksInUse % 18 == 1) ParticleBuilder.create(Type.FLASH).entity(target).pos(0, target.height/2, 0)
.scale(2.5f).time(30).clr(0.2f, 0.8f, 1).fade(1f, 1f, 1f).spawn(world);
ParticleBuilder.create(Type.SPARKLE, target).vel(0, 0.05, 0).time(15).scale(0.6f).clr(0.2f, 0.6f, 1)
.fade(1f, 1f, 1f).spawn(world);
}
return true;
}
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.canDamageBlocks(caster, world) && !WizardryUtilities.isBlockUnbreakable(world, pos)
&& world.getBlockState(pos).getMaterial().isSolid()
&& (world.getTileEntity(pos) == null || !world.getTileEntity(pos).getTileData().hasUniqueId(ArcaneLock.NBT_KEY))){
if(!world.isRemote){
EntityLevitatingBlock block = new EntityLevitatingBlock(world, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
world.getBlockState(pos));
block.fallTime = 1;
block.damageMultiplier = modifiers.get(SpellModifiers.POTENCY);
block.setCaster(caster);
world.spawnEntity(block);
world.setBlockToAir(pos);
}
return true;
}
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
}
@@ -1,16 +1,8 @@
package electroblob.wizardry.spell;
import java.util.List;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase;
@@ -19,10 +11,14 @@ import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.world.World;
import java.util.List;
public class GroupHeal extends Spell {
public GroupHeal(){
super("group_heal", Tier.ADVANCED, Element.HEALING, SpellType.DEFENCE, 35, 150, EnumAction.BOW, false);
super("group_heal", EnumAction.BOW, false);
this.soundValues(0.7f, 1.2f, 0.4f);
addProperties(EFFECT_RADIUS, HEALTH);
}
@Override
@@ -30,64 +26,20 @@ public class GroupHeal extends Spell {
boolean flag = false;
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(
5 * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world);
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(getProperty(EFFECT_RADIUS).floatValue()
* modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world);
for(EntityLivingBase target : targets){
if(target instanceof EntityPlayer){
if(target == caster || AllyDesignationSystem.isAllied(caster, target)){
if(WizardryUtilities.isPlayerAlly(caster, (EntityPlayer)target) || target == caster){
if(target.getHealth() < target.getMaxHealth() && target.getHealth() > 0){
if(((EntityPlayer)target).shouldHeal()){
target.heal(getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY));
target.heal(6 * modifiers.get(SpellModifiers.POTENCY));
if(world.isRemote){
for(int i = 0; i < 10; i++){
double x = caster.posX + world.rand.nextDouble() * 2 - 1;
double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble();
double z = caster.posZ + world.rand.nextDouble() * 2 - 1;
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1, 0).clr(1, 1, 0.3f).spawn(world);
}
ParticleBuilder.create(Type.BUFF).entity(caster).clr(1, 1, 0.3f).spawn(world);
}
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F,
world.rand.nextFloat() * 0.4F + 1.0F);
flag = true;
}
}
// Now also works on summoned creatures
}else if(target instanceof ISummonedCreature){
EntityLivingBase summoner = ((ISummonedCreature)target).getCaster();
if(summoner == caster || (summoner instanceof EntityPlayer
&& WizardryUtilities.isPlayerAlly(caster, (EntityPlayer)summoner))){
if(target.getHealth() < target.getMaxHealth() && target.getHealth() > 0){
target.heal(6 * modifiers.get(SpellModifiers.POTENCY));
if(world.isRemote){
for(int i = 0; i < 10; i++){
double x = caster.posX + world.rand.nextDouble() * 2 - 1;
double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble();
double z = caster.posZ + world.rand.nextDouble() * 2 - 1;
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1, 0).clr(1, 1, 0.3f).spawn(world);
}
ParticleBuilder.create(Type.BUFF).entity(caster).clr(1, 1, 0.3f).spawn(world);
}
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F,
world.rand.nextFloat() * 0.4F + 1.0F);
flag = true;
}
if(world.isRemote) ParticleBuilder.spawnHealParticles(world, target);
playSound(world, target, ticksInUse, -1, modifiers);
flag = true;
}
}
}
@@ -1,9 +1,7 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.IGrowable;
@@ -15,14 +13,18 @@ import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import java.util.List;
public class GrowthAura extends Spell {
public GrowthAura(){
super("growth_aura", Tier.APPRENTICE, Element.EARTH, SpellType.UTILITY, 20, 50, EnumAction.NONE, false);
super("growth_aura", EnumAction.NONE, false);
addProperties(EFFECT_RADIUS);
soundValues(0.7f, 1.2f, 0.2f);
}
@Override
public boolean doesSpellRequirePacket(){
public boolean requiresPacket(){
return false;
}
@@ -31,45 +33,43 @@ public class GrowthAura extends Spell {
boolean flag = false;
for(int i = -2; i < 1; i++){
List<BlockPos> sphere = WizardryUtilities.getBlockSphere(caster.getPosition(),
getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade));
for(int j = -1; j < 2; j++){
for(BlockPos pos : sphere){
int x = (int)caster.posX + i;
int y = WizardryUtilities.getNearestFloorLevelC(world,
new BlockPos(caster.posX + i, caster.posY, caster.posZ + j), 2) - 1;
int z = (int)caster.posZ + j;
IBlockState state = world.getBlockState(pos);
BlockPos pos = new BlockPos(x, y, z);
if(state.getBlock() instanceof IGrowable){
if(y > -1 && caster.getDistance(x, y, z) <= 2){
IGrowable plant = (IGrowable)state.getBlock();
IBlockState state = world.getBlockState(pos);
if(plant.canGrow(world, pos, state, world.isRemote)){
if(state.getBlock() instanceof IGrowable){
IGrowable igrowable = (IGrowable)state.getBlock();
if(igrowable.canGrow(world, pos, state, world.isRemote)){
if(!world.isRemote){
if(igrowable.canUseBonemeal(world, world.rand, pos, state)){
igrowable.grow(world, world.rand, pos, state);
if(!world.isRemote){
if(plant.canUseBonemeal(world, world.rand, pos, state)){
if(world.rand.nextFloat() < 0.35f && ItemArtefact.isArtefactActive(caster, WizardryItems.charm_growth)){
while(plant.canGrow(world, pos, state, false)){
plant.grow(world, world.rand, pos, state);
state = world.getBlockState(pos); // Update the state with the new one
plant = (IGrowable)state.getBlock(); // Update the block with the new one
}
}else{
// Yes, it's meant to be 0, and it automatically changes it to 15.
ItemDye.spawnBonemealParticles(world, pos, 0);
plant.grow(world, world.rand, pos, state);
}
flag = true;
}
}else{
// Yes, it's meant to be 0, and it automatically changes it to 15.
ItemDye.spawnBonemealParticles(world, pos, 0);
}
flag = true;
}
}
}
if(flag) WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_HEAL, 0.7F,
world.rand.nextFloat() * 0.4F + 1.0F);
if(flag) this.playSound(world, caster, ticksInUse, -1, modifiers);
return flag;
}
@@ -1,23 +1,20 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.construct.EntityHailstorm;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.EnumFacing;
import net.minecraft.world.World;
public class Hailstorm extends SpellConstructRanged<EntityHailstorm> {
public Hailstorm(){
super("hailstorm", Tier.MASTER, Element.ICE, SpellType.ATTACK, 75, 300, EntityHailstorm::new, 120, 20, WizardrySounds.SPELL_ICE);
super("hailstorm", EntityHailstorm::new, false);
this.floor(true);
}
@Override
protected boolean spawnConstruct(World world, double x, double y, double z, EntityLivingBase caster, SpellModifiers modifiers){
protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){
// Moves the entity back towards the caster a bit, so the area of effect is better centred on the position.
// 3 is the distance to move the entity back towards the caster.
@@ -29,13 +26,13 @@ public class Hailstorm extends SpellConstructRanged<EntityHailstorm> {
// Moves the entity up 5 blocks so that it is above mobs' heads.
y += 5;
return super.spawnConstruct(world, x, y, z, caster, modifiers);
return super.spawnConstruct(world, x, y, z, side, caster, modifiers);
}
@Override
protected void addConstructExtras(EntityHailstorm construct, EntityLivingBase caster, SpellModifiers modifiers){
protected void addConstructExtras(EntityHailstorm construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){
// Makes the arrows shoot in the direction the caster was looking when they cast the spell.
construct.rotationYaw = caster.rotationYawHead;
if(caster != null) construct.rotationYaw = caster.rotationYawHead;
}
}
@@ -1,24 +1,21 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
public class Heal extends SpellBuff {
public Heal(){
super("heal", Tier.BASIC, Element.HEALING, SpellType.DEFENCE, 5, 20, WizardrySounds.SPELL_HEAL, 1, 1, 0.3f);
super("heal", 1, 1, 0.3f);
this.soundValues(0.7f, 1.2f, 0.4f);
addProperties(HEALTH);
}
@Override
protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){
if(caster.getHealth() < caster.getMaxHealth() && caster.getHealth() > 0){
caster.heal(4 * modifiers.get(SpellModifiers.POTENCY));
caster.heal(getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY));
return true;
}
@@ -1,29 +1,26 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class HealAlly extends SpellRay {
public HealAlly(){
super("heal_ally", Tier.APPRENTICE, Element.HEALING, SpellType.DEFENCE, 10, 20, false, 10, WizardrySounds.SPELL_HEAL);
super("heal_ally", false, EnumAction.NONE);
this.soundValues(0.7f, 1.2f, 0.4f);
addProperties(HEALTH);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
@@ -31,21 +28,10 @@ public class HealAlly extends SpellRay {
if(entity.getHealth() < entity.getMaxHealth() && entity.getHealth() > 0){
entity.heal((int)(5 * modifiers.get(SpellModifiers.POTENCY)));
entity.heal(getProperty(HEALTH).floatValue() * modifiers.get(SpellModifiers.POTENCY));
if(world.isRemote){
float r = 1; float g = 1; float b = 0.3f;
for(int i = 0; i < 10; i++){
double x1 = (double)((float)entity.posX + world.rand.nextFloat() * 2 - 1.0f);
double y1 = (double)((float)entity.getEntityBoundingBox().minY + entity.getEyeHeight() - 0.5f + world.rand.nextFloat());
double z1 = (double)((float)entity.posZ + world.rand.nextFloat() * 2 - 1.0f);
ParticleBuilder.create(Type.SPARKLE).pos(x1, y1, z1).vel(0, 0.1F, 0).clr(r, g, b).spawn(world);
}
ParticleBuilder.create(Type.BUFF).entity(caster).clr(r, g, b).spawn(world);
}
if(world.isRemote) ParticleBuilder.spawnHealParticles(world, entity);
playSound(world, entity, ticksInUse, -1, modifiers);
}
return true;
@@ -55,12 +41,12 @@ public class HealAlly extends SpellRay {
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@@ -1,163 +1,89 @@
package electroblob.wizardry.spell;
import java.util.List;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.block.BlockStatue;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.tileentity.TileEntityStatue;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.EntityBlaze;
import net.minecraft.entity.monster.EntityMagmaCube;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import java.util.List;
public class IceAge extends Spell {
private static final int baseDuration = 1200;
public IceAge(){
super("ice_age", Tier.MASTER, Element.ICE, SpellType.ATTACK, 70, 250, EnumAction.BOW, false);
super("ice_age", EnumAction.BOW, false);
this.soundValues(0.7f, 1.0f, 0);
addProperties(EFFECT_RADIUS, EFFECT_DURATION);
}
@Override
public boolean doesSpellRequirePacket(){
public boolean requiresPacket(){
return false;
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(
7 * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world);
float radius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade);
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(radius, caster.posX, caster.posY, caster.posZ, world);
for(EntityLivingBase target : targets){
if(WizardryUtilities.isValidTarget(caster, target)){
if(AllyDesignationSystem.isValidTarget(caster, target)){
if(!world.isRemote){
if(target instanceof EntityBlaze || target instanceof EntityMagmaCube){
// These have been removed for the time being because they cause the entity to sink into the
// floor when it breaks out.
// target.attackEntityFrom(WizardryUtilities.causePlayerMagicDamage(entityplayer), 8.0f *
// modifiers.get(SpellModifiers.DAMAGE));
}else{
// target.attackEntityFrom(WizardryUtilities.causePlayerMagicDamage(entityplayer), 4.0f *
// modifiers.get(SpellModifiers.DAMAGE));
}
if(target.isBurning()){
target.extinguish();
}
if(target instanceof EntityBlaze) WizardryAdvancementTriggers.freeze_blaze.triggerFor(caster);
if(target instanceof EntityLiving){
// Stops the entity looking red while frozen and the resulting z-fighting
target.hurtTime = 0;
BlockPos pos = new BlockPos(target);
// Short mobs such as spiders and pigs
if((target.height < 1.2 || target.isChild())
&& WizardryUtilities.canBlockBeReplaced(world, pos)){
world.setBlockState(pos, WizardryBlocks.ice_statue.getDefaultState());
if(world.getTileEntity(pos) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart((EntityLiving)target, 1,
1);
((TileEntityStatue)world.getTileEntity(pos)).setLifetime(
(int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade)));
}
target.setDead();
target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F);
}
// Normal sized mobs like zombies and skeletons
else if(target.height < 2.5 && WizardryUtilities.canBlockBeReplaced(world, pos)
&& WizardryUtilities.canBlockBeReplaced(world, pos.up())){
world.setBlockState(pos, WizardryBlocks.ice_statue.getDefaultState());
if(world.getTileEntity(pos) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart((EntityLiving)target, 1,
2);
((TileEntityStatue)world.getTileEntity(pos)).setLifetime(
(int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade)));
}
world.setBlockState(pos.up(), WizardryBlocks.ice_statue.getDefaultState());
if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos.up()))
.setCreatureAndPart((EntityLiving)target, 2, 2);
}
target.setDead();
target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F);
}
// Tall mobs like endermen
else if(WizardryUtilities.canBlockBeReplaced(world, pos)
&& WizardryUtilities.canBlockBeReplaced(world, pos.up())
&& WizardryUtilities.canBlockBeReplaced(world, pos.up(2))){
world.setBlockState(pos, WizardryBlocks.ice_statue.getDefaultState());
if(world.getTileEntity(pos) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos)).setCreatureAndPart((EntityLiving)target, 1,
3);
((TileEntityStatue)world.getTileEntity(pos)).setLifetime(
(int)(baseDuration * modifiers.get(WizardryItems.duration_upgrade)));
}
world.setBlockState(pos.up(), WizardryBlocks.ice_statue.getDefaultState());
if(world.getTileEntity(pos.up()) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos.up()))
.setCreatureAndPart((EntityLiving)target, 2, 3);
}
world.setBlockState(pos.up(2), WizardryBlocks.ice_statue.getDefaultState());
if(world.getTileEntity(pos.up(2)) instanceof TileEntityStatue){
((TileEntityStatue)world.getTileEntity(pos.up(2)))
.setCreatureAndPart((EntityLiving)target, 3, 3);
}
target.setDead();
target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F);
if(((BlockStatue)WizardryBlocks.ice_statue).convertToStatue((EntityLiving)target,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))){
target.playSound(WizardrySounds.MISC_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F);
}
}
}
}
}
if(!world.isRemote){
for(int i = -7; i < 8; i++){
for(int j = -7; j < 8; j++){
if(!world.isRemote && WizardryUtilities.canDamageBlocks(caster, world)){
for(int i = -(int)radius; i < (int)radius + 1; i++){
for(int j = -(int)radius; j < (int)radius + 1; j++){
BlockPos pos = new BlockPos(caster).add(i, 0, j);
int y = WizardryUtilities.getNearestFloorLevelB(world, new BlockPos(pos), 7);
Integer y = WizardryUtilities.getNearestSurface(world, new BlockPos(pos), EnumFacing.UP, (int)radius, true, WizardryUtilities.SurfaceCriteria.BUILDABLE);
pos = new BlockPos(pos.getX(), y, pos.getZ());
if(y != null){
double dist = caster.getDistance((int)caster.posX + i, y, (int)caster.posZ + j);
pos = new BlockPos(pos.getX(), y, pos.getZ());
// Randomised with weighting so that the nearer the block the more likely it is to be snowed.
if(y != -1 && world.rand.nextInt((int)dist * 2 + 1) < 7 && dist < 8){
if(world.getBlockState(pos.down()) == Blocks.WATER.getDefaultState()){
world.setBlockState(pos.down(), Blocks.ICE.getDefaultState());
}else if(world.getBlockState(pos.down()) == Blocks.LAVA.getDefaultState()){
world.setBlockState(pos.down(), Blocks.OBSIDIAN.getDefaultState());
}else if(world.getBlockState(pos.down()) == Blocks.FLOWING_LAVA.getDefaultState()){
world.setBlockState(pos.down(), Blocks.COBBLESTONE.getDefaultState());
}else if(Blocks.SNOW_LAYER.canPlaceBlockAt(world, pos)){
world.setBlockState(pos, Blocks.SNOW_LAYER.getDefaultState());
double dist = caster.getDistance((int)caster.posX + i, y, (int)caster.posZ + j);
// Randomised with weighting so that the nearer the block the more likely it is to be snowed.
if(y != -1 && world.rand.nextInt((int)dist * 2 + 1) < radius && dist < radius){
if(world.getBlockState(pos.down()) == Blocks.WATER.getDefaultState()){
world.setBlockState(pos.down(), Blocks.ICE.getDefaultState());
}else if(world.getBlockState(pos.down()) == Blocks.LAVA.getDefaultState()){
world.setBlockState(pos.down(), Blocks.OBSIDIAN.getDefaultState());
}else if(world.getBlockState(pos.down()) == Blocks.FLOWING_LAVA.getDefaultState()){
world.setBlockState(pos.down(), Blocks.COBBLESTONE.getDefaultState());
}else if(Blocks.SNOW_LAYER.canPlaceBlockAt(world, pos)){
world.setBlockState(pos, Blocks.SNOW_LAYER.getDefaultState());
}
}
}
}
}
}
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_ICE, 0.7F, 1.0f);
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_WIND, 1.0F, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -1,49 +1,62 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.construct.EntityIceSpike;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class IceSpikes extends SpellConstructRanged<EntityIceSpike> {
private static final int BASE_SPAWN_COUNT = 17; // Was 18, reduced to 17 to account for the extra one in the middle.
public static final String ICE_SPIKE_COUNT = "ice_spike_count";
public IceSpikes(){
// Base duration is set to -1 so that the superclass doesn't waste time retrieving the duration multiplier
super("ice_spikes", Tier.ADVANCED, Element.ICE, SpellType.ATTACK, 30, 75, EntityIceSpike::new, -1, 20, WizardrySounds.SPELL_ICE);
super("ice_spikes", EntityIceSpike::new, true);
addProperties(EFFECT_RADIUS, ICE_SPIKE_COUNT, DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH);
this.ignoreUncollidables(true);
}
@Override
protected boolean spawnConstruct(World world, double x, double y, double z, EntityLivingBase caster, SpellModifiers modifiers){
if(world.getBlockState(new BlockPos(x, y, z)).isNormalCube()) return false;
protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){
BlockPos blockHit = new BlockPos(x, y, z);
if(side != null && side.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE) blockHit = blockHit.offset(side);
if(world.getBlockState(blockHit).isNormalCube()) return false;
Vec3d origin = new Vec3d(x, y, z);
Vec3d pos = origin.add(new Vec3d(side.getOpposite().getDirectionVec()));
// Now always spawns a spike exactly at the position aimed at
super.spawnConstruct(world, x, y-1, z, caster, modifiers);
int quantity = (int)(BASE_SPAWN_COUNT * modifiers.get(WizardryItems.blast_upgrade));
super.spawnConstruct(world, pos.x, pos.y, pos.z, side, caster, modifiers);
// -1 because of the one spawned above
int quantity = (int)(getProperty(ICE_SPIKE_COUNT).floatValue() * modifiers.get(WizardryItems.blast_upgrade)) - 1;
float maxRadius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade);
for(int i=0; i<quantity; i++){
float angle = (float)(world.rand.nextFloat() * Math.PI * 2);
double radius = 0.5 + world.rand.nextDouble() * 2 * modifiers.get(WizardryItems.blast_upgrade);
double x1 = x + radius * MathHelper.sin(angle);
double z1 = z + radius * MathHelper.cos(angle);
double y1 = WizardryUtilities.getNearestFloorLevel(world,
new BlockPos(MathHelper.floor(x1), (int)y, MathHelper.floor(z1)), 2) - 1;
double radius = 0.5 + world.rand.nextDouble() * (maxRadius - 0.5);
if(y1 > -1){
super.spawnConstruct(world, x1, y1, z1, caster, modifiers);
// First, generate a random vector of length radius with a z component of zero
// Then rotate it so that what was south is now the side that was hit
Vec3d offset = Vec3d.fromPitchYaw(world.rand.nextFloat() * 180 - 90, world.rand.nextBoolean() ? 0 : 180)
.scale(radius).rotateYaw(side.getHorizontalAngle() * (float)Math.PI/180).rotatePitch(WizardryUtilities.getPitch(side) * (float)Math.PI/180);
if(side.getAxis().isHorizontal()) offset = offset.rotateYaw((float)Math.PI/2);
Integer surface = WizardryUtilities.getNearestSurface(world, new BlockPos(origin.add(offset)), side,
(int)maxRadius, true, WizardryUtilities.SurfaceCriteria.basedOn(World::isBlockFullCube));
if(surface != null){
Vec3d vec = WizardryUtilities.replaceComponent(origin.add(offset), side.getAxis(), surface)
.subtract(new Vec3d(side.getDirectionVec()));
super.spawnConstruct(world, vec.x, vec.y, vec.z, side, caster, modifiers);
}
}
@@ -51,9 +64,10 @@ public class IceSpikes extends SpellConstructRanged<EntityIceSpike> {
}
@Override
protected void addConstructExtras(EntityIceSpike construct, EntityLivingBase caster, SpellModifiers modifiers){
protected void addConstructExtras(EntityIceSpike construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){
// In this particular case, lifetime is implemented as a delay instead so is treated differently.
construct.lifetime = 30 + construct.world.rand.nextInt(15);
construct.setFacing(side);
}
}
@@ -1,54 +1,56 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.block.BlockStatue;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class IceStatue extends SpellRay {
private static final int BASE_DURATION = 400;
public IceStatue(){
super("ice_statue", Tier.APPRENTICE, Element.ICE, SpellType.ATTACK, 15, 40, false, 10, WizardrySounds.SPELL_ICE);
super("ice_statue", false, EnumAction.NONE);
this.soundValues(1, 1.4f, 0.4f);
addProperties(EFFECT_DURATION);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected SoundEvent[] createSounds(){
return createSoundsWithSuffixes("shoot", "freeze");
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(target instanceof EntityLiving && !world.isRemote){
// Unchecked cast is fine because the block is a static final field
if(((BlockStatue)WizardryBlocks.ice_statue).convertToStatue((EntityLiving)target,
(int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)))){
target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F);
return true;
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))){
//target.playSound(WizardrySounds.SPELL_FREEZE, 1.0F, world.rand.nextFloat() * 0.4F + 0.8F);
}
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@@ -1,34 +1,32 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class Ignite extends SpellRay {
/** The base duration for which entities are set on fire by this spell. */
private static final int BASE_DURATION = 10;
public Ignite(){
super("ignite", Tier.BASIC, Element.FIRE, SpellType.ATTACK, 5, 10, false, 10, SoundEvents.ITEM_FLINTANDSTEEL_USE);
super("ignite", false, EnumAction.NONE);
this.soundValues(1, 1, 0.4f);
addProperties(BURN_DURATION);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
// Fire can damage armour stands, so this includes them
if(target instanceof EntityLivingBase) {
@@ -36,7 +34,7 @@ public class Ignite extends SpellRay {
if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage(
new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true);
}else{
target.setFire((int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)));
target.setFire((int)(getProperty(BURN_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
}
return true;
@@ -46,8 +44,10 @@ public class Ignite extends SpellRay {
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(!WizardryUtilities.canDamageBlocks(caster, world)) return false;
pos = pos.offset(side);
if(world.isAirBlock(pos)){
@@ -63,7 +63,7 @@ public class Ignite extends SpellRay {
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@@ -1,30 +1,27 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.registry.WizardryEnchantments;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraft.item.*;
import net.minecraft.util.EnumHand;
import net.minecraft.world.World;
import java.util.Arrays;
public class ImbueWeapon extends Spell {
public ImbueWeapon(){
super("imbue_weapon", Tier.APPRENTICE, Element.SORCERY, SpellType.UTILITY, 20, 50, EnumAction.BOW, false);
super("imbue_weapon", EnumAction.BOW, false);
addProperties(EFFECT_DURATION);
}
@Override
@@ -35,29 +32,29 @@ public class ImbueWeapon extends Spell {
for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(caster)){
if(stack.getItem() instanceof ItemSword
if(isSword(stack.getItem())
&& !EnchantmentHelper.getEnchantments(stack).containsKey(WizardryEnchantments.magic_sword)
&& WizardData.get(caster).getImbuementDuration(WizardryEnchantments.magic_sword) <= 0){
// The enchantment level as determined by the damage multiplier. The + 0.5f is so that
// weird float processing doesn't incorrectly round it down.
stack.addEnchantment(WizardryEnchantments.magic_sword, modifiers.get(SpellModifiers.POTENCY) == 1.0f
? 1
: (int)((modifiers.get(SpellModifiers.POTENCY) - 1.0f) / Constants.DAMAGE_INCREASE_PER_TIER
: (int)((modifiers.get(SpellModifiers.POTENCY) - 1.0f) / Constants.POTENCY_INCREASE_PER_TIER
+ 0.5f));
WizardData.get(caster).setImbuementDuration(WizardryEnchantments.magic_sword,
(int)(900 * modifiers.get(WizardryItems.duration_upgrade)));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
}else if(stack.getItem() instanceof ItemBow
}else if(isBow(stack.getItem())
&& !EnchantmentHelper.getEnchantments(stack).containsKey(WizardryEnchantments.magic_bow)
&& WizardData.get(caster).getImbuementDuration(WizardryEnchantments.magic_bow) <= 0){
// The enchantment level as determined by the damage multiplier. The + 0.5f is so that
// weird float processing doesn't incorrectly round it down.
stack.addEnchantment(WizardryEnchantments.magic_bow, modifiers.get(SpellModifiers.POTENCY) == 1.0f
? 1
: (int)((modifiers.get(SpellModifiers.POTENCY) - 1.0f) / Constants.DAMAGE_INCREASE_PER_TIER
: (int)((modifiers.get(SpellModifiers.POTENCY) - 1.0f) / Constants.POTENCY_INCREASE_PER_TIER
+ 0.5f));
WizardData.get(caster).setImbuementDuration(WizardryEnchantments.magic_bow,
(int)(900 * modifiers.get(WizardryItems.duration_upgrade)));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
}else{
continue;
@@ -72,11 +69,21 @@ public class ImbueWeapon extends Spell {
}
}
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1, 1);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
}
return false;
}
/** Returns true if the given item counts as a sword, i.e. it extends {@link ItemSword} or is in the whitelist. */
public static boolean isSword(Item item){
return item instanceof ItemSword || Arrays.asList(Wizardry.settings.swordItemWhitelist).contains(item.getRegistryName());
}
/** Returns true if the given item counts as a bow, i.e. it extends {@link ItemBow} or is in the whitelist. */
public static boolean isBow(Item item){
return item instanceof ItemBow || Arrays.asList(Wizardry.settings.bowItemWhitelist).contains(item.getRegistryName());
}
}
@@ -1,10 +1,5 @@
package electroblob.wizardry.spell;
import java.util.List;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.ParticleBuilder;
@@ -13,11 +8,9 @@ import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityCreature;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.ai.RandomPositionGenerator;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.pathfinding.PathPoint;
@@ -29,17 +22,22 @@ import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.List;
@Mod.EventBusSubscriber
public class Intimidate extends Spell {
/** The NBT tag name for storing the feared entity's UUID in the target's tag compound. */
public static final String NBT_KEY = "fearedEntity";
private static final double BASE_RANGE = 8;
private static final int BASE_DURATION = 600;
// These aren't spell properties because they're part fo the actual potion effect, not the spell itself.
// However, the avoid distance can be modified using the potion amplifier.
private static final double BASE_AVOID_DISTANCE = 16;
private static final double AVOID_DISTANCE_PER_LEVEL = 4;
public Intimidate(){
super("intimidate", Tier.APPRENTICE, Element.NECROMANCY, SpellType.ATTACK, 20, 100, EnumAction.BOW, false);
super("intimidate", EnumAction.BOW, false);
addProperties(EFFECT_RADIUS, EFFECT_DURATION, EFFECT_STRENGTH);
}
@Override
@@ -48,19 +46,21 @@ public class Intimidate extends Spell {
if(!world.isRemote){
List<EntityCreature> entities = WizardryUtilities.getEntitiesWithinRadius(
BASE_RANGE * modifiers.get(WizardryItems.range_upgrade), caster.posX, caster.posY, caster.posZ,
world, EntityCreature.class);
getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.range_upgrade),
caster.posX, caster.posY, caster.posZ, world, EntityCreature.class);
for(EntityCreature target : entities){
// Why do we need this here?
//runAway(target, caster);
runAway(target, caster);
int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY));
NBTTagCompound entityNBT = target.getEntityData();
if(entityNBT != null) entityNBT.setUniqueId(NBT_KEY, caster.getUniqueID());
((EntityLiving)target).addPotionEffect(new PotionEffect(WizardryPotions.fear,
(int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)), 0));
target.addPotionEffect(new PotionEffect(WizardryPotions.fear,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier));
}
}else{
@@ -71,7 +71,7 @@ public class Intimidate extends Spell {
ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.9f, 0.1f, 0).spawn(world);
}
}
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERDRAGON_GROWL, 1.0f, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -81,13 +81,14 @@ public class Intimidate extends Spell {
*
* @param target The entity running away
* @param caster The entity that is being run away from
* @param distance How far the entity will run from the caster
* @return True if a new path was found and set, false if not.
*/
public static boolean runAway(EntityCreature target, EntityLivingBase caster){
public static boolean runAway(EntityCreature target, EntityLivingBase caster, double distance){
if(target.getDistance(caster) < 16){
if(target.getDistance(caster) < distance){
Vec3d Vec3d = RandomPositionGenerator.findRandomTargetBlockAwayFrom(target, 16, 7,
Vec3d Vec3d = RandomPositionGenerator.findRandomTargetBlockAwayFrom(target, (int)distance, (int)(distance/2),
new Vec3d(caster.posX, caster.posY, caster.posZ));
if(Vec3d == null){
@@ -95,15 +96,14 @@ public class Intimidate extends Spell {
}else{
// In both cases it is necessary to check if the entity already has a path so it doesn't change
// direction
// every tick, unless that path is towards the caster.
// direction every tick, unless that path is towards the caster.
// Path path = target.getNavigator().getPathToXYZ(Vec3d.xCoord, Vec3d.yCoord, Vec3d.zCoord);
boolean flag = true;
if(!target.getNavigator().noPath()){
PathPoint point = target.getNavigator().getPath().getFinalPathPoint();
if(point != null) flag = caster.getDistance(point.x, point.y, point.z) < 16;
if(point != null) flag = caster.getDistance(point.x, point.y, point.z) < distance;
}
// Has a built in mind trick effect because for whatever reason this makes it work with skeletons.
target.setAttackTarget(null);
@@ -129,7 +129,9 @@ public class Intimidate extends Spell {
Entity caster = WizardryUtilities.getEntityByUUID(creature.world, entityNBT.getUniqueId(NBT_KEY));
if(caster instanceof EntityLivingBase){
runAway(creature, (EntityLivingBase)caster);
double distance = BASE_AVOID_DISTANCE + AVOID_DISTANCE_PER_LEVEL
* event.getEntityLiving().getActivePotionEffect(WizardryPotions.fear).getAmplifier();
runAway(creature, (EntityLivingBase)caster, distance);
}
}
}
@@ -1,12 +1,7 @@
package electroblob.wizardry.spell;
import java.util.List;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
@@ -16,29 +11,34 @@ import net.minecraft.init.MobEffects;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import java.util.List;
public class InvigoratingPresence extends Spell {
private static final double BASE_RANGE = 5;
private static final int BASE_DURATION = 900;
public InvigoratingPresence(){
super("invigorating_presence", Tier.APPRENTICE, Element.HEALING, SpellType.UTILITY, 30, 60, EnumAction.BOW, false);
super("invigorating_presence", EnumAction.BOW, false);
this.soundValues(0.7f, 1.2f, 0.4f);
addProperties(EFFECT_RADIUS, EFFECT_DURATION, EFFECT_STRENGTH);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
List<EntityPlayer> targets = WizardryUtilities.getEntitiesWithinRadius(
BASE_RANGE * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world,
EntityPlayer.class);
getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade),
caster.posX, caster.posY, caster.posZ, world, EntityPlayer.class);
for(EntityPlayer target : targets){
if(WizardryUtilities.isPlayerAlly(caster, target) || target == caster){
// Strength 2 for 45 seconds.
if(AllyDesignationSystem.isPlayerAlly(caster, target) || target == caster){
int bonusAmplifier = SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY));
target.addPotionEffect(new PotionEffect(MobEffects.STRENGTH,
(int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)), 1, false, false));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + bonusAmplifier));
}
}
@@ -47,18 +47,18 @@ public class InvigoratingPresence extends Spell {
for(int i = 0; i < 50 * modifiers.get(WizardryItems.blast_upgrade); i++){
double radius = (1 + world.rand.nextDouble() * 4) * modifiers.get(WizardryItems.blast_upgrade);
double angle = world.rand.nextDouble() * Math.PI * 2;
float angle = world.rand.nextFloat() * (float)Math.PI * 2;;
double x = caster.posX + radius * Math.cos(angle);
double x = caster.posX + radius * MathHelper.cos(angle);
double y = caster.getEntityBoundingBox().minY;
double z = caster.posZ + radius * Math.sin(angle);
double z = caster.posZ + radius * MathHelper.sin(angle);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.03, 0).time(50).clr(1, 0.2f, 0.2f).spawn(world);
}
}
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1, 1 + 0.2f * world.rand.nextFloat());
playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -1,25 +1,26 @@
package electroblob.wizardry.spell;
import java.util.Random;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import java.util.Random;
public class InvokeWeather extends Spell {
public static final String THUNDERSTORM_CHANCE = "thunderstorm_chance";
public InvokeWeather(){
super("invoke_weather", Tier.ADVANCED, Element.LIGHTNING, SpellType.UTILITY, 30, 100, EnumAction.BOW, false);
super("invoke_weather", EnumAction.BOW, false);
addProperties(THUNDERSTORM_CHANCE);
soundValues(0.5f, 1, 0);
}
@Override
@@ -32,20 +33,21 @@ public class InvokeWeather extends Spell {
int standardWeatherTime = (300 + (new Random()).nextInt(600)) * 20;
if(world.isRaining()){
caster.sendMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".sun"));
caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".sun"), true);
world.getWorldInfo().setCleanWeatherTime(standardWeatherTime);
world.getWorldInfo().setRainTime(0);
world.getWorldInfo().setThunderTime(0);
world.getWorldInfo().setRaining(false);
world.getWorldInfo().setThundering(false);
}else{
caster.sendMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".rain"));
caster.sendStatusMessage(new TextComponentTranslation("spell." + this.getUnlocalisedName() + ".rain"), true);
world.getWorldInfo().setCleanWeatherTime(0);
world.getWorldInfo().setRainTime(standardWeatherTime);
world.getWorldInfo().setThunderTime(standardWeatherTime);
world.getWorldInfo().setRaining(true);
// 1/3 chance for a thunderstorm
world.getWorldInfo().setThundering(world.rand.nextInt(3) == 0);
// Thunderstorm is guaranteed if the caster has a bottled thundercloud charm equipped
world.getWorldInfo().setThundering(ItemArtefact.isArtefactActive(caster, WizardryItems.charm_storm)
|| world.rand.nextFloat() < getProperty(THUNDERSTORM_CHANCE).floatValue());
}
}
@@ -58,7 +60,7 @@ public class InvokeWeather extends Spell {
}
}
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_LIGHTNING_THUNDER, 0.5f, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -1,12 +1,7 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
@@ -14,8 +9,13 @@ import net.minecraft.world.World;
public class Leap extends Spell {
public static final String HORIZONTAL_SPEED = "horizontal_speed";
public static final String VERTICAL_SPEED = "vertical_speed";
public Leap(){
super("leap", Tier.BASIC, Element.EARTH, SpellType.UTILITY, 10, 20, EnumAction.NONE, false);
super("leap", EnumAction.NONE, false);
addProperties(HORIZONTAL_SPEED, VERTICAL_SPEED);
soundValues(0.5f, 1, 0);
}
@Override
@@ -23,19 +23,20 @@ public class Leap extends Spell {
if(caster.onGround){
caster.motionY = 0.65 * modifiers.get(SpellModifiers.POTENCY);
caster.addVelocity(caster.getLookVec().x * 0.3, 0, caster.getLookVec().z * 0.3);
caster.motionY = getProperty(VERTICAL_SPEED).floatValue() * modifiers.get(SpellModifiers.POTENCY);
double horizontalSpeed = getProperty(HORIZONTAL_SPEED).floatValue();
caster.addVelocity(caster.getLookVec().x * horizontalSpeed, 0, caster.getLookVec().z * horizontalSpeed);
if(world.isRemote){
for(int i = 0; i < 10; i++){
double x = (double)(caster.posX + world.rand.nextFloat() - 0.5F);
double y = (double)(caster.getEntityBoundingBox().minY);
double z = (double)(caster.posZ + world.rand.nextFloat() - 0.5F);
double x = caster.posX + world.rand.nextFloat() - 0.5F;
double y = caster.getEntityBoundingBox().minY;
double z = caster.posZ + world.rand.nextFloat() - 0.5F;
world.spawnParticle(EnumParticleTypes.CLOUD, x, y, z, 0, 0, 0);
}
}
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERDRAGON_FLAP, 0.5F, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
caster.swingArm(hand);
return true;
}
@@ -1,30 +1,49 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.world.World;
public class Levitation extends Spell {
public static final String SPEED = "speed";
public static final String ACCELERATION = "acceleration";
public Levitation(){
super("levitation", Tier.ADVANCED, Element.SORCERY, SpellType.UTILITY, 10, 0, EnumAction.BOW, true);
super("levitation", EnumAction.BOW, true);
addProperties(SPEED, ACCELERATION);
soundValues(0.5f, 1, 0);
}
@Override
protected SoundEvent[] createSounds(){
return this.createContinuousSpellSounds();
}
@Override
protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, entity, ticksInUse);
}
@Override
protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, x, y, z, ticksInUse, duration);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
caster.fallDistance = 0;
if(!Wizardry.settings.replaceVanillaFallDamage) caster.fallDistance = 0;
caster.motionY = caster.motionY < 0.5 ? caster.motionY + 0.1 : caster.motionY;
caster.motionY = caster.motionY < getProperty(SPEED).floatValue() ? caster.motionY
+ getProperty(ACCELERATION).floatValue() : caster.motionY;
if(world.isRemote){
double x = caster.posX - 0.25 + world.rand.nextDouble() * 0.5;
@@ -32,9 +51,9 @@ public class Levitation extends Spell {
double z = caster.posZ - 0.25 + world.rand.nextDouble() * 0.5;
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, -0.1, 0).time(15).clr(0.5f, 1, 0.7f).spawn(world);
}
if(ticksInUse % 24 == 0 && world.isRemote){
Wizardry.proxy.playMovingSound(caster, WizardrySounds.SPELL_LOOP_SPARKLE, 0.5f, 1, false);
}
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -1,9 +1,5 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
@@ -11,79 +7,67 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class LifeDrain extends SpellRay {
private static final float BASE_DAMAGE = 2;
/** The fraction of the damage dealt that is transferred to the caster as healing. */
private static final float HEAL_FACTOR = 0.35f;
public static final String HEAL_FACTOR = "heal_factor";
public LifeDrain(){
super("life_drain", Tier.APPRENTICE, Element.NECROMANCY, SpellType.ATTACK, 10, 0, true, 10, null);
super("life_drain", true, EnumAction.NONE);
this.particleVelocity(-0.5);
this.particleSpacing(0.4);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
// TODO: Temporary solution until I implement a better continuous sound system
boolean flag = super.cast(world, caster, hand, ticksInUse, modifiers);
if(flag){
if(ticksInUse % 18 == 0){
if(ticksInUse == 0) WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SUMMONING, 1, 0.6f);
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_CRACKLE, 2, 1);
}
}
return flag;
addProperties(DAMAGE, HEAL_FACTOR);
this.soundValues(0.6f, 1, 0);
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){
boolean flag = super.cast(world, caster, hand, ticksInUse, target, modifiers);
if(flag){
if(ticksInUse % 18 == 0){
if(ticksInUse == 0) caster.playSound(WizardrySounds.SPELL_SUMMONING, 1, 0.6f);
caster.playSound(WizardrySounds.SPELL_LOOP_CRACKLE, 2, 1);
}
}
return flag;
protected SoundEvent[] createSounds(){
return this.createContinuousSpellSounds();
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, entity, ticksInUse);
}
@Override
protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, x, y, z, ticksInUse, duration);
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
if(ticksInUse % 12 == 0){
float damage = BASE_DAMAGE * modifiers.get(SpellModifiers.POTENCY);
float damage = getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY);
WizardryUtilities.attackEntityWithoutKnockback(target, MagicDamage.causeDirectMagicDamage(caster,
DamageType.MAGIC), damage);
caster.heal(damage * HEAL_FACTOR);
if(caster != null) caster.heal(damage * getProperty(HEAL_FACTOR).floatValue());
}
return true;
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@@ -1,14 +1,11 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.tileentity.TileEntityTimer;
import electroblob.wizardry.util.RayTracer;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
@@ -19,18 +16,21 @@ import net.minecraft.world.World;
public class Light extends Spell {
public Light(){
super("light", Tier.BASIC, Element.SORCERY, SpellType.UTILITY, 5, 15, EnumAction.NONE, false);
super("light", EnumAction.NONE, false);
addProperties(RANGE, DURATION);
}
@Override
public boolean doesSpellRequirePacket(){
public boolean requiresPacket(){
return false;
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
RayTraceResult rayTrace = WizardryUtilities.standardBlockRayTrace(world, caster, 4, false);
double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade);
RayTraceResult rayTrace = RayTracer.standardBlockRayTrace(world, caster, range, false);
if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){
@@ -41,20 +41,21 @@ public class Light extends Spell {
if(!world.isRemote){
world.setBlockState(pos, WizardryBlocks.magic_light.getDefaultState());
if(world.getTileEntity(pos) instanceof TileEntityTimer){
((TileEntityTimer)world.getTileEntity(pos))
.setLifetime((int)(600 * modifiers.get(WizardryItems.duration_upgrade)));
int lifetime = ItemArtefact.isArtefactActive(caster, WizardryItems.charm_light) ? -1
: (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade));
((TileEntityTimer)world.getTileEntity(pos)).setLifetime(lifetime);
}
}
caster.swingArm(hand);
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
}else{
int x = (int)(Math.floor(caster.posX) + caster.getLookVec().x * 4);
int y = (int)(Math.floor(caster.posY) + caster.eyeHeight + caster.getLookVec().y * 4);
int z = (int)(Math.floor(caster.posZ) + caster.getLookVec().z * 4);
int x = (int)(Math.floor(caster.posX) + caster.getLookVec().x * range);
int y = (int)(Math.floor(caster.posY) + caster.eyeHeight + caster.getLookVec().y * range);
int z = (int)(Math.floor(caster.posZ) + caster.getLookVec().z * range);
BlockPos pos = new BlockPos(x, y, z);
@@ -62,12 +63,13 @@ public class Light extends Spell {
if(!world.isRemote){
world.setBlockState(pos, WizardryBlocks.magic_light.getDefaultState());
if(world.getTileEntity(pos) instanceof TileEntityTimer){
((TileEntityTimer)world.getTileEntity(pos))
.setLifetime((int)(600 * modifiers.get(WizardryItems.duration_upgrade)));
int lifetime = ItemArtefact.isArtefactActive(caster, WizardryItems.charm_light) ? -1
: (int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade));
((TileEntityTimer)world.getTileEntity(pos)).setLifetime(lifetime);
}
}
caster.swingArm(hand);
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
}
@@ -1,52 +1,51 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.effect.EntityLightningBolt;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.passive.EntityPig;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.EntityStruckByLightningEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
//@Mod.EventBusSubscriber
public class LightningBolt extends SpellRay {
/** The NBT key used to store the UUID of the player that summoned the lightning bolt. Used for achievements. */
public static final String NBT_KEY = "summoningPlayer";
public LightningBolt(){
super("lightning_bolt", Tier.ADVANCED, Element.LIGHTNING, SpellType.ATTACK, 40, 80, false, 200, null);
this.ignoreEntities(true);
super("lightning_bolt", false, EnumAction.NONE);
this.ignoreLivingEntities(true);
}
@Override public boolean doesSpellRequirePacket(){ return false; }
@Override public boolean requiresPacket(){ return false; }
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(world.canBlockSeeSky(pos.up())){
if(!world.isRemote){
// Temporarily disable the fire tick gamerule if player block damage is disabled
// Bit of a hack but it works fine!
boolean doFireTick = world.getGameRules().getBoolean("doFireTick");
if(doFireTick && !Wizardry.settings.playerBlockDamage) world.getGameRules().setOrCreateGameRule("doFireTick", "false");
EntityLightningBolt entitylightning = new EntityLightningBolt(world, pos.getX(), pos.getY(),
pos.getZ(), false);
world.addWeatherEffect(entitylightning);
// Reset doFireTick to true if it was true before
if(doFireTick && !Wizardry.settings.playerBlockDamage) world.getGameRules().setOrCreateGameRule("doFireTick", "true");
// Code for eventhandler recognition for achievements
if(caster instanceof EntityPlayer){
@@ -62,26 +61,26 @@ public class LightningBolt extends SpellRay {
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@SubscribeEvent
public static void onEntityStruckByLightningEvent(EntityStruckByLightningEvent event){
if(event.getLightning().getEntityData() != null && event.getLightning().getEntityData().hasUniqueId(NBT_KEY)){
EntityPlayer player = (EntityPlayer)WizardryUtilities.getEntityByUUID(event.getLightning().world,
event.getLightning().getEntityData().getUniqueId("summoningPlayer"));
if(event.getEntity() instanceof EntityCreeper){
WizardryAdvancementTriggers.charge_creeper.triggerFor(player);
}
if(event.getEntity() instanceof EntityPig){
WizardryAdvancementTriggers.frankenstein.triggerFor(player);
}
}
}
// @SubscribeEvent
// public static void onEntityStruckByLightningEvent(EntityStruckByLightningEvent event){
//
// if(event.getLightning().getEntityData() != null && event.getLightning().getEntityData().hasUniqueId(NBT_KEY)){
//
// EntityPlayer player = (EntityPlayer)WizardryUtilities.getEntityByUUID(event.getLightning().world,
// event.getLightning().getEntityData().getUniqueId("summoningPlayer"));
//
// if(event.getEntity() instanceof EntityCreeper){
// WizardryAdvancementTriggers.charge_creeper.triggerFor(player);
// }
//
// if(event.getEntity() instanceof EntityPig){
// WizardryAdvancementTriggers.frankenstein.triggerFor(player);
// }
// }
// }
}
@@ -1,33 +1,33 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.construct.EntityHammer;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class LightningHammer extends SpellConstructRanged<EntityHammer> {
public static final String ATTACK_INTERVAL = "attack_interval";
public static final String SECONDARY_MAX_TARGETS = "secondary_max_targets";
public LightningHammer(){
super("lightning_hammer", Tier.MASTER, Element.LIGHTNING, SpellType.ATTACK, 100, 300, EntityHammer::new, 600, 40, WizardrySounds.SPELL_SUMMONING);
super("lightning_hammer", EntityHammer::new, false);
this.soundValues(3, 1, 0);
this.floor(true);
this.overlap(true);
addProperties(EFFECT_RADIUS, SECONDARY_MAX_TARGETS, ATTACK_INTERVAL, DIRECT_DAMAGE, SPLASH_DAMAGE);
}
@Override
protected boolean spawnConstruct(World world, double x, double y, double z, EntityLivingBase caster, SpellModifiers modifiers){
// TESTME: Does this need to be one block higher?
protected boolean spawnConstruct(World world, double x, double y, double z, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){
if(!world.canBlockSeeSky(new BlockPos(x, y, z))) return false;
return super.spawnConstruct(world, x, y + 50, z, caster, modifiers);
return super.spawnConstruct(world, x, y + 50, z, side, caster, modifiers);
}
@Override
protected void addConstructExtras(EntityHammer construct, EntityLivingBase caster, SpellModifiers modifiers){
protected void addConstructExtras(EntityHammer construct, EnumFacing side, EntityLivingBase caster, SpellModifiers modifiers){
construct.motionY = -2;
}
@@ -1,56 +1,52 @@
package electroblob.wizardry.spell;
import java.util.List;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.EnumAction;
import net.minecraft.network.play.server.SPacketEntityVelocity;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import java.util.List;
public class LightningPulse extends Spell {
private static final double BASE_RADIUS = 3;
private static final float BASE_DAMAGE = 8;
public static final String REPULSION_VELOCITY = "repulsion_velocity";
public LightningPulse(){
super("lightning_pulse", Tier.ADVANCED, Element.LIGHTNING, SpellType.ATTACK, 25, 75, EnumAction.NONE, false);
}
@Override
public boolean doesSpellRequirePacket(){
return false;
super("lightning_pulse", EnumAction.NONE, false);
addProperties(EFFECT_RADIUS, DAMAGE, REPULSION_VELOCITY);
this.soundValues(2, 1, 0);
}
// TODO: NPC casting support
@Override
protected SoundEvent[] createSounds(){
return createSoundsWithSuffixes("spark", "explosion");
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
if(caster.onGround){
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(
BASE_RADIUS * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world);
getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade),
caster.posX, caster.posY, caster.posZ, world);
for(EntityLivingBase target : targets){
if(WizardryUtilities.isValidTarget(caster, target)){
if(AllyDesignationSystem.isValidTarget(caster, target)){
// Base damage is 4 hearts no matter where the target is.
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK),
BASE_DAMAGE * modifiers.get(SpellModifiers.POTENCY));
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
if(!world.isRemote){
@@ -61,9 +57,9 @@ public class LightningPulse extends Spell {
dx /= vectorLength;
dz /= vectorLength;
target.motionX = 0.8 * dx;
target.motionX = getProperty(REPULSION_VELOCITY).floatValue() * dx;
target.motionY = 0;
target.motionZ = 0.8 * dz;
target.motionZ = getProperty(REPULSION_VELOCITY).floatValue() * dz;
// Player motion is handled on that player's client so needs packets
if(target instanceof EntityPlayerMP){
@@ -80,8 +76,7 @@ public class LightningPulse extends Spell {
}
caster.swingArm(hand);
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LIGHTNING, 1, 1);
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SHOCKWAVE, 2, 1);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
return false;
@@ -1,9 +1,5 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
@@ -11,100 +7,90 @@ import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class LightningRay extends SpellRay {
private static final float BASE_DAMAGE = 3;
public LightningRay(){
super("lightning_ray", Tier.APPRENTICE, Element.LIGHTNING, SpellType.ATTACK, 5, 0, true, 10, null);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
// TODO: Temporary solution until I implement a better continuous sound system
boolean flag = super.cast(world, caster, hand, ticksInUse, modifiers);
if(flag){
if(ticksInUse == 1){
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LIGHTNING, 1, 1);
}else if(ticksInUse > 0 && ticksInUse % 20 == 0){
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_LIGHTNING, 1, 1);
}
}
return flag;
super("lightning_ray", true, EnumAction.NONE);
this.aimAssist(0.6f);
addProperties(DAMAGE);
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){
boolean flag = super.cast(world, caster, hand, ticksInUse, target, modifiers);
if(flag){
if(ticksInUse == 1){
caster.playSound(WizardrySounds.SPELL_LIGHTNING, 1, 1);
}else if(ticksInUse > 0 && ticksInUse % 20 == 0){
caster.playSound(WizardrySounds.SPELL_LOOP_LIGHTNING, 1, 1);
}
}
return flag;
protected SoundEvent[] createSounds(){
return this.createContinuousSpellSounds();
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, entity, ticksInUse);
}
@Override
protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, x, y, z, ticksInUse, duration);
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){
if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer)
((EntityPlayer)caster).sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(),
this.getNameForTranslationFormatted()), true);
}else{
// This now only damages in line with the maxHurtResistantTime. Some mods don't play nicely and fiddle
// with this mechanic for their own purposes, so this line makes sure that doesn't affect wizardry.
}else if(ticksInUse % ((EntityLivingBase)target).maxHurtResistantTime == 1){
WizardryUtilities.attackEntityWithoutKnockback(target,
MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK),
BASE_DAMAGE * modifiers.get(SpellModifiers.POTENCY));
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
}
if(world.isRemote){
if(ticksInUse % 3 == 0) ParticleBuilder.create(Type.LIGHTNING).entity(caster)
.pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(target).spawn(world);
// Particle effect
for(int i=0; i<5; i++){
ParticleBuilder.create(Type.SPARK, target).spawn(world);
}
}
return true;
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
// This is a nice example of when onMiss is used for more than just returning a boolean
if(world.isRemote && ticksInUse % 4 == 0){
if(caster != null) origin = origin.subtract(0, Y_OFFSET, 0);
double freeRange = 0.8 * baseRange; // The arc does not reach full range when it has a free end
Vec3d endpoint = origin.add(direction.scale(freeRange));
ParticleBuilder.create(Type.LIGHTNING).entity(caster)
.pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(endpoint).spawn(world);
// The arc does not reach full range when it has a free end
double freeRange = 0.8 * getRange(world, origin, direction, caster, ticksInUse, modifiers);
if(caster != null){
ParticleBuilder.create(Type.LIGHTNING).entity(caster).pos(origin.subtract(caster.getPositionVector()))
.length(freeRange).spawn(world);
}else{
ParticleBuilder.create(Type.LIGHTNING).pos(origin).target(origin.add(direction.scale(freeRange))).spawn(world);
}
}
return true;
@@ -1,146 +1,138 @@
package electroblob.wizardry.spell;
import java.util.List;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class LightningWeb extends SpellRay {
private static final float PRIMARY_DAMAGE = 5;
private static final float SECONDARY_DAMAGE = 4;
private static final float TERTIARY_DAMAGE = 3;
import java.util.List;
private static final double PRIMARY_RANGE = 10;
private static final double SECONDARY_RANGE = 5;
private static final double TERTIARY_RANGE = 5;
private static final int SECONDARY_MAX_TARGETS = 5;
private static final int TERTIARY_MAX_TARGETS = 2; // This is per secondary target, giving 10 in total
public class LightningWeb extends SpellRay {
public static final String PRIMARY_DAMAGE = "primary_damage";
public static final String SECONDARY_DAMAGE = "secondary_damage";
public static final String TERTIARY_DAMAGE = "tertiary_damage";
public static final String SECONDARY_RANGE = "secondary_range";
public static final String TERTIARY_RANGE = "tertiary_range";
public static final String SECONDARY_MAX_TARGETS = "secondary_max_targets";
public static final String TERTIARY_MAX_TARGETS = "tertiary_max_targets"; // This is per secondary target
public LightningWeb(){
super("lightning_web", Tier.MASTER, Element.LIGHTNING, SpellType.ATTACK, 15, 0, true, PRIMARY_RANGE, null);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
// TODO: Temporary solution until I implement a better continuous sound system
boolean flag = super.cast(world, caster, hand, ticksInUse, modifiers);
if(flag){
if(ticksInUse == 1){
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LIGHTNING, 1, 1);
}else if(ticksInUse > 0 && ticksInUse % 20 == 0){
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_LOOP_LIGHTNING, 1, 1);
}
}
return flag;
super("lightning_web", true, EnumAction.NONE);
this.aimAssist(0.6f);
addProperties(PRIMARY_DAMAGE, SECONDARY_DAMAGE, TERTIARY_DAMAGE, SECONDARY_RANGE, TERTIARY_RANGE,
SECONDARY_MAX_TARGETS, TERTIARY_MAX_TARGETS);
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){
boolean flag = super.cast(world, caster, hand, ticksInUse, target, modifiers);
if(flag){
if(ticksInUse == 1){
caster.playSound(WizardrySounds.SPELL_LIGHTNING, 1, 1);
}else if(ticksInUse > 0 && ticksInUse % 20 == 0){
caster.playSound(WizardrySounds.SPELL_LOOP_LIGHTNING, 1, 1);
}
}
return flag;
protected SoundEvent[] createSounds(){
return this.createContinuousSpellSounds();
}
@Override
protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, entity, ticksInUse);
}
@Override
protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, x, y, z, ticksInUse, duration);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
electrocute(world, caster, caster, target, PRIMARY_DAMAGE * modifiers.get(SpellModifiers.POTENCY), ticksInUse);
electrocute(world, caster, origin, target, getProperty(PRIMARY_DAMAGE).floatValue()
* modifiers.get(SpellModifiers.POTENCY), ticksInUse);
// Secondary chaining effect
List<EntityLivingBase> secondaryTargets = WizardryUtilities.getEntitiesWithinRadius(SECONDARY_RANGE,
target.posX, target.posY + target.height / 2, target.posZ, world);
List<EntityLivingBase> secondaryTargets = WizardryUtilities.getEntitiesWithinRadius(
getProperty(SECONDARY_RANGE).floatValue(), target.posX, target.posY + target.height / 2,
target.posZ, world);
secondaryTargets.remove(target);
secondaryTargets.removeIf(e -> !WizardryUtilities.isLiving(e));
secondaryTargets.removeIf(e -> !WizardryUtilities.isValidTarget(caster, e));
if(secondaryTargets.size() > SECONDARY_MAX_TARGETS) secondaryTargets = secondaryTargets.subList(0, SECONDARY_MAX_TARGETS);
secondaryTargets.removeIf(e -> !AllyDesignationSystem.isValidTarget(caster, e));
if(secondaryTargets.size() > getProperty(SECONDARY_MAX_TARGETS).intValue())
secondaryTargets = secondaryTargets.subList(0, getProperty(SECONDARY_MAX_TARGETS).intValue());
for(EntityLivingBase secondaryTarget : secondaryTargets){
electrocute(world, caster, target, secondaryTarget,
SECONDARY_DAMAGE * modifiers.get(SpellModifiers.POTENCY), ticksInUse);
electrocute(world, caster, target.getPositionVector().add(0, target.height/2, 0), secondaryTarget,
getProperty(SECONDARY_DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY), ticksInUse);
// Tertiary chaining effect
List<EntityLivingBase> tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius(TERTIARY_RANGE,
secondaryTarget.posX, secondaryTarget.posY + secondaryTarget.height / 2,
secondaryTarget.posZ, world);
List<EntityLivingBase> tertiaryTargets = WizardryUtilities.getEntitiesWithinRadius(
getProperty(TERTIARY_RANGE).floatValue(), secondaryTarget.posX,
secondaryTarget.posY + secondaryTarget.height / 2, secondaryTarget.posZ, world);
tertiaryTargets.remove(target);
tertiaryTargets.removeAll(secondaryTargets);
tertiaryTargets.removeIf(e -> !WizardryUtilities.isLiving(e));
tertiaryTargets.removeIf(e -> !WizardryUtilities.isValidTarget(caster, e));
if(tertiaryTargets.size() > TERTIARY_MAX_TARGETS) tertiaryTargets = tertiaryTargets.subList(0, TERTIARY_MAX_TARGETS);
tertiaryTargets.removeIf(e -> !AllyDesignationSystem.isValidTarget(caster, e));
if(tertiaryTargets.size() > getProperty(TERTIARY_MAX_TARGETS).intValue())
tertiaryTargets = tertiaryTargets.subList(0, getProperty(TERTIARY_MAX_TARGETS).intValue());
for(EntityLivingBase tertiaryTarget : tertiaryTargets){
electrocute(world, caster, secondaryTarget, tertiaryTarget,
TERTIARY_DAMAGE * modifiers.get(SpellModifiers.POTENCY), ticksInUse);
electrocute(world, caster, secondaryTarget.getPositionVector().add(0, secondaryTarget.height/2, 0),
tertiaryTarget, getProperty(TERTIARY_DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY), ticksInUse);
}
}
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
// This is a nice example of when onMiss is used for more than just returning a boolean
if(world.isRemote){
if(caster != null) origin = origin.subtract(0, Y_OFFSET, 0);
double freeRange = 0.8 * baseRange; // The arc does not reach full range when it has a free end
Vec3d endpoint = origin.add(direction.scale(freeRange));
ParticleBuilder.create(Type.BEAM).entity(caster).clr(0.2f, 0.6f, 1)
.pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(endpoint).spawn(world);
if(ticksInUse % 2 == 0){
ParticleBuilder.create(Type.LIGHTNING).entity(caster)
.pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(endpoint).spawn(world);
// The arc does not reach full range when it has a free end
double freeRange = 0.8 * getRange(world, origin, direction, caster, ticksInUse, modifiers);
if(caster != null){
ParticleBuilder.create(Type.BEAM).entity(caster).pos(origin.subtract(caster.getPositionVector()))
.length(freeRange).clr(0.2f, 0.6f, 1).spawn(world);
}else{
ParticleBuilder.create(Type.BEAM).pos(origin).target(origin.add(direction.scale(freeRange)))
.clr(0.2f, 0.6f, 1).spawn(world);
}
if(ticksInUse % 4 == 0){
if(caster != null){
ParticleBuilder.create(Type.LIGHTNING).entity(caster).pos(origin.subtract(caster.getPositionVector()))
.length(freeRange).spawn(world);
}else{
ParticleBuilder.create(Type.LIGHTNING).pos(origin).target(origin.add(direction.scale(freeRange))).spawn(world);
}
}
}
return true;
}
private void electrocute(World world, Entity caster, Entity origin, Entity target, float damage, int ticksInUse){
private void electrocute(World world, Entity caster, Vec3d origin, Entity target, float damage, int ticksInUse){
if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){
if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer)
@@ -2,35 +2,18 @@ package electroblob.wizardry.spell;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.living.EntitySkeletonMinion;
import electroblob.wizardry.entity.living.EntityWitherSkeletonMinion;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.entity.living.*;
import electroblob.wizardry.util.NBTExtras;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.EntityCaveSpider;
import net.minecraft.entity.monster.EntityHusk;
import net.minecraft.entity.monster.EntityMagmaCube;
import net.minecraft.entity.monster.EntityPigZombie;
import net.minecraft.entity.monster.EntitySkeleton;
import net.minecraft.entity.monster.EntitySlime;
import net.minecraft.entity.monster.EntitySpider;
import net.minecraft.entity.monster.EntityStray;
import net.minecraft.entity.monster.EntityWitherSkeleton;
import net.minecraft.entity.monster.EntityZombie;
import net.minecraft.entity.passive.EntityBat;
import net.minecraft.entity.passive.EntityChicken;
import net.minecraft.entity.passive.EntityCow;
import net.minecraft.entity.passive.EntityMooshroom;
import net.minecraft.entity.passive.EntityPig;
import net.minecraft.entity.monster.*;
import net.minecraft.entity.passive.*;
import net.minecraft.item.EnumAction;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
@@ -46,10 +29,11 @@ public class Metamorphosis extends SpellRay {
addTransformation(EntityCow.class, EntityMooshroom.class);
addTransformation(EntityChicken.class, EntityBat.class);
addTransformation(EntityZombie.class, EntityHusk.class);
addTransformation(EntitySkeleton.class, EntityWitherSkeleton.class, EntityStray.class);
addTransformation(EntitySkeleton.class, EntityStray.class, EntityWitherSkeleton.class);
addTransformation(EntitySpider.class, EntityCaveSpider.class);
addTransformation(EntitySlime.class, EntityMagmaCube.class);
addTransformation(EntitySkeletonMinion.class, EntityWitherSkeletonMinion.class);
addTransformation(EntityZombieMinion.class, EntityHuskMinion.class);
addTransformation(EntitySkeletonMinion.class, EntityStrayMinion.class, EntityWitherSkeletonMinion.class);
}
/** Adds circular mappings between the given entity classes to the transformations map. In other words, given an
@@ -64,23 +48,24 @@ public class Metamorphosis extends SpellRay {
}
public Metamorphosis(){
super("metamorphosis", Tier.APPRENTICE, Element.NECROMANCY, SpellType.UTILITY, 15, 30, false, 10, WizardrySounds.SPELL_DEFLECTION);
this.soundValues(0.5f, 0.8f, 0);
super("metamorphosis", false, EnumAction.NONE);
this.soundValues(0.5f, 1f, 0);
}
@Override public boolean canBeCastByNPCs() { return false; }
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
double xPos = target.posX;
double yPos = target.posY;
double zPos = target.posZ;
// Sneaking allows the entities to be cycled through in the other direction.
Class<? extends EntityLivingBase> newEntityClass = caster.isSneaking() ?
// Dispensers always cycle through entities in the normal direction.
Class<? extends EntityLivingBase> newEntityClass = caster != null && caster.isSneaking() ?
TRANSFORMATIONS.inverse().get(target.getClass()) : TRANSFORMATIONS.get(target.getClass());
if(newEntityClass == null) return false;
@@ -103,7 +88,7 @@ public class Metamorphosis extends SpellRay {
NBTTagCompound tag = new NBTTagCompound();
target.writeToNBT(tag);
// Remove the UUID because keeping it the same causes the entity to disappear
WizardryUtilities.removeUniqueId(tag, "UUID");
NBTExtras.removeUniqueId(tag, "UUID");
newEntity.readFromNBT(tag);
target.setDead();
@@ -111,22 +96,27 @@ public class Metamorphosis extends SpellRay {
world.spawnEntity(newEntity);
}else{
for(int i=0; i<5; i++){
ParticleBuilder.create(Type.DARK_MAGIC).pos(xPos, yPos, zPos).clr(0.1f, 0, 0).spawn(world);
for(int i=0; i<20; i++){
ParticleBuilder.create(Type.DARK_MAGIC, world.rand, xPos, yPos + 1, zPos, 1, false)
.clr(0.1f, 0, 0).spawn(world);
}
ParticleBuilder.create(Type.BUFF).pos(xPos, yPos, zPos).clr(0xd363cb).spawn(world);
}
this.playSound(world, (EntityLivingBase)target, ticksInUse, -1, modifiers);
return true;
}
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@@ -1,41 +1,44 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.EntityMeteor;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class Meteor extends SpellRay {
// It doesn't really make sense to have a blast radius when the explosion is measured by strength
public static final String BLAST_STRENGTH = "blast_strength";
public Meteor(){
super("meteor", Tier.MASTER, Element.FIRE, SpellType.ATTACK, 100, 200, false, 40, WizardrySounds.SPELL_SUMMONING);
super("meteor", false, EnumAction.NONE);
this.soundValues(3, 1, 0);
this.ignoreEntities(true);
this.ignoreLivingEntities(true);
addProperties(BLAST_STRENGTH);
}
@Override public boolean doesSpellRequirePacket(){ return false; }
@Override public boolean requiresPacket(){ return false; }
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(world.canBlockSeeSky(pos.up())){
if(!world.isRemote){
EntityMeteor meteor = new EntityMeteor(world, pos.getX(), pos.getY() + 50, pos.getZ(),
modifiers.get(WizardryItems.blast_upgrade));
modifiers.get(WizardryItems.blast_upgrade), WizardryUtilities.canDamageBlocks(caster, world));
world.spawnEntity(meteor);
}
@@ -46,7 +49,7 @@ public class Meteor extends SpellRay {
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@@ -1,31 +1,27 @@
package electroblob.wizardry.spell;
import java.util.Arrays;
import java.util.List;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.living.EntityEvilWizard;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.AllyDesignationSystem;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.INpc;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.*;
import net.minecraft.entity.item.EntityArmorStand;
import net.minecraft.entity.passive.EntitySheep;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.item.EnumDyeColor;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
@@ -33,45 +29,57 @@ import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.Arrays;
import java.util.List;
@Mod.EventBusSubscriber
public class MindControl extends SpellRay {
/** The NBT tag name for storing the controlling entity's UUID in the target's tag compound. */
public static final String NBT_KEY = "controllingEntity";
private static final int BASE_DURATION = 600;
public MindControl(){
super("mind_control", Tier.ADVANCED, Element.NECROMANCY, SpellType.ATTACK, 40, 150, false, 8, WizardrySounds.SPELL_SUMMONING);
super("mind_control", false, EnumAction.NONE);
addProperties(EFFECT_DURATION);
}
@Override public boolean canBeCastByNPCs() { return false; }
@Override public boolean canBeCastByDispensers() { return false; }
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
if(!world.isRemote){
if(!canControl(target)){
// Adds a message saying that the player/boss entity/wizard resisted mind control
((EntityPlayer)caster).sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(),
this.getNameForTranslationFormatted()), true);
}else if(target instanceof EntityLiving){
if(!canControl(target)){
if(!world.isRemote){
if(caster instanceof EntityPlayer){
// Adds a message saying that the player/boss entity/wizard resisted mind control
((EntityPlayer)caster).sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(),
this.getNameForTranslationFormatted()), true);
}
}
}else if(target instanceof EntityLiving){
if(!world.isRemote){
if(!MindControl.findMindControlTarget((EntityLiving)target, caster, world)){
// If no valid target was found, this just acts like mind trick.
((EntityLiving)target).setAttackTarget(null);
}
NBTTagCompound entityNBT = target.getEntityData();
if(entityNBT != null) entityNBT.setUniqueId(NBT_KEY, caster.getUniqueID());
((EntityLiving)target).addPotionEffect(new PotionEffect(WizardryPotions.mind_control,
(int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)), 0));
}
}else{
if(target instanceof EntitySheep && ((EntitySheep)target).getFleeceColor() == EnumDyeColor.BLUE
&& WizardryUtilities.canDamageBlocks(caster, world)){
if(!world.isRemote) ((EntitySheep)target).setFleeceColor(EnumDyeColor.RED); // Wololo!
world.playSound(caster.posX, caster.posY, caster.posZ, SoundEvents.EVOCATION_ILLAGER_PREPARE_WOLOLO, WizardrySounds.SPELLS, 1, 1, false);
}
if(!world.isRemote) startControlling((EntityLiving)target, caster,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
}
if(world.isRemote){
for(int i=0; i<10; i++){
ParticleBuilder.create(Type.DARK_MAGIC, world.rand, target.posX,
@@ -90,12 +98,12 @@ public class MindControl extends SpellRay {
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@@ -106,6 +114,11 @@ public class MindControl extends SpellRay {
.contains(EntityList.getKey(target.getClass()));
}
public static void startControlling(EntityLiving target, EntityLivingBase controller, int duration){
target.getEntityData().setUniqueId(NBT_KEY, controller.getUniqueID());
target.addPotionEffect(new PotionEffect(WizardryPotions.mind_control, duration, 0));
}
/**
* Finds the nearest creature to the given target which it is allowed to attack according to the given caster and
* sets it as the target's attack target. Handles both new and old AI and takes follow range into account. Defined
@@ -122,7 +135,7 @@ public class MindControl extends SpellRay {
// no longer lasts until the creature dies; instead it is a potion effect which continues to
// set the target until it wears off.
List<EntityLivingBase> possibleTargets = WizardryUtilities.getEntitiesWithinRadius(
((EntityLiving)target).getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).getAttributeValue(),
target.getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).getAttributeValue(),
target.posX, target.posY, target.posZ, world);
possibleTargets.remove(target);
@@ -131,7 +144,7 @@ public class MindControl extends SpellRay {
EntityLivingBase newAITarget = null;
for(EntityLivingBase possibleTarget : possibleTargets){
if(WizardryUtilities.isValidTarget(caster, possibleTarget) && (newAITarget == null
if(AllyDesignationSystem.isValidTarget(caster, possibleTarget) && (newAITarget == null
|| target.getDistance(possibleTarget) < target.getDistance(newAITarget))){
newAITarget = possibleTarget;
}
@@ -140,7 +153,7 @@ public class MindControl extends SpellRay {
if(newAITarget != null){
// From 1.7.10 - this seems not to work quite right; the entity appears to continue attacking this target
// after it gets killed (not noticeable in survival since it will target the player again immediately.)
((EntityLiving)target).setAttackTarget(newAITarget);
target.setAttackTarget(newAITarget);
return true;
}
@@ -163,7 +176,7 @@ public class MindControl extends SpellRay {
if(caster instanceof EntityLivingBase){
// If the current target is already a valid mind control target, nothing happens.
if(WizardryUtilities.isValidTarget(caster, currentTarget)) return;
if(AllyDesignationSystem.isValidTarget(caster, currentTarget)) return;
if(MindControl.findMindControlTarget(entity, (EntityLivingBase)caster, world)){
// If it worked, skip setting the target to null.
@@ -1,11 +1,7 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
@@ -15,9 +11,11 @@ import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent;
@@ -26,16 +24,15 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class MindTrick extends SpellRay {
private static final int BASE_DURATION = 300;
public MindTrick(){
super("mind_trick", Tier.BASIC, Element.NECROMANCY, SpellType.ATTACK, 10, 40, false, 8, WizardrySounds.SPELL_DEFLECTION);
super("mind_trick", false, EnumAction.NONE);
this.soundValues(0.7f, 1, 0.4f);
addProperties(EFFECT_DURATION);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
@@ -44,13 +41,13 @@ public class MindTrick extends SpellRay {
if(target instanceof EntityPlayer){
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.NAUSEA,
(int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)), 0));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), 0));
}else if(target instanceof EntityLiving){
((EntityLiving)target).setAttackTarget(null);
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.mind_trick,
(int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)), 0));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), 0));
}
}else{
@@ -68,12 +65,12 @@ public class MindTrick extends SpellRay {
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@@ -0,0 +1,157 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.EnumAction;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.event.ForgeEventFactory;
import java.util.List;
public class Mine extends SpellRay {
public Mine(){
super("mine", false, EnumAction.NONE);
this.ignoreLivingEntities(true);
this.particleSpacing(0.5);
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
// Needs to be outside because it gets run on the client-side
if(caster instanceof EntityPlayer){
if(caster.getHeldItemMainhand().getItem() instanceof ISpellCastingItem){
caster.swingArm(EnumHand.MAIN_HAND);
}else if(caster.getHeldItemOffhand().getItem() instanceof ISpellCastingItem){
caster.swingArm(EnumHand.OFF_HAND);
}
}
if(!world.isRemote){
if(WizardryUtilities.isBlockUnbreakable(world, pos)) return false;
// The mine spell ignores the block damage setting for players, since that's the entire point of the spell
// Instead, it triggers block break events at the appropriate points, which protection mods should be able to
// pick up and allow/disallow accordingly
// For the time being, dispensers respect the mobGriefing gamerule
if(!(caster instanceof EntityPlayer) && !WizardryUtilities.canDamageBlocks(caster, world)) return false;
// Can't mine arcane-locked blocks
if(world.getTileEntity(pos) != null && world.getTileEntity(pos).getTileData().hasUniqueId(ArcaneLock.NBT_KEY)) return false;
IBlockState state = world.getBlockState(pos);
// The maximum harvest level as determined by the potency multiplier. The + 0.5f is so that
// weird float processing doesn't incorrectly round it down.
int harvestLevel = (int)((modifiers.get(SpellModifiers.POTENCY) - 1) / Constants.POTENCY_INCREASE_PER_TIER + 0.5f);
if(harvestLevel > 0) harvestLevel--; // Shifts them all down one since normally novice wands give some potency
// The >= 3 is to allow master earth wands to break anything.
if(state.getBlock().getHarvestLevel(state) <= harvestLevel || harvestLevel >= 3){
boolean flag = false;
int blastUpgradeCount = (int)((modifiers.get(WizardryItems.blast_upgrade) - 1) / Constants.RANGE_INCREASE_PER_LEVEL + 0.5f);
// Results in the following patterns:
// 0 blast upgrades: single block
// 1 blast upgrade: 3x3 without corners or edges
// 2 blast upgrades: 3x3 with corners
// 3 blast upgrades: 5x5 without corners or edges
float radius = 0.5f + 0.73f * blastUpgradeCount;
List<BlockPos> sphere = WizardryUtilities.getBlockSphere(pos, radius);
for(BlockPos pos1 : sphere){
if(WizardryUtilities.isBlockUnbreakable(world, pos1)) continue;
IBlockState state1 = world.getBlockState(pos1);
if(state1.getBlock().getHarvestLevel(state1) <= harvestLevel || harvestLevel >= 3){
if(caster instanceof EntityPlayerMP){ // Everything in here is server-side only so this is fine
boolean silkTouch = state1.getBlock().canSilkHarvest(world, pos1, state1, (EntityPlayer)caster)
&& ItemArtefact.isArtefactActive((EntityPlayer)caster, WizardryItems.charm_silk_touch);
// Some protection mods seem to use this event instead so let's trigger it to check
if(ForgeEventFactory.getBreakSpeed((EntityPlayer)caster, state1, 1, pos1) <= 0) continue;
int xp = ForgeHooks.onBlockBreakEvent(world,
((EntityPlayerMP)caster).interactionManager.getGameType(), (EntityPlayerMP)caster, pos1);
if(xp == -1) continue; // Event was cancelled
if(silkTouch){
flag = world.destroyBlock(pos1, false);
if(flag) Block.spawnAsEntity(world, pos1, getSilkTouchDrop(state1));
}else{
flag = world.destroyBlock(pos1, true);
if(flag) state1.getBlock().dropXpOnBlockBreak(world, pos1, xp);
}
}else{
// NPCs can dig the block under the target's feet
flag = world.destroyBlock(pos1, true) || flag;
}
}
}
return flag;
}
}else{
return true;
}
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){
ParticleBuilder.create(Type.DUST).pos(x, y, z).time(20 + world.rand.nextInt(5)).clr(0.9f, 0.95f, 1)
.shaded(false).spawn(world);
}
// Copied from Block, where (for some reason) it's protected
private static ItemStack getSilkTouchDrop(IBlockState state){
Item item = Item.getItemFromBlock(state.getBlock());
int i = 0;
if(item.getHasSubtypes()){
i = state.getBlock().getMetaFromState(state);
}
return new ItemStack(item, 1, i);
}
}
@@ -1,8 +1,5 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
@@ -17,11 +14,11 @@ import net.minecraft.world.World;
public class None extends Spell {
public None(){
super("none", Tier.BASIC, Element.MAGIC, SpellType.UTILITY, 0, 0, EnumAction.NONE, false);
super("none", EnumAction.NONE, false);
}
@Override
public boolean doesSpellRequirePacket(){
public boolean requiresPacket(){
return false;
}
@@ -0,0 +1,123 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class Paralysis extends SpellRay {
/** Creatures with this much health or less will snap out of paralysis - but only when they take damage, so a
* creature on critical health may still be paralysed, but if it takes any damage at all the paralysis effect
* will end. */
private static final String CRITICAL_HEALTH = "critical_health";
public Paralysis(){
super("paralysis", false, EnumAction.NONE);
addProperties(DAMAGE, EFFECT_DURATION, CRITICAL_HEALTH);
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
if(world.isRemote){
// Rather neatly, the entity can be set here and if it's null nothing will happen.
ParticleBuilder.create(Type.BEAM).entity(caster).clr(0.2f, 0.6f, 1)
.pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(target).spawn(world);
ParticleBuilder.create(Type.LIGHTNING).entity(caster)
.pos(caster != null ? origin.subtract(caster.getPositionVector()) : origin).target(target).spawn(world);
}
// This is a lot neater than it was, thanks to the damage type system.
if(MagicDamage.isEntityImmune(DamageType.SHOCK, target)){
if(!world.isRemote && caster instanceof EntityPlayer) ((EntityPlayer)caster).sendStatusMessage(
new TextComponentTranslation("spell.resist",
target.getName(), this.getNameForTranslationFormatted()), true);
}else{
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.SHOCK),
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
}
((EntityLivingBase)target).addPotionEffect(new PotionEffect(WizardryPotions.paralysis,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)), 0));
}
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(world.isRemote){
if(world.getBlockState(pos).getMaterial().isSolid()){
Vec3d vec = hit.add(new Vec3d(side.getDirectionVec()).scale(WizardryUtilities.ANTI_Z_FIGHTING_OFFSET));
ParticleBuilder.create(Type.SCORCH).pos(vec).face(side).clr(0.4f, 0.8f, 1).spawn(world);
}
}
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
// This is first because we want the endpoint to be unaffected by the offset
Vec3d endpoint = origin.add(direction.scale(getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade)));
if(world.isRemote){
ParticleBuilder.create(Type.LIGHTNING).time(4).pos(origin).target(endpoint).scale(0.5f).spawn(world);
ParticleBuilder.create(Type.BEAM).clr(0.2f, 0.6f, 1).time(4).pos(origin)
.target(endpoint).spawn(world);
}
return true;
}
// See WizardryClientEventHandler for prevention of players' movement under the effects of paralysis
// TODO: (Animated?) screen overlay effect for paralysed players in first-person
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
// Disables entities' AI when under the effects of paralysis and re-enables it on the last update of the effect
// - this can't be in the potion class because it requires access to the duration and hence the actual
// PotionEffect instance
if(event.getEntity() instanceof EntityLiving && event.getEntityLiving().isPotionActive(WizardryPotions.paralysis)){
int timeLeft = event.getEntityLiving().getActivePotionEffect(WizardryPotions.paralysis).getDuration();
((EntityLiving)event.getEntity()).setNoAI(timeLeft > 1);
}
}
@SubscribeEvent
public static void onLivingHurtEvent(LivingHurtEvent event){
// Paralysed creatures snap out of paralysis when they take critical damage
if(event.getEntityLiving().isPotionActive(WizardryPotions.paralysis) && event.getEntityLiving().getHealth()
- event.getAmount() <= Spells.paralysis.getProperty(CRITICAL_HEALTH).floatValue()){
event.getEntityLiving().removePotionEffect(WizardryPotions.paralysis);
}
}
}
@@ -1,9 +1,6 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.block.BlockStatue;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.ParticleBuilder;
@@ -12,42 +9,43 @@ import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class Petrify extends SpellRay {
private static final int BASE_DURATION = 900;
// This is more descriptive and more accurate than the standard "effect_duration" in this case
public static final String MINIMUM_EFFECT_DURATION = "minimum_effect_duration";
public Petrify(){
super("petrify", Tier.ADVANCED, Element.SORCERY, SpellType.ATTACK, 40, 100, false, 10, SoundEvents.ENTITY_WITHER_SPAWN);
super("petrify", false, EnumAction.NONE);
this.soundValues(1, 1.1f, 0.2f);
addProperties(MINIMUM_EFFECT_DURATION);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(target instanceof EntityLiving && !world.isRemote){
// Unchecked cast is fine because the block is a static final field
if(((BlockStatue)WizardryBlocks.petrified_stone).convertToStatue((EntityLiving)target,
(int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)))){
return true;
(int)(getProperty(MINIMUM_EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)))){
}
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@@ -2,73 +2,39 @@ package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.RayTracer;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class PhaseStep extends Spell {
public static final String WALL_THICKNESS = "wall_thickness";
public PhaseStep(){
super("phase_step", Tier.ADVANCED, Element.SORCERY, SpellType.UTILITY, 35, 40, EnumAction.NONE, false);
super("phase_step", EnumAction.NONE, false);
addProperties(RANGE, WALL_THICKNESS);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
// Phase step does not gain range from range multiplier, instead it increases the thickness
// of the wall you can teleport through.
RayTraceResult rayTrace = WizardryUtilities.standardBlockRayTrace(world, caster, 5, false);
if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){
BlockPos pos = new BlockPos(rayTrace.getBlockPos().getX(), (int)caster.posY, rayTrace.getBlockPos().getZ());
// The maximum wall thickness as determined by the range multiplier. The + 0.5f is so that
// weird float processing doesn't incorrectly round it down.
int maxThickness = 1 + (int)((modifiers.get(WizardryItems.range_upgrade) - 1) / Constants.RANGE_INCREASE_PER_LEVEL + 0.5f);
if(rayTrace.sideHit.getAxis().isHorizontal()){
// i represents how far the player needs to teleport to get through the wall
for(int i = 0; i <= maxThickness; i++){
BlockPos pos1 = pos.offset(rayTrace.sideHit.getOpposite(), i);
// Prevents the player from teleporting through unbreakable blocks, so they cannot cheat in other
// mods' mazes and dungeons.
if((WizardryUtilities.isBlockUnbreakable(world, pos1) || WizardryUtilities.isBlockUnbreakable(world, pos1.up()))
&& !Wizardry.settings.teleportThroughUnbreakableBlocks)
return false;
if(!world.getBlockState(pos1).getMaterial().blocksMovement()
&& !world.getBlockState(pos1.up()).getMaterial().blocksMovement()){
if(!world.isRemote){
caster.setPositionAndUpdate(pos1.getX() + 0.5, caster.posY, pos1.getZ() + 0.5);
}
caster.swingArm(hand);
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0f);
return true;
}
}
}
}
double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade);
RayTraceResult rayTrace = RayTracer.standardBlockRayTrace(world, caster, range, false);
// This is here because the conditions are false on the client for whatever reason. (see the Javadoc for cast()
// for an explanation)
if(world.isRemote){
for(int i = 0; i < 10; i++){
double dx1 = caster.posX;
double dy1 = caster.getEntityBoundingBox().minY + 2 * world.rand.nextFloat();
@@ -76,12 +42,94 @@ public class PhaseStep extends Spell {
world.spawnParticle(EnumParticleTypes.PORTAL, dx1, dy1, dz1, world.rand.nextDouble() - 0.5,
world.rand.nextDouble() - 0.5, world.rand.nextDouble() - 0.5);
}
// Can't be bothered to route this through the proxies!
electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect();
}
return false;
if(rayTrace != null && rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){
BlockPos pos = rayTrace.getBlockPos();
// The maximum wall thickness as determined by the range multiplier. The + 0.5f is so that
// weird float processing doesn't incorrectly round it down.
int maxThickness = getProperty(WALL_THICKNESS).intValue()
+ (int)((modifiers.get(WizardryItems.range_upgrade) - 1) / Constants.RANGE_INCREASE_PER_LEVEL + 0.5f);
if(rayTrace.sideHit == EnumFacing.UP) maxThickness++; // Allow space for the player's head
// i represents how far the player needs to teleport to get through the wall
for(int i = 0; i <= maxThickness; i++){
BlockPos pos1 = pos.offset(rayTrace.sideHit.getOpposite(), i);
// Prevents the player from teleporting through unbreakable blocks, so they cannot cheat in other
// mods' mazes and dungeons.
if((WizardryUtilities.isBlockUnbreakable(world, pos1) || WizardryUtilities.isBlockUnbreakable(world, pos1.up()))
&& !Wizardry.settings.teleportThroughUnbreakableBlocks)
break; // Don't return false yet, there are other possible outcomes below now
if(!world.getBlockState(pos1).getMaterial().blocksMovement()
&& !world.getBlockState(pos1.up()).getMaterial().blocksMovement()){
// Plays before and after so it is heard from both positions
this.playSound(world, caster, ticksInUse, -1, modifiers);
if(!world.isRemote){
caster.setPositionAndUpdate(pos1.getX() + 0.5, pos1.getY() + 0.5, pos1.getZ() + 0.5);
}
caster.swingArm(hand);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
}
// If no suitable position was found on the other side of the wall, works like blink instead
// Leave space for the player's head
if(rayTrace.sideHit == EnumFacing.DOWN) pos = pos.down();
// This means stuff like snow layers is ignored, meaning when on snow-covered ground the player does
// not teleport 1 block above the ground.
if(rayTrace.sideHit == EnumFacing.UP && !world.getBlockState(pos).getMaterial().blocksMovement()){
pos = pos.down();
}
pos = pos.offset(rayTrace.sideHit);
// Prevents the player from teleporting into blocks and suffocating
if(world.getBlockState(pos).getMaterial().blocksMovement()
|| world.getBlockState(pos.up()).getMaterial().blocksMovement()){
return false;
}
// Plays before and after so it is heard from both positions
this.playSound(world, caster, ticksInUse, -1, modifiers);
if(!world.isRemote) caster.setPositionAndUpdate(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5);
this.playSound(world, caster, ticksInUse, -1, modifiers);
caster.swingArm(hand);
return true;
}else{ // The ray trace missed
Vec3d destination = caster.getPositionVector().add(caster.getLookVec().scale(range));
BlockPos pos = new BlockPos(destination);
// Prevents the player from teleporting into blocks and suffocating.
if(world.getBlockState(pos).getMaterial().blocksMovement()
|| world.getBlockState(pos.up()).getMaterial().blocksMovement()){
return false;
}
if(!world.isRemote) caster.setPositionAndUpdate(destination.x, destination.y, destination.z);
this.playSound(world, caster, ticksInUse, -1, modifiers);
caster.swingArm(hand);
return true;
}
}
}
@@ -1,54 +1,48 @@
package electroblob.wizardry.spell;
import java.util.List;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.world.World;
import java.util.List;
public class PlagueOfDarkness extends Spell {
private static final double BASE_RADIUS = 5;
private static final float BASE_DAMAGE = 8;
private static final int BASE_DURATION = 140;
public PlagueOfDarkness(){
super("plague_of_darkness", Tier.MASTER, Element.NECROMANCY, SpellType.ATTACK, 75, 200, EnumAction.BOW, false);
super("plague_of_darkness", EnumAction.BOW, false);
addProperties(EFFECT_RADIUS, DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH);
soundValues(1, 1.1f, 0.2f);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(
BASE_RADIUS * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world);
double radius = getProperty(EFFECT_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade);
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(radius, caster.posX, caster.posY, caster.posZ, world);
for(EntityLivingBase target : targets){
if(WizardryUtilities.isValidTarget(caster, target)
if(AllyDesignationSystem.isValidTarget(caster, target)
&& !MagicDamage.isEntityImmune(DamageType.WITHER, target)){
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.WITHER),
BASE_DAMAGE * modifiers.get(SpellModifiers.POTENCY));
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
target.addPotionEffect(new PotionEffect(MobEffects.WITHER,
(int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)), 2));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
}
}
if(world.isRemote){
double particleX, particleZ;
@@ -74,10 +68,16 @@ public class PlagueOfDarkness extends Spell {
particleZ, particleX - caster.posX, 0, particleZ - caster.posZ, Block.getStateId(block));
}
}
ParticleBuilder.create(Type.SPHERE)
.pos(caster.posX, caster.getEntityBoundingBox().minY + 0.1, caster.posZ)
.scale((float)radius * 0.8f)
.clr(0.8f, 0, 0.05f)
.spawn(world);
}
caster.swingArm(hand);
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_DEATH, 1, 1 + 0.2f * world.rand.nextFloat());
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -1,29 +1,31 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemStack;
import net.minecraft.item.*;
import net.minecraft.item.crafting.FurnaceRecipes;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.world.World;
import java.util.Arrays;
public class PocketFurnace extends Spell {
public static final String ITEMS_SMELTED = "items_smelted";
public PocketFurnace(){
super("pocket_furnace", Tier.APPRENTICE, Element.FIRE, SpellType.UTILITY, 30, 40, EnumAction.BOW, false);
super("pocket_furnace", EnumAction.BOW, false);
addProperties(ITEMS_SMELTED);
soundValues(1, 0.75f, 0);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
int usesLeft = 5;
int usesLeft = (int)(getProperty(ITEMS_SMELTED).floatValue() * modifiers.get(SpellModifiers.POTENCY));
ItemStack stack, result;
@@ -31,11 +33,14 @@ public class PocketFurnace extends Spell {
stack = caster.inventory.getStackInSlot(i);
if(!stack.isEmpty()){
if(!stack.isEmpty() && !world.isRemote){
result = FurnaceRecipes.instance().getSmeltingResult(stack);
if(!result.isEmpty()){
if(!result.isEmpty() && !(result.getItem() instanceof ItemTool) && !(result.getItem() instanceof ItemSword)
&& !(result.getItem() instanceof ItemArmor)
&& !Arrays.asList(Wizardry.settings.pocketFurnaceItemBlacklist).contains(result.getItem().getRegistryName())){
if(stack.getCount() <= usesLeft){
ItemStack stack2 = new ItemStack(result.getItem(), stack.getCount(), result.getItemDamage());
if(WizardryUtilities.doesPlayerHaveItem(caster, result.getItem())){
@@ -55,7 +60,7 @@ public class PocketFurnace extends Spell {
}
}
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.BLOCK_FURNACE_FIRE_CRACKLE, 1, 0.75f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
if(world.isRemote){
for(int i = 0; i < 10; i++){
@@ -2,12 +2,7 @@ package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.WizardryGuiHandler;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
@@ -16,11 +11,11 @@ import net.minecraft.world.World;
public class PocketWorkbench extends Spell {
public PocketWorkbench(){
super("pocket_workbench", Tier.APPRENTICE, Element.SORCERY, SpellType.UTILITY, 30, 40, EnumAction.BOW, false);
super("pocket_workbench", EnumAction.BOW, false);
}
@Override
public boolean doesSpellRequirePacket(){
public boolean requiresPacket(){
return false;
}
@@ -33,7 +28,7 @@ public class PocketWorkbench extends Spell {
(int)caster.posY, (int)caster.posZ);
}
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1, 1);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -1,10 +1,6 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
@@ -13,20 +9,22 @@ import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class Poison extends SpellRay {
private static final int BASE_DURATION = 200;
public Poison(){
super("poison", Tier.APPRENTICE, Element.EARTH, SpellType.ATTACK, 10, 20, false, 10, WizardrySounds.SPELL_ICE);
super("poison", false, EnumAction.NONE);
this.soundValues(1, 1.1f, 0.2f);
addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH);
}
@Override
@@ -35,7 +33,7 @@ public class Poison extends SpellRay {
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
@@ -45,24 +43,23 @@ public class Poison extends SpellRay {
new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true);
}else{
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.POISON),
1 * modifiers.get(SpellModifiers.POTENCY));
getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY));
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.POISON,
(int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)), 1));
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
getProperty(EFFECT_STRENGTH).intValue() + SpellBuff.getStandardBonusAmplifier(modifiers.get(SpellModifiers.POTENCY))));
}
return true;
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@@ -0,0 +1,714 @@
package electroblob.wizardry.spell;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.data.IVariable;
import electroblob.wizardry.data.IVariable.Variable;
import electroblob.wizardry.data.Persistence;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.entity.living.*;
import electroblob.wizardry.entity.projectile.*;
import electroblob.wizardry.integration.DamageSafetyChecker;
import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.PacketPossession;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.*;
import net.minecraft.entity.ai.EntityAIAttackMelee;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.ai.attributes.IAttribute;
import net.minecraft.entity.ai.attributes.IAttributeInstance;
import net.minecraft.entity.monster.*;
import net.minecraft.entity.passive.EntityChicken;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.projectile.EntityPotion;
import net.minecraft.entity.projectile.EntitySnowball;
import net.minecraft.init.Enchantments;
import net.minecraft.init.Items;
import net.minecraft.init.PotionTypes;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.potion.PotionEffect;
import net.minecraft.potion.PotionUtils;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingDamageEvent;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.entity.player.EntityItemPickupEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Function;
@Mod.EventBusSubscriber
public class Possession extends SpellRay {
/** A {@code ResourceLocation} representing the shader file used when possessing an entity. */
public static final ResourceLocation SHADER = new ResourceLocation(Wizardry.MODID, "shaders/post/possession.json");
/** The NBT tag name for storing the possessing entity's UUID in the target's tag compound. */
public static final String NBT_KEY = "possessor";
/** The NBT tag name for storing the possessor's previous inventory in their tag compound. */
public static final String INVENTORY_NBT_KEY = "prevInventory";
/** The health (in half-hearts) below or equal to which the possessor will automatically stop possessing. */
public static final String CRITICAL_HEALTH = "critical_health";
private static final int PROJECTILE_COOLDOWN = 30;
public static final IVariable<Integer> TIMER_KEY = new Variable<Integer>(Persistence.DIMENSION_CHANGE).withTicker(Possession::update);
public static final IVariable<EntityLiving> POSSESSEE_KEY = new Variable<>(Persistence.DIMENSION_CHANGE);
public static final IVariable<Integer> SHOOT_COOLDOWN_KEY = new Variable<Integer>(Persistence.DIMENSION_CHANGE).withTicker((p, n) -> Math.max(n-1, 0));
private static final Multimap<Class<? extends EntityLiving>, BiConsumer<?, EntityPlayer>> abilities = HashMultimap.create();
private static final Map<Class<? extends EntityLiving>, Function<World, ? extends IProjectile>> projectiles = new HashMap<>();
private static final Map<IAttribute, UUID> INHERITED_ATTRIBUTES;
static {
INHERITED_ATTRIBUTES = ImmutableMap.of(
SharedMonsterAttributes.MOVEMENT_SPEED, UUID.fromString("f65cfcaf-e7ec-4dfb-aa6c-711735d007e3"),
SharedMonsterAttributes.ATTACK_DAMAGE, UUID.fromString("ab67c89e-74a5-4e27-9621-40bffb4f7a03"),
SharedMonsterAttributes.KNOCKBACK_RESISTANCE, UUID.fromString("05529535-9bcf-42bb-8822-45f5ce6a8f08"));
addAbility(EntitySpider.class, (spider, player) -> { if(player.collidedHorizontally) player.motionY = 0.2; });
addAbility(EntityChicken.class, (chicken, player) -> { if(!player.onGround && player.motionY < 0) player.motionY *= 0.6D; });
addAbility(EntityLiving.class, (entity, player) -> { if(!entity.isImmuneToFire() && player.isBurning()) player.extinguish(); });
addProjectile(EntitySnowman.class, EntitySnowball::new); // Woooo snowballs!
addProjectile(EntityBlaze.class, EntityMagicFireball::new); // Ugh normal fireballs don't fit so let's just use mine!
addProjectile(EntityGhast.class, EntityLargeMagicFireball::new);
addProjectile(EntityIceWraith.class, EntityIceShard::new);
addProjectile(EntityShadowWraith.class, EntityDarknessOrb::new);
addProjectile(EntityStormElemental.class, EntityLightningDisc::new);
addProjectile(EntityWitch.class, EntityPotion::new);
}
public Possession(){
super("possession", false, EnumAction.NONE);
addProperties(EFFECT_DURATION, CRITICAL_HEALTH);
}
@Override public boolean canBeCastByNPCs() { return false; }
@Override public boolean canBeCastByDispensers() { return false; }
@Override
public boolean requiresPacket(){
return false; // Has its own packet
}
@Override
protected SoundEvent[] createSounds(){
return createSoundsWithSuffixes("possess", "end");
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
Vec3d look = caster.getLookVec();
Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight() - Y_OFFSET, caster.posZ);
if(!shootSpell(world, origin, look, caster, ticksInUse, modifiers)) return false;
if(casterSwingsArm(world, caster, hand, ticksInUse, modifiers)) caster.swingArm(hand);
this.playSound(world, caster, ticksInUse, -1, modifiers, "possess"); // TODO: There must be a better way...
return true;
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse,
SpellModifiers modifiers){
if(target instanceof EntityLiving && caster instanceof EntityPlayer && !isPossessing((EntityPlayer)caster)){
EntityPlayer player = (EntityPlayer)caster;
if(!player.isCreative() && player.getHealth() <= getProperty(CRITICAL_HEALTH).floatValue()){
player.sendStatusMessage(new TextComponentTranslation(
"spell." + this.getRegistryName() + ".insufficienthealth"), true);
return false;
}
if(!world.isRemote){
int duration = (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade));
if(possess(player, (EntityLiving)target, duration)){
return true;
}
}
}
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse,
SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
// ================================================ Helper methods ================================================
/**
* Causes the given player to start possessing the given target for the given duration, and sets all relevant data
* for both entities accordingly. Also takes care of sending packets to update clients.
* @param possessor The player doing the possessing.
* @param target The entity being possessed.
* @param duration The number of ticks for which the possession should last. Pass in a negative integer to make the
* possession last indefinitely (until manually ended with the dismount key).
* @return True if the possession succeeded, false if for some reason it did not (only happens if the player's
* {@link WizardData} is null).
*/
public boolean possess(EntityPlayer possessor, EntityLiving target, int duration){
if(WizardData.get(possessor) != null){
WizardData.get(possessor).setVariable(POSSESSEE_KEY, target);
WizardData.get(possessor).setVariable(TIMER_KEY, duration);
possessor.setPositionAndRotation(target.posX, target.posY, target.posZ, target.rotationYaw, target.rotationPitch);
possessor.eyeHeight = target.getEyeHeight();
setSize(possessor, target.width, target.height);
target.setDead();
target.setNoAI(true);
target.setAttackTarget(null);
// Attributes
if(target instanceof EntityFlying || target instanceof net.minecraft.entity.passive.EntityFlying){
possessor.capabilities.allowFlying = true;
possessor.capabilities.isFlying = true;
}
// Apply attribute modifiers which change the player's attribute value to the target's value
// Uses predefined UUIDs so we can easily remove them later
attributes:
for(IAttribute attribute : INHERITED_ATTRIBUTES.keySet()){
IAttributeInstance instance = target.getAttributeMap().getAttributeInstance(attribute);
if(instance != null){
double targetValue = instance.getAttributeValue();
double currentValue = possessor.getAttributeMap().getAttributeInstance(attribute).getAttributeValue();
// Don't ask me why, but the player's base movement speed seems to be 0.1
if(attribute == SharedMonsterAttributes.MOVEMENT_SPEED) currentValue /= possessor.capabilities.getWalkSpeed();
for(EntityEquipmentSlot slot : EntityEquipmentSlot.values()){
if(target.getItemStackFromSlot(slot).getAttributeModifiers(slot).containsKey(attribute.getName())){
// If the mob has equipment, use the modifiers for that equipment instead of the mob's normal ones
// Not doing this results in the player being able to one-hit most mobs when possessing a zombie pigman!
continue attributes;
}
}
possessor.getAttributeMap().getAttributeInstance(attribute).applyModifier(new AttributeModifier(
INHERITED_ATTRIBUTES.get(attribute), "possessionModifier", targetValue / currentValue,
WizardryUtilities.Operations.MULTIPLY_FLAT));
}
}
if(possessor.world.isRemote){
// Shaders and effects
if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER);
electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); // Looks quite nice...
}else{
// Targeting
for(EntityLiving creature : WizardryUtilities.getEntitiesWithinRadius(16, possessor.posX,
possessor.posY, possessor.posZ, possessor.world, EntityLiving.class)){
// Mobs are dumb, if a player possesses something they're like "Huh?! Where'd you go?"
// Of course, this won't last long if the player attacks them, since they'll revenge-target them
if(creature.getAttackTarget() == possessor && !creature.canAttackClass(target.getClass()))
creature.setAttackTarget(null);
}
// Inventory and items
if(possessor.getEntityData() != null){
possessor.getEntityData().setTag(INVENTORY_NBT_KEY, possessor.inventory.writeToNBT(new NBTTagList()));
}
possessor.inventory.clear();
possessor.inventoryContainer.detectAndSendChanges();
ItemStack stack = target.getHeldItemMainhand().copy();
if(target instanceof EntityEnderman && ((EntityEnderman)target).getHeldBlockState() != null){
stack = new ItemStack(((EntityEnderman)target).getHeldBlockState().getBlock());
}else if(stack.getItem() instanceof ItemBow){
Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(stack);
enchantments.put(Enchantments.INFINITY, 1);
EnchantmentHelper.setEnchantments(enchantments, stack);
ItemStack arrow = new ItemStack(Items.ARROW);
if(target instanceof EntityStray || target instanceof EntityStrayMinion){
arrow = new ItemStack(Items.TIPPED_ARROW);
PotionUtils.addPotionToItemStack(arrow, PotionTypes.SLOWNESS);
}
possessor.setHeldItem(EnumHand.OFF_HAND, arrow);
}
possessor.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, stack);
// Packets
WizardryPacketHandler.net.sendToAllTracking(new PacketPossession.Message(possessor, target, duration), possessor);
if(possessor instanceof EntityPlayerMP){
WizardryPacketHandler.net.sendTo(new PacketPossession.Message(possessor, target, duration), (EntityPlayerMP)possessor);
}
}
return true;
}
return false;
}
/** Causes the given player to stop possessing their current possessee, if any, and resets all relevant data for
* both entities. Also takes care of sending packets to update clients. */
public void endPossession(EntityPlayer player){
// Reverts the possessed entity back to normal
EntityLiving victim = getPossessee(player);
if(victim != null){
victim.isDead = false;
victim.setNoAI(false);
victim.setPosition(player.posX, player.posY, player.posZ);
if(!player.world.isRemote) player.world.spawnEntity(victim);
for(PotionEffect effect : player.getActivePotionEffects()){
victim.addPotionEffect(effect);
}
}
// Reverts the player back to normal
player.clearActivePotions();
player.eyeHeight = player.getDefaultEyeHeight(); // How convenient!
if(WizardData.get(player) != null){
WizardData.get(player).setVariable(TIMER_KEY, 0);
WizardData.get(player).setVariable(POSSESSEE_KEY, null);
}
if(!player.capabilities.isCreativeMode){
player.capabilities.allowFlying = false;
player.capabilities.isFlying = false;
}
if(player.world.isRemote){
net.minecraft.client.Minecraft.getMinecraft().entityRenderer.stopUseShader();
electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); // Looks quite nice...
}
for(IAttribute attribute : INHERITED_ATTRIBUTES.keySet()){
player.getAttributeMap().getAttributeInstance(attribute).removeModifier(INHERITED_ATTRIBUTES.get(attribute));
}
if(player instanceof EntityPlayerMP){
player.inventory.clear();
if(player.getEntityData() != null){
player.inventory.readFromNBT(player.getEntityData().getTagList(INVENTORY_NBT_KEY, NBT.TAG_COMPOUND));
}
player.inventoryContainer.detectAndSendChanges();
}
this.playSound(player.world, player, 0, -1, null, "end");
if(!player.world.isRemote && player instanceof EntityPlayerMP){
WizardryPacketHandler.net.sendToAllTracking(new PacketPossession.Message(player, null, 0), player);
WizardryPacketHandler.net.sendTo(new PacketPossession.Message(player, null, 0), (EntityPlayerMP)player);
}
}
/** Returns the {@code EntityLiving} that is currently being possessed by the given player, or null if the player is
* not currently possessing an entity. */
@Nullable
public static EntityLiving getPossessee(EntityPlayer player){
return WizardData.get(player) == null ? null : WizardData.get(player).getVariable(POSSESSEE_KEY);
}
/** Returns true if the given player is currently possessing an entity, false otherwise. Just a shortcut for
* {@code Possession.getPossessee(player) != null}. */
public static boolean isPossessing(EntityPlayer player){
return getPossessee(player) != null;
}
private static int update(EntityPlayer player, Integer possessionTimer){
if(possessionTimer == null) possessionTimer = 0;
if(possessionTimer > 0){
if(isPossessing(player) && !player.isSneaking()){
possessionTimer--;
if(player.world.isRemote){
ParticleBuilder.create(Type.DARK_MAGIC, player).clr(0.1f, 0, 0.3f).spawn(player.world);
// TODO: This (and a few other similar uses) needs ClientProxy-ing.
if(!net.minecraft.client.Minecraft.getMinecraft().entityRenderer.isShaderActive())
if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER);
}
}else{
((Possession)Spells.possession).endPossession(player);
return 0;
}
}else if(isPossessing(player)){
((Possession)Spells.possession).endPossession(player);
}
return possessionTimer;
}
/** Adds the given {@link BiConsumer} to the list of abilities. An <i>ability</i> is an entity-specific action or
* effect that happens when a certain type of entity is possessed. For example, spiders can climb walls, endermen
* can pick up blocks, creepers explode, etc. Other mods may use this method to */
public static <T extends EntityLiving> void addAbility(Class<T> entityType, BiConsumer<T, EntityPlayer> ability){
abilities.put(entityType, ability);
}
@SuppressWarnings("unchecked") // Guess what? Type erasure again!
private static <T extends EntityLiving> void performAbilities(EntityLiving entity, EntityPlayer player){
// Now we have a type parameter T to work with we can ram the entity into the consumer without a compiler error
for(Class<? extends EntityLiving> entityType : abilities.keySet()){
if(entityType.isAssignableFrom(entity.getClass())){
abilities.get(entityType).forEach(a -> ((BiConsumer<T, EntityPlayer>)a).accept((T)entity, player));
}
}
}
/** Adds the given factory to the list of projectiles. When a player right-clicks while possessing an entity of the
* given type, the given projectile factory will be invoked to create a projectile, which is then aimed and spawned. */
public static <T extends Entity & IProjectile> void addProjectile(Class<? extends EntityLiving> entityType, Function<World, T> factory){
projectiles.put(entityType, factory);
}
/** Copied from Entity#setSize, with the call to move(...) removed. This is presumably also better than reflecting
* into Entity#setSize, which is protected. */
private static void setSize(Entity entity, float width, float height){
if(width != entity.width || height != entity.height){
entity.width = width;
entity.height = height;
double halfWidth = (double)width / 2.0D;
entity.setEntityBoundingBox(new AxisAlignedBB(entity.posX - halfWidth, entity.posY, entity.posZ - halfWidth, entity.posX + halfWidth, entity.posY + (double)entity.height, entity.posZ + halfWidth));
}
}
// ================================================ Event Handlers ================================================
// We got every kind of event handler goin', folks!
@SubscribeEvent
public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){
if(event.phase == TickEvent.Phase.START){
EntityLiving possessee = getPossessee(event.player);
if(possessee != null){
// Updating these to the player's variables won't have an effect on player movement, but it will
// affect various bits of mob-specific logic
possessee.setPosition(event.player.posX, event.player.posY, event.player.posZ);
possessee.motionX = event.player.motionX;
possessee.motionY = event.player.motionY;
possessee.motionZ = event.player.motionZ;
possessee.onGround = event.player.onGround;
possessee.onUpdate(); // Event though it's not in the world, it still needs updating
possessee.ticksExisted++; // Normally gets updated from World
if(possessee.getHealth() <= 0){
((Possession)Spells.possession).endPossession(event.player);
}
performAbilities(possessee, event.player);
}
}
// Right at the end of EntityPlayer#onUpdate() it calls EntityPlayer#updateSize(), which resets the player's
// size (and is also where this event is fired from, oddly enough) ... but not on my watch!
if(event.phase == TickEvent.Phase.END){
EntityLiving possessee = getPossessee(event.player);
if(possessee != null){
setSize(event.player, possessee.width, possessee.height);
}
}
}
// When possessing, attacks are diverted to the possessed entity for armour, immunity, resistance calculations
// and so on, then when the damage is actually applied, the player is also damaged via onLivingDamageEvent below
@SubscribeEvent(priority = EventPriority.HIGH)
public static void onLivingAttackEvent(LivingAttackEvent event){
if(event.getEntity() instanceof EntityPlayer && event.getSource() != DamageSource.OUT_OF_WORLD){
EntityLiving possessee = getPossessee((EntityPlayer)event.getEntity());
if(possessee != null){
DamageSafetyChecker.attackEntitySafely(possessee, event.getSource(), event.getAmount(), event.getSource().getDamageType());
event.setCanceled(true);
}
}
}
// LivingDamageEvent used in preference to LivingHurtEvent because the player is 'inside' the possessed entity, so
// any damage should come through that entity (and any armour, potions, enchantments etc. it has) first.
@SubscribeEvent
public static void onLivingDamageEvent(LivingDamageEvent event){
for(EntityPlayer player : event.getEntity().world.playerEntities){
EntityLiving possessee = getPossessee(player);
if(possessee == event.getEntity()){
// Possessors take half of all damage taken by the entity they are possessing. If the possessor receives
// fatal/critical damage (i.e. damage that takes them to half a heart or less), their health is reset to half
// a heart and the possession ends.
if(!player.capabilities.isCreativeMode){
// TODO: Make this a proper DamageSource?
DamageSafetyChecker.attackEntitySafely(player, DamageSource.OUT_OF_WORLD, event.getAmount() / 2,
DamageSource.OUT_OF_WORLD.getDamageType());
}
if(player.getHealth() <= Spells.possession.getProperty(CRITICAL_HEALTH).floatValue()){
player.setHealth(Spells.possession.getProperty(CRITICAL_HEALTH).floatValue());
((Possession)Spells.possession).endPossession(player);
}
}
}
}
// Prevents possessing players from interacting with blocks and controls projectile shooting
@SubscribeEvent
public static void onPlayerInteractEvent(PlayerInteractEvent event){
if(event instanceof PlayerInteractEvent.RightClickItem) return; // Can always do this
EntityLiving possessee = getPossessee(event.getEntityPlayer());
if(possessee != null){
// Let endermen interact with blocks
if(possessee instanceof EntityEnderman && (
(event instanceof PlayerInteractEvent.RightClickBlock && ((EntityEnderman)possessee).getHeldBlockState() != null)
|| (event instanceof PlayerInteractEvent.LeftClickBlock && ((EntityEnderman)possessee).getHeldBlockState() == null)))
return;
if(WizardData.get(event.getEntityPlayer()) != null && event.getWorld().isRemote
&& (event instanceof PlayerInteractEvent.RightClickEmpty
|| event instanceof PlayerInteractEvent.EntityInteract
|| event instanceof PlayerInteractEvent.RightClickBlock)){
Integer cooldown = WizardData.get(event.getEntityPlayer()).getVariable(SHOOT_COOLDOWN_KEY);
if(cooldown == null || cooldown == 0){
WizardryPacketHandler.net.sendToServer(new PacketControlInput.Message(PacketControlInput.ControlType.POSSESSION_PROJECTILE));
WizardData.get(event.getEntityPlayer()).setVariable(SHOOT_COOLDOWN_KEY, PROJECTILE_COOLDOWN);
if(possessee instanceof EntityLightningWraith){
Spells.arc.cast(event.getWorld(), event.getEntityPlayer(), EnumHand.MAIN_HAND, 0, new SpellModifiers());
}
if(possessee instanceof EntityCreeper){
((EntityCreeper)possessee).ignite();
}
}
}
if(event.isCancelable()) event.setCanceled(true);
}
}
/** Called via packets to shoot a projectile if the entity currently possessed by the given player can do so. */
public static void shootProjectile(EntityPlayer possessor){
if(WizardData.get(possessor) != null){
Integer cooldown = WizardData.get(possessor).getVariable(SHOOT_COOLDOWN_KEY);
if(cooldown == null || cooldown == 0){
EntityLiving possessee = getPossessee(possessor);
if(possessee != null){
if(possessee instanceof EntityLightningWraith){
Spells.arc.cast(possessor.world, possessor, EnumHand.MAIN_HAND, 0, new SpellModifiers());
}
if(possessee instanceof EntityCreeper){
((Possession)Spells.possession).endPossession(possessor);
((EntityCreeper)possessee).ignite(); // Aaaaaaand.... RUN!
}
Function<World, ? extends IProjectile> factory = projectiles.get(possessee.getClass());
if(factory != null){
IProjectile projectile = factory.apply(possessor.world);
Vec3d look = possessor.getLookVec();
((Entity)projectile).setPosition(possessor.posX + look.x, possessor.posY + possessor.getEyeHeight() + look.y, possessor.posZ + look.z);
projectile.shoot(look.x, look.y, look.z, 1.6f, WizardryUtilities.getDefaultAimingError(possessor.world.getDifficulty()));
if(projectile instanceof EntityMagicProjectile) ((EntityMagicProjectile)projectile).setCaster(possessor);
else if(projectile instanceof EntityMagicArrow) ((EntityMagicArrow)projectile).setCaster(possessor);
possessor.world.spawnEntity((Entity)projectile);
}
WizardData.get(possessor).setVariable(SHOOT_COOLDOWN_KEY, PROJECTILE_COOLDOWN);
}
}
}
}
@SubscribeEvent
public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){ // Not fired for revenge-targeting
if(event.getTarget() instanceof EntityPlayer && event.getEntityLiving() instanceof EntityLiving){
EntityLiving possessee = getPossessee((EntityPlayer)event.getTarget());
EntityLiving attacker = (EntityLiving)event.getEntityLiving();
if(possessee != null && !attacker.canAttackClass(possessee.getClass())){
event.setCanceled(true); // Mobs can't target a player possessing an entity they don't normally attack
}
}
}
// With these two methods I'm pretty sure it's watertight
@SubscribeEvent
public static void onLivingDeathEvent(LivingDeathEvent event){
if(event.getEntity() instanceof EntityPlayer && isPossessing((EntityPlayer)event.getEntity())){
((Possession)Spells.possession).endPossession((EntityPlayer)event.getEntity()); // Just in case, to make sure the player drops their items
}
}
@SubscribeEvent
public static void onPlayerLoggedOutEvent(PlayerEvent.PlayerLoggedOutEvent event){
if(isPossessing(event.player)) ((Possession)Spells.possession).endPossession(event.player);
}
@SubscribeEvent
public static void onBlockBreakEvent(BlockEvent.BreakEvent event){
EntityLiving possessee = getPossessee(event.getPlayer());
if(possessee instanceof EntityEnderman){
if(((EntityEnderman)possessee).getHeldBlockState() == null){
((EntityEnderman)possessee).setHeldBlockState(event.getState());
event.getPlayer().setHeldItem(EnumHand.MAIN_HAND, new ItemStack(event.getState().getBlock()));
event.setExpToDrop(0);
event.getWorld().setBlockToAir(event.getPos()); // Remove block before it can drop
}else{
event.setCanceled(true);
}
}
}
@SubscribeEvent
public static void onBlockPlaceEvent(BlockEvent.PlaceEvent event){
EntityLiving possessee = getPossessee(event.getPlayer());
if(possessee instanceof EntityEnderman){
if(((EntityEnderman)possessee).getHeldBlockState() == event.getState()){
((EntityEnderman)possessee).setHeldBlockState(null);
}else{
event.setCanceled(true);
}
}
}
@SubscribeEvent
public static void onEntityItemPickupEvent(EntityItemPickupEvent event){ // Why are there two item pickup events?
EntityLiving possessee = getPossessee(event.getEntityPlayer());
if(possessee != null){
if(possessee.canPickUpLoot() && possessee.getHeldItemMainhand().isEmpty()){
possessee.setHeldItem(EnumHand.MAIN_HAND, event.getItem().getItem());
}else{
event.setCanceled(true);
}
}
}
@SubscribeEvent
static void onItemTossEvent(ItemTossEvent event){
if(isPossessing(event.getPlayer())){ // Can't drop items while possessing
event.setCanceled(true);
event.getPlayer().inventory.addItemStackToInventory(event.getEntityItem().getItem());
}
}
@SubscribeEvent
public static void onAttackEntityEvent(AttackEntityEvent event){
EntityLiving possessee = getPossessee(event.getEntityPlayer());
if(possessee == null) return;
if(possessee instanceof EntityCreeper){
event.setCanceled(true); // Why do creepers have a melee AI?!
}else if(possessee.tasks.taskEntries.stream().noneMatch(t -> t.action instanceof EntityAIAttackMelee)){
event.setCanceled(true); // Can't melee with a non-melee mob
}
}
}
@@ -0,0 +1,103 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class RayOfPurification extends SpellRay {
/** The number by which this spell's damage is multiplied for undead entities. */
public static final String UNDEAD_DAMAGE_MULTIPLIER = "undead_damage_multiplier";
public RayOfPurification(){
super("ray_of_purification", true, EnumAction.NONE);
addProperties(DAMAGE, EFFECT_DURATION, BURN_DURATION, UNDEAD_DAMAGE_MULTIPLIER);
}
// The following three methods serve as a good example of how to implement continuous spell sounds (hint: it's easy)
@Override
protected SoundEvent[] createSounds(){
return this.createContinuousSpellSounds();
}
@Override
protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, entity, ticksInUse);
}
@Override
protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, x, y, z, ticksInUse, duration);
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target)){
if(MagicDamage.isEntityImmune(DamageType.RADIANT, target)){
if(!world.isRemote && ticksInUse == 1 && caster instanceof EntityPlayer) ((EntityPlayer)caster)
.sendStatusMessage(new TextComponentTranslation("spell.resist", target.getName(),
this.getNameForTranslationFormatted()), true);
}else{
float damage = getProperty(DAMAGE).floatValue() * modifiers.get(SpellModifiers.POTENCY);
// Fire
if(((EntityLivingBase)target).isEntityUndead()){
target.setFire((int)(getProperty(BURN_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
damage *= getProperty(UNDEAD_DAMAGE_MULTIPLIER).floatValue();
}
// Damage
WizardryUtilities.attackEntityWithoutKnockback(target,
MagicDamage.causeDirectMagicDamage(caster, DamageType.RADIANT), damage);
// Blindness
((EntityLivingBase)target).addPotionEffect(new PotionEffect(MobEffects.BLINDNESS,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade))));
}
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@Override
protected void spawnParticleRay(World world, Vec3d origin, Vec3d direction, EntityLivingBase caster, double distance){
if(caster != null){
ParticleBuilder.create(Type.BEAM).entity(caster).pos(origin.subtract(caster.getPositionVector()))
.length(distance).clr(1, 0.6f + 0.3f * world.rand.nextFloat(), 0.2f)
.scale(MathHelper.sin(world.getTotalWorldTime() * 0.2f) * 0.1f + 1.4f).spawn(world);
}else{
ParticleBuilder.create(Type.BEAM).pos(origin).target(origin.add(direction.scale(distance)))
.clr(1, 0.6f + 0.3f * world.rand.nextFloat(), 0.2f)
.scale(MathHelper.sin(world.getTotalWorldTime() * 0.2f) * 0.1f + 1.4f).spawn(world);
}
}
}
@@ -0,0 +1,53 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.potion.Curse;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.potion.PotionEffect;
import net.minecraft.world.World;
public class RemoveCurse extends SpellBuff {
public RemoveCurse(){
super("remove_curse", 1, 1, 0.3f);
this.soundValues(0.7f, 1.2f, 0.4f);
}
@Override
protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){
if(!caster.getActivePotionEffects().isEmpty()){
boolean flag = false;
for(PotionEffect effect : caster.getActivePotionEffects()){
// The PotionEffect version (as opposed to Potion) does not call cleanup callbacks
if(effect.getPotion() instanceof Curse){
caster.removePotionEffect(effect.getPotion());
flag = true;
}
}
return flag;
}
return false;
}
@Override
protected void spawnParticles(World world, EntityLivingBase caster, SpellModifiers modifiers){
super.spawnParticles(world, caster, modifiers);
for(int i = 0; i < particleCount*2; i++){
double x = caster.posX + world.rand.nextDouble() * 2 - 1;
double y = caster.getEntityBoundingBox().minY + caster.getEyeHeight() - 0.5 + world.rand.nextDouble();
double z = caster.posZ + world.rand.nextDouble() * 2 - 1;
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.14, 0).clr(0x0f001b)
.time(20 + world.rand.nextInt(12)).spawn(world);
ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0x0f001b).spawn(world);
}
}
}
@@ -1,9 +1,5 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
@@ -12,9 +8,13 @@ import net.minecraft.world.World;
public class ReplenishHunger extends SpellBuff {
public static final String HUNGER_POINTS = "hunger_points";
public static final String SATURATION_MODIFIER = "saturation_modifier";
public ReplenishHunger(){
super("replenish_hunger", Tier.APPRENTICE, Element.HEALING, SpellType.BUFF, 10, 30, WizardrySounds.SPELL_HEAL, 1, 0.7f, 0.3f);
super("replenish_hunger", 1, 0.7f, 0.3f);
this.soundValues(0.7f, 1.2f, 0.4f);
addProperties(HUNGER_POINTS, SATURATION_MODIFIER);
}
@Override public boolean canBeCastByNPCs(){ return false; }
@@ -28,9 +28,9 @@ public class ReplenishHunger extends SpellBuff {
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
if(caster.getFoodStats().needFood()){
int foodAmount = (int)(4 * modifiers.get(SpellModifiers.POTENCY));
int foodAmount = (int)(getProperty(HUNGER_POINTS).floatValue() * modifiers.get(SpellModifiers.POTENCY));
// Fixed issue #6: Changed to addStats, since setFoodLevel is client-side only
caster.getFoodStats().addStats(foodAmount, foodAmount * 0.1f);
caster.getFoodStats().addStats(foodAmount, getProperty(SATURATION_MODIFIER).floatValue());
return super.cast(world, caster, hand, ticksInUse, modifiers);
}
@@ -0,0 +1,111 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.item.ISpellCastingItem;
import electroblob.wizardry.packet.PacketResurrection;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import java.util.Arrays;
import java.util.Comparator;
public class Resurrection extends Spell {
public static final String WAIT_TIME = "wait_time";
public Resurrection(){
super("resurrection", EnumAction.NONE, false);
addProperties(EFFECT_RADIUS, WAIT_TIME);
}
@Override
public boolean requiresPacket(){
return false; // Has its own packets
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
WizardData data = WizardData.get(caster);
double radius = getProperty(EFFECT_RADIUS).doubleValue() * modifiers.get(WizardryItems.range_upgrade);
if(!world.isRemote && caster.getServer() != null){
// Potency reduces the time you have to wait to resurrect an ally
int waitTime = (int)(getProperty(WAIT_TIME).floatValue() / modifiers.get(SpellModifiers.POTENCY));
EntityPlayerMP nearestDeadAlly = caster.getServer().getPlayerList().getPlayers().stream()
.filter(p -> !p.isEntityAlive() && p.deathTime > waitTime && (data.isPlayerAlly(p) || caster == p)
&& p.getDistanceSq(caster) < radius * radius)
.min(Comparator.comparingDouble(caster::getDistanceSq))
.orElse(null);
if(nearestDeadAlly != null){
// When the player entity dies, it is removed from world#loadedEntityList. However, it is NOT removed
// from playerEntityList (and probably a few other places) until respawn is clicked, and since that
// never happens here we need to clean up those references or the player will have duplicate entries
// in some entity lists - and weirdness will ensue!
world.removeEntity(nearestDeadAlly); // Clean up the old entity references
resurrect(nearestDeadAlly); // Reset isDead, must be before spawning the player again
world.spawnEntity(nearestDeadAlly); // Re-add the player to all the relevant entity lists
// Notify clients to reset the appropriate fields, spawn particles and play sounds
IMessage msg = new PacketResurrection.Message(nearestDeadAlly.getEntityId());
WizardryPacketHandler.net.sendToDimension(msg, caster.dimension);
if(caster == nearestDeadAlly){
caster.getServer().getPlayerList().sendMessage(new TextComponentTranslation(
"spell." + this.getRegistryName() + ".resurrect_self", caster.getDisplayName()));
}else{
caster.getServer().getPlayerList().sendMessage(new TextComponentTranslation(
"spell." + this.getRegistryName() + ".resurrect_ally", nearestDeadAlly.getDisplayName(), caster.getDisplayName()));
}
return true;
}
}
return false;
}
/** Sets the given player back to alive, sets their health to half-full and (on the client) spawns particles. */
public void resurrect(EntityPlayer player){
player.isDead = false;
player.setHealth(player.getMaxHealth() / 2);
// Experience doesn't normally get reset until respawn, so we need to do that here too
player.deathTime = 0;
player.experience = 0;
player.experienceLevel = 0;
player.experienceTotal = 0;
if(player.world.isRemote){
ParticleBuilder.spawnHealParticles(player.world, player);
this.playSound(player.world, player, 0, -1, null); // We know the modifiers parameter isn't used
}
}
public static int getRemainingWaitTime(int timeSinceDeath){
return Math.max(0, MathHelper.ceil((Spells.resurrection.getProperty(Resurrection.WAIT_TIME).floatValue() - timeSinceDeath) / 20));
}
/** Helper method for detecting if a stack can be used to cast the resurrection spell. */
public static boolean canStackResurrect(ItemStack stack, EntityPlayer player){
return stack.getItem() instanceof ISpellCastingItem
&& Arrays.asList(((ISpellCastingItem)stack.getItem()).getSpells(stack)).contains(Spells.resurrection)
&& ((ISpellCastingItem)stack.getItem()).canCast(stack, Spells.resurrection, player, EnumHand.MAIN_HAND, 0, new SpellModifiers());
}
}
@@ -0,0 +1,83 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Reversal extends SpellRay {
public static final String REVERSED_EFFECTS = "reversed_effects";
public Reversal(){
super("reversal", false, EnumAction.NONE);
addProperties(REVERSED_EFFECTS);
}
@Override
public boolean canBeCastByDispensers(){
return false;
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, @Nullable EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
// Naturally, this spell won't work unless it has a living caster and target
if(caster != null && target instanceof EntityLivingBase){
List<PotionEffect> negativePotions = new ArrayList<>(caster.getActivePotionEffects());
negativePotions.removeIf(p -> !p.getPotion().isBadEffect());
if(!world.isRemote){
if(negativePotions.isEmpty()) return false; // Needs potion effects to reverse!
// 1 effect for non-necromancy wands, 2 for apprentice necromancy wands, 3 for advanced and 4 for master
int bonusEffects = (int)((modifiers.get(SpellModifiers.POTENCY) - 1) / Constants.POTENCY_INCREASE_PER_TIER + 0.5f) - 1;
int n = getProperty(REVERSED_EFFECTS).intValue() + bonusEffects;
// Chooses n random negative potion effects, where n is the potency level
Collections.shuffle(negativePotions);
negativePotions = negativePotions.subList(0, negativePotions.size() < n ? negativePotions.size() : n);
// Now reverse them!
negativePotions.forEach(p -> caster.removePotionEffect(p.getPotion()));
negativePotions.forEach(((EntityLivingBase)target)::addPotionEffect);
}else{
ParticleBuilder.create(Type.BUFF).entity(caster).clr(1, 1, 0.3f).spawn(world);
}
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, @Nullable EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, @Nullable EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@Override
protected void spawnParticle(World world, double x, double y, double z, double vx, double vy, double vz){
ParticleBuilder.create(Type.DARK_MAGIC).pos(x, y, z).clr(0.1f, 0, 0).spawn(world);
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).time(12 + world.rand.nextInt(8)).clr(0.1f, 0, 0.05f).spawn(world);
}
}
@@ -0,0 +1,40 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.EnumHand;
import net.minecraft.world.World;
public class Satiety extends SpellBuff {
public static final String HUNGER_POINTS = "hunger_points";
public static final String SATURATION_MODIFIER = "saturation_modifier";
public Satiety(){
super("satiety", 1, 0.7f, 0.3f);
this.soundValues(0.7f, 1.2f, 0.4f);
addProperties(HUNGER_POINTS, SATURATION_MODIFIER);
}
@Override public boolean canBeCastByNPCs(){ return false; }
@Override
protected boolean applyEffects(EntityLivingBase caster, SpellModifiers modifiers){
return true; // In this case the best solution is to remove the functionality of this method and override cast.
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
if(caster.getFoodStats().needFood()){
int foodAmount = (int)(getProperty(HUNGER_POINTS).floatValue() * modifiers.get(SpellModifiers.POTENCY));
// Fixed issue #6: Changed to addStats, since setFoodLevel is client-side only
caster.getFoodStats().addStats(foodAmount, getProperty(SATURATION_MODIFIER).floatValue());
return super.cast(world, caster, hand, ticksInUse, modifiers);
}
return false;
}
}
@@ -1,24 +1,20 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.integration.DamageSafetyChecker;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.util.IElementalDamage;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemStack;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.fml.common.Mod;
@@ -27,8 +23,27 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class ShadowWard extends Spell {
public static final String REFLECTED_FRACTION = "reflected_fraction";
public ShadowWard(){
super("shadow_ward", Tier.ADVANCED, Element.NECROMANCY, SpellType.DEFENCE, 10, 0, EnumAction.BLOCK, true);
super("shadow_ward", EnumAction.BLOCK, true);
addProperties(REFLECTED_FRACTION);
soundValues(0.6f, 1, 0);
}
@Override
protected SoundEvent[] createSounds(){
return this.createContinuousSpellSounds();
}
@Override
protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, entity, ticksInUse);
}
@Override
protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, x, y, z, ticksInUse, duration);
}
@Override
@@ -42,7 +57,7 @@ public class ShadowWard extends Spell {
}
if(ticksInUse % 50 == 0){
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.BLOCK_PORTAL_AMBIENT, 0.6f, 1.5f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
}
return true;
@@ -50,22 +65,24 @@ public class ShadowWard extends Spell {
@SubscribeEvent
public static void onLivingAttackEvent(LivingAttackEvent event){
if(event.getSource() != null && event.getSource().getTrueSource() instanceof EntityLivingBase){
// There used to be a check that the target was a player here, but I don't see any reason for it.
ItemStack wand = event.getEntityLiving().getActiveItemStack();
if(wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof ShadowWard && !event.getSource().isUnblockable()
if(WizardryUtilities.isCasting(event.getEntityLiving(), Spells.shadow_ward) && !event.getSource().isUnblockable()
&& !(event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).isRetaliatory())){
event.setCanceled(true);
float reflectedFraction = MathHelper.clamp(Spells.shadow_ward.getProperty(REFLECTED_FRACTION).floatValue(), 0, 1);
// Now we can preserve the original damage source (sort of) as long as we make it retaliatory.
// For some reason this isn't working, so I've reverted to plain old magic damage for now.
//event.getEntityLiving().attackEntityFrom(
// MagicDamage.causeDirectMagicDamage(event.getSource().getTrueSource(), DamageType.MAGIC, true), event.getAmount() * 0.5f);
DamageSafetyChecker.attackEntitySafely(event.getEntity(), DamageSource.MAGIC, event.getAmount() * 0.5f, event.getSource().getDamageType());
((EntityLivingBase)event.getSource().getTrueSource()).attackEntityFrom(
MagicDamage.causeDirectMagicDamage(event.getEntityLiving(), DamageType.MAGIC, true), event.getAmount() * 0.5f);
DamageSafetyChecker.attackEntitySafely(event.getEntity(), DamageSource.MAGIC, event.getAmount()
* (1 - reflectedFraction), event.getSource().getDamageType());
event.getSource().getTrueSource().attackEntityFrom(MagicDamage.causeDirectMagicDamage(
event.getEntityLiving(), DamageType.MAGIC, true), event.getAmount() * reflectedFraction);
}
}
}
@@ -1,40 +1,60 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.data.IVariable;
import electroblob.wizardry.data.Persistence;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.entity.EntityShield;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundEvent;
import net.minecraft.world.World;
public class Shield extends Spell {
public static final IVariable<EntityShield> SHIELD_KEY = new IVariable.Variable<>(Persistence.NEVER);
public Shield(){
super("shield", Tier.APPRENTICE, Element.HEALING, SpellType.DEFENCE, 5, 0, EnumAction.BLOCK, true);
super("shield", EnumAction.BLOCK, true);
addProperties(EFFECT_STRENGTH);
}
@Override
protected SoundEvent[] createSounds(){
return this.createContinuousSpellSounds();
}
@Override
protected void playSound(World world, EntityLivingBase entity, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, entity, ticksInUse);
}
@Override
protected void playSound(World world, double x, double y, double z, int ticksInUse, int duration, SpellModifiers modifiers, String... sounds){
this.playSoundLoop(world, x, y, z, ticksInUse, duration);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
caster.addPotionEffect(new PotionEffect(MobEffects.RESISTANCE, 10, 0, false, false));
caster.addPotionEffect(new PotionEffect(MobEffects.RESISTANCE, 10,
getProperty(EFFECT_STRENGTH).intValue(), false, false));
if(WizardData.get(caster).shield == null){
WizardData.get(caster).shield = new EntityShield(world, caster);
if(WizardData.get(caster).getVariable(SHIELD_KEY) == null){
EntityShield shield = new EntityShield(world, caster);
WizardData.get(caster).setVariable(SHIELD_KEY, shield);
if(!world.isRemote){
world.spawnEntity(WizardData.get(caster).shield);
world.spawnEntity(shield);
}
}
if(ticksInUse == 0){
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION, 1.0f, 1.0f);
}
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -1,18 +1,11 @@
package electroblob.wizardry.spell;
import java.util.List;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLivingBase;
@@ -22,39 +15,59 @@ import net.minecraft.item.EnumAction;
import net.minecraft.network.play.server.SPacketEntityVelocity;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import java.util.List;
public class Shockwave extends Spell {
private static final double BASE_RADIUS = 5;
private static final float BASE_DAMAGE = 8;
public static final String MAX_REPULSION_VELOCITY = "max_repulsion_velocity";
/** The radius within which maximum damage is dealt and maximum repulsion velocity is applied. */
private static final double EPICENTRE_RADIUS = 1;
public Shockwave(){
super("shockwave", Tier.MASTER, Element.SORCERY, SpellType.ATTACK, 65, 150, EnumAction.BOW, false);
super("shockwave", EnumAction.BOW, false);
this.soundValues(2, 0.5f, 0);
addProperties(BLAST_RADIUS, DAMAGE, MAX_REPULSION_VELOCITY);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(
BASE_RADIUS * modifiers.get(WizardryItems.blast_upgrade), caster.posX, caster.posY, caster.posZ, world);
double radius = getProperty(BLAST_RADIUS).floatValue() * modifiers.get(WizardryItems.blast_upgrade);
List<EntityLivingBase> targets = WizardryUtilities.getEntitiesWithinRadius(radius, caster.posX, caster.posY, caster.posZ, world);
for(EntityLivingBase target : targets){
if(WizardryUtilities.isValidTarget(caster, target)){
if(target instanceof EntityPlayer && (!Wizardry.settings.playersMoveEachOther
|| ItemArtefact.isArtefactActive((EntityPlayer)target, WizardryItems.amulet_anchoring))){
if(!world.isRemote) caster.sendStatusMessage(new TextComponentTranslation("spell.resist",
target.getName(), this.getNameForTranslationFormatted()), true);
return false;
}
if(AllyDesignationSystem.isValidTarget(caster, target)){
// Produces a linear profile from 0 at the edge of the radius to 1 at the epicentre radius, then
// a constant value of 1 within the epicentre radius.
float proximity = (float)(1 - (Math.max(target.getDistance(caster) - EPICENTRE_RADIUS, 0))/(radius - EPICENTRE_RADIUS));
// Damage increases closer to player up to a maximum of 4 hearts (at 1 block distance).
float damage = Math.min(BASE_DAMAGE / target.getDistance(caster), BASE_DAMAGE);
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(caster, DamageType.BLAST),
damage * modifiers.get(SpellModifiers.POTENCY));
getProperty(DAMAGE).floatValue() * proximity * modifiers.get(SpellModifiers.POTENCY));
if(!world.isRemote){
// Entity speed increases closer to the player to a maximum of 3 (at 1 block distance).
// This is the entity's speed compared to its distance from the player. Used for a similar triangles
// based x, y and z speed calculation.
double velocityFactor = Math.min(5 / target.getDistanceSq(caster), 3.0d);
double velocityFactor = proximity * getProperty(MAX_REPULSION_VELOCITY).floatValue();
double dx = target.posX - caster.posX;
double dy = target.posY + 1 - caster.posY;
double dy = target.getEntityBoundingBox().minY + 1 - caster.posY;
double dz = target.posZ - caster.posZ;
target.motionX = velocityFactor * dx;
@@ -74,17 +87,17 @@ public class Shockwave extends Spell {
double particleX, particleZ;
for(int i = 0; i < 40; i++){
particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble();
particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble();
ParticleBuilder.create(Type.SPARKLE).pos(particleX, caster.getEntityBoundingBox().minY, particleZ)
.vel(particleX - caster.posX, 0, particleZ - caster.posZ).time(30).clr(0.8f, 0.8f, 1).spawn(world);
particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble();
particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble();
ParticleBuilder.create(Type.SPARKLE).pos(particleX, caster.getEntityBoundingBox().minY, particleZ)
.vel(particleX - caster.posX, 0, particleZ - caster.posZ).time(30).clr(0.9f, 0.9f, 0.9f).spawn(world);
// particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble();
// particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble();
// ParticleBuilder.create(Type.SPARKLE).pos(particleX, caster.getEntityBoundingBox().minY, particleZ)
// .vel(particleX - caster.posX, 0, particleZ - caster.posZ).time(30).clr(0.8f, 0.8f, 1).spawn(world);
//
// particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble();
// particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble();
// ParticleBuilder.create(Type.SPARKLE).pos(particleX, caster.getEntityBoundingBox().minY, particleZ)
// .vel(particleX - caster.posX, 0, particleZ - caster.posZ).time(30).clr(0.9f, 0.9f, 0.9f).spawn(world);
particleX = caster.posX - 1.0d + 2 * world.rand.nextDouble();
particleZ = caster.posZ - 1.0d + 2 * world.rand.nextDouble();
IBlockState block = WizardryUtilities.getBlockEntityIsStandingOn(caster);
@@ -95,14 +108,19 @@ public class Shockwave extends Spell {
}
}
ParticleBuilder.create(Type.SPHERE)
.pos(caster.posX, caster.getEntityBoundingBox().minY + 0.1, caster.posZ)
.scale((float)radius * 0.8f)
.clr(0.8f, 0.9f, 1)
.spawn(world);
world.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, caster.posX,
caster.getEntityBoundingBox().minY + 0.1, caster.posZ, 0, 0, 0);
}
caster.swingArm(hand);
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SHOCKWAVE, 1.0f, 0.7f);
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_SHOCKWAVE, 2.0f, 0.3f);
playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@@ -0,0 +1,101 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityArmorStand;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityShulkerBullet;
import net.minecraft.item.EnumAction;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.Comparator;
import java.util.List;
public class ShulkerBullet extends Spell {
public ShulkerBullet(){
super("shulker_bullet", EnumAction.NONE, false);
this.soundValues(2, 1, 0.3f);
addProperties(RANGE);
}
@Override public boolean canBeCastByNPCs(){ return true; }
@Override public boolean canBeCastByDispensers(){ return true; }
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
if(!shoot(world, caster, caster.posX, caster.posY, caster.posZ, EnumFacing.UP, modifiers)) return false;
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){
if(!shoot(world, caster, caster.posX, caster.posY, caster.posZ, EnumFacing.UP, modifiers)) return false;
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@Override
public boolean cast(World world, double x, double y, double z, EnumFacing direction, int duration, int ticksInUse, SpellModifiers modifiers){
if(!shoot(world, null, x, y, z, direction, modifiers)) return false;
this.playSound(world, x, y, z, ticksInUse, -1, modifiers);
return true;
}
private boolean shoot(World world, @Nullable EntityLivingBase caster, double x, double y, double z, EnumFacing direction, SpellModifiers modifiers){
if(!world.isRemote){
double range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade);
List<EntityLivingBase> possibleTargets = WizardryUtilities.getEntitiesWithinRadius(range, x, y, z, world);
possibleTargets.remove(caster);
possibleTargets.removeIf(t -> t instanceof EntityArmorStand);
if(possibleTargets.isEmpty()) return false;
// getDistanceSq doesn't require square-rooting so it's faster when only comparing
possibleTargets.sort(Comparator.comparingDouble(t -> t.getDistanceSq(x, y, z)));
Entity target = possibleTargets.get(0);
// Y axis because the player is always upright
if(caster != null){
world.spawnEntity(new EntityShulkerBullet(world, caster, target, direction.getAxis()));
}else{
// Can't use the normal constructor because doesn't accept null for the owner
EntityShulkerBullet bullet = new EntityShulkerBullet(world);
bullet.setLocationAndAngles(x, y, z, bullet.rotationYaw, bullet.rotationPitch);
// Where there's a will there's a way...
NBTTagCompound nbt = new NBTTagCompound();
bullet.writeToNBT(nbt);
nbt.setInteger("Dir", direction.getIndex());
BlockPos pos = new BlockPos(target);
NBTTagCompound targetTag = NBTUtil.createUUIDTag(target.getUniqueID());
targetTag.setInteger("X", pos.getX());
targetTag.setInteger("Y", pos.getY());
targetTag.setInteger("Z", pos.getZ());
nbt.setTag("Target", targetTag);
bullet.readFromNBT(nbt); // LOL I just modified private fields without reflection
world.spawnEntity(bullet);
}
}
return true;
}
}
@@ -1,43 +1,60 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.PotionEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class SixthSense extends Spell {
/** A {@code ResourceLocation} representing the shader file used when under the effects of sixth sense. */
public static final ResourceLocation SHADER = new ResourceLocation(Wizardry.MODID, "shaders/post/sixth_sense.json");
public SixthSense(){
super("sixth_sense", Tier.APPRENTICE, Element.EARTH, SpellType.BUFF, 20, 100, EnumAction.BOW, false);
super("sixth_sense", EnumAction.BOW, false);
addProperties(EFFECT_DURATION, EFFECT_RADIUS);
soundValues(1, 1.1f, 0.2f);
}
@Override
public boolean doesSpellRequirePacket(){
public boolean requiresPacket(){
return false;
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
// Cannot be cast when it has already been cast
if(!world.isRemote){
caster.addPotionEffect(new PotionEffect(WizardryPotions.sixth_sense,
(int)(400 * modifiers.get(WizardryItems.duration_upgrade)),
(int)((modifiers.get(WizardryItems.range_upgrade) - 1f) / Constants.RANGE_INCREASE_PER_LEVEL)));
caster.addPotionEffect(new PotionEffect(WizardryPotions.sixth_sense,
(int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)),
(int)((modifiers.get(WizardryItems.range_upgrade) - 1f) / Constants.RANGE_INCREASE_PER_LEVEL)));
if(world.isRemote && caster == net.minecraft.client.Minecraft.getMinecraft().player){
if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER);
electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect();
}
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SHOOT, 1.0F,
world.rand.nextFloat() * 0.2F + 1.0F);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return true;
}
@SubscribeEvent
public static void onPotionAddedEvent(PotionEvent.PotionAddedEvent event){
if(event.getEntity().world.isRemote && event.getPotionEffect().getPotion() == WizardryPotions.sixth_sense
&& event.getEntity() == net.minecraft.client.Minecraft.getMinecraft().player){
if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER);
electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect();
}
}
}
@@ -1,55 +1,32 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.living.EntityMagicSlime;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.EntitySkeleton;
import net.minecraft.entity.monster.EntitySlime;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
public class Slime extends SpellRay {
private static final int BASE_DURATION = 200;
public Slime(){
super("slime", Tier.ADVANCED, Element.EARTH, SpellType.ATTACK, 20, 50, false, 8, WizardrySounds.SPELL_ICE);
super("slime", false, EnumAction.NONE);
addProperties(DURATION);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
// This spell uses more than one sound, so this is required...
boolean flag = super.cast(world, caster, hand, ticksInUse, modifiers);
if(flag) WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_SLIME_ATTACK, 1.0F, 0.5F);
return flag;
}
@Override
public boolean cast(World world, EntityLiving caster, EnumHand hand, int ticksInUse, EntityLivingBase target, SpellModifiers modifiers){
boolean flag = super.cast(world, caster, hand, ticksInUse, target, modifiers);
if(flag) caster.playSound(SoundEvents.ENTITY_SLIME_ATTACK, 1.0F, 0.5F);
return flag;
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(WizardryUtilities.isLiving(target) && !(target instanceof EntityMagicSlime)){
@@ -58,29 +35,24 @@ public class Slime extends SpellRay {
new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()), true);
}else{
if(target instanceof EntitySkeleton && caster instanceof EntityPlayer)
WizardryAdvancementTriggers.slime_skeleton.triggerFor((EntityPlayer)caster);
if(!world.isRemote){
EntityMagicSlime slime = new EntityMagicSlime(world, caster, (EntityLivingBase)target,
(int)(BASE_DURATION * modifiers.get(WizardryItems.duration_upgrade)));
(int)(getProperty(DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade)));
world.spawnEntity(slime);
}
}
return true;
}
return true;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return true;
}
@@ -0,0 +1,46 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
public class SlowTime extends SpellBuff {
/** A {@code ResourceLocation} representing the shader file used when possessing an entity. */
public static final ResourceLocation SHADER = new ResourceLocation(Wizardry.MODID, "shaders/post/slow_time.json");
public SlowTime(){
super("slow_time", 0.2f, 0.8f, 0.8f, () -> WizardryPotions.slow_time);
addProperties(EFFECT_RADIUS);
soundValues(0.6f, 1.5f, 0);
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
if(caster.world.isRemote && caster == net.minecraft.client.Minecraft.getMinecraft().player){
if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER);
}
return super.cast(world, caster, hand, ticksInUse, modifiers);
}
@Override
public boolean requiresPacket(){
return false;
}
@Override
public boolean canBeCastByDispensers(){
return false;
}
@Override
public boolean canBeCastByNPCs(){
return false;
}
}
@@ -1,8 +1,5 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.tileentity.TileEntityPlayerSave;
import electroblob.wizardry.util.ParticleBuilder;
@@ -11,26 +8,28 @@ import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class Snare extends SpellRay {
public Snare(){
super("snare", Tier.BASIC, Element.EARTH, SpellType.ATTACK, 10, 10, false, 10, SoundEvents.BLOCK_GRASS_PLACE);
super("snare", false, EnumAction.NONE);
this.soundValues(1, 1.4f, 0.4f);
this.ignoreEntities(true);
this.ignoreLivingEntities(true);
addProperties(DAMAGE, EFFECT_DURATION, EFFECT_STRENGTH);
}
@Override
protected boolean onEntityHit(World world, Entity target, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse, SpellModifiers modifiers){
if(side == EnumFacing.UP && world.isSideSolid(pos, EnumFacing.UP)
&& WizardryUtilities.canBlockBeReplaced(world, pos.up())){
@@ -47,7 +46,7 @@ public class Snare extends SpellRay {
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, int ticksInUse, SpellModifiers modifiers){
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
@@ -1,25 +1,24 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntitySnowball;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
public class Snowball extends Spell {
public Snowball(){
super("snowball", Tier.BASIC, Element.ICE, SpellType.ATTACK, 1, 1, EnumAction.NONE, false);
super("snowball", EnumAction.NONE, false);
addProperties(RANGE);
soundValues(0.5f, 0.4f, 0.2f);
}
@Override
public boolean doesSpellRequirePacket(){
public boolean requiresPacket(){
return false;
}
@@ -27,13 +26,18 @@ public class Snowball extends Spell {
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
if(!world.isRemote){
// Trajectory calculation - see SpellProjectile for a more detailed explanation
float g = 0.03f;
float launchHeight = caster.getEyeHeight();
float range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade);
float velocity = MathHelper.sqrt(MathHelper.sqrt(g*g * (launchHeight*launchHeight + range*range)) - g*launchHeight);
EntitySnowball snowball = new EntitySnowball(world, caster);
snowball.shoot(caster, caster.rotationPitch, caster.rotationYaw, 0.0f, 1.5f, 1.0f);
snowball.shoot(caster, caster.rotationPitch, caster.rotationYaw, 0.0f, velocity, 1.0f);
world.spawnEntity(snowball);
}
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F,
0.4F / (world.rand.nextFloat() * 0.4F + 0.8F));
this.playSound(world, caster, ticksInUse, -1, modifiers);
caster.swingArm(hand);
return true;
}
@@ -1,11 +1,7 @@
package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.tileentity.TileEntityTimer;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
@@ -19,13 +15,17 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class SpectralPathway extends Spell {
/** The base length of the conjured bridge, in blocks. */
public static final String LENGTH = "length";
public SpectralPathway(){
super("spectral_pathway", Tier.ADVANCED, Element.SORCERY, SpellType.UTILITY, 40, 300, EnumAction.BOW, false);
super("spectral_pathway", EnumAction.BOW, false);
addProperties(LENGTH, DURATION);
}
@Override
public boolean doesSpellRequirePacket(){
public boolean requiresPacket(){
return false;
}
@@ -44,8 +44,6 @@ public class SpectralPathway extends Spell {
if(!world.isRemote){
int baseLength = 15;
// Gets the coordinates of the nearest block intersection to the player's feet.
// Remember that a block always takes the coordinates of its northwestern corner.
BlockPos origin = new BlockPos(Math.round(caster.posX), (int)caster.getEntityBoundingBox().minY - 1,
@@ -53,7 +51,7 @@ public class SpectralPathway extends Spell {
int startPoint = direction.getAxisDirection() == AxisDirection.POSITIVE ? -1 : 0;
for(int i = 0; i < (int)(baseLength * modifiers.get(WizardryItems.range_upgrade)); i++){
for(int i = 0; i < (int)(getProperty(LENGTH).floatValue() * modifiers.get(WizardryItems.range_upgrade)); i++){
// If either a block gets placed or one has already been placed, flag is set to true.
flag = placePathwayBlockIfPossible(world, origin.offset(direction, startPoint + i),
modifiers.get(WizardryItems.duration_upgrade)) || flag;
@@ -63,17 +61,17 @@ public class SpectralPathway extends Spell {
modifiers.get(WizardryItems.duration_upgrade)) || flag;
}
}
// TODO: There may be some client/server discrepancies here.
WizardryUtilities.playSoundAtPlayer(caster, WizardrySounds.SPELL_CONJURATION_LARGE, 1.0f, 1.0f);
this.playSound(world, caster, ticksInUse, -1, modifiers);
return flag;
}
private static boolean placePathwayBlockIfPossible(World world, BlockPos pos, float durationMultiplier){
if(WizardryUtilities.canBlockBeReplacedB(world, pos)){
private boolean placePathwayBlockIfPossible(World world, BlockPos pos, float durationMultiplier){
if(WizardryUtilities.canBlockBeReplaced(world, pos, true)){
world.setBlockState(pos, WizardryBlocks.spectral_block.getDefaultState());
if(world.getTileEntity(pos) instanceof TileEntityTimer){
((TileEntityTimer)world.getTileEntity(pos)).setLifetime((int)(1200 * durationMultiplier));
((TileEntityTimer)world.getTileEntity(pos)).setLifetime((int)(getProperty(DURATION).floatValue() * durationMultiplier));
}
return true;
}

Some files were not shown because too many files have changed in this diff Show More