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,59 +1,118 @@
package electroblob.wizardry.item;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.spell.SpellConjuration;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.item.IItemPropertyGetter;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import javax.annotation.Nullable;
import java.util.UUID;
/**
* Allows wizardry to identify items that are conjured (and therefore need destroying if they leave the inventory)
* without explicitly referencing each, thereby allowing for better expandibility.
* without explicitly referencing each, thereby allowing for better expandability.
*/
@Mod.EventBusSubscriber
public interface IConjuredItem {
/** The NBT tag key used to store the duration multiplier for conjured items. */
public static final String DURATION_MULTIPLIER_KEY = "durationMultiplier";
String DURATION_MULTIPLIER_KEY = "durationMultiplier";
/** The NBT tag key used to store the damage multiplier for conjured items. */
String DAMAGE_MULTIPLIER = "damageMultiplier";
UUID POTENCY_MODIFIER = UUID.fromString("da067ea6-0b35-4140-8436-5476224de9dd");
/** Helper method for setting the duration multiplier (via NBT) for conjured items. */
public static void setDurationMultiplier(ItemStack stack, float multiplier){
static void setDurationMultiplier(ItemStack stack, float multiplier){
if(!stack.hasTagCompound()) stack.setTagCompound(new NBTTagCompound());
stack.getTagCompound().setFloat(DURATION_MULTIPLIER_KEY, multiplier);
}
/** Helper method for setting the damage multiplier (via NBT) for conjured items. */
static void setDamageMultiplier(ItemStack stack, float multiplier){
if(!stack.hasTagCompound()) stack.setTagCompound(new NBTTagCompound());
stack.getTagCompound().setFloat(DAMAGE_MULTIPLIER, multiplier);
}
/** Helper method for getting the damage multiplier (via NBT) for conjured items. */
static float getDamageMultiplier(ItemStack stack){
if(!stack.hasTagCompound()) return 1;
return stack.getTagCompound().getFloat(DAMAGE_MULTIPLIER);
}
/**
* Helper method for returning the max damage of a conjured item based on its NBT data. Centralises the code.
* Implementors will almost certainly want to call this from {@link Item#getMaxDamage(ItemStack stack)}.
*/
public default int getMaxDamageFromNBT(ItemStack stack){
default int getMaxDamageFromNBT(ItemStack stack, Spell spell){
float baseDuration = spell.getProperty(SpellConjuration.ITEM_LIFETIME).floatValue();
if(stack.hasTagCompound() && stack.getTagCompound().hasKey(DURATION_MULTIPLIER_KEY)){
return (int)(this.getBaseDuration() * stack.getTagCompound().getFloat(DURATION_MULTIPLIER_KEY));
return (int)(baseDuration * stack.getTagCompound().getFloat(DURATION_MULTIPLIER_KEY));
}
return this.getBaseDuration();
return (int)baseDuration;
}
/**
* Returns the base duration in ticks for this conjured item. Should be a constant (commonly 600). Implementors may
* want to call this when setting an item's max damage in its constructor.
* Adds property overrides to define the conjuring/vanishing animation. Call this from the item's constructor.
*/
public int getBaseDuration();
default void addAnimationPropertyOverrides(){
if(!(this instanceof Item)) throw new ClassCastException("Cannot set up conjuring animations for a non-item!");
Item item = (Item)this;
final int frames = getAnimationFrames();
item.addPropertyOverride(new ResourceLocation("conjure"), new IItemPropertyGetter(){
@SideOnly(Side.CLIENT)
public float apply(ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity){
return stack.getItemDamage() < frames ? (float)stack.getItemDamage() / frames
: (float)(stack.getMaxDamage() - stack.getItemDamage()) / frames;
}
});
item.addPropertyOverride(new ResourceLocation("conjuring"), new IItemPropertyGetter(){
@SideOnly(Side.CLIENT)
public float apply(ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity){
return stack.getItemDamage() < frames
|| stack.getItemDamage() > stack.getMaxDamage() - frames ? 1.0F : 0.0F;
}
});
}
/** Returns the number of frames in the conjuring/vanishing animation. Override to change the number of frames
* set by {@link IConjuredItem#addAnimationPropertyOverrides()}. */
default int getAnimationFrames(){
return 8;
}
@SubscribeEvent
public static void onLivingDropsEvent(LivingDropsEvent event){
static void onLivingDropsEvent(LivingDropsEvent event){
// Destroys conjured items if their caster dies.
for(EntityItem item : event.getDrops()){
if(item.getItem().getItem() instanceof IConjuredItem){
// Apparently some mods don't behave and shove null items in the list, quite why I have no idea
if(item != null && item.getItem() != null && item.getItem().getItem() instanceof IConjuredItem){
item.setDead();
}
}
}
@SubscribeEvent
public static void onItemTossEvent(ItemTossEvent event){
static void onItemTossEvent(ItemTossEvent event){
// Prevents conjured items being thrown by dragging and dropping outside the inventory.
if(event.getEntityItem().getItem().getItem() instanceof IConjuredItem){
event.setCanceled(true);
@@ -0,0 +1,72 @@
package electroblob.wizardry.item;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
/**
* Interface for any items that store mana. This interface simply specifies methods for setting and getting the amount
* of mana held in the item (plus a few convenience methods); implementations may differ between items.
* <p></p>
* In wizardry itself, mana is still implemented as durability, however, as of wizardry 4.2, the vanilla method
* {@code Item.setDamage()} has been overridden to do nothing. Instead, mana must be interacted with using the methods
* in this interface. This means operating via the item rather than the stack.
* <p></p>
* This change prevents general item repair methods working on mana items (see issues #66 and #153), and also allows
* other items to implement mana differently if they wish. For example, a weapon that can cast spells as an ability
* might want regular durability in addition to mana, so the mana might be stored in NBT instead. Items that do not use
* durability to represent mana may need to do zero-checking themselves, as appropriate.
* <p></p>
* <i>Wizardry's items implement mana as the <b>inverse</b> of item damage; i.e. the <b>more damaged</b> the item, the
* <b>less mana</b> it has. Beware of this when converting to the new system.</i>
* <p></p>
* @author Electroblob
* @since Wizardry 4.2
*/
public interface IManaStoringItem {
/** Returns the amount of mana contained in the given item stack. */
int getMana(ItemStack stack);
/** Sets the amount of mana contained in the given item stack to the given value. This method does not perform any
* checks for creative mode, etc. */
void setMana(ItemStack stack, int mana);
/** Returns the maximum amount of mana that the given item stack can hold. */
int getManaCapacity(ItemStack stack);
/**
* Returns whether this item's mana should be displayed in the arcane workbench tooltip. Only called client-side.
* Ignore this method if this item is not an {@link IWorkbenchItem}.
* @param player The player using the workbench.
* @param stack The itemstack to query.
* @return True if the mana should be shown, false if not. Returns true by default.
*/
default boolean showManaInWorkbench(EntityPlayer player, ItemStack stack){
return true;
}
/** Convenience method that decreases the amount of mana contained in the given item stack by the given value. This
* method automatically limits the mana to a minimum of 0 and performs the relevant checks for creative mode, etc. */
default void consumeMana(ItemStack stack, int mana, EntityLivingBase wielder){
if(wielder instanceof EntityPlayer && ((EntityPlayer)wielder).isCreative()) return; // Mana isn't consumed in creative
setMana(stack, Math.max(getMana(stack) - mana, 0));
}
/** Convenience method that increases the amount of mana contained in the given item stack by the given value.
* This method automatically limits the mana to within the item's capacity. */
// We don't really need to limit this one because Item#setDamage() ultimately limits it anyway, but we may as well
default void rechargeMana(ItemStack stack, int mana){
setMana(stack, Math.min(getMana(stack) + mana, getManaCapacity(stack)));
}
/** Convenience method that returns true if the given stack contains the maximum amount of mana, false otherwise. */
default boolean isManaFull(ItemStack stack){
return getMana(stack) == getManaCapacity(stack);
}
/** Convenience method that returns true if the given stack contains no mana, false otherwise. */
default boolean isManaEmpty(ItemStack stack){
return getMana(stack) == 0;
}
}
@@ -0,0 +1,23 @@
package electroblob.wizardry.item;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
/**
* Interface for items that change their texture depending on their metadata. This is mainly to facilitate use of the
* convenience method {@link electroblob.wizardry.client.model.WizardryModels#registerMultiTexturedModel(Item) WizardryModels.registerMultiTexturedModel(T)}. Also works well for {@code ItemBlock}s!
* @author Electroblob
* @since Wizardry 4.2
* @see ItemBlockMultiTexturedElemental
*/
public interface IMultiTexturedItem {
/**
* Returns the appropriate {@code ResourceLocation} for this item's model, based on the given itemstack.
* @param stack The itemstack to return the model name for.
* @return A {@code ResourceLocation} pointing to the appropriate model file. As with any other model, this should
* include the domain (mod ID) and filename, without the rest of the filepath.
*/
ResourceLocation getModelName(ItemStack stack);
}
@@ -0,0 +1,129 @@
package electroblob.wizardry.item;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumHand;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
/**
* Interface for items that can hold and cast one or more spells. These may be consumables, like scrolls, or they may be
* durability-based, like wands. Custom spell casting items should implement this interface to integrate properly into
* wizardry. <i>It is no longer necessary to extend {@code ItemWand}, but you may still do so instead of implementing
* this interface if appropriate.</i>
* <p></p>
* This interface is used for the following:<br>
* - General-purpose detection of continuous spell casting (see {@link electroblob.wizardry.util.WizardryUtilities#isCasting(EntityLivingBase, Spell)})<br>
* - Display of the arcane workbench tooltip (in conjunction with {@link IManaStoringItem})<br>
* - Spell HUD visibility<br>
* - Spell switching controls (they won't do anything unless the player is holding an {@code ISpellCastingItem})<br>
* - Artefacts that trigger a player's wands/scrolls to cast spells
* @author Electroblob
* @since Wizardry 4.2
*/
// This could probably be turned into a capability at some point, but for the moment it's fine like this
// As we've already noted, capabilities are only useful for optional dependencies anyway
public interface ISpellCastingItem {
/**
* Returns the spell currently equipped on the given itemstack. The given itemstack will be of this item.
* @param stack The itemstack to query.
* @return The currently equipped spell, or {@link electroblob.wizardry.registry.Spells#none Spells.none} if no spell
* is equipped.
*/
@Nonnull
Spell getCurrentSpell(ItemStack stack);
/**
* Returns all the spells currently bound to the given itemstack. The given itemstack will be of this item.
* @param stack The itemstack to query.
* @return The bound spells, or {@link electroblob.wizardry.registry.Spells#none Spells.none} if no spell
* is equipped.
*/
default Spell[] getSpells(ItemStack stack){
return new Spell[]{getCurrentSpell(stack)}; // Default implementation for single-spell items, because I'm lazy
}
/**
* Selects the next spell bound to the given itemstack. The given itemstack will be of this item.
* @param stack The itemstack to query.
*/
default void selectNextSpell(ItemStack stack){
// If it doesn't need spell-switching then don't bother the implementor with it
}
/**
* Selects the previous spell bound to the given itemstack. The given itemstack will be of this item.
* @param stack The itemstack to query.
*/
default void selectPreviousSpell(ItemStack stack){
// Nothing here either
}
/**
* Returns whether the spell HUD should be shown when a player is holding this item. Only called client-side.
* @param player The player holding the item.
* @param stack The itemstack to query.
* @return True if the spell HUD should be shown, false if not.
*/
boolean showSpellHUD(EntityPlayer player, ItemStack stack);
/**
* Returns whether this item's spells should be displayed in the arcane workbench tooltip. Only called client-side.
* Ignore this method if this item is not an {@link IWorkbenchItem}.
* @param player The player using the workbench.
* @param stack The itemstack to query.
* @return True if the spells should be shown, false if not. Returns true by default.
*/
default boolean showSpellsInWorkbench(EntityPlayer player, ItemStack stack){
return true;
}
// These methods were made with intention of standardising the code for casting spells using items.
// For most external uses there's no reason for them to be separate, however, it makes more sense to do so because
// then we can eliminate a bit of duplicate code from continuous vs. non-continuous spell casting. Otherwise, we'd
// need a separate method for casting continuous spells anyway.
/**
* Returns whether the given spell can be cast by the given stack in its current state. Does not perform any actual
* spellcasting.
*
* @param stack The stack being queried; will be of this item.
* @param spell The spell to be cast.
* @param caster The player doing the casting.
* @param hand The hand in which the casting item is being held.
* @param castingTick For continuous spells, the number of ticks the spell has already been cast for. For all other
* spells, this will be zero.
* @param modifiers The modifiers with which the spell is being cast.
* @return True if the spell can be cast, false if not.
*/
boolean canCast(ItemStack stack, Spell spell, EntityPlayer caster, EnumHand hand, int castingTick, SpellModifiers modifiers);
/**
* Casts the given spell using the given item stack. <b>This method does not perform any checks</b>; these are done
* in {@link ISpellCastingItem#canCast(ItemStack, Spell, EntityPlayer, EnumHand, int, SpellModifiers)}. This method
* also performs any post-casting logic, such as mana costs and cooldowns.
* <p></p>
* <i>N.B. Continuous spell casting from outside of the items requires a bit of extra legwork, see
* {@link WizardData} for an example.</i>
*
* @param stack The stack being queried; will be of this item.
* @param spell The spell to be cast.
* @param caster The player doing the casting.
* @param hand The hand in which the casting item is being held.
* @param castingTick For continuous spells, the number of ticks the spell has already been cast for. For all other
* spells, this will be zero.
* @param modifiers The modifiers with which the spell is being cast.
* @return True if the spell succeeded, false if not. This is only really for the purpose of returning a result from
* {@link net.minecraft.item.Item#onItemRightClick(World, EntityPlayer, EnumHand)} and similar methods; mana costs,
* cooldowns and whatever else you might want to do post-spellcasting should be done within this method so that
* external sources don't allow spells to be cast for free, for example.
*/
boolean cast(ItemStack stack, Spell spell, EntityPlayer caster, EnumHand hand, int castingTick, SpellModifiers modifiers);
}
@@ -9,7 +9,7 @@ import net.minecraft.item.ItemStack;
* Items that implement this interface may be placed in the central slot of the arcane workbench as long as
* {@link IWorkbenchItem#canPlace(ItemStack)} returns true. The number of spell book slots displayed is also specified
* using {@link IWorkbenchItem#getSpellSlotCount(ItemStack)}.
* <p>
* <p></p>
* Items that implement this interface define what happens if they are in the central slot of the arcane workbench and
* the apply button is pressed, in {@link IWorkbenchItem#onApplyButtonPressed(EntityPlayer, Slot, Slot, Slot, Slot[])}.
* This is a core part of the arcane workbench refactoring in version 4.2 and allows for custom spell casting items and
@@ -52,5 +52,13 @@ public interface IWorkbenchItem {
* @return True if anything changed, false if not.
*/
boolean onApplyButtonPressed(EntityPlayer player, Slot centre, Slot crystals, Slot upgrade, Slot[] spellBooks);
/**
* Returns whether the tooltip (dark grey box) should be drawn when this item is in an arcane workbench. Only
* called client-side.
* @param stack The itemstack to query.
* @return True if the workbench tooltip should be shown, false if not.
*/
boolean showTooltip(ItemStack stack);
}
@@ -1,11 +1,8 @@
package electroblob.wizardry.item;
import java.util.List;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
@@ -15,6 +12,8 @@ import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.util.List;
public class ItemArcaneTome extends Item {
public ItemArcaneTome(){
@@ -54,7 +53,12 @@ public class ItemArcaneTome extends Item {
@SideOnly(Side.CLIENT)
@Override
public void addInformation(ItemStack stack, World world, List<String> tooltip, ITooltipFlag showAdvanced){
public void addInformation(ItemStack stack, World world, List<String> tooltip, net.minecraft.client.util.ITooltipFlag showAdvanced){
if(stack.getItemDamage() < 1){
return; // If something's up with the metadata it will display a 'generic' tome of arcana with no info
}
Tier tier = Tier.values()[stack.getItemDamage()];
Tier tier2 = Tier.values()[stack.getItemDamage() - 1];
tooltip.add(tier.getDisplayNameWithFormatting());
@@ -1,12 +1,7 @@
package electroblob.wizardry.item;
import java.util.List;
import javax.annotation.Nullable;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
@@ -14,6 +9,9 @@ import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import javax.annotation.Nullable;
import java.util.List;
public class ItemArmourUpgrade extends Item {
public ItemArmourUpgrade(){
@@ -35,7 +33,7 @@ public class ItemArmourUpgrade extends Item {
@Override
@SideOnly(Side.CLIENT)
public void addInformation(ItemStack stack, @Nullable World worldIn, List<String> tooltip, ITooltipFlag flagIn) {
public void addInformation(ItemStack stack, @Nullable World worldIn, List<String> tooltip, net.minecraft.client.util.ITooltipFlag flagIn) {
tooltip.add(net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":armour_upgrade.desc1", "\u00A77"));
tooltip.add(
net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":armour_upgrade.desc2", "\u00A77", "\u00A7d"));
@@ -0,0 +1,875 @@
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.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;
public ItemArtefact(EnumRarity rarity, Type type){
setMaxStackSize(1);
setCreativeTab(WizardryTabs.GEAR);
this.rarity = rarity;
this.type = type;
}
@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 worldIn, List<String> tooltip, net.minecraft.client.util.ITooltipFlag flagIn){
Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc");
}
@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(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 WizardryUtilities.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()){
return WizardryBaublesIntegration.getEquippedArtefacts(player, types);
}else{
List<ItemArtefact> artefacts = new ArrayList<>();
for(Type type : types){
artefacts.addAll(WizardryUtilities.getPrioritisedHotbarAndOffhand(player).stream()
.filter(s -> s.getItem() instanceof ItemArtefact)
.map(s -> (ItemArtefact)s.getItem())
.filter(i -> type == i.type)
.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 = WizardryUtilities.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(world.isRemote && player.ticksExisted % 150 == 0){
for(ItemStack stack : WizardryUtilities.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(world.isRemote && 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 = WizardryUtilities.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 near starving
if(player.ticksExisted % 100 == 0 && player.getFoodStats().getFoodLevel() < 2){
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().getItem())){
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){
// FIXME: Does not appear to be working, for some reason
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 = WizardryUtilities.getEntitiesWithinRadius(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 = WizardryUtilities.getEntityByUUID(entity.world, entityNBT.getUniqueId(MindControl.NBT_KEY));
if(caster instanceof EntityPlayer){
if(isArtefactActive((EntityPlayer)caster, WizardryItems.ring_mind_control)){
WizardryUtilities.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 && WizardryUtilities.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 = WizardryUtilities.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 && WizardryUtilities.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() <= 2 && 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(WizardryUtilities.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(WizardryUtilities.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(WizardryUtilities.isMeleeDamage(event.getSource()) && mainhandItem.getItem() instanceof ItemWand
&& ((ItemWand)mainhandItem.getItem()).element == Element.LIGHTNING){
WizardryUtilities.getEntitiesWithinRadius(3, player.posX, player.posY, player.posZ, world).stream()
.filter(WizardryUtilities::isLiving)
.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(WizardryUtilities.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(WizardryUtilities.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)
&& WizardryUtilities.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 && !WizardryUtilities.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 && !WizardryUtilities.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 && !WizardryUtilities.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 && !WizardryUtilities.isMeleeDamage(event.getSource())
&& Streams.stream(player.getHeldEquipment()).anyMatch(s -> s.getItem() instanceof ISpellCastingItem
&& ((ISpellCastingItem)s.getItem()).getCurrentSpell(s).getElement() == Element.SORCERY))){
WizardryUtilities.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(!WizardryUtilities.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);
}
}
}
}
@@ -1,11 +1,12 @@
package electroblob.wizardry.item;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellProperties;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
@@ -22,28 +23,33 @@ public class ItemBlankScroll extends Item implements IWorkbenchItem {
return 1;
}
@Override
public boolean showTooltip(ItemStack stack){
return false;
}
@Override
public boolean onApplyButtonPressed(EntityPlayer player, Slot centre, Slot crystals, Slot upgrade, Slot[] spellBooks){
if(!spellBooks[0].getStack().isEmpty() && !crystals.getStack().isEmpty()){
Spell spell = Spell.get(spellBooks[0].getStack().getItemDamage());
Spell spell = Spell.byMetadata(spellBooks[0].getStack().getItemDamage());
WizardData data = WizardData.get(player);
// Spells can only be bound to scrolls if the player has already cast them (prevents casting of master
// spells without getting a master wand)
// This restriction does not apply in creative mode
if(spell != Spells.none && player.capabilities.isCreativeMode || (data != null
&& data.hasSpellBeenDiscovered(spell))){
if(spell != Spells.none && player.isCreative() || (data != null
&& data.hasSpellBeenDiscovered(spell)) && spell.isEnabled(SpellProperties.Context.SCROLL)){
int cost = spell.cost;
int cost = spell.getCost() * centre.getStack().getCount();
// Continuous spell scrolls require enough mana to cast them for the duration defined in ItemScroll.
if(spell.isContinuous) cost *= ItemScroll.CASTING_TIME / 20;
if(crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL > cost){
// Rounds up to the nearest whole crystal
crystals.decrStackSize(cost / Constants.MANA_PER_CRYSTAL + 1);
centre.putStack(new ItemStack(WizardryItems.scroll, 1, spell.id()));
centre.putStack(new ItemStack(WizardryItems.scroll, centre.getStack().getCount(), spell.metadata()));
return true;
}
@@ -0,0 +1,42 @@
package electroblob.wizardry.item;
import electroblob.wizardry.constants.Element;
import net.minecraft.block.Block;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
public class ItemBlockMultiTexturedElemental extends ItemBlock implements IMultiTexturedItem {
private final boolean separateNames;
public ItemBlockMultiTexturedElemental(Block block, boolean separateNames){
super(block);
this.setHasSubtypes(true);
this.setMaxDamage(0);
this.separateNames = separateNames;
}
@Override
public ResourceLocation getModelName(ItemStack stack){
int metadata = stack.getMetadata();
if(metadata >= Element.values().length) metadata = 0;
return getModelName(metadata);
}
public ResourceLocation getModelName(int metadata){
return new ResourceLocation(this.block.getRegistryName().getNamespace(),
Element.values()[metadata].getName() + "_" + this.block.getRegistryName().getPath());
}
@Override
public String getTranslationKey(ItemStack stack){
return this.separateNames ? "tile." + this.getModelName(stack).toString() : super.getTranslationKey(stack);
}
@Override
public int getMetadata(int metadata){
return metadata;
}
}
@@ -0,0 +1,44 @@
package electroblob.wizardry.item;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
/** Note that in 1.13, <i>the flattening</i> will make this class redundant, much like ItemCoal, which is probably its
* closest analog in vanilla. */
public class ItemCrystal extends Item implements IMultiTexturedItem {
public ItemCrystal(){
super();
this.setHasSubtypes(true);
this.setMaxDamage(0);
this.setCreativeTab(WizardryTabs.WIZARDRY);
}
@Override
public ResourceLocation getModelName(ItemStack stack){
int metadata = stack.getMetadata();
if(metadata >= Element.values().length) metadata = 0;
return new ResourceLocation(Wizardry.MODID, "crystal_" + Element.values()[metadata].getName());
}
@Override
public String getTranslationKey(ItemStack stack){
return "item." + this.getModelName(stack).toString();
}
@Override
public void getSubItems(CreativeTabs tab, NonNullList<ItemStack> items){
if(tab == WizardryTabs.WIZARDRY){
for(Element element : Element.values()){
items.add(new ItemStack(this, 1, element.ordinal()));
}
}
}
}
@@ -1,9 +1,9 @@
package electroblob.wizardry.item;
import electroblob.wizardry.entity.projectile.EntityFirebomb;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
@@ -23,15 +23,15 @@ public class ItemFirebomb extends Item {
ItemStack stack = player.getHeldItem(hand);
if(!player.capabilities.isCreativeMode){
if(!player.isCreative()){
stack.shrink(1);
}
player.playSound(SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F));
player.playSound(WizardrySounds.ENTITY_FIREBOMB_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F));
if(!world.isRemote){
EntityFirebomb firebomb = new EntityFirebomb(world);
firebomb.aim(player, 1.5f);
firebomb.aim(player, 1);
world.spawnEntity(firebomb);
}
@@ -1,10 +1,19 @@
package electroblob.wizardry.item;
import com.google.common.collect.Multimap;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemAxe;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
@@ -13,21 +22,42 @@ import net.minecraftforge.fml.relauncher.SideOnly;
public class ItemFlamingAxe extends ItemAxe implements IConjuredItem {
private EnumRarity rarity = EnumRarity.COMMON;
public ItemFlamingAxe(ToolMaterial material){
super(material, 8, -3);
setMaxDamage(getBaseDuration());
setMaxDamage(1200); // Might cause problems if removed, the actual number is irrelevant as long as it's > 0
setNoRepair();
setCreativeTab(null);
addAnimationPropertyOverrides();
}
@Override
public int getBaseDuration(){
return 1200;
public Multimap<String, AttributeModifier> getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack){
Multimap<String, AttributeModifier> multimap = super.getItemAttributeModifiers(slot);
if(slot == EntityEquipmentSlot.MAINHAND){
multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(POTENCY_MODIFIER,
"Potency modifier", IConjuredItem.getDamageMultiplier(stack) - 1, WizardryUtilities.Operations.MULTIPLY_CUMULATIVE));
}
return multimap;
}
public Item setRarity(EnumRarity rarity){
this.rarity = rarity;
return this;
}
@Override
public EnumRarity getRarity(ItemStack stack){
return rarity;
}
@Override
public int getMaxDamage(ItemStack stack){
return this.getMaxDamageFromNBT(stack);
return this.getMaxDamageFromNBT(stack, Spells.flaming_axe);
}
@Override
@@ -50,9 +80,16 @@ public class ItemFlamingAxe extends ItemAxe implements IConjuredItem {
stack.setItemDamage(damage + 1);
}
@Override
public Multimap<String, AttributeModifier> getItemAttributeModifiers(EntityEquipmentSlot equipmentSlot){
attackDamage = Spells.flaming_axe.getProperty(Spell.DAMAGE).floatValue();
return super.getItemAttributeModifiers(equipmentSlot);
}
@Override
public boolean hitEntity(ItemStack stack, EntityLivingBase target, EntityLivingBase wielder){
if(!MagicDamage.isEntityImmune(DamageType.FIRE, target)) target.setFire(8);
if(!MagicDamage.isEntityImmune(DamageType.FIRE, target))
target.setFire(Spells.flaming_axe.getProperty(Spell.BURN_DURATION).intValue());
return false;
}
@@ -72,6 +109,16 @@ public class ItemFlamingAxe extends ItemAxe implements IConjuredItem {
return 0;
}
@Override
public boolean isEnchantable(ItemStack stack){
return false;
}
@Override
public boolean isBookEnchantable(ItemStack stack, ItemStack book){
return false;
}
// Cannot be dropped
@Override
public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){
@@ -1,11 +1,19 @@
package electroblob.wizardry.item;
import com.google.common.collect.Multimap;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemAxe;
import net.minecraft.item.ItemStack;
import net.minecraft.potion.PotionEffect;
@@ -15,21 +23,42 @@ import net.minecraftforge.fml.relauncher.SideOnly;
public class ItemFrostAxe extends ItemAxe implements IConjuredItem {
private EnumRarity rarity = EnumRarity.COMMON;
public ItemFrostAxe(ToolMaterial material){
super(material, 8, -3);
setMaxDamage(getBaseDuration());
setMaxDamage(1200);
setNoRepair();
setCreativeTab(null);
addAnimationPropertyOverrides();
}
@Override
public int getBaseDuration(){
return 1200;
public Multimap<String, AttributeModifier> getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack){
Multimap<String, AttributeModifier> multimap = super.getItemAttributeModifiers(slot);
if(slot == EntityEquipmentSlot.MAINHAND){
multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(POTENCY_MODIFIER,
"Potency modifier", IConjuredItem.getDamageMultiplier(stack) - 1, WizardryUtilities.Operations.MULTIPLY_CUMULATIVE));
}
return multimap;
}
public Item setRarity(EnumRarity rarity){
this.rarity = rarity;
return this;
}
@Override
public EnumRarity getRarity(ItemStack stack){
return rarity;
}
@Override
public int getMaxDamage(ItemStack stack){
return this.getMaxDamageFromNBT(stack);
return this.getMaxDamageFromNBT(stack, Spells.frost_axe);
}
@Override
@@ -75,6 +104,16 @@ public class ItemFrostAxe extends ItemAxe implements IConjuredItem {
return 0;
}
@Override
public boolean isEnchantable(ItemStack stack){
return false;
}
@Override
public boolean isBookEnchantable(ItemStack stack, ItemStack book){
return false;
}
// Cannot be dropped
@Override
public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){
@@ -1,19 +1,14 @@
package electroblob.wizardry.item;
import java.util.List;
import javax.annotation.Nullable;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.event.DiscoverSpellEvent;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
@@ -25,6 +20,9 @@ import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import javax.annotation.Nullable;
import java.util.List;
public class ItemIdentificationScroll extends Item {
public ItemIdentificationScroll(){
@@ -38,11 +36,15 @@ public class ItemIdentificationScroll extends Item {
return true;
}
@Override
public EnumRarity getRarity(ItemStack stack){
return EnumRarity.UNCOMMON;
}
@Override
@SideOnly(Side.CLIENT)
public void addInformation(ItemStack stack, @Nullable World worldIn, List<String> tooltip, ITooltipFlag flagIn) {
tooltip.add(net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":identification_scroll.desc1", "\u00A77"));
tooltip.add(net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":identification_scroll.desc2", "\u00A77"));
public void addInformation(ItemStack stack, @Nullable World world, List<String> tooltip, net.minecraft.client.util.ITooltipFlag flag) {
Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc");
}
@Override
@@ -52,27 +54,26 @@ public class ItemIdentificationScroll extends Item {
if(WizardData.get(player) != null){
WizardData properties = WizardData.get(player);
WizardData data = WizardData.get(player);
for(ItemStack stack1 : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){
if(!stack1.isEmpty()){
Spell spell = Spell.get(stack1.getItemDamage());
Spell spell = Spell.byMetadata(stack1.getItemDamage());
if((stack1.getItem() instanceof ItemSpellBook || stack1.getItem() instanceof ItemScroll)
&& !properties.hasSpellBeenDiscovered(spell)){
&& !data.hasSpellBeenDiscovered(spell)){
if(!MinecraftForge.EVENT_BUS.post(new DiscoverSpellEvent(player, spell,
DiscoverSpellEvent.Source.IDENTIFICATION_SCROLL))){
// Identification scrolls give the chat readout in creative mode, otherwise it looks like
// nothing happens!
properties.discoverSpell(spell);
WizardryAdvancementTriggers.identify_spell.triggerFor(player);
player.playSound(SoundEvents.ENTITY_PLAYER_LEVELUP, 1.25f, 1);
if(!player.capabilities.isCreativeMode) stack.shrink(1);
data.discoverSpell(spell);
player.playSound(WizardrySounds.MISC_DISCOVER_SPELL, 1.25f, 1);
if(!player.isCreative()) stack.shrink(1);
if(!world.isRemote) player.sendMessage(new TextComponentTranslation("spell.discover",
spell.getNameForTranslationFormatted()));
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
return new ActionResult<>(EnumActionResult.SUCCESS, stack);
}
}
}
@@ -82,7 +83,7 @@ public class ItemIdentificationScroll extends Item {
new TextComponentTranslation("item." + Wizardry.MODID + ":identification_scroll.nothing_to_identify"));
}
return new ActionResult<ItemStack>(EnumActionResult.FAIL, stack);
return new ActionResult<>(EnumActionResult.FAIL, stack);
}
}
@@ -0,0 +1,220 @@
package electroblob.wizardry.item;
import com.google.common.collect.Multimap;
import electroblob.wizardry.entity.construct.EntityHammer;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.LightningHammer;
import electroblob.wizardry.spell.Spell;
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.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.util.List;
import java.util.UUID;
@Mod.EventBusSubscriber
public class ItemLightningHammer extends Item implements IConjuredItem {
public static final String DURATION_NBT_KEY = "duration";
// Annoyingly we can't implement this for attack damage, but at least it gets saved for when the hammer is thrown
public static final String DAMAGE_MULTIPLIER_NBT_KEY = "damageMultiplier";
public static final UUID MOVEMENT_SPEED_MODIFIER = UUID.fromString("d4c3bd93-c8e3-49c5-b35b-9356663bad1b");
private static final double ATTACK_SPEED = -3.2;
private static final double CHAINING_RANGE = 4;
private static final float CHAINING_DAMAGE = 4;
private static final double THROW_SPEED = 0.75;
private static final double MOVEMENT_SPEED_REDUCTION = -0.25;
public ItemLightningHammer(){
super();
setMaxDamage(600);
setMaxStackSize(1);
setNoRepair();
setCreativeTab(null);
}
@Override
public EnumRarity getRarity(ItemStack stack){
return EnumRarity.EPIC;
}
@Override
public int getMaxDamage(ItemStack stack){
if(stack.hasTagCompound() && stack.getTagCompound().hasKey(DURATION_NBT_KEY)){
return stack.getTagCompound().getInteger(DURATION_NBT_KEY);
}
return super.getMaxDamage(stack);
}
private float getDamageMultiplier(ItemStack stack){
if(stack.hasTagCompound() && stack.getTagCompound().hasKey(DAMAGE_MULTIPLIER_NBT_KEY)){
return stack.getTagCompound().getFloat(DAMAGE_MULTIPLIER_NBT_KEY);
}
return 1;
}
@Override
public Multimap<String, AttributeModifier> getItemAttributeModifiers(EntityEquipmentSlot slot){
Multimap<String, AttributeModifier> multimap = super.getItemAttributeModifiers(slot);
if(slot == EntityEquipmentSlot.MAINHAND){
multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(ATTACK_DAMAGE_MODIFIER, "Weapon modifier", Spells.lightning_hammer.getProperty(Spell.DIRECT_DAMAGE).floatValue(), WizardryUtilities.Operations.ADD));
multimap.put(SharedMonsterAttributes.ATTACK_SPEED.getName(), new AttributeModifier(ATTACK_SPEED_MODIFIER, "Weapon modifier", ATTACK_SPEED, WizardryUtilities.Operations.ADD));
multimap.put(SharedMonsterAttributes.MOVEMENT_SPEED.getName(), new AttributeModifier(MOVEMENT_SPEED_MODIFIER, "Weapon modifier", MOVEMENT_SPEED_REDUCTION, WizardryUtilities.Operations.MULTIPLY_FLAT));
}
return multimap;
}
@Override
// This method allows the code for the item's timer to be greatly simplified by damaging it directly from
// onUpdate() and removing the workaround that involved WizardData and all sorts of crazy stuff.
public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged){
if(!oldStack.isEmpty() || !newStack.isEmpty()){
// We only care about the situation where we specifically want the animation NOT to play.
if(oldStack.getItem() == newStack.getItem() && !slotChanged) return false;
}
return super.shouldCauseReequipAnimation(oldStack, newStack, slotChanged);
}
@Override
public void onUpdate(ItemStack stack, World world, Entity entity, int slot, boolean selected){
int damage = stack.getItemDamage();
if(damage > stack.getMaxDamage()) entity.replaceItemInInventory(slot, ItemStack.EMPTY);
stack.setItemDamage(damage + 1);
}
@Override
public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer player, EnumHand hand){
ItemStack stack = player.getHeldItem(hand);
if(!world.isRemote){
EntityHammer hammer = new EntityHammer(world);
Vec3d look = player.getLookVec();
Vec3d vec = player.getPositionEyes(1).add(look);
hammer.setPositionAndRotation(vec.x, vec.y - hammer.height/2, vec.z, player.rotationYawHead - 90, 0);
// For some reason the above method insists on clamping the pitch to between -90 and 90
hammer.rotationPitch = 180 + player.rotationPitch;
hammer.prevRotationPitch = hammer.rotationPitch;
float attackStrength = player.getCooledAttackStrength(0);
double speed = THROW_SPEED * attackStrength; // Throw distance depends on the attack meter
hammer.addVelocity(look.x * speed, look.y * speed, look.z * speed);
hammer.lifetime = stack.getMaxDamage() - stack.getItemDamage();
hammer.setCaster(player);
hammer.damageMultiplier = getDamageMultiplier(stack);
hammer.spin = true;
world.spawnEntity(hammer);
}
WizardryUtilities.playSoundAtPlayer(player, WizardrySounds.ENTITY_HAMMER_THROW, 1.0F, 0.8f);
//player.swingArm(hand);
// Use this instead of stack.shrink so it works regardless of whether the player is in creative mode or not
player.setHeldItem(hand, ItemStack.EMPTY);
return ActionResult.newResult(EnumActionResult.SUCCESS, stack);
}
@Override
public boolean getIsRepairable(ItemStack stack, ItemStack par2ItemStack){
return false;
}
@Override
public int getItemEnchantability(){
return 0;
}
@Override
public boolean isEnchantable(ItemStack stack){
return false;
}
@Override
public boolean isBookEnchantable(ItemStack stack, ItemStack book){
return false;
}
// Cannot be dropped
@Override
public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){
return false;
}
// Can't be done in hitEntity because that's only called server-side, and after the cooldown is reset
@SubscribeEvent
public static void onAttackEntityEvent(AttackEntityEvent event){
ItemStack stack = event.getEntityPlayer().getHeldItemMainhand();
if(stack.getItem() instanceof ItemLightningHammer && event.getTarget() instanceof EntityLivingBase){
EntityPlayer wielder = event.getEntityPlayer();
EntityLivingBase hit = (EntityLivingBase)event.getTarget();
float attackStrength = wielder.getCooledAttackStrength(0);
double dx = wielder.posX - hit.posX;
double dz;
for(dz = wielder.posZ - hit.posZ; dx * dx + dz * dz < 1.0E-4D; dz = (Math.random() - Math.random())
* 0.01D){
dx = (Math.random() - Math.random()) * 0.01D;
}
hit.knockBack(wielder, 2 * attackStrength, dx, dz);
if(attackStrength == 1){ // Only chains when the attack meter is full
List<EntityLivingBase> nearby = WizardryUtilities.getEntitiesWithinRadius(CHAINING_RANGE, hit.posX, hit.posY, hit.posZ, hit.world);
nearby.remove(hit);
nearby.remove(wielder);
// When held, the number of chaining targets is halved
int maxTargets = Spells.lightning_hammer.getProperty(LightningHammer.SECONDARY_MAX_TARGETS).intValue() / 2;
while(nearby.size() > maxTargets) nearby.remove(nearby.size() - 1);
for(EntityLivingBase target : nearby){
target.attackEntityFrom(MagicDamage.causeDirectMagicDamage(wielder, DamageType.SHOCK), CHAINING_DAMAGE * ((ItemLightningHammer)stack.getItem()).getDamageMultiplier(stack));
if(hit.world.isRemote){
ParticleBuilder.create(Type.LIGHTNING).pos(hit.getPositionVector().add(0, hit.height / 2, 0))
.target(target).spawn(hit.world);
ParticleBuilder.spawnShockParticles(hit.world, target.posX, target.getEntityBoundingBox().minY + target.height / 2, target.posZ);
}
//target.playSound(WizardrySounds.SPELL_SPARK, 1, 1.5f + 0.4f * world.rand.nextFloat());
}
}
}
}
}
@@ -0,0 +1,37 @@
package electroblob.wizardry.item;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
public class ItemManaFlask extends Item {
public enum Size {
SMALL(75, EnumRarity.COMMON),
MEDIUM(700, EnumRarity.COMMON),
LARGE(1400, EnumRarity.RARE);
public int capacity;
public EnumRarity rarity;
Size(int capacity, EnumRarity rarity){
this.capacity = capacity;
this.rarity = rarity;
}
}
public final Size size;
public ItemManaFlask(Size size){
super();
this.size = size;
this.setCreativeTab(WizardryTabs.WIZARDRY);
}
@Override
public EnumRarity getRarity(ItemStack stack){
return size.rarity;
}
}
@@ -1,9 +1,9 @@
package electroblob.wizardry.item;
import electroblob.wizardry.entity.projectile.EntityPoisonBomb;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
@@ -23,15 +23,15 @@ public class ItemPoisonBomb extends Item {
ItemStack stack = player.getHeldItem(hand);
if(!player.capabilities.isCreativeMode){
if(!player.isCreative()){
stack.shrink(1);
}
player.playSound(SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F));
player.playSound(WizardrySounds.ENTITY_POISON_BOMB_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F));
if(!world.isRemote){
EntityPoisonBomb poisonbomb = new EntityPoisonBomb(world);
poisonbomb.aim(player, 1.5f);
poisonbomb.aim(player, 1);
world.spawnEntity(poisonbomb);
}
@@ -0,0 +1,99 @@
package electroblob.wizardry.item;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Items;
import net.minecraft.item.EnumAction;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundCategory;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import javax.annotation.Nullable;
import java.util.List;
public class ItemPurifyingElixir extends Item {
public ItemPurifyingElixir(){
this.setMaxStackSize(1);
this.setCreativeTab(WizardryTabs.WIZARDRY);
}
@Override
public boolean hasEffect(ItemStack stack){
return true;
}
@Override
public EnumRarity getRarity(ItemStack stack){
return EnumRarity.RARE;
}
@Override
@SideOnly(Side.CLIENT)
public void addInformation(ItemStack stack, @Nullable World world, List<String> tooltip, net.minecraft.client.util.ITooltipFlag flag) {
Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc");
}
@Override
public ItemStack onItemUseFinish(ItemStack stack, World world, EntityLivingBase entity){
if(!world.isRemote){
entity.curePotionEffects(stack);
}else{
ParticleBuilder.spawnHealParticles(world, entity);
for(int i = 0; i < 20; i++){
double x = entity.posX + world.rand.nextDouble() * 2 - 1;
double y = entity.getEntityBoundingBox().minY + entity.getEyeHeight() - 0.5 + world.rand.nextDouble();
double z = entity.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);
}
}
world.playSound(entity.posX, entity.posY, entity.posZ, WizardrySounds.ITEM_PURIFYING_ELIXIR_DRINK, SoundCategory.PLAYERS, 1, 1, false);
if(entity instanceof EntityPlayerMP){
EntityPlayerMP entityplayermp = (EntityPlayerMP)entity;
CriteriaTriggers.CONSUME_ITEM.trigger(entityplayermp, stack);
}
if(entity instanceof EntityPlayer && !((EntityPlayer)entity).capabilities.isCreativeMode){
stack.shrink(1);
}
return stack.isEmpty() ? new ItemStack(Items.GLASS_BOTTLE) : stack;
}
@Override
public int getMaxItemUseDuration(ItemStack stack){
return 32;
}
@Override
public EnumAction getItemUseAction(ItemStack stack){
return EnumAction.DRINK;
}
@Override
public ActionResult<ItemStack> onItemRightClick(World worldIn, EntityPlayer playerIn, EnumHand handIn){
playerIn.setActiveHand(handIn);
return new ActionResult<>(EnumActionResult.SUCCESS, playerIn.getHeldItem(handIn));
}
}
@@ -8,7 +8,6 @@ import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
@@ -24,10 +23,9 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
public class ItemScroll extends Item {
public class ItemScroll extends Item implements ISpellCastingItem {
/** The maximum number of ticks a continuous spell scroll can be cast for (by holding the use item button). */
// TODO: Make this configurable
public static final int CASTING_TIME = 120;
public ItemScroll(){
@@ -36,14 +34,24 @@ public class ItemScroll extends Item {
setMaxStackSize(16);
setCreativeTab(WizardryTabs.SPELLS);
}
@Override
public Spell getCurrentSpell(ItemStack stack){
return Spell.byMetadata(stack.getItemDamage());
}
@Override
public boolean showSpellHUD(EntityPlayer player, ItemStack stack){
return false;
}
@Override
public void getSubItems(CreativeTabs tab, NonNullList<ItemStack> list){
if(tab == WizardryTabs.SPELLS){
// In this particular case, getTotalSpellCount() is a more efficient way of doing this since the spell instance
// is not required, only the id.
// is not required, only the metadata.
for(int i = 0; i < Spell.getTotalSpellCount(); i++){
// i+1 is used so that the metadata ties up with the id() method. In other words, the none spell has id
// i+1 is used so that the metadata ties up with the metadata() method. In other words, the none spell has metadata
// 0 and since this is not used as a spell book the metadata starts at 1.
list.add(new ItemStack(this, 1, i + 1));
}
@@ -83,54 +91,25 @@ public class ItemScroll extends Item {
ItemStack stack = player.getHeldItem(hand);
Spell spell = Spell.get(stack.getItemDamage());
Spell spell = Spell.byMetadata(stack.getItemDamage());
// By default, scrolls have no modifiers - but with the event system, they could be added.
SpellModifiers modifiers = new SpellModifiers();
// If anything stops the spell working at this point, nothing else happens.
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(player, spell, modifiers, Source.SCROLL))){
return new ActionResult<ItemStack>(EnumActionResult.FAIL, stack);
}
// Now we can cast continuous spells with scrolls!
if(spell.isContinuous){
if(!player.isHandActive()){
player.setActiveHand(hand);
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
}
}else{
if(!world.isRemote){
if(spell.cast(world, player, hand, 0, new SpellModifiers())){
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(player, spell, modifiers, Source.SCROLL));
if(spell.doesSpellRequirePacket()){
// Sends a packet to all players in dimension to tell them to spawn particles.
IMessage msg = new PacketCastSpell.Message(player.getEntityId(), hand, spell.id(), modifiers);
WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension());
}
// Scrolls are consumed upon successful use in survival mode
if(!player.capabilities.isCreativeMode) stack.shrink(1);
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
if(canCast(stack, spell, player, hand, 0, modifiers)){
// Now we can cast continuous spells with scrolls!
if(spell.isContinuous){
if(!player.isHandActive()){
player.setActiveHand(hand);
return new ActionResult<>(EnumActionResult.SUCCESS, stack);
}
// This else if check was bugging me for AGES! I can't believe I didn't compare to ItemWand before.
}else if(!spell.doesSpellRequirePacket()){
// Client-inconsistent spell casting. This code only runs client-side.
if(spell.cast(world, player, hand, 0, modifiers)){
// This is all that needs to happen, because everything above works fine on just the server side.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(player, spell, modifiers, Source.SCROLL));
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
}else{
if(cast(stack, spell, player, hand, 0, modifiers)){
return new ActionResult<>(EnumActionResult.SUCCESS, stack);
}
}
}
return new ActionResult<ItemStack>(EnumActionResult.FAIL, stack);
return new ActionResult<>(EnumActionResult.FAIL, stack);
}
// For continuous spells. The count argument actually decrements by 1 each tick.
@@ -141,31 +120,68 @@ public class ItemScroll extends Item {
EntityPlayer player = (EntityPlayer)user;
Spell spell = Spell.get(stack.getItemDamage());
Spell spell = Spell.byMetadata(stack.getItemDamage());
// By default, scrolls have no modifiers - but with the event system, they could be added.
SpellModifiers modifiers = new SpellModifiers();
int castingTick = stack.getMaxItemUseDuration() - count;
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(Source.SCROLL, spell, player, modifiers, castingTick)))
return;
// Continuous spells (these must check if they can be cast each tick since the mana changes)
if(spell.isContinuous){
if(spell.cast(player.world, player, player.getActiveHand(), castingTick, modifiers)){
if(castingTick == 0)
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.SCROLL, spell, player, modifiers));
}
// In theory the spell is always continuous here but just in case it isn't...
if(spell.isContinuous && canCast(stack, spell, player, player.getActiveHand(), castingTick, modifiers)){
cast(stack, spell, player, player.getActiveHand(), castingTick, modifiers);
}else{
// Scrolls normally work on the max use duration so this isn't ever reached by wizardry, but if the
// casting was interrupted by SpellCastEvent.Tick it will be used
player.stopActiveHand();
}
}
}
@Override
public boolean canCast(ItemStack stack, Spell spell, EntityPlayer caster, EnumHand hand, int castingTick, SpellModifiers modifiers){
// Even neater!
if(castingTick == 0){
return !MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(Source.SCROLL, spell, caster, modifiers));
}else{
return !MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(Source.SCROLL, spell, caster, modifiers, castingTick));
}
}
@Override
public boolean cast(ItemStack stack, Spell spell, EntityPlayer caster, EnumHand hand, int castingTick, SpellModifiers modifiers){
World world = caster.world;
if(world.isRemote && !spell.isContinuous && spell.requiresPacket()) return false;
if(spell.cast(world, caster, hand, castingTick, modifiers)){
if(castingTick == 0) MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.SCROLL, spell, caster, modifiers));
if(!world.isRemote){
// Continuous spells never require packets so don't rely on the requiresPacket method to specify it
if(!spell.isContinuous && spell.requiresPacket()){
// Sends a packet to all players in dimension to tell them to spawn particles.
IMessage msg = new PacketCastSpell.Message(caster.getEntityId(), hand, spell, modifiers);
WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension());
}
// Scrolls are consumed upon successful use in survival mode
if(!spell.isContinuous && !caster.isCreative()) stack.shrink(1);
}
return true;
}
return false;
}
@Override
public void onPlayerStoppedUsing(ItemStack stack, World world, EntityLivingBase user, int timeLeft){
// Consumes a continuous spell scroll when a player in survival mode stops using it.
if(Spell.get(stack.getItemDamage()).isContinuous
&& (!(user instanceof EntityPlayer) || !((EntityPlayer)user).capabilities.isCreativeMode)){
if(Spell.byMetadata(stack.getItemDamage()).isContinuous
&& (!(user instanceof EntityPlayer) || !((EntityPlayer)user).isCreative())){
stack.shrink(1);
}
}
@@ -173,8 +189,8 @@ public class ItemScroll extends Item {
@Override
public ItemStack onItemUseFinish(ItemStack stack, World world, EntityLivingBase user){
// Consumes a continuous spell scroll when the casting elapses whilst in use by a player in survival mode.
if(Spell.get(stack.getItemDamage()).isContinuous
&& (!(user instanceof EntityPlayer) || !((EntityPlayer)user).capabilities.isCreativeMode)){
if(Spell.byMetadata(stack.getItemDamage()).isContinuous
&& (!(user instanceof EntityPlayer) || !((EntityPlayer)user).isCreative())){
stack.shrink(1);
}
@@ -183,7 +199,7 @@ public class ItemScroll extends Item {
@Override
@SideOnly(Side.CLIENT)
public FontRenderer getFontRenderer(ItemStack stack){
public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){
return Wizardry.proxy.getFontRenderer(stack);
}
}
@@ -1,9 +1,9 @@
package electroblob.wizardry.item;
import electroblob.wizardry.entity.projectile.EntitySmokeBomb;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
@@ -23,15 +23,15 @@ public class ItemSmokeBomb extends Item {
ItemStack stack = player.getHeldItem(hand);
if(!player.capabilities.isCreativeMode){
if(!player.isCreative()){
stack.shrink(1);
}
player.playSound(SoundEvents.ENTITY_SNOWBALL_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F));
player.playSound(WizardrySounds.ENTITY_SMOKE_BOMB_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F));
if(!world.isRemote){
EntitySmokeBomb smokebomb = new EntitySmokeBomb(world);
smokebomb.aim(player, 1.5f);
smokebomb.aim(player, 1);
world.spawnEntity(smokebomb);
}
@@ -0,0 +1,41 @@
package electroblob.wizardry.item;
import electroblob.wizardry.entity.projectile.EntitySparkBomb;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumHand;
import net.minecraft.world.World;
public class ItemSparkBomb extends Item {
public ItemSparkBomb(){
setMaxStackSize(16);
setCreativeTab(WizardryTabs.WIZARDRY);
}
@Override
public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer player, EnumHand hand){
ItemStack stack = player.getHeldItem(hand);
if(!player.isCreative()){
stack.shrink(1);
}
player.playSound(WizardrySounds.ENTITY_SPARK_BOMB_THROW, 0.5F, 0.4F / (itemRand.nextFloat() * 0.4F + 0.8F));
if(!world.isRemote){
EntitySparkBomb sparkBomb = new EntitySparkBomb(world);
sparkBomb.aim(player, 1);
world.spawnEntity(sparkBomb);
}
return ActionResult.newResult(EnumActionResult.SUCCESS, stack);
}
}
@@ -1,7 +1,6 @@
package electroblob.wizardry.item;
import net.minecraft.client.model.ModelBiped;
import net.minecraft.client.renderer.GlStateManager;
import electroblob.wizardry.registry.Spells;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
@@ -18,17 +17,12 @@ public class ItemSpectralArmour extends ItemArmor implements IConjuredItem {
public ItemSpectralArmour(ArmorMaterial material, int renderIndex, EntityEquipmentSlot armourType){
super(material, renderIndex, armourType);
setCreativeTab(null);
setMaxDamage(getBaseDuration());
}
@Override
public int getBaseDuration(){
return 1800;
setMaxDamage(1200);
}
@Override
public int getMaxDamage(ItemStack stack){
return this.getMaxDamageFromNBT(stack);
return this.getMaxDamageFromNBT(stack, Spells.conjure_armour);
}
// Overridden to stop the enchantment trick making the name turn blue.
@@ -73,6 +67,16 @@ public class ItemSpectralArmour extends ItemArmor implements IConjuredItem {
return 0;
}
@Override
public boolean isEnchantable(ItemStack stack){
return false;
}
@Override
public boolean isBookEnchantable(ItemStack stack, ItemStack book){
return false;
}
// Cannot be dropped
@Override
public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){
@@ -89,14 +93,14 @@ public class ItemSpectralArmour extends ItemArmor implements IConjuredItem {
@Override
@SideOnly(Side.CLIENT)
public ModelBiped getArmorModel(EntityLivingBase entityLiving, ItemStack itemStack,
EntityEquipmentSlot armorSlot, ModelBiped _default){
GlStateManager.enableBlend();
GlStateManager.tryBlendFuncSeparate(
GlStateManager.SourceFactor.SRC_ALPHA,
GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA,
GlStateManager.SourceFactor.ONE,
GlStateManager.DestFactor.ZERO
public net.minecraft.client.model.ModelBiped getArmorModel(EntityLivingBase entityLiving, ItemStack itemStack,
EntityEquipmentSlot armorSlot, net.minecraft.client.model.ModelBiped _default){
net.minecraft.client.renderer.GlStateManager.enableBlend();
net.minecraft.client.renderer.GlStateManager.tryBlendFuncSeparate(
net.minecraft.client.renderer.GlStateManager.SourceFactor.SRC_ALPHA,
net.minecraft.client.renderer.GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA,
net.minecraft.client.renderer.GlStateManager.SourceFactor.ONE,
net.minecraft.client.renderer.GlStateManager.DestFactor.ZERO
);
return super.getArmorModel(entityLiving, itemStack, armorSlot, _default);
}
@@ -1,9 +1,7 @@
package electroblob.wizardry.item;
import javax.annotation.Nullable;
import electroblob.wizardry.Wizardry;
import net.minecraft.client.Minecraft;
import electroblob.wizardry.registry.Spells;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
@@ -17,20 +15,18 @@ import net.minecraft.item.ItemArrow;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemStack;
import net.minecraft.stats.StatList;
import net.minecraft.util.ActionResult;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.*;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import javax.annotation.Nullable;
public class ItemSpectralBow extends ItemBow implements IConjuredItem {
public ItemSpectralBow(){
super();
setMaxDamage(getBaseDuration());
setMaxDamage(1200);
setNoRepair();
setCreativeTab(null);
this.addPropertyOverride(new ResourceLocation("pull"), new IItemPropertyGetter(){
@@ -40,7 +36,6 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem {
return 0.0F;
}else{
ItemStack itemstack = entityIn.getActiveItemStack();
// Mojang, observe - hardcoding item references into their own classes is NOT good Java.
return itemstack.getItem() == ItemSpectralBow.this
? (float)(stack.getMaxItemUseDuration() - entityIn.getItemInUseCount()) / 20.0F
: 0.0F;
@@ -54,6 +49,7 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem {
: 0.0F;
}
});
addAnimationPropertyOverrides();
}
@Override
@@ -63,14 +59,9 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem {
return true;
}
@Override
public int getBaseDuration(){
return 1200;
}
@Override
public int getMaxDamage(ItemStack stack){
return this.getMaxDamageFromNBT(stack);
return this.getMaxDamageFromNBT(stack, Spells.conjure_bow);
}
@Override
@@ -78,27 +69,38 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem {
// onUpdate() and removing the workaround that involved WizardData and all sorts of crazy stuff.
public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged){
// TODO: For some reason there used to be an && here instead of an ||, which makes me wonder if there's a weird
// fix I did that needs removing.
if(!oldStack.isEmpty() || !newStack.isEmpty()){
// We only care about the situation where we specifically want the animation NOT to play.
if(oldStack.getItem() == newStack.getItem() && !slotChanged
// This code should only run on the client side, so using Minecraft is ok.
&& !Minecraft.getMinecraft().player.isHandActive())
&& !net.minecraft.client.Minecraft.getMinecraft().player.isHandActive())
return false;
}
return super.shouldCauseReequipAnimation(oldStack, newStack, slotChanged);
}
// Copied fixes from ItemWand made possible by recently-added Forge hooks
@Override
public boolean canContinueUsing(ItemStack oldStack, ItemStack newStack){
// Ignore durability changes
if(ItemStack.areItemsEqualIgnoreDurability(oldStack, newStack)) return true;
return super.canContinueUsing(oldStack, newStack);
}
@Override
public boolean shouldCauseBlockBreakReset(ItemStack oldStack, ItemStack newStack){
// Ignore durability changes
if(ItemStack.areItemsEqualIgnoreDurability(oldStack, newStack)) return false;
return super.shouldCauseBlockBreakReset(oldStack, newStack);
}
@Override
public void onUpdate(ItemStack stack, World world, Entity entity, int slot, boolean selected){
int damage = stack.getItemDamage();
if(damage > stack.getMaxDamage()) entity.replaceItemInInventory(slot, ItemStack.EMPTY);
// Can't damage it whilst in use because for some reason it causes the item use to constantly reset.
if(!(entity instanceof EntityLivingBase) || !((EntityLivingBase)entity).isHandActive()){
stack.setItemDamage(damage + 1);
}
stack.setItemDamage(damage + 1);
}
// The following two methods re-route the displayed durability through the proxies in order to override the pausing
@@ -145,19 +147,29 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem {
return 0;
}
@Override
public boolean isEnchantable(ItemStack stack){
return false;
}
@Override
public boolean isBookEnchantable(ItemStack stack, ItemStack book){
return false;
}
// Cannot be dropped
@Override
public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){
return false;
}
@Override
public void onUsingTick(ItemStack stack, EntityLivingBase player, int count){
// player.getItemInUseMaxCount() is named incorrectly; you only have to look at the method to see what it really
// does.
if(stack.getItemDamage() + player.getItemInUseMaxCount() > stack.getMaxDamage())
player.replaceItemInInventory(player.getActiveHand() == EnumHand.MAIN_HAND ? 98 : 99, ItemStack.EMPTY);
}
// @Override
// public void onUsingTick(ItemStack stack, EntityLivingBase player, int count){
// // player.getItemInUseMaxCount() is named incorrectly; you only have to look at the method to see what it really
// // does.
// if(stack.getItemDamage() + player.getItemInUseMaxCount() > stack.getMaxDamage())
// player.replaceItemInInventory(player.getActiveHand() == EnumHand.MAIN_HAND ? 98 : 99, ItemStack.EMPTY);
// }
@Override
public void onPlayerStoppedUsing(ItemStack stack, World world, EntityLivingBase entity, int timeLeft){
@@ -205,10 +217,12 @@ public class ItemSpectralBow extends ItemBow implements IConjuredItem {
entityarrow.pickupStatus = EntityArrow.PickupStatus.DISALLOWED;
entityarrow.setDamage(entityarrow.getDamage() * IConjuredItem.getDamageMultiplier(stack));
world.spawnEntity(entityarrow);
}
world.playSound((EntityPlayer)null, entityplayer.posX, entityplayer.posY, entityplayer.posZ,
world.playSound(null, entityplayer.posX, entityplayer.posY, entityplayer.posZ,
SoundEvents.ENTITY_ARROW_SHOOT, SoundCategory.NEUTRAL, 1.0F,
1.0F / (itemRand.nextFloat() * 0.4F + 1.2F) + f * 0.5F);
@@ -1,30 +1,44 @@
package electroblob.wizardry.item;
import electroblob.wizardry.registry.Spells;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemPickaxe;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import javax.annotation.Nullable;
public class ItemSpectralPickaxe extends ItemPickaxe implements IConjuredItem {
private EnumRarity rarity = EnumRarity.COMMON;
public ItemSpectralPickaxe(ToolMaterial material){
super(material);
setMaxDamage(getBaseDuration());
setMaxDamage(1200);
setNoRepair();
setCreativeTab(null);
addAnimationPropertyOverrides();
}
public Item setRarity(EnumRarity rarity){
this.rarity = rarity;
return this;
}
@Override
public int getBaseDuration(){
return 1200;
public EnumRarity getRarity(ItemStack stack){
return rarity;
}
@Override
public int getMaxDamage(ItemStack stack){
return this.getMaxDamageFromNBT(stack);
return this.getMaxDamageFromNBT(stack, Spells.conjure_pickaxe);
}
@Override
@@ -47,6 +61,18 @@ public class ItemSpectralPickaxe extends ItemPickaxe implements IConjuredItem {
stack.setItemDamage(damage + 1);
}
@Override
public float getDestroySpeed(ItemStack stack, IBlockState state){
float speed = super.getDestroySpeed(stack, state);
return speed > 1 ? speed * IConjuredItem.getDamageMultiplier(stack) : speed;
}
@Override
public int getHarvestLevel(ItemStack stack, String toolClass, @Nullable EntityPlayer player, @Nullable IBlockState blockState){
// Reuses the standard bonus amplifier calculation from SpellBuff to increase the mining level at advanced and master tier
return super.getHarvestLevel(stack, toolClass, player, blockState) + (int)((IConjuredItem.getDamageMultiplier(stack) - 1) / 0.4);
}
@Override
@SideOnly(Side.CLIENT)
public boolean hasEffect(ItemStack stack){
@@ -63,10 +89,19 @@ public class ItemSpectralPickaxe extends ItemPickaxe implements IConjuredItem {
return 0;
}
@Override
public boolean isEnchantable(ItemStack stack){
return false;
}
@Override
public boolean isBookEnchantable(ItemStack stack, ItemStack book){
return false;
}
// Cannot be dropped
@Override
public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){
return false;
}
}
@@ -1,7 +1,15 @@
package electroblob.wizardry.item;
import com.google.common.collect.Multimap;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraft.world.World;
@@ -10,21 +18,42 @@ import net.minecraftforge.fml.relauncher.SideOnly;
public class ItemSpectralSword extends ItemSword implements IConjuredItem {
private EnumRarity rarity = EnumRarity.COMMON;
public ItemSpectralSword(ToolMaterial material){
super(material);
setMaxDamage(getBaseDuration());
setMaxDamage(1200);
setNoRepair();
setCreativeTab(null);
addAnimationPropertyOverrides();
}
@Override
public int getBaseDuration(){
return 1200;
public Multimap<String, AttributeModifier> getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack){
Multimap<String, AttributeModifier> multimap = super.getItemAttributeModifiers(slot);
if(slot == EntityEquipmentSlot.MAINHAND){
multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(POTENCY_MODIFIER,
"Potency modifier", IConjuredItem.getDamageMultiplier(stack) - 1, WizardryUtilities.Operations.MULTIPLY_CUMULATIVE));
}
return multimap;
}
public Item setRarity(EnumRarity rarity){
this.rarity = rarity;
return this;
}
@Override
public EnumRarity getRarity(ItemStack stack){
return rarity;
}
@Override
public int getMaxDamage(ItemStack stack){
return this.getMaxDamageFromNBT(stack);
return this.getMaxDamageFromNBT(stack, Spells.conjure_sword);
}
@Override
@@ -63,6 +92,16 @@ public class ItemSpectralSword extends ItemSword implements IConjuredItem {
return 0;
}
@Override
public boolean isEnchantable(ItemStack stack){
return false;
}
@Override
public boolean isBookEnchantable(ItemStack stack, ItemStack book){
return false;
}
// Cannot be dropped
@Override
public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){
@@ -1,17 +1,10 @@
package electroblob.wizardry.item;
import java.util.List;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.WizardryGuiHandler;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.spell.Spell;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
@@ -25,6 +18,8 @@ import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.oredict.OreDictionary;
import java.util.List;
public class ItemSpellBook extends Item {
public ItemSpellBook(){
@@ -38,9 +33,9 @@ public class ItemSpellBook extends Item {
public void getSubItems(CreativeTabs tab, NonNullList<ItemStack> list){
if(tab == WizardryTabs.SPELLS){
// In this particular case, getTotalSpellCount() is a more efficient way of doing this since the spell instance
// is not required, only the id.
// is not required, only the metadata.
for(int i = 0; i < Spell.getTotalSpellCount(); i++){
// i+1 is used so that the metadata ties up with the id() method. In other words, the none spell has id
// i+1 is used so that the metadata ties up with the metadata() method. In other words, the none spell has metadata
// 0 and since this is not used as a spell book the metadata starts at 1.
list.add(new ItemStack(this, 1, i + 1));
}
@@ -56,23 +51,19 @@ public class ItemSpellBook extends Item {
@Override
@SideOnly(Side.CLIENT)
public void addInformation(ItemStack itemstack, World world, List<String> tooltip, ITooltipFlag advanced){
public void addInformation(ItemStack itemstack, World world, List<String> tooltip, net.minecraft.client.util.ITooltipFlag advanced){
// Tooltip is left blank for wizards buying generic spell books.
if(itemstack.getItemDamage() != OreDictionary.WILDCARD_VALUE){
EntityPlayerSP player = Minecraft.getMinecraft().player;
if(world != null && itemstack.getItemDamage() != OreDictionary.WILDCARD_VALUE){
Spell spell = Spell.get(itemstack.getItemDamage());
Spell spell = Spell.byMetadata(itemstack.getItemDamage());
boolean discovered = true;
if(player != null && Wizardry.settings.discoveryMode && !player.capabilities.isCreativeMode && WizardData.get(player) != null
&& !WizardData.get(player).hasSpellBeenDiscovered(spell)){
discovered = false;
}
boolean discovered = Wizardry.proxy.shouldDisplayDiscovered(spell, itemstack);
// Element colour is not given for undiscovered spells
tooltip.add(discovered ? "\u00A77" + spell.getDisplayNameWithFormatting()
: "#\u00A79" + SpellGlyphData.getGlyphName(spell, player.world));
tooltip.add(spell.tier.getDisplayNameWithFormatting());
: "#\u00A79" + SpellGlyphData.getGlyphName(spell, world));
tooltip.add(spell.getTier().getDisplayNameWithFormatting());
}
/* Removed to streamline the tooltip a bit. Information is now within the book. if(spell.isContinuous){
* tooltip.add("\u00A79Mana Cost: " + spell.cost + " per second"); }else{ tooltip.add("\u00A79Mana Cost: " +
@@ -81,7 +72,7 @@ public class ItemSpellBook extends Item {
@Override
@SideOnly(Side.CLIENT)
public FontRenderer getFontRenderer(ItemStack stack){
public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){
return Wizardry.proxy.getFontRenderer(stack);
}
@@ -1,34 +1,26 @@
package electroblob.wizardry.item;
import java.util.List;
import electroblob.wizardry.SpellGlyphData;
import electroblob.wizardry.WizardData;
import com.google.common.collect.Multimap;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.data.SpellGlyphData;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.packet.PacketCastSpell;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.registry.*;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.util.ITooltipFlag;
import electroblob.wizardry.util.*;
import electroblob.wizardry.util.ParticleBuilder.Type;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.inventory.Slot;
@@ -38,46 +30,115 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResult;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.util.List;
import java.util.Random;
/**
* This class is (literally) where the magic happens! All wand types are single instances of this class. There's a lot
* of quite hard-to-read code in here, but unfortunately there's not much I can do about that. For this reason, I have
* written the {@link WandHelper} class.<i> I strongly recommend you use it for interacting with wand items wherever
* possible.</i>
* <p>
* It's unlikely that anything in this class will be of much use externally, but should you wish to use it for whatever
* reason (perhaps if you extend it), it works as follows:
* <p>
* - onItemRightClick is where non-continuous spells are cast, and it sets the item in use for continuous spells<br>
* - onUsingTick does the casting for continuous spells<br>
* - onUpdate deals with the cooldowns for the spells
*
* This class is (literally) where the magic happens! All of wizardry's wand items are instances of this class. As of
* wizardry 4.2, it is no longer necessary to extend {@code ItemWand} thanks to {@link ISpellCastingItem}, though
* extending {@code ItemWand} may still be more appropriate for items using the same casting implementation.
* <p></p>
* This class handles spell casting as follows:
* <p></p>
* - {@code onItemRightClick} is where non-continuous spells are cast, and it sets the item in use for continuous spells<br>
* - {@code onUsingTick} does the casting for continuous spells<br>
* - {@code onUpdate} deals with the cooldowns for the spells<br>
* <br>
* See {@link ISpellCastingItem} for more detail on the {@code canCast(...)} and {@code cast(...)} methods.<br>
* See {@link WandHelper} for everything related to wand NBT.
*
* @since Wizardry 1.0
*/
public class ItemWand extends Item implements IWorkbenchItem {
@Mod.EventBusSubscriber
public class ItemWand extends Item implements IWorkbenchItem, ISpellCastingItem, IManaStoringItem {
/** The number of spell slots a wand has with no attunement upgrades applied. */
public static final int BASE_SPELL_SLOTS = 5;
/** The number of ticks between each time a continuous spell is added to the player's recently-cast spells. */
private static final int CONTINUOUS_TRACKING_INTERVAL = 20;
/** The increase in progression for casting spells of the matching element. */
private static final float ELEMENTAL_PROGRESSION_MODIFIER = 1.2f;
/** The fraction of progression lost when all recently-cast spells are the same as the one being cast. */
private static final float MAX_PROGRESSION_REDUCTION = 0.75f;
public Tier tier;
public Element element;
public ItemWand(Tier tier, Element element){
super();
setMaxStackSize(1);
if(element == null || tier == Tier.BASIC){
setCreativeTab(WizardryTabs.WIZARDRY);
}
setCreativeTab(WizardryTabs.GEAR);
this.tier = tier;
this.element = element;
setMaxDamage(this.tier.maxCharge);
WizardryRecipes.addToManaFlaskCharging(this);
}
@Override
public Spell getCurrentSpell(ItemStack stack){
return WandHelper.getCurrentSpell(stack);
}
@Override
public Spell[] getSpells(ItemStack stack){
return WandHelper.getSpells(stack);
}
@Override
public void selectNextSpell(ItemStack stack){
WandHelper.selectNextSpell(stack);
}
@Override
public void selectPreviousSpell(ItemStack stack){
WandHelper.selectPreviousSpell(stack);
}
@Override
public boolean showSpellHUD(EntityPlayer player, ItemStack stack){
return true;
}
@Override
public boolean showTooltip(ItemStack stack){
return true;
}
/** Does nothing, use {@link ItemWand#setMana(ItemStack, int)} to modify wand mana. */
@Override
public void setDamage(ItemStack stack, int damage){
// Overridden to do nothing to stop repair things from 'repairing' the mana in a wand
}
@Override
public void setMana(ItemStack stack, int mana){
// Using super (which can only be done from in here) bypasses the above override
super.setDamage(stack, getManaCapacity(stack) - mana);
}
@Override
public int getMana(ItemStack stack){
return getManaCapacity(stack) - getDamage(stack);
}
@Override
public int getManaCapacity(ItemStack stack){
return this.getMaxDamage(stack);
}
@Override
@@ -88,40 +149,111 @@ public class ItemWand extends Item implements IWorkbenchItem {
@Override
@SideOnly(Side.CLIENT)
public FontRenderer getFontRenderer(ItemStack stack){
public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){
return Wizardry.proxy.getFontRenderer(stack);
}
@Override
public boolean isEnchantable(ItemStack stack){
return false;
}
@Override
public boolean isBookEnchantable(ItemStack stack, ItemStack book){
return false;
}
@Override
public boolean hasEffect(ItemStack stack){
return !Wizardry.settings.legacyWandLevelling && this.tier.level < Tier.MASTER.level
&& WandHelper.getProgression(stack) >= Tier.values()[tier.ordinal() + 1].progression;
}
// Max damage is modifiable with upgrades.
@Override
public int getMaxDamage(ItemStack itemstack){
public int getMaxDamage(ItemStack stack){
// + 0.5f corrects small float errors rounding down
return (int)(super.getMaxDamage(itemstack) * (1.0f + Constants.STORAGE_INCREASE_PER_LEVEL
* WandHelper.getUpgradeLevel(itemstack, WizardryItems.storage_upgrade)) + 0.5f);
return (int)(super.getMaxDamage(stack) * (1.0f + Constants.STORAGE_INCREASE_PER_LEVEL
* WandHelper.getUpgradeLevel(stack, WizardryItems.storage_upgrade)) + 0.5f);
}
@Override
public void onUpdate(ItemStack itemstack, World world, Entity entity, int slot, boolean isHeld){
public void onCreated(ItemStack stack, World worldIn, EntityPlayer playerIn){
setMana(stack, 0); // Wands are empty when first crafted
}
WandHelper.decrementCooldowns(itemstack);
@Override
public void onUpdate(ItemStack stack, World world, Entity entity, int slot, boolean isHeld){
WandHelper.decrementCooldowns(stack);
// Decrements wand damage (increases mana) every 1.5 seconds if it has a condenser upgrade
if(!world.isRemote && itemstack.isItemDamaged()
&& world.getWorldTime() % Constants.CONDENSER_TICK_INTERVAL == 0){
if(!world.isRemote && !this.isManaFull(stack) && world.getTotalWorldTime() % Constants.CONDENSER_TICK_INTERVAL == 0){
// If the upgrade level is 0, this does nothing anyway.
itemstack.setItemDamage(
itemstack.getItemDamage() - WandHelper.getUpgradeLevel(itemstack, WizardryItems.condenser_upgrade));
}
if(entity instanceof EntityPlayer && this.element != null && this.element != Element.MAGIC){
// As it stands, this will trigger every tick. Not ideal, but I can't find a way to detect if a player
// has a certain achievement.
// TODO: check if this is somehow triggerable via JSON conditions.
WizardryAdvancementTriggers.element_master.triggerFor((EntityPlayer)entity);
this.rechargeMana(stack, WandHelper.getUpgradeLevel(stack, WizardryItems.condenser_upgrade));
}
}
@Override
public Multimap<String, AttributeModifier> getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack){
Multimap<String, AttributeModifier> multimap = super.getAttributeModifiers(slot, stack);
if(slot == EntityEquipmentSlot.MAINHAND){
int level = WandHelper.getUpgradeLevel(stack, WizardryItems.melee_upgrade);
// This check doesn't affect the damage output, but it does stop a blank line from appearing in the tooltip.
if(level > 0 && !this.isManaEmpty(stack)){
multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(),
new AttributeModifier(ATTACK_DAMAGE_MODIFIER, "Melee upgrade modifier", 2 * level, 0));
multimap.put(SharedMonsterAttributes.ATTACK_SPEED.getName(), new AttributeModifier(ATTACK_SPEED_MODIFIER, "Melee upgrade modifier", -2.4000000953674316D, 0));
}
}
return multimap;
}
@Override
public boolean hitEntity(ItemStack stack, EntityLivingBase target, EntityLivingBase wielder){
int level = WandHelper.getUpgradeLevel(stack, WizardryItems.melee_upgrade);
int mana = this.getMana(stack);
if(level > 0 && mana > 0) this.consumeMana(stack, level * 4, wielder);
return true;
}
@Override
public boolean canDestroyBlockInCreative(World world, BlockPos pos, ItemStack stack, EntityPlayer player){
return WandHelper.getUpgradeLevel(stack, WizardryItems.melee_upgrade) == 0;
}
// A proper hook was introduced for this in Forge build 14.23.5.2805 - Hallelujah, finally!
// The discussion about this was quite interesting, see the following:
// https://github.com/TeamTwilight/twilightforest/blob/1.12.x/src/main/java/twilightforest/item/ItemTFScepterLifeDrain.java
// https://github.com/MinecraftForge/MinecraftForge/pull/4834
// Among the things mentioned were that it can be 'fixed' by doing the exact same hacks that I did, and that
// returning a result of PASS rather than SUCCESS from onItemRightClick also solves the problem (not sure why
// though, and again it's not a perfect solution)
// Edit: It seems that the hacky fix in previous versions actually introduced a wand duplication bug... oops
@Override
public boolean canContinueUsing(ItemStack oldStack, ItemStack newStack){
// Ignore durability changes
if(ItemStack.areItemsEqualIgnoreDurability(oldStack, newStack)) return true;
return super.canContinueUsing(oldStack, newStack);
}
@Override
public boolean shouldCauseBlockBreakReset(ItemStack oldStack, ItemStack newStack){
// Ignore durability changes
if(ItemStack.areItemsEqualIgnoreDurability(oldStack, newStack)) return false;
return super.shouldCauseBlockBreakReset(oldStack, newStack);
}
@Override
// Only called client-side
// This method is always called on the item in oldStack, meaning that oldStack.getItem() == this
public boolean shouldCauseReequipAnimation(ItemStack oldStack, ItemStack newStack, boolean slotChanged){
// This method does some VERY strange things! Despite its name, it also seems to affect the updating of NBT...
@@ -149,18 +281,19 @@ public class ItemWand extends Item implements IWorkbenchItem {
@SideOnly(Side.CLIENT)
@Override
public void addInformation(ItemStack itemstack, World world, List<String> text, ITooltipFlag advanced){
EntityPlayerSP player = Minecraft.getMinecraft().player;
public void addInformation(ItemStack stack, World world, List<String> text, net.minecraft.client.util.ITooltipFlag advanced){
EntityPlayer player = net.minecraft.client.Minecraft.getMinecraft().player;
if (player == null) { return; }
// +0.5f is necessary due to the error in the way floats are calculated.
if(element != null) text.add("\u00A78" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wand.buff",
(int)((tier.level + 1) * Constants.DAMAGE_INCREASE_PER_TIER * 100 + 0.5f) + "%",
(int)((tier.level + 1) * Constants.POTENCY_INCREASE_PER_TIER * 100 + 0.5f) + "%",
element.getDisplayName()));
Spell spell = WandHelper.getCurrentSpell(itemstack);
Spell spell = WandHelper.getCurrentSpell(stack);
boolean discovered = true;
if(Wizardry.settings.discoveryMode && !player.capabilities.isCreativeMode && WizardData.get(player) != null
if(Wizardry.settings.discoveryMode && !player.isCreative() && WizardData.get(player) != null
&& !WizardData.get(player).hasSpellBeenDiscovered(spell)){
discovered = false;
}
@@ -169,8 +302,19 @@ public class ItemWand extends Item implements IWorkbenchItem {
discovered ? "\u00A77" + spell.getDisplayNameWithFormatting()
: "#\u00A79" + SpellGlyphData.getGlyphName(spell, player.world)));
text.add("\u00A79" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wand.mana",
(this.getMaxDamage(itemstack) - this.getDamage(itemstack)), this.getMaxDamage(itemstack)));
if(advanced.isAdvanced()){
// Advanced tooltips for debugging
text.add("\u00A79" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wand.mana",
this.getMana(stack), this.getManaCapacity(stack)));
text.add("\u00A77" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wand.progression",
WandHelper.getProgression(stack), this.tier.level < Tier.MASTER.level ? Tier.values()[tier.ordinal() + 1].progression : 0));
// }else{
//
// ChargeStatus status = ChargeStatus.getChargeStatus(stack);
// text.add(status.getFormattingCode() + status.getDisplayName());
}
}
@Override
@@ -188,89 +332,29 @@ public class ItemWand extends Item implements IWorkbenchItem {
ItemStack stack = player.getHeldItem(hand);
// Alternate right-click function; overrides spell casting.
if(this.selectMinionTarget(player, world)) return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
if(this.selectMinionTarget(player, world)) return new ActionResult<>(EnumActionResult.SUCCESS, stack);
Spell spell = WandHelper.getCurrentSpell(stack);
SpellModifiers modifiers = this.calculateModifiers(stack, spell);
// If anything stops the spell working at this point, nothing else happens.
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(player, spell, modifiers, Source.WAND))){
return new ActionResult<ItemStack>(EnumActionResult.FAIL, stack);
}
// This is here to start the inUse thing, otherwise the onUsingTick method will not fire.
if(spell.isContinuous && !player.isHandActive()){
player.setActiveHand(hand);
// Probably ought to be here. (Does it succeed though?)
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
}
// Conditions for the spell to be attempted. The tier check is a failsafe; it should never be false unless the
// NBT is modified directly.
if(!spell.isContinuous && spell.tier.level <= this.tier.level
// Checks that the wand has enough mana to cast the spell
&& spell.cost <= (stack.getMaxDamage() - stack.getItemDamage())
// Checks that the spell is not in cooldown or that the player is in creative mode
&& (WandHelper.getCurrentCooldown(stack) == 0 || player.capabilities.isCreativeMode)){
// If the spell does not require a packet, the code is run in the old client-inconsistent way, since this
// means that swingItem() doesn't need packets in order to work, improving performance.
if(!world.isRemote){
if(spell.cast(world, player, hand, 0, modifiers)){
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(player, spell, modifiers, Source.WAND));
// = Packets =
if(spell.doesSpellRequirePacket()){
// Sends a packet to all players in dimension to tell them to spawn particles.
// Only sent if the spell succeeded, because if the spell failed, you wouldn't
// need to spawn any particles!
IMessage msg = new PacketCastSpell.Message(player.getEntityId(), hand, spell.id(), modifiers);
WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension());
}
SpellModifiers modifiers = this.calculateModifiers(stack, player, spell);
if(canCast(stack, spell, player, hand, 0, modifiers)){
// Now we can cast continuous spells with scrolls!
if(spell.isContinuous){
if(!player.isHandActive()){
player.setActiveHand(hand);
// = Cooldown =
// Spells only have a cooldown in survival
if(!player.capabilities.isCreativeMode){
float cooldownMultiplier = 1.0f
- WandHelper.getUpgradeLevel(stack, WizardryItems.cooldown_upgrade)
* Constants.COOLDOWN_REDUCTION_PER_LEVEL;
if(player.isPotionActive(WizardryPotions.font_of_mana)){
// Dividing by this rather than setting it takes upgrades and font of mana into account
// simultaneously
cooldownMultiplier /= 2
+ player.getActivePotionEffect(WizardryPotions.font_of_mana).getAmplifier();
}
WandHelper.setCurrentCooldown(stack, (int)(spell.cooldown * cooldownMultiplier));
}
// = Mana cost =
// The spell costs 20% less for every armour piece of the matching element.
int armourPieces = getMatchingArmourCount(player, spell);
stack.damageItem((int)(spell.cost * (1.0f - armourPieces * Constants.COST_REDUCTION_PER_ARMOUR)),
player);
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
// Store the modifiers for use each tick
if(WizardData.get(player) != null) WizardData.get(player).itemCastingModifiers = modifiers;
// Return the player's held item so spells can change it if they wish (e.g. possession)
return new ActionResult<>(EnumActionResult.SUCCESS, player.getHeldItem(hand));
}
}else if(!spell.doesSpellRequirePacket()){
// Client-inconsistent spell casting. This code only runs client-side.
if(spell.cast(world, player, hand, 0, modifiers)){
// This is all that needs to happen, because everything above works fine on just the server side.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(player, spell, modifiers, Source.WAND));
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
}else{
if(cast(stack, spell, player, hand, 0, modifiers)){
return new ActionResult<>(EnumActionResult.SUCCESS, player.getHeldItem(hand));
}
}
}
return new ActionResult<ItemStack>(EnumActionResult.FAIL, stack);
return new ActionResult<>(EnumActionResult.FAIL, stack);
}
// For continuous spells. The count argument actually decrements by 1 each tick.
@@ -282,72 +366,155 @@ public class ItemWand extends Item implements IWorkbenchItem {
EntityPlayer player = (EntityPlayer)user;
Spell spell = WandHelper.getCurrentSpell(stack);
SpellModifiers modifiers = this.calculateModifiers(stack, spell);
SpellModifiers modifiers;
if(WizardData.get(player) != null){
modifiers = WizardData.get(player).itemCastingModifiers;
}else{
modifiers = this.calculateModifiers(stack, (EntityPlayer)user, spell); // Fallback to the old way, should never be used
}
int castingTick = stack.getMaxItemUseDuration() - count;
if(MinecraftForge.EVENT_BUS
.post(new SpellCastEvent.Tick(player, spell, modifiers, Source.WAND, castingTick)))
return;
// Continuous spells (these must check if they can be cast each tick since the mana changes)
if(spell.isContinuous && spell.tier.level <= this.tier.level
&& spell.cost / 5 <= (stack.getMaxDamage() - stack.getItemDamage())){
// Don't call canCast when castingTick == 0 because we already did it in onItemRightClick
if(spell.isContinuous && (castingTick == 0 || canCast(stack, spell, player, player.getActiveHand(), castingTick, modifiers))){
cast(stack, spell, player, player.getActiveHand(), castingTick, modifiers);
}else{
// Stops the casting if it was interrupted, either by events or because the wand ran out of mana
player.stopActiveHand();
}
}
}
if(spell.cast(player.world, player, player.getActiveHand(), castingTick, modifiers)){
@Override
public boolean canCast(ItemStack stack, Spell spell, EntityPlayer caster, EnumHand hand, int castingTick, SpellModifiers modifiers){
if(castingTick == 0)
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(player, spell, modifiers, Source.WAND));
// Spells can only be cast if the casting events aren't cancelled...
if(castingTick == 0){
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(Source.WAND, spell, caster, modifiers))) return false;
}else{
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(Source.WAND, spell, caster, modifiers, castingTick))) return false;
}
// = Mana cost =
// Divides the mana cost over a second appropriately; since damage is an integer it cannot
// just be divided by 20.
// Now does five times per second regardless of the spell cost, but each time it does 1/5 of the
// cost per second.
int tickNumber = (count % 20) + 1;
// Tests if the tick counter is a multiple of 4 plus 1, i.e. is true when tickNumber = 1, 5, 9, 13
// or 17.
// Made a slight adjustment since the counter starts on 1 and not 4.
if(tickNumber % 4 == 1){
int cost = (int)(spell.getCost() * modifiers.get(SpellModifiers.COST));
int armourPieces = getMatchingArmourCount(player, spell);
// As of wizardry 4.2 mana cost is only divided over two intervals each second
if(spell.isContinuous) cost = getDistributedCost(cost, castingTick);
switch(armourPieces){
// ...and the wand has enough mana to cast the spell...
return cost <= this.getMana(stack) // This comes first because it changes over time
// ...and the wand is the same tier as the spell or higher...
&& spell.getTier().level <= this.tier.level
// ...and either the spell is not in cooldown or the player is in creative mode
&& (WandHelper.getCurrentCooldown(stack) == 0 || caster.isCreative());
}
case 0:
stack.damageItem(spell.cost / 5, player);
break;
@Override
public boolean cast(ItemStack stack, Spell spell, EntityPlayer caster, EnumHand hand, int castingTick, SpellModifiers modifiers){
case 1:
if(tickNumber != 17) stack.damageItem(spell.cost / 5, player);
break;
World world = caster.world;
case 2:
if(tickNumber != 9 && tickNumber != 17) stack.damageItem(spell.cost / 5, player);
break;
if(world.isRemote && !spell.isContinuous && spell.requiresPacket()) return false;
case 3:
if(tickNumber != 5 && tickNumber != 13 && tickNumber != 17)
stack.damageItem(spell.cost / 5, player);
break;
if(spell.cast(world, caster, hand, castingTick, modifiers)){
case 4:
if(tickNumber == 1) stack.damageItem(spell.cost / 5, player);
break;
if(castingTick == 0) MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.WAND, spell, caster, modifiers));
}
if(!world.isRemote){
// Continuous spells never require packets so don't rely on the requiresPacket method to specify it
if(!spell.isContinuous && spell.requiresPacket()){
// Sends a packet to all players in dimension to tell them to spawn particles.
IMessage msg = new PacketCastSpell.Message(caster.getEntityId(), hand, spell, modifiers);
WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension());
}
caster.setActiveHand(hand);
// Mana cost
int cost = (int)(spell.getCost() * modifiers.get(SpellModifiers.COST));
// As of wizardry 4.2 mana cost is only divided over two intervals each second
if(spell.isContinuous) cost = getDistributedCost(cost, castingTick);
if(cost > 0) this.consumeMana(stack, cost, caster);
}
// Cooldown
if(!spell.isContinuous && !caster.isCreative()){ // Spells only have a cooldown in survival
WandHelper.setCurrentCooldown(stack, (int)(spell.getCooldown() * modifiers.get(WizardryItems.cooldown_upgrade)));
}
// Progression
if(this.tier.level < Tier.MASTER.level && castingTick % CONTINUOUS_TRACKING_INTERVAL == 0){
// We don't care about cost modifiers here, otherwise players would be penalised for wearing robes!
int progression = (int)(spell.getCost() * modifiers.get(SpellModifiers.PROGRESSION));
WandHelper.addProgression(stack, progression);
if(!Wizardry.settings.legacyWandLevelling){ // Don't display the message if legacy wand levelling is enabled
// If the wand just gained enough progression to be upgraded...
Tier nextTier = Tier.values()[tier.ordinal() + 1];
int excess = WandHelper.getProgression(stack) - nextTier.progression;
if(excess >= 0 && excess < progression){
// ...display a message above the player's hotbar
caster.playSound(WizardrySounds.ITEM_WAND_LEVELUP, 1.25f, 1);
if(!world.isRemote)
caster.sendMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":wand.levelup",
this.getItemStackDisplayName(stack), nextTier.getNameForTranslationFormatted()));
}
}
WizardData.get(caster).trackRecentSpell(spell);
}
return true;
}
return false;
}
@Override
public void onPlayerStoppedUsing(ItemStack stack, World world, EntityLivingBase user, int timeLeft){
if(user instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)user;
Spell spell = WandHelper.getCurrentSpell(stack);
SpellModifiers modifiers;
if(WizardData.get(player) != null){
modifiers = WizardData.get(player).itemCastingModifiers;
}else{
modifiers = this.calculateModifiers(stack, (EntityPlayer)user, spell); // Fallback to the old way, should never be used
}
int castingTick = stack.getMaxItemUseDuration() - timeLeft; // Might as well include this
int cost = getDistributedCost((int)(spell.getCost() * modifiers.get(SpellModifiers.COST)), castingTick);
// Still need to check there's enough mana or the spell will finish twice, since running out of mana is
// handled separately.
if(spell.isContinuous && spell.getTier().level <= this.tier.level && cost <= this.getMana(stack)){
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Finish(Source.WAND, spell, player, modifiers, castingTick));
spell.finishCasting(world, player, Double.NaN, Double.NaN, Double.NaN, null, castingTick, modifiers);
if(!player.isCreative()){ // Spells only have a cooldown in survival
WandHelper.setCurrentCooldown(stack, (int)(spell.getCooldown() * modifiers.get(WizardryItems.cooldown_upgrade)));
}
}
}
}
@Override
public boolean itemInteractionForEntity(ItemStack stack, EntityPlayer player, EntityLivingBase entity,
EnumHand hand){
public boolean itemInteractionForEntity(ItemStack stack, EntityPlayer player, EntityLivingBase entity, EnumHand hand){
if(player.isSneaking() && entity instanceof EntityPlayer && WizardData.get(player) != null){
// This is one of those "the method doing the work looks as if it's just returning a value" situations.
// ... I know, right?! I feel very programmer-y. But it's not too confusing here, and it looks neat.
String string = WizardData.get(player).toggleAlly((EntityPlayer)entity) ? "item." + Wizardry.MODID + ":wand.addally"
: "item." + Wizardry.MODID + ":wand.removeally";
if(!player.world.isRemote) player.sendMessage(new TextComponentTranslation(string, entity.getName()));
@@ -357,8 +524,27 @@ public class ItemWand extends Item implements IWorkbenchItem {
return false;
}
/** Distributes the given cost (which should be the per-second cost of a continuous spell) over a second and
* returns the appropriate cost to be applied for the given tick. Currently the cost is distributed over 2
* intervals per second, meaning the returned value is 0 unless {@code castingTick} is a multiple of 10.*/
protected static int getDistributedCost(int cost, int castingTick){
int partialCost;
if(castingTick % 20 == 0){ // Whole number of seconds has elapsed
partialCost = cost / 2 + cost % 2; // Make sure cost adds up to the correct value by adding the remainder here
}else if(castingTick % 10 == 0){ // Something-and-a-half seconds has elapsed
partialCost = cost/2;
}else{ // Some other number of ticks has elapsed
partialCost = 0; // Wands aren't damaged within half-seconds
}
return partialCost;
}
/** Returns a SpellModifiers object with the appropriate modifiers applied for the given ItemStack and Spell. */
protected SpellModifiers calculateModifiers(ItemStack stack, Spell spell){
// This is now public because artefacts use it
public SpellModifiers calculateModifiers(ItemStack stack, EntityPlayer player, Spell spell){
SpellModifiers modifiers = new SpellModifiers();
@@ -375,38 +561,26 @@ public class ItemWand extends Item implements IWorkbenchItem {
if(level > 0)
modifiers.set(WizardryItems.blast_upgrade, 1.0f + level * Constants.BLAST_RADIUS_INCREASE_PER_LEVEL, true);
// I would have liked to have made potion effects increase in strength according to the damage multiplier,
// but the amplifier level is too discrete to make this work. For example, wither 3 for 10 seconds will kill a
// normal mob on full 20 health, but wither 2 for the same duration only deals about 6 hearts of damage in
// total.
if(this.element == spell.element){
modifiers.set(SpellModifiers.POTENCY, 1.0f + (this.tier.level + 1) * Constants.DAMAGE_INCREASE_PER_TIER,
true);
level = WandHelper.getUpgradeLevel(stack, WizardryItems.cooldown_upgrade);
if(level > 0)
modifiers.set(WizardryItems.cooldown_upgrade, 1.0f - level * Constants.COOLDOWN_REDUCTION_PER_LEVEL, true);
float progressionModifier = 1.0f - ((float)WizardData.get(player).countRecentCasts(spell) / WizardData.MAX_RECENT_SPELLS)
* MAX_PROGRESSION_REDUCTION;
if(this.element == spell.getElement()){
modifiers.set(SpellModifiers.POTENCY, 1.0f + (this.tier.level + 1) * Constants.POTENCY_INCREASE_PER_TIER, true);
progressionModifier *= ELEMENTAL_PROGRESSION_MODIFIER;
}
modifiers.set(SpellModifiers.PROGRESSION, progressionModifier, false);
return modifiers;
}
/** Counts the number of armour pieces the given player is wearing that match the given spell's element. */
private int getMatchingArmourCount(EntityPlayer player, Spell spell){
int armourPieces = 0;
for(EntityEquipmentSlot slot : WizardryUtilities.ARMOUR_SLOTS){
ItemStack armour = player.getItemStackFromSlot(slot);
if(armour != null && armour.getItem() instanceof ItemWizardArmour
&& ((ItemWizardArmour)armour.getItem()).element == spell.element)
armourPieces++;
}
return armourPieces;
}
private boolean selectMinionTarget(EntityPlayer player, World world){
RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(world, player, 16, false);
RayTraceResult rayTrace = RayTracer.standardEntityRayTrace(world, player, 16, false);
if(rayTrace != null && WizardryUtilities.isLiving(rayTrace.entityHit)){
@@ -430,6 +604,8 @@ public class ItemWand extends Item implements IWorkbenchItem {
return false;
}
// Workbench stuff
@Override
public int getSpellSlotCount(ItemStack stack){
return BASE_SPELL_SLOTS + WandHelper.getUpgradeLevel(stack, WizardryItems.attunement_upgrade);
@@ -444,24 +620,31 @@ public class ItemWand extends Item implements IWorkbenchItem {
// and also the entire NBT tag compound.
if(upgrade.getStack().getItem() == WizardryItems.arcane_tome){
// Checks the wand upgrade is for the tier above the wand's tier.
Tier tier = Tier.values()[upgrade.getStack().getItemDamage()];
// Checks the wand upgrade is for the tier above the wand's tier, and that either the wand has enough
// progression or the player is in creative mode.
// It is guaranteed that: this == centre.getStack().getItem()
if(upgrade.getStack().getItemDamage() - 1 == this.tier.ordinal()){
Tier tier = Tier.values()[upgrade.getStack().getItemDamage()];
ItemStack newWand = new ItemStack(WizardryUtilities.getWand(tier, this.element));
if((player.isCreative() || Wizardry.settings.legacyWandLevelling
|| WandHelper.getProgression(centre.getStack()) >= tier.progression)
&& tier.ordinal() - 1 == this.tier.ordinal()){
// We're not carrying over excess progression for now, but if we do want to, this is how
// if(!Wizardry.settings.legacyWandLevelling){
// // Easy way to carry excess progression over to the new stack
// WandHelper.setProgression(centre.getStack(), WandHelper.getProgression(centre.getStack()) - tier.progression);
// }
ItemStack newWand = new ItemStack(WizardryItems.getWand(tier, this.element));
newWand.setTagCompound(centre.getStack().getTagCompound());
// This needs to be done after copying the tag compound so the max damage for the new wand
// takes storage upgrades into account.
newWand.setItemDamage(newWand.getMaxDamage() - (centre.getStack().getMaxDamage() - centre.getStack().getItemDamage()));
// This needs to be done after copying the tag compound so the mana capacity for the new wand
// takes storage upgrades into account
// Note the usage of the new wand item and not 'this' to ensure the correct capacity is used
((IManaStoringItem)newWand.getItem()).setMana(newWand, this.getMana(centre.getStack()));
centre.putStack(newWand);
upgrade.decrStackSize(1);
if(tier == Tier.APPRENTICE) WizardryAdvancementTriggers.apprentice.triggerFor(player);
if(tier == Tier.MASTER) WizardryAdvancementTriggers.master.triggerFor(player);
changed = true;
}
@@ -474,14 +657,14 @@ public class ItemWand extends Item implements IWorkbenchItem {
&& WandHelper.getUpgradeLevel(centre.getStack(), specialUpgrade) < Constants.UPGRADE_STACK_LIMIT){
// Used to preserve existing mana when upgrading storage rather than creating free mana.
int prevMana = centre.getStack().getMaxDamage() - centre.getStack().getItemDamage();
int prevMana = this.getMana(centre.getStack());
WandHelper.applyUpgrade(centre.getStack(), specialUpgrade);
// Special behaviours for specific upgrades
if(specialUpgrade == WizardryItems.storage_upgrade){
centre.getStack().setItemDamage(centre.getStack().getMaxDamage() - prevMana);
this.setMana(centre.getStack(), prevMana);
}else if(specialUpgrade == WizardryItems.attunement_upgrade){
@@ -501,9 +684,7 @@ public class ItemWand extends Item implements IWorkbenchItem {
int[] newCooldowns = new int[newSlotCount];
if(cooldowns.length > 0){
for(int i = 0; i < cooldowns.length; i++){
newCooldowns[i] = cooldowns[i];
}
System.arraycopy(cooldowns, 0, newCooldowns, 0, cooldowns.length);
}
WandHelper.setCooldowns(centre.getStack(), newCooldowns);
@@ -520,7 +701,7 @@ public class ItemWand extends Item implements IWorkbenchItem {
}
}
// Reads NBT spell id array to variable, edits this, then writes it back to NBT.
// Reads NBT spell metadata array to variable, edits this, then writes it back to NBT.
// Original spells are preserved; if a slot is left empty the existing spell binding will remain.
// Accounts for spells which cannot be applied because they are above the wand's tier; these spells
// will not bind but the existing spell in that slot will remain and other applicable spells will
@@ -535,9 +716,9 @@ public class ItemWand extends Item implements IWorkbenchItem {
for(int i = 0; i < spells.length; i++){
if(spellBooks[i].getStack() != ItemStack.EMPTY){
Spell spell = Spell.get(spellBooks[i].getStack().getItemDamage());
// If the wand is powerful enough for the spell and it's not already bound to that slot
if(!(spell.tier.level > this.tier.level) && spells[i] != spell){
Spell spell = Spell.byMetadata(spellBooks[i].getStack().getItemDamage());
// If the wand is powerful enough for the spell, it's not already bound to that slot and it's enabled for wands
if(!(spell.getTier().level > this.tier.level) && spells[i] != spell && spell.isEnabled(SpellProperties.Context.WANDS)){
spells[i] = spell;
changed = true;
}
@@ -547,24 +728,67 @@ public class ItemWand extends Item implements IWorkbenchItem {
WandHelper.setSpells(centre.getStack(), spells);
// Charges wand by appropriate amount
if(crystals.getStack() != ItemStack.EMPTY){
if(crystals.getStack() != ItemStack.EMPTY && !this.isManaFull(centre.getStack())){
int chargeDepleted = centre.getStack().getItemDamage();
if(crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL < chargeDepleted){
centre.getStack().setItemDamage(chargeDepleted - crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL);
crystals.decrStackSize(crystals.getStack().getCount());
changed = true;
}else if(chargeDepleted != 0){
int chargeDepleted = this.getManaCapacity(centre.getStack()) - this.getMana(centre.getStack());
centre.getStack().setItemDamage(0);
crystals.decrStackSize((int)Math.ceil(((double)chargeDepleted) / Constants.MANA_PER_CRYSTAL));
changed = true;
int manaPerItem = Constants.MANA_PER_CRYSTAL;
if(crystals.getStack().getItem() == WizardryItems.crystal_shard) manaPerItem = Constants.MANA_PER_SHARD;
if(crystals.getStack().getItem() == WizardryItems.grand_crystal) manaPerItem = Constants.GRAND_CRYSTAL_MANA;
if(crystals.getStack().getCount() * manaPerItem < chargeDepleted){
// If there aren't enough crystals to fully charge the wand
this.rechargeMana(centre.getStack(), crystals.getStack().getCount() * manaPerItem);
crystals.decrStackSize(crystals.getStack().getCount());
}else{
// If there are excess crystals (or just enough)
this.setMana(centre.getStack(), this.getManaCapacity(centre.getStack()));
crystals.decrStackSize((int)Math.ceil(((double)chargeDepleted) / manaPerItem));
}
changed = true;
}
return changed;
}
// hitEntity is only called server-side, so we'll have to use events
@SubscribeEvent
public static void onAttackEntityEvent(AttackEntityEvent event){
EntityPlayer player = event.getEntityPlayer();
ItemStack stack = player.getHeldItemMainhand(); // Can't melee with offhand items
if(stack.getItem() instanceof IManaStoringItem){
// Nobody said it had to be a wand, as long as it's got a melee upgrade it counts
int level = WandHelper.getUpgradeLevel(stack, WizardryItems.melee_upgrade);
int mana = ((IManaStoringItem)stack.getItem()).getMana(stack);
if(level > 0 && mana > 0){
Random random = player.world.rand;
player.world.playSound(player.posX, player.posY, player.posZ, WizardrySounds.ITEM_WAND_MELEE, SoundCategory.PLAYERS, 0.75f, 1, false);
if(player.world.isRemote){
Vec3d origin = new Vec3d(player.posX, player.getEntityBoundingBox().minY + player.getEyeHeight(), player.posZ);
Vec3d hit = origin.add(player.getLookVec().scale(player.getDistance(event.getTarget())));
// Generate two perpendicular vectors in the plane perpendicular to the look vec
Vec3d vec1 = player.getLookVec().rotatePitch(90);
Vec3d vec2 = player.getLookVec().crossProduct(vec1);
for(int i = 0; i < 15; i++){
ParticleBuilder.create(Type.SPARKLE).pos(hit)
.vel(vec1.scale(random.nextFloat() * 0.3f - 0.15f).add(vec2.scale(random.nextFloat() * 0.3f - 0.15f)))
.clr(1f, 1f, 1f).fade(0.3f, 0.5f, 1)
.time(8 + random.nextInt(4)).spawn(player.world);
}
}
}
}
}
}
@@ -0,0 +1,32 @@
package electroblob.wizardry.item;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.item.EnumRarity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import javax.annotation.Nullable;
import java.util.List;
public class ItemWandUpgrade extends Item {
public ItemWandUpgrade(){
super();
this.setCreativeTab(WizardryTabs.WIZARDRY);
}
@Override
public EnumRarity getRarity(ItemStack stack){
return EnumRarity.UNCOMMON;
}
@Override
@SideOnly(Side.CLIENT)
public void addInformation(ItemStack stack, @Nullable World world, List<String> tooltip, net.minecraft.client.util.ITooltipFlag flag) {
Wizardry.proxy.addMultiLineDescription(tooltip, "item." + this.getRegistryName() + ".desc");
}
}
@@ -1,24 +1,24 @@
package electroblob.wizardry.item;
import java.util.List;
import java.util.UUID;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Streams;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockStatue;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryRecipes;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.client.model.ModelBiped;
import net.minecraft.client.util.ITooltipFlag;
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.SharedMonsterAttributes;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.ai.attributes.IAttributeInstance;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.inventory.Slot;
@@ -28,14 +28,19 @@ import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumHandSide;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@Mod.EventBusSubscriber
public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem {
public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem, IManaStoringItem {
// VanillaCopy, ItemArmor has this set to private for some reason.
public static final UUID[] ARMOR_MODIFIERS = new UUID[] {UUID.fromString("845DB27C-C624-495F-8C9F-6020A9A58B6B"), UUID.fromString("D8499B04-0E66-4726-AB29-64469D734E0D"), UUID.fromString("9F3D476D-C118-4544-8365-64846904B48E"), UUID.fromString("2AD3F246-FEE1-4E67-B886-69FD380BB150")};
@@ -47,20 +52,53 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem {
public ItemWizardArmour(ArmorMaterial material, int renderIndex, EntityEquipmentSlot armourType, Element element){
super(material, renderIndex, armourType);
this.element = element;
setCreativeTab(WizardryTabs.WIZARDRY);
setCreativeTab(WizardryTabs.GEAR);
WizardryRecipes.addToManaFlaskCharging(this);
}
/** Should only be used by vanilla's armour damage calculations; use {@link ItemWizardArmour#setMana(ItemStack, int)}
* to modify wand mana from elsewhere. */
@Override
public void setDamage(ItemStack stack, int damage){
// Overridden to stop repair things from 'repairing' the mana in wizard armour
// This being armour, it's much easier to let its damage increase normally, but block it from being decreased
if(stack.getItemDamage() < damage) super.setDamage(stack, damage);
}
@Override
public void setMana(ItemStack stack, int mana){
// Using super (which can only be done from in here) bypasses the above override
super.setDamage(stack, getManaCapacity(stack) - mana);
}
@Override
public int getMana(ItemStack stack){
return getManaCapacity(stack) - getDamage(stack);
}
@Override
public int getManaCapacity(ItemStack stack){
return this.getMaxDamage(stack);
}
@Override
@SideOnly(Side.CLIENT)
public void addInformation(ItemStack stack, World world, List<String> tooltip, ITooltipFlag advanced){
public void addInformation(ItemStack stack, World world, List<String> tooltip, net.minecraft.client.util.ITooltipFlag advanced){
if(stack.hasTagCompound() && stack.getTagCompound().getBoolean("legendary")) tooltip
.add("\u00A7d" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wizard_armour.legendary"));
if(element != null)
if(stack.hasTagCompound() && stack.getTagCompound().getBoolean("legendary"))
tooltip.add("\u00A7d" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wizard_armour.legendary"));
if(element != null){
tooltip.add("\u00A78" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wizard_armour.buff",
(int)(Constants.COST_REDUCTION_PER_ARMOUR * 100) + "%", element.getDisplayName()));
tooltip.add("\u00A79" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wizard_armour.mana",
(this.getMaxDamage(stack) - this.getDamage(stack)), this.getMaxDamage(stack)));
}
// tooltip.add("\u00A79" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wizard_armour.mana",
// (this.getMaxDamage(stack) - this.getDamage(stack)), this.getMaxDamage(stack)));
// ChargeStatus status = ChargeStatus.getChargeStatus(stack);
//
// tooltip.add(status.getFormattingCode() + status.getDisplayName());
}
@Override
@@ -70,32 +108,23 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem {
@Override
@SideOnly(Side.CLIENT)
public boolean hasEffect(ItemStack stack){
return stack.hasTagCompound() && stack.getTagCompound().getBoolean("legendary");
}
@Override
@SideOnly(Side.CLIENT)
public ModelBiped getArmorModel(EntityLivingBase entityLiving, ItemStack itemStack,
EntityEquipmentSlot armourSlot, ModelBiped _default){
ModelBiped model = Wizardry.proxy.getWizardArmourModel();
public net.minecraft.client.model.ModelBiped getArmorModel(EntityLivingBase entityLiving, ItemStack itemStack,
EntityEquipmentSlot armourSlot, net.minecraft.client.model.ModelBiped _default){
// Legs use modelBiped
if(armourSlot == EntityEquipmentSlot.LEGS) return null;
if(armourSlot == EntityEquipmentSlot.LEGS && !entityLiving.isInvisible()) return null;
net.minecraft.client.model.ModelBiped model = Wizardry.proxy.getWizardArmourModel();
if(model != null){
model.bipedHead.showModel = armourSlot == EntityEquipmentSlot.HEAD;
model.bipedHeadwear.showModel = false;
model.bipedBody.showModel = armourSlot == EntityEquipmentSlot.CHEST
|| armourSlot == EntityEquipmentSlot.LEGS;
model.bipedBody.showModel = armourSlot == EntityEquipmentSlot.CHEST;
model.bipedRightArm.showModel = armourSlot == EntityEquipmentSlot.CHEST;
model.bipedLeftArm.showModel = armourSlot == EntityEquipmentSlot.CHEST;
model.bipedRightLeg.showModel = armourSlot == EntityEquipmentSlot.LEGS
|| armourSlot == EntityEquipmentSlot.FEET;
model.bipedLeftLeg.showModel = armourSlot == EntityEquipmentSlot.LEGS
|| armourSlot == EntityEquipmentSlot.FEET;
model.bipedRightLeg.showModel = armourSlot == EntityEquipmentSlot.FEET;
model.bipedLeftLeg.showModel = armourSlot == EntityEquipmentSlot.FEET;
model.isSneak = entityLiving.isSneaking();
model.isRiding = entityLiving.isRiding();
@@ -107,29 +136,29 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem {
ItemStack itemstackL = leftHanded ? entityLiving.getHeldItemMainhand() : entityLiving.getHeldItemOffhand();
if(!itemstackR.isEmpty()){
model.rightArmPose = ModelBiped.ArmPose.ITEM;
model.rightArmPose = net.minecraft.client.model.ModelBiped.ArmPose.ITEM;
if(entityLiving.getItemInUseCount() > 0){
EnumAction enumaction = itemstackR.getItemUseAction();
if(enumaction == EnumAction.BLOCK){
model.rightArmPose = ModelBiped.ArmPose.BLOCK;
model.rightArmPose = net.minecraft.client.model.ModelBiped.ArmPose.BLOCK;
}else if(enumaction == EnumAction.BOW){
model.rightArmPose = ModelBiped.ArmPose.BOW_AND_ARROW;
model.rightArmPose = net.minecraft.client.model.ModelBiped.ArmPose.BOW_AND_ARROW;
}
}
}
if(!itemstackL.isEmpty()){
model.leftArmPose = ModelBiped.ArmPose.ITEM;
model.leftArmPose = net.minecraft.client.model.ModelBiped.ArmPose.ITEM;
if(entityLiving.getItemInUseCount() > 0){
EnumAction enumaction1 = itemstackL.getItemUseAction();
if(enumaction1 == EnumAction.BLOCK){
model.leftArmPose = ModelBiped.ArmPose.BLOCK;
model.leftArmPose = net.minecraft.client.model.ModelBiped.ArmPose.BLOCK;
}else if(enumaction1 == EnumAction.BOW){
model.leftArmPose = ModelBiped.ArmPose.BOW_AND_ARROW;
model.leftArmPose = net.minecraft.client.model.ModelBiped.ArmPose.BOW_AND_ARROW;
}
}
}
@@ -144,33 +173,33 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem {
// Returns a completely transparent texture if the player is invisible. This is such an annoyingly easy
// fix, considering how long I spent trying to do this before - a bit of lateral thinking was all it took.
// Do note however that a texture pack could override this.
if(entity instanceof EntityLivingBase && ((EntityLivingBase)entity).isInvisible()
&& !entity.getEntityData().getBoolean(BlockStatue.NBT_KEY))
return "ebwizardry:textures/armour/invisible_armour.png";
// if(entity instanceof EntityLivingBase && entity.isInvisible() && !entity.getEntityData().getBoolean(BlockStatue.PETRIFIED_NBT_KEY))
// return "ebwizardry:textures/armour/invisible_armour.png";
if(slot == EntityEquipmentSlot.LEGS)
return this.element == null ? "ebwizardry:textures/armour/wizard_armour_legs.png"
: "ebwizardry:textures/armour/wizard_armour_" + this.element.getUnlocalisedName() + "_legs.png";
String s = "wizard_armour";
return this.element == null ? "ebwizardry:textures/armour/wizard_armour.png"
: "ebwizardry:textures/armour/wizard_armour_" + this.element.getUnlocalisedName() + ".png";
if(this.element != null) s = s + "_" + this.element.getName();
if(slot == EntityEquipmentSlot.LEGS) s = s + "_legs";
if(stack.hasTagCompound() && stack.getTagCompound().getBoolean("legendary")) s = "legendary_" + s;
return "ebwizardry:textures/armour/" + s + ".png";
}
@Override
public boolean getIsRepairable(ItemStack stack, ItemStack par2ItemStack){
public boolean getIsRepairable(ItemStack stack, ItemStack material){
return false;
}
/*
* Properly handles the defense value of the armor. This method is responisble for the tooltip on top of
* the armor value. It is also what handles armor toughness, but the wizard armor had a value of 0 for that.
/**
* Properly handles the defence value of the armour. This method is responsible for the tooltip on top of
* the armour value. It is also what handles armour toughness.
*/
@Override
public Multimap<String, AttributeModifier> getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack){
Multimap<String, AttributeModifier> map = HashMultimap.create();
if(stack.getItemDamage() < stack.getMaxDamage() && this.armorType == slot){
if(!this.isManaEmpty(stack) && this.armorType == slot){
int defense = reductions[slot.getIndex()];
float toughness = 0f;
@@ -189,30 +218,10 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem {
return map;
}
// Fixes wizard armor breaking by disallowing setting damage above the max, since damageArmor is not always called.
// Since ISpecialArmor has been removed from this class, this may no longer be necessary, but keeping it won't hurt.
// Workbench stuff
@Override
public void setDamage(ItemStack stack, int damage) {
if(damage <= stack.getMaxDamage()) super.setDamage(stack, damage);
else super.setDamage(stack, stack.getMaxDamage());
}
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
if(event.getEntityLiving() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getEntityLiving();
for(ItemStack stack : player.getArmorInventoryList()){
if(!(stack.getItem() instanceof ItemWizardArmour)){
return; // If any of the armour slots doesn't contain wizard armour, don't trigger the achievement.
}
}
// If it gets this far, then all slots must be wizard armour, so trigger the achievement.
WizardryAdvancementTriggers.armour_set.triggerFor(player);
}
}
public boolean showTooltip(ItemStack stack){ return true; }
@Override
public int getSpellSlotCount(ItemStack stack){
@@ -241,25 +250,87 @@ public class ItemWizardArmour extends ItemArmor implements IWorkbenchItem {
}
// Charges armour by appropriate amount
if(crystals.getStack() != ItemStack.EMPTY){
int chargeDepleted = centre.getStack().getItemDamage();
if(crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL < chargeDepleted){
centre.getStack().setItemDamage(chargeDepleted - crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL);
crystals.decrStackSize(crystals.getStack().getCount());
changed = true;
}else if(chargeDepleted != 0){
if(crystals.getStack() != ItemStack.EMPTY && !this.isManaFull(centre.getStack())){
centre.getStack().setItemDamage(0);
int chargeDepleted = this.getManaCapacity(centre.getStack()) - this.getMana(centre.getStack());
if(crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL < chargeDepleted){
// If there aren't enough crystals to fully charge the armour
this.rechargeMana(centre.getStack(), crystals.getStack().getCount() * Constants.MANA_PER_CRYSTAL);
crystals.decrStackSize(crystals.getStack().getCount());
}else{
// If there are excess crystals (or just enough)
this.setMana(centre.getStack(), this.getManaCapacity(centre.getStack()));
crystals.decrStackSize((int)Math.ceil(((double)chargeDepleted) / Constants.MANA_PER_CRYSTAL));
changed = true;
}
changed = true;
}
return changed;
}
// Event Handlers
// @SubscribeEvent
// public static void onLivingUpdateEvent(LivingUpdateEvent event){
//
// if(event.getEntityLiving() instanceof EntityPlayer){
//
// EntityPlayer player = (EntityPlayer)event.getEntityLiving();
//
// for(ItemStack stack : player.getArmorInventoryList()){
// if(!(stack.getItem() instanceof ItemWizardArmour)){
// return; // If any of the armour slots doesn't contain wizard armour, don't trigger the achievement.
// }
// }
// // If it gets this far, then all slots must be wizard armour, so trigger the achievement.
// WizardryAdvancementTriggers.armour_set.triggerFor(player);
// }
// }
@SubscribeEvent(priority = EventPriority.LOW)
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
// Armour cost reduction
if(event.getCaster() == null) return;
int armourPieces = getMatchingArmourCount(event.getCaster(), event.getSpell().getElement());
float multiplier = 1f - armourPieces * Constants.COST_REDUCTION_PER_ARMOUR;
if(armourPieces == WizardryUtilities.ARMOUR_SLOTS.length) multiplier -= Constants.FULL_ARMOUR_SET_BONUS;
event.getModifiers().set(SpellModifiers.COST, event.getModifiers().get(SpellModifiers.COST) * multiplier, false);
}
/** Counts the number of armour pieces the given entity is wearing that match the given element. */
public static int getMatchingArmourCount(EntityLivingBase entity, Element element){
return (int)Arrays.stream(WizardryUtilities.ARMOUR_SLOTS)
.map(s -> entity.getItemStackFromSlot(s).getItem())
.filter(i -> i instanceof ItemWizardArmour && ((ItemWizardArmour)i).element == element)
.count();
}
@SubscribeEvent
public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){
// Undo the mob detection penalty for wearing armour when invisible
// Only bother doing this for players because the penalty only applies to them
if(event.getTarget() instanceof EntityPlayer && event.getEntityLiving() instanceof EntityLiving
&& event.getEntityLiving().isInvisible()){
int armourPieces = (int)Streams.stream(event.getTarget().getArmorInventoryList())
.filter(s -> !s.isEmpty() && !(s.getItem() instanceof ItemWizardArmour))
.count();
if(armourPieces == 0) return;
// Repeat the calculation from EntityAIFindNearestPlayer, but ignoring wizard armour
IAttributeInstance attribute = event.getEntityLiving().getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE);
double followRange = attribute == null ? 16 : attribute.getAttributeValue();
if(event.getTarget().isSneaking()) followRange *= 0.8;
float f = armourPieces / ((EntityPlayer)event.getTarget()).inventory.armorInventory.size();
if(f < 0.1F) f = 0.1F;
followRange *= (double)(0.7F * f);
// Don't need to worry about the isSuitableTarget check since it must already have been checked to get this far
if(event.getTarget().getDistance(event.getEntity()) > followRange) ((EntityLiving)event.getEntityLiving()).setAttackTarget(null);
}
}
}
@@ -1,13 +1,8 @@
package electroblob.wizardry.item;
import java.util.List;
import javax.annotation.Nullable;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.WizardryGuiHandler;
import electroblob.wizardry.registry.WizardryTabs;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
@@ -16,6 +11,9 @@ import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumHand;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.List;
public class ItemWizardHandbook extends Item {
// Yep, I hardcoded my own name into the mod. Don't want people changing it now, do I?
@@ -28,7 +26,7 @@ public class ItemWizardHandbook extends Item {
}
@Override
public void addInformation(ItemStack stack, @Nullable World worldIn, List<String> tooltip, ITooltipFlag flagIn) {
public void addInformation(ItemStack stack, @Nullable World world, List<String> tooltip, net.minecraft.client.util.ITooltipFlag flag) {
tooltip.add(
"\u00A77" + net.minecraft.client.resources.I18n.format("item." + Wizardry.MODID + ":wizard_handbook.desc", AUTHOR));
}