Files
Wizardry/src/main/java/electroblob/wizardry/item/ItemArtefact.java
T
2020-06-20 13:14:16 +01:00

896 lines
40 KiB
Java

package electroblob.wizardry.item;
import com.google.common.collect.Streams;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.entity.construct.EntityFireRing;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.entity.projectile.EntityDart;
import electroblob.wizardry.entity.projectile.EntityForceOrb;
import electroblob.wizardry.entity.projectile.EntityIceShard;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.integration.DamageSafetyChecker;
import electroblob.wizardry.integration.baubles.WizardryBaublesIntegration;
import electroblob.wizardry.registry.*;
import electroblob.wizardry.spell.*;
import electroblob.wizardry.util.*;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.IProjectile;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.FurnaceRecipes;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.minecraftforge.common.BiomeDictionary;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.event.entity.living.PotionEvent;
import net.minecraftforge.event.entity.player.PlayerDropsEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.Event;
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 net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Base class for all artefact items, which handles effects, textures and so on. The majority of artefacts are
* event-driven so it is unlikely that this class will need to be extended, unless other {@code Item} methods are to be
* overridden.
* <p></p>
* This class contains methods and an enum that mirror those in {@code IBauble} from the Baubles mod. If Baubles is
* loaded, these are called via the bauble capability; otherwise, they are called from regular {@link Item} methods
* or events with appropriate checks. This allows wizardry to run with Baubles as an optional dependency.
* <p></p>
* <b>Do not reference any Baubles classes from subclasses of this</b>, or the dependency will no longer be optional!
* Use {@link ItemArtefact#isArtefactActive(EntityPlayer, Item)} to test if a particular artefact is active. Use
* {@link ItemArtefact#getActiveArtefacts(EntityPlayer, Type...)} to get a list of active artefacts.
* <p></p>
* @author Electroblob
* @since Wizardry 4.2
* @see electroblob.wizardry.integration.baubles.WizardryBaublesIntegration
*/
@Mod.EventBusSubscriber
public class ItemArtefact extends Item {
// Artefact checklist:
// - Create and register item, add model and texture
// - Program effect, using events if possible (if it only affects a specific spell or entity, in there is ok)
// - Add name AND description to lang files
// - Add to loot_tables/subsets/[rarity]_artefacts.json
// - Add to advancements/artefact.json and advancements/all_artefacts.json
public enum Type {
/** An artefact that improves attacking spells. Two of these can be active at any one time. */ RING(2),
/** An artefact that improves defensive spells. One of these can be active at any one time. */ AMULET(1),
/** An artefact that improves utility spells. One of these can be active at any one time. */ CHARM(1);
public final int maxAtOnce;
Type(int maxAtOnce){
this.maxAtOnce = maxAtOnce;
}
}
// If Baubles is not installed, artefacts will still work, but must instead be
// on the player's hotbar (and only the first n of a given type will work, where n is the number of baubles slots of that
// artefact's type).
// Rarity was chosen over Tier here for a couple of reasons: firstly, displaying a tier would add unnecessary clutter
// to a potentially already-long tooltip whereas rarity provides a compact, neat way of displaying it that everyone is
// reasonably familiar with. Secondly, rarity makes more sense for an artefact, since it's something you find rather
// than upgrade to, and artefacts are not tied to tiers of wand/spell/whatever - they can be used whenever.
private final EnumRarity rarity;
private final Type type;
/** False if this artefact has been disabled in the config, true otherwise. */
private boolean enabled = true;
public ItemArtefact(EnumRarity rarity, Type type){
setMaxStackSize(1);
setCreativeTab(WizardryTabs.GEAR);
this.rarity = rarity;
this.type = type;
}
/** Sets whether this artefact is enabled or not. */
public void setEnabled(boolean enabled){
this.enabled = enabled;
}
@Override
public EnumRarity getRarity(ItemStack stack){
return rarity;
}
public Type getType(){
return type;
}
@Override
public boolean hasEffect(ItemStack stack){
return rarity == EnumRarity.EPIC;
}
@Override
@SideOnly(Side.CLIENT)
public void addInformation(ItemStack stack, @Nullable World world, List<String> tooltip, net.minecraft.client.util.ITooltipFlag advanced){
Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc");
if(!enabled) tooltip.add(Wizardry.proxy.translate("item." + Wizardry.MODID + ":generic.disabled", new Style().setColor(TextFormatting.RED)));
}
@Nullable
@Override
public ICapabilityProvider initCapabilities(ItemStack stack, @Nullable NBTTagCompound nbt){
return WizardryBaublesIntegration.enabled() ? new WizardryBaublesIntegration.ArtefactBaubleProvider(type) : null;
}
// IBauble does of course have an onWornTick method. However, because it's an optional dependency, it doesn't really
// make sense to use that method when it's easier to just use the isBaubleEquipped method in the same
// place as the non-baubles check. In other words, most artefacts are event-driven anyway so I'd rather have the
// tick-driven ones use events as well for the sake of consistency.
/**
* Returns whether the given artefact is active for the given player. If Baubles is loaded, an artefact is active
* when it is equipped in an appropriate bauble slot. If Baubles is not loaded, an artefact is active if it is one
* of the first n of its type on the player's hands/hotbar, where n is the number of bauble slots of that type.
* <p></p>
* N.B. This method is inefficient if you are defining multiple artefact behaviours in the same place. In this use
* case, it is preferable to use {@link ItemArtefact#getActiveArtefacts(EntityPlayer, Type...)}.
*
* @param player The player whose inventory is to be checked.
* @param artefact The artefact to check for.
* @return True if the player has the artefact and it is active, false if not. Always returns false if the given
* item is not an instance of {@code ItemArtefact}.
* @throws IllegalArgumentException If the given item is not an artefact.
*/
// It's cleaner to cast to ItemArtefact here than wherever it is used - items can't be stored as ItemWhatever objects
public static boolean isArtefactActive(EntityPlayer player, Item artefact){
if(!(artefact instanceof ItemArtefact)) throw new IllegalArgumentException("Not an artefact!");
if(!((ItemArtefact)artefact).enabled) return false; // Disabled in the config
if(WizardryBaublesIntegration.enabled()){
return WizardryBaublesIntegration.isBaubleEquipped(player, artefact);
}else{
// To find out if the given artefact is one of the first n on the player's hotbar (where n is the maximum
// number of that kind of artefact that can be active at once):
return InventoryUtils.getPrioritisedHotbarAndOffhand(player).stream() // Retrieve the stacks in question
// Filter out all except artefacts of the same type as the given one (preserving order)
.filter(s -> s.getItem() instanceof ItemArtefact && ((ItemArtefact)s.getItem()).type == ((ItemArtefact)artefact).type)
.limit(((ItemArtefact)artefact).type.maxAtOnce) // Ignore all but the first n
.anyMatch(s -> s.getItem() == artefact); // Check if the remaining stacks contain the artefact
// Note that streaming a list DOES retain the order (unless you call unordered(), obviously)
}
}
/**
* Returns the currently active artefacts for the given player. If Baubles is loaded, an artefact is active
* when it is equipped in an appropriate bauble slot. If Baubles is not loaded, an artefact is active if it is one
* of the first n of its type on the player's hands/hotbar, where n is the number of bauble slots of that type.
* <p></p>
* This method is more efficient for processing multiple artefact behaviours at once.
*
* @param player The player whose inventory is to be checked.
* @param types The artefact types to check for. If omitted, all artefact types will be checked.
* @return True if the player has the artefact and it is active, false if not. Always returns false if the given
* item is not an instance of {@code ItemArtefact}.
*/
public static List<ItemArtefact> getActiveArtefacts(EntityPlayer player, Type... types){
if(types.length == 0) types = Type.values();
if(WizardryBaublesIntegration.enabled()){
List<ItemArtefact> artefacts = WizardryBaublesIntegration.getEquippedArtefacts(player, types);
artefacts.removeIf(i -> !i.enabled); // Remove artefacts that are disabled in the config
return artefacts;
}else{
List<ItemArtefact> artefacts = new ArrayList<>();
for(Type type : types){
artefacts.addAll(InventoryUtils.getPrioritisedHotbarAndOffhand(player).stream()
.filter(s -> s.getItem() instanceof ItemArtefact)
.map(s -> (ItemArtefact)s.getItem())
.filter(i -> type == i.type && i.enabled)
.limit(type.maxAtOnce)
.collect(Collectors.toList()));
}
return artefacts;
}
}
/**
* Helper method that scans through all wands on the given player's hotbar and offhand and executes the given action
* if any of them have the given spell bound to them. This is a useful code pattern for artefact effects.
*
* @param player The player whose hotbar is to be checked
* @param spell The spell to search for
* @param action A {@link Consumer} specifying the action to be performed if a wand with the given spell is found.
* The stack passed to this consumer will be the wand in question.
* @return True if the action was executed, false otherwise.
*/
public static boolean findMatchingWandAndExecute(EntityPlayer player, Spell spell, Consumer<? super ItemStack> action){
List<ItemStack> hotbar = InventoryUtils.getPrioritisedHotbarAndOffhand(player);
Optional<ItemStack> stack = hotbar.stream().filter(s -> s.getItem() instanceof ISpellCastingItem
&& Arrays.asList(((ISpellCastingItem)s.getItem()).getSpells(s)).contains(spell)).findFirst();
stack.ifPresent(action);
return stack.isPresent();
}
/**
* Helper method that scans through all wands on the given player's hotbar and offhand and casts the given spell if
* it is bound to any of them. This is a useful code pattern for artefact effects.
*
* @param player The player whose hotbar is to be checked
* @param spell The spell to search for and cast
* @return True if the spell was cast, false otherwise.
*/
public static boolean findMatchingWandAndCast(EntityPlayer player, Spell spell){
return findMatchingWandAndExecute(player, spell, wand -> {
SpellModifiers modifiers = new SpellModifiers();
if(((ISpellCastingItem)wand.getItem()).canCast(wand, spell, player, EnumHand.MAIN_HAND, 0, modifiers)){
((ISpellCastingItem)wand.getItem()).cast(wand, spell, player, EnumHand.MAIN_HAND, 0, modifiers);
}
});
}
// ================================================ Event Handlers ================================================
@SubscribeEvent
public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){
if(event.phase == TickEvent.Phase.START){
EntityPlayer player = event.player;
World world = player.world;
for(ItemArtefact artefact : getActiveArtefacts(player)){
if(artefact == WizardryItems.ring_condensing){
if(player.ticksExisted % 150 == 0){
for(ItemStack stack : InventoryUtils.getHotbar(player)){
// Needs to be both of these interfaces because this ring only recharges wands
// (or more accurately, chargeable spellcasting items)
if(stack.getItem() instanceof ISpellCastingItem && stack.getItem() instanceof IManaStoringItem)
((IManaStoringItem)stack.getItem()).rechargeMana(stack, 1);
}
}
}else if(artefact == WizardryItems.amulet_arcane_defence){
if(player.ticksExisted % 300 == 0){
for(ItemStack stack : player.getArmorInventoryList()){
// IManaStoringItem is sufficient, since anything in the armour slots is probably armour
if(stack.getItem() instanceof IManaStoringItem)
((IManaStoringItem)stack.getItem()).rechargeMana(stack, 1);
}
}
}else if(artefact == WizardryItems.amulet_recovery){
if(player.shouldHeal() && player.getHealth() < player.getMaxHealth()/2
&& player.ticksExisted % 50 == 0){
int totalArmourMana = Streams.stream(player.getArmorInventoryList())
.filter(s -> s.getItem() instanceof IManaStoringItem)
.mapToInt(s -> ((IManaStoringItem)s.getItem()).getMana(s))
.sum();
if(totalArmourMana >= 2){
player.heal(1);
// 2 mana per half-heart, randomly distributed
List<ItemStack> chargedArmour = Streams.stream(player.getArmorInventoryList())
.filter(s -> s.getItem() instanceof IManaStoringItem)
.filter(s -> !((IManaStoringItem)s.getItem()).isManaEmpty(s))
.collect(Collectors.toList());
if(chargedArmour.size() == 1){
((IManaStoringItem)chargedArmour.get(0).getItem()).consumeMana(chargedArmour.get(0), 2, player);
}else{
Collections.shuffle(chargedArmour);
((IManaStoringItem)chargedArmour.get(0).getItem()).consumeMana(chargedArmour.get(0), 1, player);
((IManaStoringItem)chargedArmour.get(1).getItem()).consumeMana(chargedArmour.get(1), 1, player);
}
}
}
}else if(artefact == WizardryItems.amulet_glide){
// This should be a chance per fall, so we can't just check fall distance is greater than 3 each tick
// Based on a stationary start and a gravity acceleration of 0.02 blocks/tick^2, at 3 blocks of fall
// distance the player should be falling at about 0.35b/t, so 0.5 blocks should be enough of a window
if(player.fallDistance > 3f && player.fallDistance < 3.5f && player.world.rand.nextFloat() < 0.5f){
if(!WizardData.get(player).isCasting()) WizardData.get(player).startCastingContinuousSpell(Spells.glide, new SpellModifiers(), 600);
}else if(player.onGround){
WizardData data = WizardData.get(player);
if(data.currentlyCasting() == Spells.glide) data.stopCastingContinuousSpell();
}
}else if(artefact == WizardryItems.amulet_auto_shield){
findMatchingWandAndExecute(player, Spells.shield, wand -> {
List<Entity> projectiles = EntityUtils.getEntitiesWithinRadius(5, player.posX, player.posY, player.posZ, world, Entity.class);
projectiles.removeIf(e -> !(e instanceof IProjectile));
Vec3d look = player.getLookVec();
Vec3d playerPos = player.getPositionVector().add(0, player.height/2, 0);
for(Entity projectile : projectiles){
Vec3d vec = playerPos.subtract(projectile.getPositionVector()).normalize();
double angle = Math.acos(vec.scale(-1).dotProduct(look));
if(angle > Math.PI * 0.4f) continue; // (Roughly) the angle the shield will protect
Vec3d velocity = new Vec3d(projectile.motionX, projectile.motionY, projectile.motionZ).normalize();
double angle1 = Math.acos(vec.dotProduct(velocity));
if(angle1 < Math.PI * 0.2f){
SpellModifiers modifiers = new SpellModifiers();
if(((ISpellCastingItem)wand.getItem()).canCast(wand, Spells.shield, player, EnumHand.MAIN_HAND, 0, modifiers)){
((ISpellCastingItem)wand.getItem()).cast(wand, Spells.shield, player, EnumHand.MAIN_HAND, 0, modifiers);
}
break;
}
}
});
}else if(artefact == WizardryItems.charm_feeding){
// Every 5 seconds, feed the player if they are hungry enough
if(player.ticksExisted % 100 == 0){
if(player.getFoodStats().getFoodLevel() < 20 - Spells.satiety.getProperty(Satiety.HUNGER_POINTS).intValue()){
if(findMatchingWandAndCast(player, Spells.satiety)) continue;
}
if(player.getFoodStats().getFoodLevel() < 20 - Spells.replenish_hunger.getProperty(ReplenishHunger.HUNGER_POINTS).intValue()){
findMatchingWandAndCast(player, Spells.replenish_hunger);
}
}
}
}
}
}
@SubscribeEvent(priority = EventPriority.LOW)
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
if(event.getCaster() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getCaster();
SpellModifiers modifiers = event.getModifiers();
for(ItemArtefact artefact : getActiveArtefacts(player)){
float potency = modifiers.get(SpellModifiers.POTENCY);
float cooldown = modifiers.get(WizardryItems.cooldown_upgrade);
Biome biome = player.world.getBiome(player.getPosition());
if(artefact == WizardryItems.ring_battlemage){
if(player.getHeldItemOffhand().getItem() instanceof ISpellCastingItem
&& ImbueWeapon.isSword(player.getHeldItemMainhand())){
modifiers.set(SpellModifiers.POTENCY, 1.1f * potency, false);
}
}else if(artefact == WizardryItems.ring_fire_biome){
if(event.getSpell().getElement() == Element.FIRE
&& BiomeDictionary.hasType(biome, BiomeDictionary.Type.HOT)
&& BiomeDictionary.hasType(biome, BiomeDictionary.Type.DRY)){
modifiers.set(SpellModifiers.POTENCY, 1.3f * potency, false);
}
}else if(artefact == WizardryItems.ring_ice_biome){
if(event.getSpell().getElement() == Element.ICE
&& BiomeDictionary.hasType(biome, BiomeDictionary.Type.SNOWY)){
modifiers.set(SpellModifiers.POTENCY, 1.3f * potency, false);
}
}else if(artefact == WizardryItems.ring_earth_biome){
if(event.getSpell().getElement() == Element.EARTH
// If it was any forest that would be far too many, so taigas and jungles are excluded
&& BiomeDictionary.hasType(biome, BiomeDictionary.Type.FOREST)
&& !BiomeDictionary.hasType(biome, BiomeDictionary.Type.CONIFEROUS)
&& !BiomeDictionary.hasType(biome, BiomeDictionary.Type.JUNGLE)){
modifiers.set(SpellModifiers.POTENCY, 1.3f * potency, false);
}
}else if(artefact == WizardryItems.ring_storm){
if(event.getSpell().getElement() == Element.LIGHTNING && player.world.isThundering()){
modifiers.set(WizardryItems.cooldown_upgrade, cooldown * 0.3f, false);
}
}else if(artefact == WizardryItems.ring_full_moon){
if(event.getSpell().getElement() == Element.EARTH && !player.world.isDaytime()
&& player.world.provider.getMoonPhase(player.world.getWorldTime()) == 0){
modifiers.set(WizardryItems.cooldown_upgrade, cooldown * 0.3f, false);
}
}else if(artefact == WizardryItems.ring_blockwrangler){
if(event.getSpell() == Spells.greater_telekinesis){
modifiers.set(SpellModifiers.POTENCY, modifiers.get(SpellModifiers.POTENCY) * 2, false);
}
}else if(artefact == WizardryItems.ring_conjurer){
if(event.getSpell() instanceof SpellConjuration){
modifiers.set(WizardryItems.duration_upgrade, modifiers.get(WizardryItems.duration_upgrade) * 2, false);
}
}else if(artefact == WizardryItems.charm_minion_health){
// We COULD check the spell is a SpellMinion here, but there's really no point
modifiers.set(SpellMinion.HEALTH_MODIFIER, 1.25f * modifiers.get(SpellMinion.HEALTH_MODIFIER), true);
}else if(artefact == WizardryItems.charm_flight){
if(event.getSpell() == Spells.flight || event.getSpell() == Spells.glide){
modifiers.set(SpellModifiers.POTENCY, 1.5f * potency, true);
}
}else if(artefact == WizardryItems.charm_experience_tome){
modifiers.set(SpellModifiers.PROGRESSION, modifiers.get(SpellModifiers.PROGRESSION) * 1.5f, false);
}
}
}
}
@SubscribeEvent
public static void onSpellCastPostEvent(SpellCastEvent.Pre event){
if(event.getCaster() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getCaster();
if(isArtefactActive(player, WizardryItems.ring_paladin)){
if(event.getSpell() instanceof Heal || event.getSpell() instanceof HealAlly || event.getSpell() instanceof GreaterHeal){
// Spell properties allow all three of the above spells to be dealt with the same way - neat!
float healthGained = event.getSpell().getProperty(Spell.HEALTH).floatValue() * event.getModifiers().get(SpellModifiers.POTENCY);
List<EntityLivingBase> nearby = EntityUtils.getLivingWithinRadius(4, player.posX, player.posY, player.posZ, event.getWorld());
for(EntityLivingBase entity : nearby){
if(AllyDesignationSystem.isAllied(player, entity) && entity.getHealth() > 0 && entity.getHealth() < entity.getMaxHealth()){
entity.heal(healthGained * 0.2f); // 1/5 of the amount healed by the spell itself
if(event.getWorld().isRemote) ParticleBuilder.spawnHealParticles(event.getWorld(), entity);
}
}
}
}
}
}
@SubscribeEvent
public static void onLivingUpdateEvent(LivingEvent.LivingUpdateEvent event){
EntityLivingBase entity = event.getEntityLiving();
// No point doing this every tick, every 2.5 seconds should be enough
if(entity.ticksExisted % 50 == 0 && entity.isPotionActive(WizardryPotions.mind_control)){
NBTTagCompound entityNBT = entity.getEntityData();
if(entityNBT.hasUniqueId(MindControl.NBT_KEY)){
Entity caster = EntityUtils.getEntityByUUID(entity.world, entityNBT.getUniqueId(MindControl.NBT_KEY));
if(caster instanceof EntityPlayer){
if(isArtefactActive((EntityPlayer)caster, WizardryItems.ring_mind_control)){
EntityUtils.getEntitiesWithinRadius(3, entity.posX, entity.posY, entity.posZ, entity.world, EntityLiving.class).stream()
.filter(e -> e.world.rand.nextInt(10) == 0)
.filter(MindControl::canControl)
.filter(e -> AllyDesignationSystem.isValidTarget(caster, e))
.forEach(target -> MindControl.startControlling(target, (EntityPlayer)caster,
// Control the new target for only the remaining duration, otherwise it could go on forever!
entity.getActivePotionEffect(WizardryPotions.mind_control).getDuration()));
}
}
}
}
}
@SubscribeEvent
public static void onLivingHurtEvent(LivingHurtEvent event){
if(event.getEntity() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getEntity();
for(ItemArtefact artefact : getActiveArtefacts(player)){
if(artefact == WizardryItems.amulet_warding){
if(!event.getSource().isUnblockable() && event.getSource().isMagicDamage()){
event.setAmount(event.getAmount() * 0.9f);
}
}else if(artefact == WizardryItems.amulet_fire_protection){
if(event.getSource().isFireDamage()) event.setAmount(event.getAmount() * 0.7f);
}else if(artefact == WizardryItems.amulet_ice_protection){
if(event.getSource() instanceof IElementalDamage
&& ((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.FROST)
event.setAmount(event.getAmount() * 0.7f);
}else if(artefact == WizardryItems.amulet_channeling){
if(player.world.rand.nextFloat() < 0.3f && event.getSource() instanceof IElementalDamage
&& ((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.SHOCK){
event.setCanceled(true);
return;
}
}else if(artefact == WizardryItems.amulet_fire_cloaking){
if(!event.getSource().isUnblockable()){
List<EntityFireRing> fireRings = player.world.getEntitiesWithinAABB(EntityFireRing.class, player.getEntityBoundingBox());
for(EntityFireRing fireRing : fireRings){
if(fireRing.getCaster() instanceof EntityPlayer && (fireRing.getCaster() == player
|| AllyDesignationSystem.isOwnerAlly(player, fireRing))){
event.setAmount(event.getAmount() * 0.25f);
}
}
}
}else if(artefact == WizardryItems.amulet_potential){
if(player.world.rand.nextFloat() < 0.2f && EntityUtils.isMeleeDamage(event.getSource())
&& event.getSource().getTrueSource() instanceof EntityLivingBase){
EntityLivingBase target = (EntityLivingBase)event.getSource().getTrueSource();
if(player.world.isRemote){
ParticleBuilder.create(ParticleBuilder.Type.LIGHTNING).entity(event.getEntity())
.pos(0, event.getEntity().height/2, 0).target(target).spawn(player.world);
ParticleBuilder.spawnShockParticles(player.world, target.posX,
target.getEntityBoundingBox().minY + target.height/2, target.posZ);
}
DamageSafetyChecker.attackEntitySafely(target, MagicDamage.causeDirectMagicDamage(player,
MagicDamage.DamageType.SHOCK, true), Spells.static_aura.getProperty(Spell.DAMAGE).floatValue(), event.getSource().getDamageType());
target.playSound(WizardrySounds.SPELL_STATIC_AURA_RETALIATE, 1.0F, player.world.rand.nextFloat() * 0.4F + 1.5F);
}
}else if(artefact == WizardryItems.amulet_lich){
if(!event.getSource().isUnblockable() && player.world.rand.nextFloat() < 0.15f){
List<EntityLiving> nearbyMobs = EntityUtils.getEntitiesWithinRadius(5, player.posX, player.posY, player.posZ, player.world, EntityLiving.class);
nearbyMobs.removeIf(e -> !(e instanceof ISummonedCreature && ((ISummonedCreature)e).getCaster() == player));
if(!nearbyMobs.isEmpty()){
Collections.shuffle(nearbyMobs);
// Even though we're passing the same damage source through, we still need the safety check
DamageSafetyChecker.attackEntitySafely(nearbyMobs.get(0), event.getSource(), event.getAmount(), event.getSource().getDamageType());
event.setCanceled(true);
return; // Standard practice: stop as soon as the event is canceled
}
}
}else if(artefact == WizardryItems.amulet_banishing){
if(player.world.rand.nextFloat() < 0.2f && EntityUtils.isMeleeDamage(event.getSource())
&& event.getSource().getTrueSource() instanceof EntityLivingBase){
EntityLivingBase target = (EntityLivingBase)event.getSource().getTrueSource();
((Banish)Spells.banish).teleport(target, target.world, 8 + target.world.rand.nextDouble() * 8);
}
}else if(artefact == WizardryItems.amulet_transience){
if(player.getHealth() <= 6 && player.world.rand.nextFloat() < 0.25f){
player.addPotionEffect(new PotionEffect(WizardryPotions.transience, 300));
player.addPotionEffect(new PotionEffect(MobEffects.INVISIBILITY, 300, 0, false, false));
}
}
}
}
if(event.getSource().getTrueSource() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getSource().getTrueSource();
ItemStack mainhandItem = player.getHeldItemMainhand();
World world = player.world;
for(ItemArtefact artefact : getActiveArtefacts(player)){
if(artefact == WizardryItems.ring_fire_melee){
// Used ItemWand intentionally because we need the element
// Other mods can always make their own events if they want their own spellcasting items to do this
if(EntityUtils.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand
&& ((ItemWand)mainhandItem.getItem()).element == Element.FIRE){
event.getEntity().setFire(5);
}
}else if(artefact == WizardryItems.ring_ice_melee){
if(EntityUtils.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand
&& ((ItemWand)mainhandItem.getItem()).element == Element.ICE){
event.getEntityLiving().addPotionEffect(new PotionEffect(WizardryPotions.frost, 200, 0));
}
}else if(artefact == WizardryItems.ring_lightning_melee){
if(EntityUtils.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand
&& ((ItemWand)mainhandItem.getItem()).element == Element.LIGHTNING){
EntityUtils.getLivingWithinRadius(3, player.posX, player.posY, player.posZ, world).stream()
.filter(EntityUtils::isLiving)
.filter(e -> e != player)
.min(Comparator.comparingDouble(player::getDistanceSq))
.ifPresent(target -> {
if(world.isRemote){
ParticleBuilder.create(ParticleBuilder.Type.LIGHTNING).entity(event.getEntity())
.pos(0, event.getEntity().height/2, 0).target(target).spawn(world);
ParticleBuilder.spawnShockParticles(world, target.posX,
target.getEntityBoundingBox().minY + target.height/2, target.posZ);
}
DamageSafetyChecker.attackEntitySafely(target, MagicDamage.causeDirectMagicDamage(player,
MagicDamage.DamageType.SHOCK, true), Spells.static_aura.getProperty(Spell.DAMAGE).floatValue(), event.getSource().getDamageType());
target.playSound(WizardrySounds.SPELL_STATIC_AURA_RETALIATE, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F);
});
}
}else if(artefact == WizardryItems.ring_necromancy_melee){
if(EntityUtils.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand
&& ((ItemWand)mainhandItem.getItem()).element == Element.NECROMANCY){
event.getEntityLiving().addPotionEffect(new PotionEffect(MobEffects.WITHER, 200, 0));
}
}else if(artefact == WizardryItems.ring_earth_melee){
if(EntityUtils.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand
&& ((ItemWand)mainhandItem.getItem()).element == Element.EARTH){
event.getEntityLiving().addPotionEffect(new PotionEffect(MobEffects.POISON, 200, 0));
}
}else if(artefact == WizardryItems.ring_shattering){
if(!player.world.isRemote && player.world.rand.nextFloat() < 0.15f
&& event.getEntityLiving().getHealth() < 12f // Otherwise it's a bit overpowered!
&& event.getEntityLiving().isPotionActive(WizardryPotions.frost)
&& EntityUtils.isMeleeDamage(event.getSource())){
event.setAmount(12f);
for(int i = 0; i < 8; i++){
double dx = event.getEntity().world.rand.nextDouble() - 0.5;
double dy = event.getEntity().world.rand.nextDouble() - 0.5;
double dz = event.getEntity().world.rand.nextDouble() - 0.5;
EntityIceShard iceshard = new EntityIceShard(event.getEntity().world);
iceshard.setPosition(event.getEntity().posX + dx + Math.signum(dx) * event.getEntity().width,
event.getEntity().posY + event.getEntity().height/2 + dy,
event.getEntity().posZ + dz + Math.signum(dz) * event.getEntity().width);
iceshard.motionX = dx * 1.5;
iceshard.motionY = dy * 1.5;
iceshard.motionZ = dz * 1.5;
iceshard.setCaster(player);
event.getEntity().world.spawnEntity(iceshard);
}
}
}else if(artefact == WizardryItems.ring_soulbinding){
// Best guess at necromancy spell damage: either it's wither damage...
if((event.getSource() instanceof IElementalDamage
&& (((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.WITHER))
// or it's direct, non-melee damage and the player is holding a wand with a necromancy spell selected
|| (event.getSource().getImmediateSource() == player && !EntityUtils.isMeleeDamage(event.getSource())
&& Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem
&& ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.NECROMANCY))){
CurseOfSoulbinding.getSoulboundCreatures(WizardData.get(player)).add(event.getEntity().getUniqueID());
}
}else if(artefact == WizardryItems.ring_leeching){
// Best guess at necromancy spell damage: either it's wither damage...
if(player.world.rand.nextFloat() < 0.3f && ((event.getSource() instanceof IElementalDamage
&& (((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.WITHER))
// ...or it's direct, non-melee damage and the player is holding a wand with a necromancy spell selected
|| (event.getSource().getImmediateSource() == player && !EntityUtils.isMeleeDamage(event.getSource())
&& Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem
&& ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.NECROMANCY
&& ((ISpellCastingItem)s.getItem()).getCurrentSpell(s) != Spells.life_drain)))){
if(player.shouldHeal()){
player.heal(event.getAmount() * Spells.life_drain.getProperty(LifeDrain.HEAL_FACTOR).floatValue());
}
}
}else if(artefact == WizardryItems.ring_poison){
// Best guess at earth spell damage: either it's poison damage...
if((event.getSource() instanceof IElementalDamage
&& (((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.POISON))
// ...or it was from a dart...
|| event.getSource().getImmediateSource() instanceof EntityDart
// ...or it's direct, non-melee damage and the player is holding a wand with an earth spell selected
|| (event.getSource().getImmediateSource() == player && !EntityUtils.isMeleeDamage(event.getSource())
&& Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem
&& ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.EARTH))){
event.getEntityLiving().addPotionEffect(new PotionEffect(MobEffects.POISON, 200, 0));
}
}else if(artefact == WizardryItems.ring_extraction){
// Best guess at sorcery spell damage: either it's force damage...
if((event.getSource() instanceof IElementalDamage
&& (((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.FORCE))
// ...or it was from a force orb...
|| event.getSource().getImmediateSource() instanceof EntityForceOrb
// ...or it's direct, non-melee damage and the player is holding a wand with a sorcery spell selected
|| (event.getSource().getImmediateSource() == player && !EntityUtils.isMeleeDamage(event.getSource())
&& Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem
&& ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.SORCERY))){
InventoryUtils.getPrioritisedHotbarAndOffhand(player).stream()
.filter(s -> s.getItem() instanceof ISpellCastingItem && s.getItem() instanceof IManaStoringItem
&& !((IManaStoringItem)s.getItem()).isManaFull(s))
.findFirst()
.ifPresent(s -> ((IManaStoringItem)s.getItem()).rechargeMana(s, 4 + world.rand.nextInt(3)));
}
}
}
}
}
@SubscribeEvent
public static void onLivingDeathEvent(LivingDeathEvent event){
if(event.getSource().getTrueSource() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getSource().getTrueSource();
for(ItemArtefact artefact : getActiveArtefacts(player)){
if(artefact == WizardryItems.ring_combustion){
if(event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.FIRE){
event.getEntity().world.createExplosion(event.getEntity(), event.getEntity().posX, event.getEntity().posY,
event.getEntity().posZ, 1.5f, false);
}
}else if(artefact == WizardryItems.ring_disintegration){
if(event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.FIRE){
Disintegration.spawnEmbers(event.getEntity().world, player, event.getEntity(),
Spells.disintegration.getProperty(Disintegration.EMBER_COUNT).intValue());
}
}else if(artefact == WizardryItems.ring_arcane_frost){
if(!player.world.isRemote && event.getSource() instanceof IElementalDamage
&& ((IElementalDamage)event.getSource()).getType() == MagicDamage.DamageType.FROST){
for(int i = 0; i < 8; i++){
double dx = event.getEntity().world.rand.nextDouble() - 0.5;
double dy = event.getEntity().world.rand.nextDouble() - 0.5;
double dz = event.getEntity().world.rand.nextDouble() - 0.5;
EntityIceShard iceshard = new EntityIceShard(event.getEntity().world);
iceshard.setPosition(event.getEntity().posX + dx + Math.signum(dx) * event.getEntity().width,
event.getEntity().posY + event.getEntity().height/2 + dy,
event.getEntity().posZ + dz + Math.signum(dz) * event.getEntity().width);
iceshard.motionX = dx * 1.5;
iceshard.motionY = dy * 1.5;
iceshard.motionZ = dz * 1.5;
iceshard.setCaster(player);
event.getEntity().world.spawnEntity(iceshard);
}
}
}
}
}
}
@SubscribeEvent(priority = EventPriority.HIGH) // Needs to happen before gravestones, etc.
public static void onPlayerDropsEvent(PlayerDropsEvent event){
// Amulet of the immortal allows players to hold onto a wand with resurrection
// This needs to happen or we can't cast the spell with it and use up the mana
if(isArtefactActive(event.getEntityPlayer(), WizardryItems.amulet_resurrection)){
EntityItem item = event.getDrops().stream()
.filter(e -> Resurrection.canStackResurrect(e.getItem(), event.getEntityPlayer()))
.findFirst().orElse(null);
if(item == null) return; // The player didn't have a wand with resurrection on it
if(!InventoryUtils.getHotbar(event.getEntityPlayer()).contains(ItemStack.EMPTY)) return; // No space on hotbar
event.getDrops().remove(item);
// At this point the player probably has nothing in their hand, but if not just find a free space somewhere
if(event.getEntityPlayer().getHeldItemMainhand().isEmpty()) event.getEntityPlayer().setHeldItem(EnumHand.MAIN_HAND, item.getItem());
else event.getEntityPlayer().addItemStackToInventory(item.getItem()); // Always chooses hotbar slots first
}
}
@SubscribeEvent
public static void onPotionApplicableEvent(PotionEvent.PotionApplicableEvent event){
if(event.getEntity() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getEntity();
for(ItemArtefact artefact : getActiveArtefacts(player)){
if(artefact == WizardryItems.amulet_ice_immunity){
if(event.getPotionEffect().getPotion() == WizardryPotions.frost) event.setResult(Event.Result.DENY);
}else if(artefact == WizardryItems.amulet_wither_immunity){
if(event.getPotionEffect().getPotion() == MobEffects.WITHER) event.setResult(Event.Result.DENY);
}
}
}
}
@SubscribeEvent
public static void onItemPickupEvent(PlayerEvent.ItemPickupEvent event){
// ItemPickupEvent is just a convenient trigger for this; we don't actually care what got picked up
if(isArtefactActive(event.player, WizardryItems.charm_auto_smelt)){
// So this doesn't waste mana, only cast pocket furnace when it would smelt the maximum number of items
if(event.player.inventory.mainInventory.stream()
.filter(s -> !FurnaceRecipes.instance().getSmeltingResult(s).isEmpty())
.mapToInt(ItemStack::getCount)
.sum() >= Spells.pocket_furnace.getProperty(PocketFurnace.ITEMS_SMELTED).intValue()){
findMatchingWandAndCast(event.player, Spells.pocket_furnace);
}
}
}
}