Miscellaneous setup

This commit is contained in:
Electroblob
2018-01-30 16:02:13 +00:00
parent da6b007a3a
commit 2af7329478
59 changed files with 1833 additions and 13509 deletions
@@ -17,10 +17,14 @@ import net.minecraft.nbt.NBTTagList;
import net.minecraft.world.World;
import net.minecraft.world.WorldSavedData;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
/** Class responsible for generating and storing the randomised spell names and descriptions for each world, which are
* displayed as glyphs using the SGA font renderer.
* @since Wizardry 1.1 */
@Mod.EventBusSubscriber
public class SpellGlyphData extends WorldSavedData {
public static final String NAME = Wizardry.MODID + "_glyphData";
@@ -109,6 +113,8 @@ public class SpellGlyphData extends WorldSavedData {
PacketGlyphData.Message msg = new PacketGlyphData.Message(names, descriptions);
WizardryPacketHandler.net.sendTo(msg, player);
Wizardry.logger.info("Synchronising spell glyph data for " + player.getName());
}
@@ -158,4 +164,13 @@ public class SpellGlyphData extends WorldSavedData {
return nbt;
}
@SubscribeEvent
public static void onWorldLoadEvent(WorldEvent.Load event){
if(!event.getWorld().isRemote && event.getWorld().provider.getDimension() == 0){
// Called to initialise the spell glyph data when a world loads, if it isn't already.
// NOTE: Do we actually need this, or can we just let it initialise the first time it is needed? (see below)
SpellGlyphData.get(event.getWorld());
}
}
}
@@ -13,6 +13,8 @@ import electroblob.wizardry.constants.Element;
import electroblob.wizardry.enchantment.Imbuement;
import electroblob.wizardry.entity.EntityShield;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.packet.PacketCastContinuousSpell;
import electroblob.wizardry.packet.PacketPlayerSync;
import electroblob.wizardry.packet.PacketTransportation;
@@ -40,12 +42,20 @@ import net.minecraft.nbt.NBTTagString;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityInject;
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.common.util.INBTSerializable;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
/** Capability-based replacement for the old ExtendedPlayer class from 1.7.10. This has been reworked to leave
@@ -58,6 +68,7 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
* @since Wizardry 1.2
* @author Electroblob */
// On the plus side, having to rethink this class allowed me to clean it up a lot.
@Mod.EventBusSubscriber
public class WizardData implements INBTSerializable<NBTTagCompound> {
/** Static instance of what I like to refer to as the capability key. Private because, well, it's internal! */
@@ -65,6 +76,7 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
@CapabilityInject(WizardData.class)
private static final Capability<WizardData> WIZARD_DATA_CAPABILITY = null;
/** The player this WizardData instance belongs to. */
private final EntityPlayer player;
// This one is still necessary, because I can't override the equip animation for items that aren't from Wizardry.
@@ -329,6 +341,30 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
WizardryPacketHandler.net.sendToDimension(message, this.player.worldObj.provider.getDimension());
}
}
/** Casts the current continuous spell, fires relevant events and updates the castingTick field. */
public void updateContinuousSpellCasting(){
if(this.currentlyCasting != null && this.currentlyCasting.isContinuous){
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(player, currentlyCasting, spellModifiers, Source.COMMAND, castingTick))){
this.stopCastingContinuousSpell();
return;
}
if(this.currentlyCasting.cast(player.worldObj, player, EnumHand.MAIN_HAND, castingTick, this.spellModifiers)
&& this.castingTick == 0){
// On the first tick casting a continuous spell via commands, SpellCastEvent.Post is fired.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(player, currentlyCasting, spellModifiers, Source.COMMAND));
}
castingTick++;
}else{
// Why is this here? Surely castingTick will always be 0 if currentlyCasting is null?
this.castingTick = 0;
}
}
/** Returns whether this player is currently casting a continuous spell via commands. */
public boolean isCasting(){
@@ -341,8 +377,8 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
return currentlyCasting;
}
/** Called from the event handler each time the associated player is updated. */
public void update(){
/** Called each time the associated player is updated. */
private void update(){
if(this.selectedMinion != null && this.selectedMinion.get() == null) this.selectedMinion = null;
@@ -363,12 +399,8 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
setTpCountdown(getTpCountdown() - 1);
}
}
if(this.currentlyCasting != null && this.currentlyCasting.isContinuous){
this.currentlyCasting.cast(player.worldObj, player, EnumHand.MAIN_HAND, castingTick++, this.spellModifiers);
}else{
this.castingTick = 0;
}
updateContinuousSpellCasting();
}
/**
@@ -483,6 +515,52 @@ public class WizardData implements INBTSerializable<NBTTagCompound> {
}
}
}
// Event handlers
@SubscribeEvent
// The type parameter here has to be Entity, not EntityPlayer, or the event won't get fired.
public static void onCapabilityLoad(AttachCapabilitiesEvent<Entity> event){
if(event.getObject() instanceof EntityPlayer)
event.addCapability(new ResourceLocation(Wizardry.MODID, "WizardData"), new WizardData.Provider((EntityPlayer)event.getObject()));
// This demonstrates why capabilities are badly structured: The following code compiles, but what it does is put
// a player into a CapabilityDispatcher, which is in turn stored in that very same player, which makes no sense
// at all!
//event.addCapability(new ResourceLocation(Wizardry.MODID, "WizardData"), event.getObject());
}
@SubscribeEvent
public static void onPlayerCloneEvent(PlayerEvent.Clone event){
WizardData newData = WizardData.get(event.getEntityPlayer());
WizardData oldData = WizardData.get(event.getOriginal());
newData.copyFrom(oldData, event.isWasDeath());
}
@SubscribeEvent
public static void onEntityJoinWorld(EntityJoinWorldEvent event){
if(!event.getEntity().worldObj.isRemote && event.getEntity() instanceof EntityPlayerMP){
// Synchronises wizard data after loading.
WizardData data = WizardData.get((EntityPlayer)event.getEntity());
if(data != null) data.sync();
}
}
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
if(event.getEntityLiving() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getEntityLiving();
if(WizardData.get(player) != null){
WizardData.get(player).update();
}
}
}
/** This is a nested class for a few reasons: firstly, it makes sense because instances of this and WizardData go
* hand-in-hand; secondly, it's too short to be worth a separate file; and thirdly (and most importantly) it allows
@@ -73,8 +73,6 @@ public class Wizardry {
* a brewing stand). I have at least made it so only one gets used now, so it has no impact on the game.
* - When a spell is on cooldown, you can't break blocks when holding a wand. */
// FIXME: Wizardry enchantments appear on enchanted books in dungeon chests when they shouldn't.
// TODO: So somehow I have managed to overlook the fact that health is actually a float. What this means is that
// I can make the healing spells use damage multipliers - hurrah!
@@ -1,72 +1,47 @@
package electroblob.wizardry;
import java.util.List;
import java.util.Map;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.enchantment.Imbuement;
import electroblob.wizardry.entity.EntityArc;
import electroblob.wizardry.entity.construct.EntityBubble;
import electroblob.wizardry.entity.construct.EntityDecay;
import electroblob.wizardry.entity.living.EntityEvilWizard;
import electroblob.wizardry.entity.living.EntityWizard;
import electroblob.wizardry.entity.living.ISpellCaster;
import electroblob.wizardry.entity.living.ISummonedCreature;
import electroblob.wizardry.item.IConjuredItem;
import electroblob.wizardry.event.DiscoverSpellEvent;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.item.ItemWizardArmour;
import electroblob.wizardry.potion.ICustomPotionParticles;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryAchievements;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryEnchantments;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.spell.Clairvoyance;
import electroblob.wizardry.spell.FreezingWeapon;
import electroblob.wizardry.spell.Intimidate;
import electroblob.wizardry.spell.MindControl;
import electroblob.wizardry.spell.ShadowWard;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.IElementalDamage;
import electroblob.wizardry.util.IndirectMinionDamage;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.MinionDamage;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryParticleType;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityCreature;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.passive.EntityPig;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.init.Blocks;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.ContainerPlayer;
import net.minecraft.inventory.ContainerWorkbench;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EntityDamageSource;
import net.minecraft.util.EntityDamageSourceIndirect;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraft.world.storage.loot.LootEntry;
@@ -74,73 +49,41 @@ import net.minecraft.world.storage.loot.LootEntryTable;
import net.minecraft.world.storage.loot.LootPool;
import net.minecraft.world.storage.loot.RandomValueRange;
import net.minecraft.world.storage.loot.conditions.LootCondition;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.LootTableLoadEvent;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.event.entity.EntityStruckByLightningEvent;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent;
import net.minecraftforge.event.entity.player.BonemealEvent;
import net.minecraftforge.event.entity.player.EntityItemPickupEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.entity.player.PlayerEvent.BreakSpeed;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent;
// TODO: This class is waaaay too long. We need to either:
// - Split it into several separate handlers, each with a logical area to deal with
// Better ways of organising event handling:
// - Split it into several separate handlers, each with a logical area to deal with (see Astral Sorcery)
// - Remove most of the stuff, and have individual spell, item, block, entity, etc. classes handle their own events
// (see Botania)
// - Keep all the methods, but delegate near-repeated behaviours to their individual spell/potion/whatever classes
// (TGG does this, apparently)
// Incidentally, Twilight Forest still has one big event handler class - and a comment about how it's so long...!
// In the end, I decided the most pragmatic solution was to either use the second option or keep things how they were,
// whichever will result in more readable/easily maintainable code in each case. To keep in line with the modularity
// of the spell system, the goal is to have nothing in here which relates to a specific spell (mostly done, but a few
// things remain).
/**
* Wizardry's main event handler class for common code.
* As of Wizardry 2.1, most of the code in this class has been relocated somewhere sensible, leaving only a few
* miscellaneous things that don't make much sense anywhere else, or that are better kept together. Previously, this was
* a gigantic class with about half of the entire mod's logic in it!
* @author Electroblob
* @since Wizardry 1.0
*/
@Mod.EventBusSubscriber
public final class WizardryEventHandler {
@SubscribeEvent
// The type parameter here has to be Entity, not EntityPlayer, or the event won't get fired.
public static void onCapabilityLoad(AttachCapabilitiesEvent<Entity> event){
if(event.getObject() instanceof EntityPlayer)
event.addCapability(new ResourceLocation(Wizardry.MODID, "WizardData"), new WizardData.Provider((EntityPlayer)event.getObject()));
// This demonstrates why capabilities are badly structured: The following code compiles, but what it does is put
// a player into a CapabilityDispatcher, which is in turn stored in that very same player, which makes no sense
// at all!
//event.addCapability(new ResourceLocation(Wizardry.MODID, "WizardData"), event.getObject());
}
@SubscribeEvent
public static void onPlayerCloneEvent(PlayerEvent.Clone event){
WizardData newData = WizardData.get(event.getEntityPlayer());
WizardData oldData = WizardData.get(event.getOriginal());
newData.copyFrom(oldData, event.isWasDeath());
}
@SubscribeEvent
public static void onWorldLoadEvent(WorldEvent.Load event){
if(!event.getWorld().isRemote && event.getWorld().provider.getDimension() == 0){
// Called to initialise the spell glyph data when a world loads, if it isn't already.
// NOTE: Do we actually need this, or can we just let it initialise the first time it is needed? (see below)
SpellGlyphData.get(event.getWorld());
}
}
// IDEA: Config option allowing users to specify loot locations
private static final String[] LOOT_INJECTION_LOCATIONS = {
"minecraft:chests/simple_dungeon",
@@ -152,7 +95,7 @@ public final class WizardryEventHandler {
"minecraft:chests/stronghold_library",
"minecraft:chests/igloo_chest"
};
@SubscribeEvent
public static void onLootTableLoadEvent(LootTableLoadEvent event){
if(Wizardry.settings.generateLoot){
@@ -172,6 +115,60 @@ public final class WizardryEventHandler {
return new LootEntryTable(new ResourceLocation(name), weight, 0, new LootCondition[0], Wizardry.MODID + "_additive_entry");
}
@SubscribeEvent
public static void onPlayerLoggedInEvent(PlayerLoggedInEvent event){
// When a player logs in, they are sent the glyph data and the server's settings.
if(event.player instanceof EntityPlayerMP){
SpellGlyphData.get(event.player.worldObj).sync((EntityPlayerMP)event.player);
Wizardry.settings.sync((EntityPlayerMP)event.player);
}
}
@SubscribeEvent
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
// If a spell is disabled in the config, it will not work.
if(!event.getSpell().isEnabled()){
if(!event.getEntityLiving().worldObj.isRemote)
event.getEntity().addChatMessage(new TextComponentTranslation("spell.disabled", event.getSpell().getNameForTranslationFormatted()));
event.setCanceled(true);
}
}
@SubscribeEvent
public static void onSpellCastPostEvent(SpellCastEvent.Post event){
// Spell discovery (only players can discover spells, obviously)
if(event.getEntity() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getEntity();
WizardData data = WizardData.get(player);
if(data != null){
// Data is updated on both sides (This line was added client-side to fix a bug back in 1.1.3, so now
// it's in common code, which is nice!)
// Short-circuiting AND means that discoverSpell is only called if the event isn't cancelled.
if(!MinecraftForge.EVENT_BUS.post(new DiscoverSpellEvent(player, event.getSpell(),
DiscoverSpellEvent.Source.CASTING)) && data.discoverSpell(event.getSpell())){
// If the spell wasn't already discovered, other stuff happens:
if(event.getSource() == SpellCastEvent.Source.COMMAND){
// If the spell didn't send a packet itself, the extended player needs to be synced so the
// spell discovery updates on the client.
if(!event.getSpell().doesSpellRequirePacket()) data.sync();
}else if(!event.getEntity().worldObj.isRemote && !player.capabilities.isCreativeMode
&& Wizardry.settings.discoveryMode){
// Sound and text only happen server-side, in survival, with discovery mode on, and only when
// the spell wasn't cast using commands.
WizardryUtilities.playSoundAtPlayer(player, SoundEvents.ENTITY_PLAYER_LEVELUP, 1.25f, 1);
player.addChatMessage(new TextComponentTranslation("spell.discover", event.getSpell().getNameForTranslationFormatted()));
}
}
}
}
}
/* There is a subtle but important difference between LivingAttackEvent and LivingHurtEvent - LivingAttackEvent
* fires immediately when attackEntityFrom is called, whereas LivingHurtEvent only fires if the attack actually
* succeeded, i.e. if the entity in question takes damage (though the event is fired before that so you can cancel
@@ -197,115 +194,39 @@ public final class WizardryEventHandler {
@SubscribeEvent
public static void onLivingAttackEvent(LivingAttackEvent event){
// Rather than bother overriding entire attack methods in ISummonedCreature implementations, it's easier (and
// more robust) to use LivingAttackEvent to modify the damage source.
if(event.getSource().getEntity() instanceof ISummonedCreature){
EntityLivingBase summoner = ((ISummonedCreature)event.getSource().getEntity()).getCaster();
if(summoner != null){
event.setCanceled(true);
DamageSource newSource = event.getSource();
// Copies over the original DamageType if appropriate.
DamageType type = event.getSource() instanceof IElementalDamage ? ((IElementalDamage)event.getSource()).getType() : DamageType.MAGIC;
// Copies over the original isRetaliatory flag if appropriate.
boolean isRetaliatory = event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).isRetaliatory();
// All summoned creatures are classified as magic, so it makes sense to do it this way.
if(event.getSource() instanceof EntityDamageSourceIndirect){
newSource = new IndirectMinionDamage(event.getSource().damageType, event.getSource().getSourceOfDamage(), event.getSource().getEntity(), summoner, type, isRetaliatory);
}else if(event.getSource() instanceof EntityDamageSource){
// Name is copied over so it uses the appropriate vanilla death message
newSource = new MinionDamage(event.getSource().damageType, event.getSource().getEntity(), summoner, type, isRetaliatory);
}
// Copy over any relevant 'attributes' the original DamageSource might have had.
if(event.getSource().isExplosion()) newSource.setExplosion();
if(event.getSource().isFireDamage()) newSource.setFireDamage();
if(event.getSource().isProjectile()) newSource.setProjectile();
// For some reason Minecraft calculates knockback relative to DamageSource#getEntity. In vanilla this
// is unnoticeable, but it looks a bit weird with summoned creatures involved - so let's fix that!
if(WizardryUtilities.attackEntityWithoutKnockback(event.getEntity(), newSource, event.getAmount())){
WizardryUtilities.applyStandardKnockback(event.getSource().getEntity(), event.getEntityLiving());
((ISummonedCreature)event.getSource().getEntity()).onSuccessfulAttack(event.getEntityLiving());
}
return;
}
}
// Prevents any damage to allies from magic if friendly fire is enabled
if(!Wizardry.settings.friendlyFire && event.getSource() != null && event.getSource().getEntity() instanceof EntityPlayer
&& event.getEntity() instanceof EntityPlayer && event.getSource() instanceof IElementalDamage){
if(WizardryUtilities.isPlayerAlly((EntityPlayer)event.getSource().getEntity(), (EntityPlayer)event.getEntity())){
event.setCanceled(true);
// I think this ought to be here, since if the event is cancelled nothing else needs to happen.
// This needs to be here, since if the event is cancelled nothing else needs to happen.
return;
}
}
if(event.getSource() instanceof IElementalDamage){
if(MagicDamage.isEntityImmune(((IElementalDamage)event.getSource()).getType(), event.getEntity())){
event.setCanceled(true);
// I would have liked to have done the 'resist' chat message here, but I overlooked the fact that I
// would need an instance of the spell to get its display name!
return;
}
// One convenient side effect of the new damage type system is that I can get rid of all the places where
// creepers are charged and just put them here under shock damage - this is precisely the sort of
// repetitive code I was trying to get rid of, since errors can (and did!) occur.
if(event.getEntityLiving() instanceof EntityCreeper && !((EntityCreeper)event.getEntityLiving()).getPowered()
&& ((IElementalDamage)event.getSource()).getType() == DamageType.SHOCK){
// Charges creepers when they are hit by shock damage
WizardryUtilities.chargeCreeper((EntityCreeper)event.getEntityLiving());
// Gives the player that caused the shock damage the 'It's Gonna Blow' achievement
if(event.getSource().getEntity() instanceof EntityPlayer){
((EntityPlayer)event.getSource().getEntity()).addStat(WizardryAchievements.charge_creeper);
}
}
}
// Bursts bubble when the creature inside takes damage
if(event.getEntityLiving().getRidingEntity() instanceof EntityBubble &&
!((EntityBubble)event.getEntityLiving().getRidingEntity()).isDarkOrb){
event.getEntityLiving().getRidingEntity().playSound(SoundEvents.ENTITY_ITEM_PICKUP, 1.5f, 1.0f);
event.getEntityLiving().getRidingEntity().setDead();
}
// Prevents all unblockable damage while transience is active
if(event.getEntityLiving().isPotionActive(WizardryPotions.transience) && event.getSource() != null && !event.getSource().isUnblockable()){
event.setCanceled(true);
// Again, I think this ought to be here, since if the event is cancelled nothing else needs to happen.
return;
}
if(event.getSource() != null && event.getSource().getEntity() instanceof EntityLivingBase){
// Cancels the mind trick effect if the creature takes damage
// This has been moved to within the (event.getSource().getEntity() instanceof EntityLivingBase) check so it doesn't
// crash the game with a ConcurrentModificationException. If you think about it, mind trick only ought to be
// cancelled if something attacks the entity since potions, drowning, cacti etc. don't affect the targeting.
if(event.getEntityLiving().isPotionActive(WizardryPotions.mind_trick)){
event.getEntityLiving().removePotionEffect(WizardryPotions.mind_trick);
}
// 'Revenge' effects
// Retaliatory effects
// These are better off here because the revenge effects are pretty similar, and I'd rather keep the (lengthy)
// if statement in one place.
if(event.getSource() != null && event.getSource().getEntity() instanceof EntityLivingBase
&& !event.getSource().isProjectile() && !(event.getSource() instanceof IElementalDamage &&
((IElementalDamage)event.getSource()).isRetaliatory())){
EntityLivingBase attacker = (EntityLivingBase)event.getSource().getEntity();
World world = event.getEntityLiving().worldObj;
if(event.getEntityLiving().isPotionActive(WizardryPotions.fireskin) && !event.getSource().isProjectile()){
if(!MagicDamage.isEntityImmune(DamageType.FIRE, event.getEntityLiving())) attacker.setFire(5);
}
// Fireskin
if(event.getEntityLiving().isPotionActive(WizardryPotions.fireskin)
&& !MagicDamage.isEntityImmune(DamageType.FIRE, event.getEntityLiving()))
attacker.setFire(5);
if(event.getEntityLiving().isPotionActive(WizardryPotions.ice_shroud) && !event.getSource().isProjectile()){
if(!MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving()))
attacker.addPotionEffect(new PotionEffect(WizardryPotions.frost, 100, 0));
}
// Ice Shroud
if(event.getEntityLiving().isPotionActive(WizardryPotions.ice_shroud) &&
!MagicDamage.isEntityImmune(DamageType.FROST, event.getEntityLiving()))
attacker.addPotionEffect(new PotionEffect(WizardryPotions.frost, 100, 0));
// Static Aura
if(event.getEntityLiving().isPotionActive(WizardryPotions.static_aura)){
if(event.getEntityLiving().isPotionActive(WizardryPotions.static_aura) && !event.getSource().isProjectile()){
if(!world.isRemote){
EntityArc arc = new EntityArc(world);
arc.setEndpointCoords(event.getEntityLiving().posX, event.getEntityLiving().posY + 1, event.getEntityLiving().posZ,
@@ -317,44 +238,17 @@ public final class WizardryEventHandler {
world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, attacker.posX + world.rand.nextFloat() - 0.5, attacker.getEntityBoundingBox().minY + attacker.height/2 + world.rand.nextFloat()*2 - 1, attacker.posZ + world.rand.nextFloat() - 0.5, 0, 0, 0);
}
}
attacker.attackEntityFrom(MagicDamage.causeDirectMagicDamage(event.getEntityLiving(), DamageType.SHOCK, true), 4.0f);
attacker.playSound(WizardrySounds.SPELL_SPARK, 1.0F, world.rand.nextFloat() * 0.4F + 1.5F);
}
// Shadow ward
if(event.getEntityLiving() instanceof EntityPlayer){
ItemStack wand = event.getEntityLiving().getActiveItemStack();
if(wand != null && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof ShadowWard && !event.getSource().isUnblockable()){
event.setCanceled(true);
// This DamageSource.magic and not event.getSource() because the latter would cause an infinite loop.
event.getEntityLiving().attackEntityFrom(DamageSource.magic, event.getAmount()/2);
attacker.attackEntityFrom(MagicDamage.causeDirectMagicDamage(event.getEntityLiving(), DamageType.MAGIC, true), event.getAmount()/2);
}
}
// Transience
if(attacker.isPotionActive(WizardryPotions.transience)){
event.setCanceled(true);
}
}
}
@SubscribeEvent
public static void onLivingHurtEvent(LivingHurtEvent event){
// Curse of soulbinding
if(!event.getEntity().worldObj.isRemote && event.getEntityLiving() instanceof EntityPlayer && !event.getSource().isUnblockable()){
WizardData properties = WizardData.get((EntityPlayer)event.getEntityLiving());
if(properties != null){
properties.damageAllSoulboundCreatures(event.getAmount());
}
}
// Flaming and freezing swords
if(event.getSource().getEntity() instanceof EntityLivingBase){
@@ -395,212 +289,132 @@ public final class WizardryEventHandler {
}
}
@SubscribeEvent
public static void onBlockPlaceEvent(BlockEvent.PlaceEvent event){
if(event.getPlayer().isPotionActive(WizardryPotions.transience)){
event.setCanceled(true);
return;
}
// Spectral blocks cannot be built on
if(event.getPlacedAgainst() == WizardryBlocks.spectral_block){
event.setCanceled(true);
return;
}
}
@SubscribeEvent
public static void onBlockBreakEvent(BlockEvent.BreakEvent event){
if(event.getPlayer().isPotionActive(WizardryPotions.transience)){
event.setCanceled(true);
return;
}
// Makes wizards angry if a player breaks a block in their tower
if(!(event.getPlayer() instanceof FakePlayer)){
List<EntityWizard> wizards = WizardryUtilities.getEntitiesWithinRadius(64, event.getPos().getX(),
event.getPos().getY(), event.getPos().getZ(), event.getWorld(), EntityWizard.class);
if(!wizards.isEmpty()){
for(EntityWizard wizard : wizards){
if(wizard.isBlockPartOfTower(event.getPos())){
wizard.setRevengeTarget(event.getPlayer());
event.getPlayer().addStat(WizardryAchievements.anger_wizard);
}
}
}
}
}
@SubscribeEvent
public static void onEntityStruckByLightningEvent(EntityStruckByLightningEvent event){
if(event.getLightning().getEntityData() != null && event.getLightning().getEntityData().hasKey("summoningPlayer")){
EntityPlayer player = (EntityPlayer)WizardryUtilities.getEntityByUUID(event.getLightning().worldObj, event.getLightning().getEntityData().getUniqueId("summoningPlayer"));
if(event.getEntity() instanceof EntityCreeper){
player.addStat(WizardryAchievements.charge_creeper);
}
if(event.getEntity() instanceof EntityPig){
player.addStat(WizardryAchievements.frankenstein);
}
}
}
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
// TODO: Move the player stuff to a PlayerTickEvent and separate methods out for clarity
if(event.getEntityLiving() instanceof EntityPlayer){
EntityPlayer entityplayer = (EntityPlayer)event.getEntityLiving();
EntityPlayer player = (EntityPlayer)event.getEntityLiving();
if(WizardData.get(entityplayer) != null){
WizardData.get(entityplayer).update();
if(player.worldObj.isRemote) hackilyFixContinuousSpellCasting(player);
if(player.openContainer instanceof ContainerWorkbench){
craftingTableTick(player);
}
if(entityplayer.openContainer instanceof ContainerWorkbench){
craftingTableTick(entityplayer);
}
if(entityplayer.openContainer instanceof ContainerPlayer){
if(player.openContainer instanceof ContainerPlayer){
// Unfortunately I have no choice but to call this method every tick when the player isn't using another
// inventory, since the only thing tracking whether the player is looking at their inventory is the GUI
// itself, which is client-side only.
playerInventoryTick(entityplayer);
playerInventoryTick(player);
}
testForArmourSet:{
for(ItemStack stack : entityplayer.getArmorInventoryList()){
for(ItemStack stack : player.getArmorInventoryList()){
if(stack == null || !(stack.getItem() instanceof ItemWizardArmour)){
break testForArmourSet;
}
}
entityplayer.addStat(WizardryAchievements.armour_set);
player.addStat(WizardryAchievements.armour_set);
}
}
if(event.getEntityLiving().worldObj.isRemote){
// Behold the power of interfaces! TODO: Backport.
for(PotionEffect effect : event.getEntityLiving().getActivePotionEffects()){
if(effect.getPotion() instanceof ICustomPotionParticles && effect.doesShowParticles()){
double x = event.getEntityLiving().posX + (event.getEntityLiving().worldObj.rand.nextDouble() - 0.5)*event.getEntityLiving().width;
double y = event.getEntityLiving().getEntityBoundingBox().minY + event.getEntityLiving().worldObj.rand.nextDouble()*event.getEntityLiving().height;
double z = event.getEntityLiving().posZ + (event.getEntityLiving().worldObj.rand.nextDouble() - 0.5)*event.getEntityLiving().width;
((ICustomPotionParticles)effect.getPotion()).spawnCustomParticle(event.getEntityLiving().worldObj, x, y, z);
}
}
// Client-side continuous spell casting for NPCs
if(event.getEntity() instanceof ISpellCaster && event.getEntity() instanceof EntityLiving){
Spell spell = ((ISpellCaster)event.getEntity()).getContinuousSpell();
SpellModifiers modifiers = ((ISpellCaster)event.getEntity()).getModifiers();
if(spell != null && spell != Spells.none){
spell.cast(event.getEntity().worldObj, (EntityLiving)event.getEntity(), EnumHand.MAIN_HAND, 0,
// TODO: This implementation of modifiers relies on them being accessible client-side.
((EntityLiving)event.getEntity()).getAttackTarget(), ((ISpellCaster)event.getEntity()).getModifiers());
}
}
}else{
if(event.getEntityLiving().isPotionActive(WizardryPotions.decay) && event.getEntityLiving().onGround && event.getEntityLiving().ticksExisted % Constants.DECAY_SPREAD_INTERVAL == 0){
List<Entity> list = event.getEntityLiving().worldObj.getEntitiesWithinAABBExcludingEntity(event.getEntityLiving(), event.getEntityLiving().getEntityBoundingBox());
boolean flag = true;
for(Object object : list){
if(object instanceof EntityDecay) flag = false;
}
if(flag){
// The victim spreading the decay is the 'caster' here, so that it can actually wear off, otherwise it just gets infected with its own decay and the effect lasts forever.
event.getEntityLiving().worldObj.spawnEntityInWorld(new EntityDecay(event.getEntityLiving().worldObj, event.getEntityLiving().posX, event.getEntityLiving().posY, event.getEntityLiving().posZ, event.getEntityLiving()));
}
}
}
// Mind Control
// This was added because something got changed in the AI classes which means LivingSetAttackTargetEvent doesn't
// get fired when I want it to... so I'm firing it myself.
if(event.getEntityLiving().isPotionActive(WizardryPotions.mind_control) && event.getEntityLiving() instanceof EntityLiving
&& ((EntityLiving)event.getEntityLiving()).getAttackTarget() != null
&& !((EntityLiving)event.getEntityLiving()).getAttackTarget().isEntityAlive())
((EntityLiving)event.getEntityLiving()).setAttackTarget(null); // Causes the event to be fired
/* Old AI no longer exists!
// Mind trick
if(event.getEntityLiving().isPotionActive(Wizardry.mindTrick) && event.getEntityLiving() instanceof EntityLiving){
// Old AI (this can't be done in onLivingSetAttackTargetEvent because that only fires for the new AI).
if(event.getEntityLiving() instanceof EntityCreature) ((EntityCreature)event.getEntityLiving()).setTarget(null);
}
// Mind control - old AI (this can't be done in onLivingSetAttackTargetEvent because that only fires for the new AI).
mindcontrol:
if(event.getEntityLiving().isPotionActive(Wizardry.mindControl) && event.getEntityLiving() instanceof EntityLiving){
NBTTagCompound entityNBT = event.getEntityLiving().getEntityData();
if(entityNBT != null && entityNBT.hasKey(MindControl.NBT_KEY)){
Entity caster = WizardryUtilities.getEntityByUUID(event.getEntity().worldObj, UUID.fromString(entityNBT.getString(MindControl.NBT_KEY)));
if(caster instanceof EntityLivingBase){
if(MindControl.findMindControlTarget((EntityLiving)event.getEntityLiving(), (EntityLivingBase)caster, event.getEntity().worldObj)){
// If it worked, skip setting the target to null.
break mindcontrol;
}
if(!MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(event.getEntityLiving(), spell, modifiers,
SpellCastEvent.Source.NPC, 0))){
spell.cast(event.getEntity().worldObj, (EntityLiving)event.getEntity(), EnumHand.MAIN_HAND, 0,
// TODO: This implementation of modifiers relies on them being accessible client-side.
((EntityLiving)event.getEntity()).getAttackTarget(), modifiers);
}
}
// If the caster couldn't be found or no valid target was found, this just acts like mind trick.
((EntityLiving)event.getEntityLiving()).setAttackTarget(null);
}
*/
// Intimidate
if(event.getEntityLiving().isPotionActive(WizardryPotions.fear) && event.getEntityLiving() instanceof EntityCreature){
NBTTagCompound entityNBT = event.getEntityLiving().getEntityData();
EntityCreature creature = (EntityCreature)event.getEntityLiving();
if(entityNBT != null && entityNBT.hasKey(Intimidate.NBT_KEY)){
Entity caster = WizardryUtilities.getEntityByUUID(creature.worldObj, entityNBT.getUniqueId(Intimidate.NBT_KEY));
if(caster instanceof EntityLivingBase){
Intimidate.runAway(creature, (EntityLivingBase)caster);
}
}
}
}
@SubscribeEvent
public static void onBreakSpeedEvent(BreakSpeed event){
if(event.getEntityPlayer().isPotionActive(WizardryPotions.frost)){
// Amplifier + 1 because it starts at 0
event.setNewSpeed(event.getOriginalSpeed() * (1 - Constants.FROST_FATIGUE_PER_LEVEL*(event.getEntityPlayer().getActivePotionEffect(WizardryPotions.frost).getAmplifier() + 1)));
public static void onLivingDeathEvent(LivingDeathEvent event){
if(event.getSource().getEntity() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getSource().getEntity();
for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){
if(stack != null && stack.getItem() instanceof ItemWand && stack.isItemDamaged() && WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) > 0){
int damage = stack.getItemDamage() - Constants.SIPHON_MANA_PER_LEVEL*WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) - player.worldObj.rand.nextInt(Constants.SIPHON_MANA_PER_LEVEL);
if(damage < 0) damage = 0;
stack.setItemDamage(damage);
break;
}
}
if(event.getEntityLiving() == player && event.getSource() instanceof IElementalDamage){
player.addStat(WizardryAchievements.self_destruct);
}
}
}
private static void playerInventoryTick(EntityPlayer player) {
@SubscribeEvent
public static void onLivingDropsEvent(LivingDropsEvent event){
// TODO: Really, this should be in a loot table (mob_additions), however I can't seem to find a way of
// automatically adding it to all subclasses of IMob.
// Evil wizards drop spell books themselves
if(event.getEntityLiving() instanceof IMob && !(event.getEntityLiving() instanceof EntityEvilWizard)
// TODO: Backport when you backport the new summoned creature system.
&& !(event.getEntityLiving() instanceof ISummonedCreature) && event.getSource().getEntity() instanceof EntityPlayer
&& Wizardry.settings.spellBookDropChance > 0){
// This does exactly what the entity drop method does, but with a different random number so that the
// spell book doesn't always drop with other rare drops.
int rareDropNumber = event.getEntity().worldObj.rand.nextInt(200) - event.getLootingLevel();
if(rareDropNumber < Wizardry.settings.spellBookDropChance){
// Drops a spell book
int id = WizardryUtilities.getStandardWeightedRandomSpellId(event.getEntity().worldObj.rand);
event.getDrops().add(new EntityItem(event.getEntityLiving().worldObj, event.getEntityLiving().posX, event.getEntityLiving().posY, event.getEntityLiving().posZ,
new ItemStack(WizardryItems.spell_book, 1, id)));
}
}
}
@SubscribeEvent
public static void onItemPickupEvent(EntityItemPickupEvent event){
if(event.getItem().getEntityItem().getItem() == WizardryItems.magic_crystal){
event.getEntityPlayer().addStat(WizardryAchievements.crystal, 1);
}
}
// Private helper methods
// ================================================================================================================
/**
* Detects inconsistencies between player.getActiveItemStack and the actual itemstack and forces them to be equal.
* Fixes issue #25.
* @param player
*/
private static void hackilyFixContinuousSpellCasting(EntityPlayer player){
if(player.isHandActive() && player.getHeldItem(player.getActiveHand()) != null
&& player.getHeldItem(player.getActiveHand()).getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(player.getHeldItem(player.getActiveHand())).isContinuous){
if(player.getActiveItemStack() != player.getHeldItem(player.getActiveHand())){
player.setHeldItem(player.getActiveHand(), player.getActiveItemStack());
}
}
}
private static void playerInventoryTick(EntityPlayer player){
// Charges wand using mana flask. It is here rather than in the crafting handler so the result displays
// the proper damage before it is actually crafted.
@@ -650,8 +464,7 @@ public final class WizardryEventHandler {
private static void craftingTableTick(EntityPlayer player) {
// Charges wand using mana flask. It is here rather than in the crafting handler so the result displays
// the proper damage before it is actually crafted.
// Charges wand using mana flask
boolean flag = false;
ItemStack wand = null;
@@ -696,243 +509,4 @@ public final class WizardryEventHandler {
}
}
@SubscribeEvent
public static void onLivingDeathEvent(LivingDeathEvent event){
if(event.getSource().getEntity() instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)event.getSource().getEntity();
for(ItemStack stack : WizardryUtilities.getPrioritisedHotbarAndOffhand(player)){
if(stack != null && stack.getItem() instanceof ItemWand && stack.isItemDamaged() && WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) > 0){
int damage = stack.getItemDamage() - Constants.SIPHON_MANA_PER_LEVEL*WandHelper.getUpgradeLevel(stack, WizardryItems.siphon_upgrade) - player.worldObj.rand.nextInt(Constants.SIPHON_MANA_PER_LEVEL);
if(damage < 0) damage = 0;
stack.setItemDamage(damage);
break;
}
}
if(event.getEntityLiving() == player && event.getSource() instanceof IElementalDamage){
player.addStat(WizardryAchievements.self_destruct);
}
}
}
@SubscribeEvent
public static void onPlayerLoggedInEvent(PlayerLoggedInEvent event){
// When a player logs in, they are sent the glyph data and the server's settings.
if(event.player instanceof EntityPlayerMP){
SpellGlyphData.get(event.player.worldObj).sync((EntityPlayerMP)event.player);
Wizardry.settings.sync((EntityPlayerMP)event.player);
}
}
@SubscribeEvent
public static void onEntityJoinWorld(EntityJoinWorldEvent event){
if(!event.getEntity().worldObj.isRemote && event.getEntity() instanceof EntityPlayerMP){
// Synchronises wizard data after loading.
WizardData data = WizardData.get((EntityPlayer)event.getEntity());
if(data != null) data.sync();
}
// Rather long-winded (but necessary) way of getting an arrow just after it has been fired, checking if the bow
// that fired it has the imbuement enchantment, and applying extra damage accordingly.
if(!event.getEntity().worldObj.isRemote && event.getEntity() instanceof EntityArrow){
EntityArrow arrow = (EntityArrow)event.getEntity();
magicBow:
if(arrow.shootingEntity instanceof EntityLivingBase){
EntityLivingBase archer = (EntityLivingBase)arrow.shootingEntity;
ItemStack bow = archer.getHeldItemMainhand();
if(bow == null || !(bow.getItem() instanceof ItemBow)){
bow = archer.getHeldItemOffhand();
// Break used because return would skip the entire method, bypassing anything that might be added
// further down.
if(bow == null || !(bow.getItem() instanceof ItemBow)) break magicBow;
}
// Taken directly from ItemBow, so it works exactly the same as the power enchantment.
int level = EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.magic_bow, bow);
if(level > 0){
arrow.setDamage(arrow.getDamage() + (double)level * 0.5D + 0.5D);
}
if(EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.flaming_weapon, bow) > 0){
// Again, this is exactly what happens in ItemBow (flame is flame; level does nothing).
arrow.setFire(100);
}
level = EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.freezing_weapon, bow);
if(level > 0){
if(arrow.getEntityData() != null){
arrow.getEntityData().setInteger(FreezingWeapon.FREEZING_ARROW_NBT_KEY, level);
}
}
}
}
}
@SubscribeEvent
public static void onLivingDropsEvent(LivingDropsEvent event){
// TODO: Really, this should be in a loot table (mob_additions), however I can't seem to find a way of
// automatically adding it to all subclasses of IMob.
// Evil wizards drop spell books themselves
if(event.getEntityLiving() instanceof IMob && !(event.getEntityLiving() instanceof EntityEvilWizard)
// TODO: Backport when you backport the new summoned creature system.
&& !(event.getEntityLiving() instanceof ISummonedCreature) && event.getSource().getEntity() instanceof EntityPlayer
&& Wizardry.settings.spellBookDropChance > 0){
// This does exactly what the entity drop method does, but with a different random number so that the
// spell book doesn't always drop with other rare drops.
int rareDropNumber = event.getEntity().worldObj.rand.nextInt(200) - event.getLootingLevel();
if(rareDropNumber < Wizardry.settings.spellBookDropChance){
// Drops a spell book
int id = WizardryUtilities.getStandardWeightedRandomSpellId(event.getEntity().worldObj.rand);
event.getDrops().add(new EntityItem(event.getEntityLiving().worldObj, event.getEntityLiving().posX, event.getEntityLiving().posY, event.getEntityLiving().posZ,
new ItemStack(WizardryItems.spell_book, 1, id)));
}
}
for(EntityItem item : event.getDrops()){
// Destroys conjured items if their caster dies.
if(item.getEntityItem().getItem() instanceof IConjuredItem){
item.setDead();
}
// Instantly disenchants an imbued weapon if it is dropped when the player dies.
if(item.getEntityItem().isItemEnchanted()){
// No need to check what enchantments the item has, since remove() does nothing if the element does not exist.
Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(item.getEntityItem());
// Removes the magic weapon enchantments from the enchantment map
// An excellent demonstration of the usefulness of both interfaces and Java 8.
enchantments.entrySet().removeIf(entry -> entry.getKey() instanceof Imbuement);
// Applies the new enchantment map to the item
EnchantmentHelper.setEnchantments(enchantments, item.getEntityItem());
}
}
}
@SubscribeEvent
public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){
// Mind trick
// If the target is null already, no need to set it to null, or infinite loops will occur.
if((event.getEntityLiving().isPotionActive(WizardryPotions.mind_trick) || event.getEntityLiving().isPotionActive(WizardryPotions.fear)) && event.getEntityLiving() instanceof EntityLiving && event.getTarget() != null){
((EntityLiving)event.getEntityLiving()).setAttackTarget(null);
}
// Mind control
mindcontrol:
if(event.getEntityLiving().isPotionActive(WizardryPotions.mind_control) && event.getEntityLiving() instanceof EntityLiving){
NBTTagCompound entityNBT = event.getEntityLiving().getEntityData();
if(entityNBT != null && entityNBT.hasKey(MindControl.NBT_KEY + "Most")){
Entity caster = WizardryUtilities.getEntityByUUID(event.getEntity().worldObj, entityNBT.getUniqueId(MindControl.NBT_KEY));
// If the target that the event tried to set is already a valid mind control target, nothing happens.
if(event.getTarget() != null && WizardryUtilities.isValidTarget(caster, event.getTarget())) break mindcontrol;
if(caster instanceof EntityLivingBase){
if(MindControl.findMindControlTarget((EntityLiving)event.getEntityLiving(), (EntityLivingBase)caster, event.getEntity().worldObj)){
// If it worked, skip setting the target to null.
break mindcontrol;
}
}
}
// If the caster couldn't be found or no valid target was found, this just acts like mind trick.
// If the target is null already, no need to set it to null, or infinite loops will occur.
if(event.getTarget() != null) ((EntityLiving)event.getEntityLiving()).setAttackTarget(null);
}
}
@SubscribeEvent
public static void onItemPickupEvent(EntityItemPickupEvent event){
if(event.getItem().getEntityItem().getItem() == WizardryItems.magic_crystal){
event.getEntityPlayer().addStat(WizardryAchievements.crystal, 1);
}
}
@SubscribeEvent
public static void onItemTossEvent(ItemTossEvent event){
// Prevents conjured items being thrown by dragging and dropping outside the inventory.
if(event.getEntityItem().getEntityItem().getItem() instanceof IConjuredItem){
event.setCanceled(true);
event.getPlayer().inventory.addItemStackToInventory(event.getEntityItem().getEntityItem());
}
// Instantly disenchants an imbued weapon if it is thrown on the ground.
if(event.getEntityItem().getEntityItem().isItemEnchanted()){
// No need to check what enchantments the item has, since remove() does nothing if the element does not exist.
Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(event.getEntityItem().getEntityItem());
// Removes the magic weapon enchantments from the enchantment map
enchantments.entrySet().removeIf(entry -> entry.getKey() instanceof Imbuement);
// Applies the new enchantment map to the item
EnchantmentHelper.setEnchantments(enchantments, event.getEntityItem().getEntityItem());
}
}
@SubscribeEvent
public static void onRightClickBlockEvent(PlayerInteractEvent.RightClickBlock event){
if(event.getEntityPlayer().isSneaking()){
// The event now has an ItemStack, which greatly simplifies hand-related stuff.
ItemStack wand = event.getItemStack();
if(wand != null && wand.getItem() instanceof ItemWand && WandHelper.getCurrentSpell(wand) instanceof Clairvoyance){
WizardData properties = WizardData.get(event.getEntityPlayer());
if(properties != null){
// THIS is why BlockPos is a thing - in 1.7.10 this requires a clumsy switch statement.
BlockPos pos = event.getPos().offset(event.getFace());
properties.setClairvoyancePoint(pos, event.getWorld().provider.getDimension());
if(!event.getWorld().isRemote){
event.getEntityPlayer().addChatMessage(new TextComponentTranslation("spell.clairvoyance.confirm", Spells.clairvoyance.getNameForTranslationFormatted()));
}
event.setCanceled(true);
}
}
}
}
@SubscribeEvent
public static void onBonemealEvent(BonemealEvent event){
// Grows crystal flowers when bonemeal is used on grass
if(event.getBlock().getBlock() == Blocks.GRASS){
BlockPos pos = event.getPos().add(event.getWorld().rand.nextInt(8) - event.getWorld().rand.nextInt(8),
event.getWorld().rand.nextInt(4) - event.getWorld().rand.nextInt(4),
event.getWorld().rand.nextInt(8) - event.getWorld().rand.nextInt(8));
if (event.getWorld().isAirBlock(new BlockPos(pos)) && (!event.getWorld().provider.getHasNoSky() || pos.getY() < 127) && WizardryBlocks.crystal_flower.canPlaceBlockAt(event.getWorld(), pos))
{
event.getWorld().setBlockState(pos, WizardryBlocks.crystal_flower.getDefaultState(), 2);
}
}
}
}
@@ -3,18 +3,24 @@ package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.util.WizardryParticleType;
import net.minecraft.block.BlockBush;
import net.minecraft.block.SoundType;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.common.EnumPlantType;
import net.minecraftforge.event.entity.player.BonemealEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
// Extending BlockBush allows me to remove nearly everything from this class.
@Mod.EventBusSubscriber
public class BlockCrystalFlower extends BlockBush {
private static final AxisAlignedBB AABB = new AxisAlignedBB(0.5F - 0.2f, 0.0F, 0.5F - 0.2f, 0.5F + 0.2f, 0.2f * 3.0F, 0.5F + 0.2f);
@@ -43,4 +49,20 @@ public class BlockCrystalFlower extends BlockBush {
public EnumPlantType getPlantType(IBlockAccess world, BlockPos pos) {
return EnumPlantType.Plains;
}
@SubscribeEvent
public static void onBonemealEvent(BonemealEvent event){
// Grows crystal flowers when bonemeal is used on grass
if(event.getBlock().getBlock() == Blocks.GRASS){
BlockPos pos = event.getPos().add(event.getWorld().rand.nextInt(8) - event.getWorld().rand.nextInt(8),
event.getWorld().rand.nextInt(4) - event.getWorld().rand.nextInt(4),
event.getWorld().rand.nextInt(8) - event.getWorld().rand.nextInt(8));
if (event.getWorld().isAirBlock(new BlockPos(pos)) && (!event.getWorld().provider.getHasNoSky() || pos.getY() < 127) && WizardryBlocks.crystal_flower.canPlaceBlockAt(event.getWorld(), pos))
{
event.getWorld().setBlockState(pos, WizardryBlocks.crystal_flower.getDefaultState(), 2);
}
}
}
}
@@ -3,6 +3,7 @@ package electroblob.wizardry.block;
import java.util.Random;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.tileentity.TileEntityTimer;
import electroblob.wizardry.util.WizardryParticleType;
import net.minecraft.block.Block;
@@ -17,11 +18,15 @@ import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.event.world.BlockEvent;
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;
// For future reference - extend BlockContainer whenever possible because it has methods for removing tile entities on
// block break.
@Mod.EventBusSubscriber
public class BlockSpectral extends BlockContainer {
public BlockSpectral(Material material) {
@@ -90,5 +95,13 @@ public class BlockSpectral extends BlockContainer {
return block == this ? false : super.shouldSideBeRendered(blockState, blockAccess, pos, side);
}
@SubscribeEvent
public static void onBlockPlaceEvent(BlockEvent.PlaceEvent event){
// Spectral blocks cannot be built on
if(event.getPlacedAgainst() == WizardryBlocks.spectral_block){
event.setCanceled(true);
}
}
}
@@ -97,6 +97,8 @@ import electroblob.wizardry.entity.projectile.EntitySmokeBomb;
import electroblob.wizardry.entity.projectile.EntitySpark;
import electroblob.wizardry.entity.projectile.EntitySparkBomb;
import electroblob.wizardry.entity.projectile.EntityThunderbolt;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.item.ItemScroll;
import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.item.ItemWand;
@@ -112,7 +114,6 @@ import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
import electroblob.wizardry.tileentity.TileEntityMagicLight;
import electroblob.wizardry.tileentity.TileEntityStatue;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryParticleType;
import net.minecraft.block.state.IBlockState;
@@ -128,6 +129,7 @@ import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.monster.EntityBlaze;
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.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
@@ -338,14 +340,23 @@ public class ClientProxy extends CommonProxy {
// Duration isn't needed because it only ever affects things server-side, and anything that is
// seen client-side gets synced elsewhere.
spell.cast(world, (EntityPlayer)caster, message.hand, 0, new SpellModifiers());
// Updates the spell discovery data client-side. Could be done by calling sync(), but that means sending
// another packet unnecessarily.
if(WizardData.get((EntityPlayer)caster) != null){
WizardData.get((EntityPlayer)caster).discoverSpell(spell);
spell.cast(world, (EntityPlayer)caster, message.hand, 0, message.modifiers);
Source source = Source.OTHER;
if(((EntityPlayer)caster).getHeldItem(message.hand) != null){
Item item = ((EntityPlayer)caster).getHeldItem(message.hand).getItem();
if(item instanceof ItemWand){
source = Source.WAND;
}else if(item instanceof ItemScroll){
source = Source.SCROLL;
}
}
// No need to check if the spell succeeded, because the packet is only ever sent when it succeeds.
// The handler for this event now deals with discovery.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post((EntityPlayer)caster, spell, message.modifiers, source));
}else{
Wizardry.logger.warn("Recieved a PacketCastSpell, but the caster ID was not the ID of a player");
}
@@ -385,7 +396,10 @@ public class ClientProxy extends CommonProxy {
if(caster instanceof EntityLiving){
if(target instanceof EntityLivingBase){
spell.cast(world, (EntityLiving)caster, message.hand, 0, (EntityLivingBase)target, message.modifiers);
// Again, no need to check if the spell succeeded, because the packet is only ever sent when it succeeds.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post((EntityLiving)caster, spell, message.modifiers, Source.NPC));
}
if(caster instanceof ISpellCaster){
@@ -4,6 +4,8 @@ import java.util.List;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.packet.PacketCastSpell;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.spell.Spell;
@@ -24,6 +26,7 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
public class CommandCastSpell extends CommandBase {
@@ -63,12 +66,14 @@ public class CommandCastSpell extends CommandBase {
throw new WrongUsageException("commands.wizardry:cast.usage", Wizardry.settings.castCommandName);
}else{
// ===== Parameter retrieval =====
int i=0;
EntityPlayerMP entityplayermp = null;
EntityPlayerMP caster = null;
try{
entityplayermp = getCommandSenderAsPlayer(sender);
caster = getCommandSenderAsPlayer(sender);
}catch(PlayerNotFoundException exception){
// Nothing here since the player specifying is done later, I just don't want it to throw an exception here.
}
@@ -85,10 +90,10 @@ public class CommandCastSpell extends CommandBase {
try{
// If the second argument is a player and is not the player that gave the command, the spell is cast
// as the given player rather than the command sender, and there is a different chat readout.
EntityPlayerMP entityplayermp1 = getPlayer(server, sender, arguments[i++]);
if(entityplayermp != entityplayermp1){
EntityPlayerMP entityplayermp = getPlayer(server, sender, arguments[i++]);
if(caster != entityplayermp){
castAsOtherPlayer = true;
entityplayermp = entityplayermp1;
caster = entityplayermp;
}
}catch(PlayerNotFoundException exception){
// If no player was found, rather than give an error it simply assumes the player was unspecified.
@@ -98,7 +103,7 @@ public class CommandCastSpell extends CommandBase {
// If, after this point, the player is still null, the sender must be a command block or the console and the
// player must not have been specified, meaning an exception should be thrown.
if(entityplayermp == null) throw new PlayerNotFoundException("You must specify which player you wish to perform this action on.");
if(caster == null) throw new PlayerNotFoundException("You must specify which player you wish to perform this action on.");
SpellModifiers modifiers = new SpellModifiers();
@@ -122,20 +127,30 @@ public class CommandCastSpell extends CommandBase {
}
}
// ===== Spell casting =====
// If anything stops the spell working at this point, nothing else happens.
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(caster, spell, modifiers, Source.COMMAND))){
displayFailMessage(sender, spell);
return;
}
WizardData data = WizardData.get((EntityPlayer)caster);
if(spell.isContinuous){
WizardData properties = WizardData.get((EntityPlayer)entityplayermp);
// Events for continuous spell casting via commands are dealt with in WizardData.
if(properties != null){
if(properties.isCasting()){
WizardData.get((EntityPlayer)entityplayermp).stopCastingContinuousSpell();
if(data != null){
if(data.isCasting()){
data.stopCastingContinuousSpell();
}else{
WizardData.get((EntityPlayer)entityplayermp).startCastingContinuousSpell(spell, modifiers);
data.startCastingContinuousSpell(spell, modifiers);
if(castAsOtherPlayer){
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:cast.success_remote_continuous", spell.getNameForTranslationFormatted(), entityplayermp.getName()));
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:cast.success_remote_continuous", spell.getNameForTranslationFormatted(), caster.getName()));
}else{
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:cast.success_continuous", spell.getNameForTranslationFormatted()));
}
@@ -146,27 +161,20 @@ public class CommandCastSpell extends CommandBase {
}else{
if(spell.cast(entityplayermp.worldObj, entityplayermp, EnumHand.MAIN_HAND, 0, modifiers)){
if(spell.cast(caster.worldObj, caster, EnumHand.MAIN_HAND, 0, modifiers)){
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(caster, spell, modifiers, Source.COMMAND));
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(entityplayermp.getEntityId(), null, spell.id(), modifiers);
WizardryPacketHandler.net.sendToDimension(msg, entityplayermp.worldObj.provider.getDimension());
IMessage msg = new PacketCastSpell.Message(caster.getEntityId(), null, spell.id(), modifiers);
WizardryPacketHandler.net.sendToDimension(msg, caster.worldObj.provider.getDimension());
}
if(WizardData.get((EntityPlayer)entityplayermp) != null){
// Added optimisation from 1.7.10: if the spell was already discovered, nothing happens.
if(WizardData.get((EntityPlayer)entityplayermp).discoverSpell(spell)){
// If the spell didn't send a packet itself, the extended player needs to be synced so the
// spell discovery updates on the client.
if(!spell.doesSpellRequirePacket()) WizardData.get((EntityPlayer)entityplayermp).sync();
}
}
if(castAsOtherPlayer){
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:cast.success_remote", spell.getNameForTranslationFormatted(), entityplayermp.getName()));
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:cast.success_remote", spell.getNameForTranslationFormatted(), caster.getName()));
}else{
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:cast.success", spell.getNameForTranslationFormatted()));
}
@@ -174,10 +182,15 @@ public class CommandCastSpell extends CommandBase {
}
}
ITextComponent message = new TextComponentTranslation("commands.wizardry:cast.fail", spell.getNameForTranslationFormatted());
message.getStyle().setColor(TextFormatting.RED);
sender.addChatMessage(message);
displayFailMessage(sender, spell);
}
}
/** Displays the "Unable to cast [spell]" message in the chat. */
private void displayFailMessage(ICommandSender sender, Spell spell){
ITextComponent message = new TextComponentTranslation("commands.wizardry:cast.fail", spell.getNameForTranslationFormatted());
message.getStyle().setColor(TextFormatting.RED);
sender.addChatMessage(message);
}
}
@@ -4,6 +4,7 @@ import java.util.List;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.event.DiscoverSpellEvent;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell;
import net.minecraft.command.CommandBase;
@@ -16,6 +17,7 @@ import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraftforge.common.MinecraftForge;
public class CommandDiscoverSpell extends CommandBase {
@@ -66,10 +68,10 @@ public class CommandDiscoverSpell extends CommandBase {
boolean clear = false;
boolean all = false;
EntityPlayerMP entityplayermp = null;
EntityPlayerMP player = null;
try{
entityplayermp = getCommandSenderAsPlayer(sender);
player = getCommandSenderAsPlayer(sender);
}catch(PlayerNotFoundException exception){
// Nothing here since the player specifying is done later, I just don't want it to throw an exception here.
}
@@ -94,32 +96,34 @@ public class CommandDiscoverSpell extends CommandBase {
if(i < arguments.length){
// If the second argument is a player and is not the player that gave the command, the spell is
// discovered as the given player rather than the command sender.
EntityPlayerMP entityplayermp1 = getPlayer(server, sender, arguments[i++]);
if(entityplayermp != entityplayermp1){
entityplayermp = entityplayermp1;
EntityPlayerMP entityplayermp = getPlayer(server, sender, arguments[i++]);
if(player != entityplayermp){
player = entityplayermp;
}
}
// If, after this point, the player is still null, the sender must be a command block or the console and the
// player must not have been specified, meaning an exception should be thrown.
if(entityplayermp == null) throw new PlayerNotFoundException("You must specify which player you wish to perform this action on.");
if(player == null) throw new PlayerNotFoundException("You must specify which player you wish to perform this action on.");
WizardData properties = WizardData.get(entityplayermp);
WizardData properties = WizardData.get(player);
if(properties != null){
if(clear){
properties.spellsDiscovered.clear();
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:discoverspell.clear", entityplayermp.getName()));
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:discoverspell.clear", player.getName()));
}else if(all){
properties.spellsDiscovered.addAll(Spell.getSpells(Spell.allSpells));
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:discoverspell.all", entityplayermp.getName()));
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:discoverspell.all", player.getName()));
}else{
if(properties.hasSpellBeenDiscovered(spell)){
properties.spellsDiscovered.remove(spell);
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:discoverspell.removespell", spell.getNameForTranslationFormatted(), entityplayermp.getName()));
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:discoverspell.removespell", spell.getNameForTranslationFormatted(), player.getName()));
}else{
properties.discoverSpell(spell);
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:discoverspell.addspell", spell.getNameForTranslationFormatted(), entityplayermp.getName()));
if(!MinecraftForge.EVENT_BUS.post(new DiscoverSpellEvent(player, spell, DiscoverSpellEvent.Source.COMMAND))){
properties.discoverSpell(spell);
sender.addChatMessage(new TextComponentTranslation("commands.wizardry:discoverspell.addspell", spell.getNameForTranslationFormatted(), player.getName()));
}
}
}
properties.sync();
@@ -1,11 +1,131 @@
package electroblob.wizardry.enchantment;
import java.util.Map;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryEnchantments;
import electroblob.wizardry.spell.FreezingWeapon;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.inventory.ContainerChest;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemEnchantedBook;
import net.minecraft.item.ItemStack;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
/** Interface for temporary enchantments that last for a certain duration ('imbuements'). This interface allows
* {@link EnchantmentMagicSword} and {@link EnchantmentTimed} to both be treated as instances of a
* single type, rather than having to deal with each of them separately,
* which would be inefficient and cumbersome (the former of those classes cannot extend the latter because they both
* need to extend different subclasses of {@link net.minecraft.enchantment.Enchantment}).
* @since Wizardry 1.2 */
@Mod.EventBusSubscriber
public interface Imbuement {
// This interface has no abstract methods, these handlers are just here for the sake of keeping things organised.
@SubscribeEvent
public static void onLivingDropsEvent(LivingDropsEvent event){
// Instantly disenchants an imbued weapon if it is dropped when the player dies.
for(EntityItem item : event.getDrops()){
removeImbuements(item.getEntityItem());
}
}
@SubscribeEvent
public static void onItemTossEvent(ItemTossEvent event){
// Instantly disenchants an imbued weapon if it is thrown on the ground.
removeImbuements(event.getEntityItem().getEntityItem());
}
/** Removes all imbuements from the given itemstack. */
static void removeImbuements(ItemStack stack){
if(stack.isItemEnchanted()){
// No need to check what enchantments the item has, since remove() does nothing if the element does not exist.
Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(stack);
// Removes the magic weapon enchantments from the enchantment map
enchantments.entrySet().removeIf(entry -> entry.getKey() instanceof Imbuement);
// Applies the new enchantment map to the item
EnchantmentHelper.setEnchantments(enchantments, stack);
}
}
@SubscribeEvent
public static void onPlayerOpenContainerEvent(PlayerContainerEvent event){
// Brute-force fix to stop enchanted books in dungeon chests from having imbuements on them.
if(event.getContainer() instanceof ContainerChest){
// Still not sure if it's better to set stacks in slots or modify the itemstack list directly, but I would
// imagine it's the former.
for(Slot slot : event.getContainer().inventorySlots){
if(slot.getStack() != null && slot.getStack().getItem() instanceof ItemEnchantedBook){
// We don't care about the level of the enchantments
Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(slot.getStack());
// Removes all imbuements
if(enchantments.keySet().removeIf(e -> e instanceof Imbuement)){
// If any imbuements were removed, replaces the enchantments on the book with the new ones, or
// deletes the book entirely if there are none left.
if(enchantments.isEmpty()){
slot.putStack(null); // NOTE: Will need changing in 1.11
Wizardry.logger.info("Deleted enchanted book with illegal enchantments");
}else{
EnchantmentHelper.setEnchantments(enchantments, slot.getStack());
Wizardry.logger.info("Removed illegal enchantments from enchanted book");
}
}
}
}
}
}
@SubscribeEvent
public static void onEntityJoinWorld(EntityJoinWorldEvent event){
// Rather long-winded (but necessary) way of getting an arrow just after it has been fired, checking if the bow
// that fired it has the imbuement enchantment, and applying extra damage accordingly.
if(!event.getEntity().worldObj.isRemote && event.getEntity() instanceof EntityArrow){
EntityArrow arrow = (EntityArrow)event.getEntity();
if(arrow.shootingEntity instanceof EntityLivingBase){
EntityLivingBase archer = (EntityLivingBase)arrow.shootingEntity;
ItemStack bow = archer.getHeldItemMainhand();
if(bow == null || !(bow.getItem() instanceof ItemBow)){
bow = archer.getHeldItemOffhand();
if(bow == null || !(bow.getItem() instanceof ItemBow)) return;
}
// Taken directly from ItemBow, so it works exactly the same as the power enchantment.
int level = EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.magic_bow, bow);
if(level > 0){
arrow.setDamage(arrow.getDamage() + (double)level * 0.5D + 0.5D);
}
if(EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.flaming_weapon, bow) > 0){
// Again, this is exactly what happens in ItemBow (flame is flame; level does nothing).
arrow.setFire(100);
}
level = EnchantmentHelper.getEnchantmentLevel(WizardryEnchantments.freezing_weapon, bow);
if(level > 0){
if(arrow.getEntityData() != null){
arrow.getEntityData().setInteger(FreezingWeapon.FREEZING_ARROW_NBT_KEY, level);
}
}
}
}
}
}
@@ -12,7 +12,11 @@ import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class EntityBubble extends EntityMagicConstruct {
public boolean isDarkOrb;
@@ -119,5 +123,15 @@ public class EntityBubble extends EntityMagicConstruct {
super.readSpawnData(data);
this.isDarkOrb = data.readBoolean();
}
@SubscribeEvent
public static void onLivingAttackEvent(LivingAttackEvent event){
// Bursts bubble when the creature inside takes damage
if(event.getEntityLiving().getRidingEntity() instanceof EntityBubble &&
!((EntityBubble)event.getEntityLiving().getRidingEntity()).isDarkOrb){
event.getEntityLiving().getRidingEntity().playSound(SoundEvents.ENTITY_ITEM_PICKUP, 1.5f, 1.0f);
event.getEntityLiving().getRidingEntity().setDead();
}
}
}
@@ -3,16 +3,18 @@ package electroblob.wizardry.entity.living;
import java.util.ArrayList;
import java.util.List;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.packet.PacketNPCCastSpell;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.ai.EntityAIBase;
import net.minecraft.util.EnumHand;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.network.NetworkRegistry.TargetPoint;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
@@ -20,192 +22,217 @@ import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
* and the attack cooldown. Also provides an automatic implementation of continuous spell casting using the methods
* specified in {@code ISpellCaster}; all the entity class needs to do is implement those methods. */
public class EntityAIAttackSpell extends EntityAIBase {
/** The entity the AI instance has been applied to. */
private final EntityLiving attacker;
/** The entity the AI instance has been applied to, but as an ISpellCaster. */
private final ISpellCaster caster;
/** The tagret to be attacked. */
private EntityLivingBase target;
/** Decremented each tick while greater than 0. When a spell is cast, this is set to that spell's cooldown plus
* the base cooldown. */
private int cooldown;
/** The number of ticks between the entity finding a new target and when it first starts attacking, and also the
* amount that is added to the spell's cooldown between casting spells. */
private final int baseCooldown;
/** Decremented each tick while greater than 0. When a continuous spell is first cast, this is set to
* the value of {@link EntityAIAttackSpell#continuousSpellDuration}. */
// I think that in this case this is only necessary on the server side. If any inconsistent behaviour
// occurs, look into syncing this as well.
private int continuousSpellTimer;
/** The number of ticks that continuous spells will be cast for before cooling down. */
private final int continuousSpellDuration;
/** The speed that the entity should move when attacking. Only used when passed into the navigator. */
private final double speed;
private int seeTime;
private final float maxAttackDistance;
/**
* Creates a new spell attack AI with the given parameters.
* @param attacker The entity that that uses this AI.
* @param speed The speed that the entity should move when attacking. Only used when passed into the navigator.
* @param maxDistance The maximum distance the entity should be from its target.
* @param baseCooldown The number of ticks between the entity finding a new target and when it first starts attacking,
* and also the amount that is added to the cooldown of the spell that has just been cast.
* @param continuousSpellDuration The number of ticks that continuous spells will be cast for before cooling down.
*/
public EntityAIAttackSpell(ISpellCaster attacker, double speed, float maxDistance, int baseCooldown, int continuousSpellDuration){
this.cooldown = -1;
if(!(attacker instanceof EntityLiving)){
throw new IllegalArgumentException("Tried to create an EntityAICastSpell for an entity that isn't an EntityLiving");
}else{
this.caster = attacker;
this.attacker = (EntityLiving)attacker;
this.baseCooldown = baseCooldown;
this.continuousSpellDuration = continuousSpellDuration;
this.speed = speed;
this.maxAttackDistance = maxDistance * maxDistance;
this.setMutexBits(3);
}
}
/** The entity the AI instance has been applied to. */
private final EntityLiving attacker;
/** The entity the AI instance has been applied to, but as an ISpellCaster. */
private final ISpellCaster caster;
/** The tagret to be attacked. */
private EntityLivingBase target;
/** Decremented each tick while greater than 0. When a spell is cast, this is set to that spell's cooldown plus
* the base cooldown. */
private int cooldown;
/** The number of ticks between the entity finding a new target and when it first starts attacking, and also the
* amount that is added to the spell's cooldown between casting spells. */
private final int baseCooldown;
/** Decremented each tick while greater than 0. When a continuous spell is first cast, this is set to
* the value of {@link EntityAIAttackSpell#continuousSpellDuration}. */
// I think that in this case this is only necessary on the server side. If any inconsistent behaviour
// occurs, look into syncing this as well.
private int continuousSpellTimer;
/** The number of ticks that continuous spells will be cast for before cooling down. */
private final int continuousSpellDuration;
/** The speed that the entity should move when attacking. Only used when passed into the navigator. */
private final double speed;
private int seeTime;
private final float maxAttackDistance;
@Override
public boolean shouldExecute(){
EntityLivingBase entitylivingbase = this.attacker.getAttackTarget();
/**
* Creates a new spell attack AI with the given parameters.
* @param attacker The entity that that uses this AI.
* @param speed The speed that the entity should move when attacking. Only used when passed into the navigator.
* @param maxDistance The maximum distance the entity should be from its target.
* @param baseCooldown The number of ticks between the entity finding a new target and when it first starts attacking,
* and also the amount that is added to the cooldown of the spell that has just been cast.
* @param continuousSpellDuration The number of ticks that continuous spells will be cast for before cooling down.
*/
public EntityAIAttackSpell(ISpellCaster attacker, double speed, float maxDistance, int baseCooldown, int continuousSpellDuration){
if(entitylivingbase == null){
return false;
}else{
this.target = entitylivingbase;
return true;
}
}
this.cooldown = -1;
if(!(attacker instanceof EntityLiving)){
throw new IllegalArgumentException("Tried to create an EntityAICastSpell for an entity that isn't an EntityLiving");
}else{
this.caster = attacker;
this.attacker = (EntityLiving)attacker;
this.baseCooldown = baseCooldown;
this.continuousSpellDuration = continuousSpellDuration;
this.speed = speed;
this.maxAttackDistance = maxDistance * maxDistance;
this.setMutexBits(3);
}
}
@Override
public boolean continueExecuting(){
return this.shouldExecute() || !this.attacker.getNavigator().noPath();
}
public boolean shouldExecute(){
@Override
public void resetTask(){
this.target = null;
this.seeTime = 0;
this.cooldown = -1;
this.setContinuousSpellAndNotify(Spells.none, new SpellModifiers());
this.continuousSpellTimer = 0;
}
private void setContinuousSpellAndNotify(Spell spell, SpellModifiers modifiers){
caster.setContinuousSpell(spell);
WizardryPacketHandler.net.sendToAllAround(new PacketNPCCastSpell.Message(attacker.getEntityId(),
target == null ? -1 : target.getEntityId(), EnumHand.MAIN_HAND, spell.id(), modifiers),
// Particles are usually only visible from 16 blocks away, so 128 is more than far enough.
new TargetPoint(attacker.dimension, attacker.posX, attacker.posY, attacker.posZ, 128));
}
EntityLivingBase entitylivingbase = this.attacker.getAttackTarget();
@Override
public void updateTask(){
// Only executed server side.
double distanceSq = this.attacker.getDistanceSq(this.target.posX, this.target.getEntityBoundingBox().minY, this.target.posZ);
boolean targetIsVisible = this.attacker.getEntitySenses().canSee(this.target);
if(entitylivingbase == null){
return false;
}else{
this.target = entitylivingbase;
return true;
}
}
if(targetIsVisible){
++this.seeTime;
}else{
this.seeTime = 0;
}
@Override
public boolean continueExecuting(){
return this.shouldExecute() || !this.attacker.getNavigator().noPath();
}
if(distanceSq <= (double)this.maxAttackDistance && this.seeTime >= 20){
this.attacker.getNavigator().clearPathEntity();
}else{
this.attacker.getNavigator().tryMoveToEntityLiving(this.target, this.speed);
}
@Override
public void resetTask(){
this.target = null;
this.seeTime = 0;
this.cooldown = -1;
this.setContinuousSpellAndNotify(Spells.none, new SpellModifiers());
this.continuousSpellTimer = 0;
}
this.attacker.getLookHelper().setLookPositionWithEntity(this.target, 30.0F, 30.0F);
private void setContinuousSpellAndNotify(Spell spell, SpellModifiers modifiers){
caster.setContinuousSpell(spell);
WizardryPacketHandler.net.sendToAllAround(new PacketNPCCastSpell.Message(attacker.getEntityId(),
target == null ? -1 : target.getEntityId(), EnumHand.MAIN_HAND, spell.id(), modifiers),
// Particles are usually only visible from 16 blocks away, so 128 is more than far enough.
new TargetPoint(attacker.dimension, attacker.posX, attacker.posY, attacker.posZ, 128));
}
if(this.continuousSpellTimer > 0){
this.continuousSpellTimer--;
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible
|| !caster.getContinuousSpell().cast(attacker.worldObj, attacker, EnumHand.MAIN_HAND,
this.continuousSpellDuration - this.continuousSpellTimer, target, caster.getModifiers())
|| this.continuousSpellTimer == 0){
// If the spell no longer succeeds, the target goes out of range or sight, or the time has elapsed,
// reset the continuous spell timer and start the cooldown.
this.continuousSpellTimer = 0;
setContinuousSpellAndNotify(Spells.none, new SpellModifiers());
this.cooldown = this.baseCooldown;
return;
}
}else if(--this.cooldown == 0){
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible){
return;
}
if(!attacker.isPotionActive(WizardryPotions.arcane_jammer)){
double dx = target.posX - attacker.posX;
double dz = target.posZ - attacker.posZ;
@Override
public void updateTask(){
List<Spell> spells = new ArrayList<Spell>(caster.getSpells());
if(spells.size() > 0){
// Only executed server side.
if(!attacker.worldObj.isRemote){
double distanceSq = this.attacker.getDistanceSq(this.target.posX, this.target.getEntityBoundingBox().minY, this.target.posZ);
boolean targetIsVisible = this.attacker.getEntitySenses().canSee(this.target);
// New way of choosing a spell; keeps trying until one works or all have been tried
if(targetIsVisible){
++this.seeTime;
}else{
this.seeTime = 0;
}
Spell spell;
if(distanceSq <= (double)this.maxAttackDistance && this.seeTime >= 20){
this.attacker.getNavigator().clearPathEntity();
}else{
this.attacker.getNavigator().tryMoveToEntityLiving(this.target, this.speed);
}
while(!spells.isEmpty()){
this.attacker.getLookHelper().setLookPositionWithEntity(this.target, 30.0F, 30.0F);
spell = spells.get(attacker.worldObj.rand.nextInt(spells.size()));
SpellModifiers modifiers = caster.getModifiers();
if(this.continuousSpellTimer > 0){
if(spell != null && spell.cast(attacker.worldObj, attacker, EnumHand.MAIN_HAND, 0, target, modifiers)){
this.continuousSpellTimer--;
// If the target goes out of range or out of sight...
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible
// ...or the spell is cancelled via events...
|| MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Tick(attacker, caster.getContinuousSpell(),
caster.getModifiers(), Source.NPC, this.continuousSpellDuration - this.continuousSpellTimer))
// ...or the spell no longer succeeds...
|| !caster.getContinuousSpell().cast(attacker.worldObj, attacker, EnumHand.MAIN_HAND,
this.continuousSpellDuration - this.continuousSpellTimer, target, caster.getModifiers())
// ...or the time has elapsed...
|| this.continuousSpellTimer == 0){
// ...reset the continuous spell timer and start the cooldown.
this.continuousSpellTimer = 0;
setContinuousSpellAndNotify(Spells.none, new SpellModifiers());
this.cooldown = this.baseCooldown;
return;
}else if(this.continuousSpellDuration - this.continuousSpellTimer == 1){
// On the first tick, if the spell did succeed, fire SpellCastEvent.Post.
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(attacker, caster.getContinuousSpell(),
caster.getModifiers(), Source.NPC));
}
if(spell.isContinuous){
// -1 because the spell has been cast once already!
this.continuousSpellTimer = this.continuousSpellDuration - 1;
setContinuousSpellAndNotify(spell, modifiers);
}else{
// For now, the cooldown is just added to the constant base cooldown. I think this
// is a reasonable way of doing things; it's certainly better than before.
this.cooldown = this.baseCooldown + spell.cooldown;
if(spell.doesSpellRequirePacket()){
// Sends a packet to all players in dimension to tell them to spawn particles.
IMessage msg = new PacketNPCCastSpell.Message(attacker.getEntityId(),
target.getEntityId(), EnumHand.MAIN_HAND, spell.id(), modifiers);
WizardryPacketHandler.net.sendToDimension(msg, attacker.worldObj.provider.getDimension());
}
}
}else if(--this.cooldown == 0){
attacker.rotationYaw = (float)(Math.atan2(dz, dx) * 180.0D / Math.PI) - 90.0F;
if(distanceSq > (double)this.maxAttackDistance || !targetIsVisible){
return;
}
return;
double dx = target.posX - attacker.posX;
double dz = target.posZ - attacker.posZ;
}else{
spells.remove(spell);
}
}
}
}
}
}else if(this.cooldown < 0){
// This should only be reached when the entity first starts attacking. Stops it attacking instantly.
this.cooldown = this.baseCooldown;
}
}
List<Spell> spells = new ArrayList<Spell>(caster.getSpells());
if(spells.size() > 0){
if(!attacker.worldObj.isRemote){
// New way of choosing a spell; keeps trying until one works or all have been tried
Spell spell;
while(!spells.isEmpty()){
spell = spells.get(attacker.worldObj.rand.nextInt(spells.size()));
SpellModifiers modifiers = caster.getModifiers();
if(spell != null && attemptCastSpell(spell, modifiers)){
// The spell worked, so we're done!
attacker.rotationYaw = (float)(Math.atan2(dz, dx) * 180.0D / Math.PI) - 90.0F;
return;
}else{
spells.remove(spell);
}
}
}
}
}else if(this.cooldown < 0){
// This should only be reached when the entity first starts attacking. Stops it attacking instantly.
this.cooldown = this.baseCooldown;
}
}
/** Attempts to cast the given spell (including event firing) and returns true if it succeeded. */
private boolean attemptCastSpell(Spell spell, SpellModifiers modifiers){
// If anything stops the spell working at this point, nothing else happens.
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(attacker, spell, modifiers, Source.NPC))){
return false;
}
if(spell.cast(attacker.worldObj, attacker, EnumHand.MAIN_HAND, 0, target, modifiers)){
if(spell.isContinuous){
// -1 because the spell has been cast once already!
this.continuousSpellTimer = this.continuousSpellDuration - 1;
setContinuousSpellAndNotify(spell, modifiers);
}else{
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(attacker, spell, modifiers, Source.NPC));
// For now, the cooldown is just added to the constant base cooldown. I think this
// is a reasonable way of doing things; it's certainly better than before.
this.cooldown = this.baseCooldown + spell.cooldown;
if(spell.doesSpellRequirePacket()){
// Sends a packet to all players in dimension to tell them to spawn particles.
IMessage msg = new PacketNPCCastSpell.Message(attacker.getEntityId(),
target.getEntityId(), EnumHand.MAIN_HAND, spell.id(), modifiers);
WizardryPacketHandler.net.sendToDimension(msg, attacker.worldObj.provider.getDimension());
}
}
return true;
}
return false;
}
}
@@ -66,7 +66,7 @@ public class EntityEvilWizard extends EntityMob implements ISpellCaster, IEntity
protected Predicate<Entity> targetSelector;
/** Data parameter for the cooldown time for wizards healing themselves. */
private static final DataParameter<Integer> HEAL_COOLDOWN = EntityDataManager.createKey(EntityWizard.class, DataSerializers.VARINT);
private static final DataParameter<Integer> HEAL_COOLDOWN = EntityDataManager.createKey(EntityEvilWizard.class, DataSerializers.VARINT);
/** Data parameter for the wizard's element. */
private static final DataParameter<Integer> ELEMENT = EntityDataManager.createKey(EntityEvilWizard.class, DataSerializers.VARINT);
/** The resource location for the evil wizard's loot table. */
@@ -278,6 +278,7 @@ public class EntityIceWraith extends EntityBlazeMinion {
if(this.attackStep > 1){
// Proof, if it were at all needed, of the elegance and versatility of the spell system.
Spells.ice_shard.cast(this.blaze.worldObj, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, new SpellModifiers());
// TODO: Decide if an event should be fired here. I'm guessing no.
}
}
@@ -162,6 +162,7 @@ public class EntityLightningWraith extends EntityBlazeMinion {
if(this.attackStep > 1){
// Proof, if it were at all needed, of the elegance and versatility of the spell system.
Spells.arc.cast(this.blaze.worldObj, this.blaze, EnumHand.MAIN_HAND, 0, entitylivingbase, new SpellModifiers());
// TODO: Decide if an event should be fired here. I'm guessing no.
}
}
@@ -69,13 +69,18 @@ import net.minecraft.village.MerchantRecipe;
import net.minecraft.village.MerchantRecipeList;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.World;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
import net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraftforge.oredict.OreDictionary;
@Mod.EventBusSubscriber
public class EntityWizard extends EntityVillager implements ISpellCaster, IEntityAdditionalSpawnData {
/*
@@ -779,6 +784,25 @@ public class EntityWizard extends EntityVillager implements ISpellCaster, IEntit
// Uses .equals() rather than == so this will work fine.
return this.towerBlocks.contains(pos);
}
@SubscribeEvent
public static void onBlockBreakEvent(BlockEvent.BreakEvent event){
// Makes wizards angry if a player breaks a block in their tower
if(!(event.getPlayer() instanceof FakePlayer)){
List<EntityWizard> wizards = WizardryUtilities.getEntitiesWithinRadius(64, event.getPos().getX(),
event.getPos().getY(), event.getPos().getZ(), event.getWorld(), EntityWizard.class);
if(!wizards.isEmpty()){
for(EntityWizard wizard : wizards){
if(wizard.isBlockPartOfTower(event.getPos())){
wizard.setRevengeTarget(event.getPlayer());
event.getPlayer().addStat(WizardryAchievements.anger_wizard);
}
}
}
}
}
// EntityVillager overrides (that don't add features)
@@ -13,6 +13,10 @@ import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.WizardryEventHandler;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.util.IElementalDamage;
import electroblob.wizardry.util.IndirectMinionDamage;
import electroblob.wizardry.util.MagicDamage.DamageType;
import electroblob.wizardry.util.MinionDamage;
import electroblob.wizardry.util.WizardryParticleType;
import electroblob.wizardry.util.WizardryUtilities;
import io.netty.buffer.ByteBuf;
@@ -23,10 +27,16 @@ import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EntityDamageSource;
import net.minecraft.util.EntityDamageSourceIndirect;
import net.minecraft.util.EnumHand;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
/** Interface for all summoned creatures. The code for summoned creatures has been overhauled in Wizardry 1.2, and this
/** Interface for all summoned creatures. The code for summoned creatures has been overhauled in Wizardry 2.1, and this
* interface allows summoned creatures to extend vanilla (or indeed modded) entity classes, so <code>EntitySummonedZombie</code>
* now extends <code>EntityZombie</code>, for example. This change has two major benefits:
* <p>
@@ -59,13 +69,14 @@ import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
* <p>
* Due to the limitations of interfaces, some methods that really ought to be protected are public. These are clearly
* marked as 'Internal, DO NOT CALL'. <b>Don't call them, only implement them.</b>
* @since Wizardry 1.2
* @since Wizardry 2.1
* @author Electroblob */
/*
* Quite honestly, this is not what default methods are really for. However, this is modding, and in modding some
* sacrifices have to be made when it comes to Java style - because adding on to a pre-existing program is not a
* good way of doing this sort of thing anyway, but we have no choice about that!
*/
@Mod.EventBusSubscriber
public interface ISummonedCreature extends IEntityAdditionalSpawnData {
// Remember that ALL fields are static and final in interfaces, even if they don't explicitly state that.
@@ -78,7 +89,7 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
/** Returns the lifetime of the summoned creature in ticks. Allows primarily for duration multiplier support, but
* also for example the skeleton legion spell which lasts for 60 seconds instead of the usual 30. Syncing and saving
* is done automatically. As of Wizardry 1.2, despawning is handled in ISummonedCreature; see
* is done automatically. As of Wizardry 2.1, despawning is handled in ISummonedCreature; see
* {@link ISummonedCreature#onDespawn()} for details. */
int getLifetime();
@@ -274,5 +285,49 @@ public interface ISummonedCreature extends IEntityAdditionalSpawnData {
return false;
}
// Damage system
@SubscribeEvent
public static void onLivingAttackEvent(LivingAttackEvent event){
// Rather than bother overriding entire attack methods in ISummonedCreature implementations, it's easier (and
// more robust) to use LivingAttackEvent to modify the damage source.
if(event.getSource().getEntity() instanceof ISummonedCreature){
EntityLivingBase summoner = ((ISummonedCreature)event.getSource().getEntity()).getCaster();
if(summoner != null){
event.setCanceled(true);
DamageSource newSource = event.getSource();
// Copies over the original DamageType if appropriate.
DamageType type = event.getSource() instanceof IElementalDamage ? ((IElementalDamage)event.getSource()).getType() : DamageType.MAGIC;
// Copies over the original isRetaliatory flag if appropriate.
boolean isRetaliatory = event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).isRetaliatory();
// All summoned creatures are classified as magic, so it makes sense to do it this way.
if(event.getSource() instanceof EntityDamageSourceIndirect){
newSource = new IndirectMinionDamage(event.getSource().damageType, event.getSource().getSourceOfDamage(), event.getSource().getEntity(), summoner, type, isRetaliatory);
}else if(event.getSource() instanceof EntityDamageSource){
// Name is copied over so it uses the appropriate vanilla death message
newSource = new MinionDamage(event.getSource().damageType, event.getSource().getEntity(), summoner, type, isRetaliatory);
}
// Copy over any relevant 'attributes' the original DamageSource might have had.
if(event.getSource().isExplosion()) newSource.setExplosion();
if(event.getSource().isFireDamage()) newSource.setFireDamage();
if(event.getSource().isProjectile()) newSource.setProjectile();
// For some reason Minecraft calculates knockback relative to DamageSource#getEntity. In vanilla this
// is unnoticeable, but it looks a bit weird with summoned creatures involved - so this fixes that.
if(WizardryUtilities.attackEntityWithoutKnockback(event.getEntity(), newSource, event.getAmount())){
WizardryUtilities.applyStandardKnockback(event.getSource().getEntity(), event.getEntityLiving());
((ISummonedCreature)event.getSource().getEntity()).onSuccessfulAttack(event.getEntityLiving());
}
}
}
}
}
@@ -7,10 +7,9 @@ import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.fml.common.eventhandler.Cancelable;
/**
* [NYI] DiscoverSpellEvent is fired when a player discovers a spell by any method.<br>
* DiscoverSpellEvent is fired when a player discovers a spell by any method.<br>
* <br>
* This event is {@link Cancelable}.<br>
* If this event is canceled, the spell is not discovered.<br>
* This event is {@link Cancelable}. If this event is canceled, the spell is not discovered.<br>
* <br>
* This event does not have a result. {@link HasResult}<br>
* <br>
@@ -21,15 +20,30 @@ import net.minecraftforge.fml.common.eventhandler.Cancelable;
*/
public class DiscoverSpellEvent extends PlayerEvent {
private final Spell spell;
private final Source source;
public DiscoverSpellEvent(EntityPlayer player, Spell spell, Source source) {
super(player);
this.spell = spell;
this.source = source;
}
/** Returns the spell that is being discovered. */
public Spell getSpell(){
return spell;
}
/** Returns the method used to discover the spell. */
public Source getSource(){
return source;
}
public enum Source {
/** Signifies that the spell was discovered by trying to cast it. */ CASTING,
/** Signifies that the spell was discovered using a scroll of identification. */ IDENTIFICATION_SCROLL,
/** Signifies that the spell was discovered using commands. */ COMMAND,
/** Signifies that the spell was discovered by some other means (. */ OTHER
/** Signifies that the spell was discovered by some other means. */ OTHER
}
}
}
@@ -1,16 +1,18 @@
package electroblob.wizardry.event;
import electroblob.wizardry.tileentity.ContainerArcaneWorkbench;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.fml.common.eventhandler.Cancelable;
import net.minecraftforge.fml.common.eventhandler.Event.HasResult;
/**
* [NYI] SpellBindEvent is fired when a player presses the apply button in the arcane workbench.<br>
* SpellBindEvent is fired when a player presses the apply button in the arcane workbench. <i>Note that this
* event is only fired on the server side.</i><br>
* <br>
* This event is {@link Cancelable}.<br>
* If this event is canceled, no further processing takes place: spells are not bound, upgrades are not applied and
* crystals are not consumed.<br>
* This event is {@link Cancelable}. If this event is canceled, no further processing takes place: spells are not bound,
* upgrades are not applied and crystals are not consumed.<br>
* <br>
* This event does not have a result. {@link HasResult}<br>
* <br>
@@ -19,10 +21,11 @@ import net.minecraftforge.fml.common.eventhandler.Cancelable;
* @author Electroblob
* @since Wizardry 2.1
*/
public class SpellBindEvent extends PlayerEvent {
@Cancelable
public class SpellBindEvent extends PlayerContainerEvent {
public SpellBindEvent(EntityPlayer player) {
super(player);
public SpellBindEvent(EntityPlayer player, ContainerArcaneWorkbench container) {
super(player, container);
}
}
}
@@ -1,12 +1,17 @@
package electroblob.wizardry.event;
import electroblob.wizardry.entity.living.ISpellCaster;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.entity.EntityLivingBase;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.fml.common.eventhandler.Cancelable;
/**
* [NYI] SpellCastEvent is the parent event class for all spell casting events.<br>
* SpellCastEvent is the parent event class for all spell casting events. Methods which subscribe to this event
* will receive all three child events (it is recommended that you use {@link SpellCastEvent.Pre},
* {@link SpellCastEvent.Post} or {@link SpellCastEvent.Tick}, depending on the application).<br>
* <br>
* This event is fired on the {@link MinecraftForge#EVENT_BUS}.
*
@@ -14,9 +19,121 @@ import net.minecraftforge.event.entity.living.LivingEvent;
* @since Wizardry 2.1
*/
public abstract class SpellCastEvent extends LivingEvent {
private final Spell spell;
private final SpellModifiers modifiers;
private final Source source;
public SpellCastEvent(EntityLivingBase entity, Spell spell) {
super(entity);
public SpellCastEvent(EntityLivingBase caster, Spell spell, SpellModifiers modifiers, Source source) {
super(caster);
this.spell = spell;
this.modifiers = modifiers;
this.source = source;
}
/** Returns the spell being cast. */
public Spell getSpell(){
return spell;
}
/** Returns the modifiers for the spell being cast. */
public SpellModifiers getModifiers(){
return modifiers;
}
/** Returns the source of the spell being cast. */
public Source getSource(){
return source;
}
public enum Source {
/** Signifies that the spell was cast using a wand. */ WAND,
/** Signifies that the spell was cast using a scroll. */ SCROLL,
/** Signifies that the spell was cast using commands. */ COMMAND,
/** Signifies that the spell was cast by an {@link ISpellCaster}. */ NPC,
/** Signifies that the spell was cast by some other means. */ OTHER
}
/**
* SpellCastEvent.Pre is fired just before a spell is cast. Use this event to change the spell modifiers and
* generally alter the behaviour of the spell, or stop it from being cast entirely. For example, wizardry uses this
* event to cancel spells cast by entities that have the arcane jammer effect. Note that for wands, this is called
* <i>before</i> mana, tier and cooldowns are checked. Also note that this event is only fired once for continuous
* spells, when they start casting.<br>
* <br>
* This event is {@link Cancelable}. If this event is canceled, the spell is not cast, mana is not consumed, and the
* right-click action that caused it (if any) returns a result of FAIL, meaning that the right-click is passed to
* the block/entity in front of the player, if any.<br>
* <br>
* This event does not have a result. {@link HasResult}<br>
* <br>
* This event is fired on the {@link MinecraftForge#EVENT_BUS}.
*
* @author Electroblob
* @since Wizardry 2.1
*/
@Cancelable
public static class Pre extends SpellCastEvent {
public Pre(EntityLivingBase caster, Spell spell, SpellModifiers modifiers, Source source) {
super(caster, spell, modifiers, source);
}
}
/**
* SpellCastEvent.Post is fired just after a spell is cast. Use this event for any processing which depends
* on whether the spell succeeds, and does not affect the spell itself. For example, wizardry uses this event
* to keep track of spellcasting stats. Note that although this event is fired from both sides, it is not fired from
* common code; rather, both sides fire it separately, so timing is not guaranteed. Also note that this event is
* only fired once for continuous spells, after the first casting tick.<br>
* <br>
* This event is not {@link Cancelable}.<br>
* <br>
* This event does not have a result. {@link HasResult}<br>
* <br>
* This event is fired on the {@link MinecraftForge#EVENT_BUS}.
*
* @author Electroblob
* @since Wizardry 2.1
*/
public static class Post extends SpellCastEvent {
public Post(EntityLivingBase caster, Spell spell, SpellModifiers modifiers, Source source) {
super(caster, spell, modifiers, source);
}
}
/**
* SpellCastEvent.Tick is fired each tick while a continuous spell is being cast.<br>
* <br>
* This event is {@link Cancelable}. If this event is canceled, the spell is not cast, mana is not consumed, and the
* spell casting is interrupted. Cancelling this event on the client side will stop particles being spawned, but
* will not interrupt spell casting.<br>
* <br>
* This event does not have a result. {@link HasResult}<br>
* <br>
* This event is fired on the {@link MinecraftForge#EVENT_BUS}.
*
* @author Electroblob
* @since Wizardry 2.1
*/
@Cancelable
public static class Tick extends SpellCastEvent {
private final int count;
public Tick(EntityLivingBase caster, Spell spell, SpellModifiers modifiers, Source source, int count) {
super(caster, spell, modifiers, source);
this.count = count;
}
/** Returns the number of ticks this (continuous) spell has already been cast for. */
public int getCount(){
return count;
}
}
}
@@ -1,11 +1,17 @@
package electroblob.wizardry.item;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
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;
/** 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. */
@Mod.EventBusSubscriber
public interface IConjuredItem {
/** The NBT tag key used to store the duration multiplier for conjured items. */
@@ -29,4 +35,23 @@ public interface IConjuredItem {
/** 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. */
public int getBaseDuration();
@SubscribeEvent
public static void onLivingDropsEvent(LivingDropsEvent event){
// Destroys conjured items if their caster dies.
for(EntityItem item : event.getDrops()){
if(item.getEntityItem().getItem() instanceof IConjuredItem){
item.setDead();
}
}
}
@SubscribeEvent
public static void onItemTossEvent(ItemTossEvent event){
// Prevents conjured items being thrown by dragging and dropping outside the inventory.
if(event.getEntityItem().getEntityItem().getItem() instanceof IConjuredItem){
event.setCanceled(true);
event.getPlayer().inventory.addItemStackToInventory(event.getEntityItem().getEntityItem());
}
}
}
@@ -3,6 +3,7 @@ package electroblob.wizardry.item;
import java.util.List;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.event.DiscoverSpellEvent;
import electroblob.wizardry.registry.WizardryAchievements;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.spell.Spell;
@@ -17,6 +18,7 @@ import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumHand;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@@ -54,15 +56,17 @@ public class ItemIdentificationScroll extends Item {
if((stack1.getItem() instanceof ItemSpellBook || stack1.getItem() instanceof ItemScroll)
&& !properties.hasSpellBeenDiscovered(spell)){
// Identification scrolls give the chat readout in creative mode, otherwise it looks like
// nothing happens!
properties.discoverSpell(spell);
player.addStat(WizardryAchievements.identify_spell);
player.playSound(SoundEvents.ENTITY_PLAYER_LEVELUP, 1.25f, 1);
if(!player.capabilities.isCreativeMode) stack.stackSize--;
if(!world.isRemote) player.addChatMessage(new TextComponentTranslation("spell.discover", spell.getNameForTranslationFormatted()));
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);
player.addStat(WizardryAchievements.identify_spell);
player.playSound(SoundEvents.ENTITY_PLAYER_LEVELUP, 1.25f, 1);
if(!player.capabilities.isCreativeMode) stack.stackSize--;
if(!world.isRemote) player.addChatMessage(new TextComponentTranslation("spell.discover", spell.getNameForTranslationFormatted()));
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
}
}
}
}
@@ -2,24 +2,23 @@ package electroblob.wizardry.item;
import java.util.List;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
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.WizardryPotions;
import electroblob.wizardry.registry.WizardryTabs;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.creativetab.CreativeTabs;
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;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumHand;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@@ -36,7 +35,6 @@ public class ItemScroll extends Item {
@Override
@SideOnly(Side.CLIENT)
public void getSubItems(Item item, CreativeTabs par2CreativeTabs, List<ItemStack> list){
// Isn't this sooooo much neater with the filter thing?
for(Spell spell : Spell.getSpells(Spell.nonContinuousSpells)){
list.add(new ItemStack(item, 1, spell.id()));
}
@@ -70,59 +68,42 @@ public class ItemScroll extends Item {
@Override
public ActionResult<ItemStack> onItemRightClick(ItemStack stack, World world, EntityPlayer player, EnumHand hand){
if(player.isPotionActive(WizardryPotions.arcane_jammer)) return new ActionResult<ItemStack>(EnumActionResult.FAIL, stack);;
Spell spell = Spell.get(stack.getItemDamage());
// If a spell is disabled in the config, it will not work.
if(!spell.isEnabled()){
if(!world.isRemote) player.addChatMessage(new TextComponentTranslation("spell.disabled", spell.getNameForTranslationFormatted()));
// 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);
}
if(!spell.isContinuous){
/*
if(spell.chargeup > 0 && !entityplayer.isUsingItem()){
// Spells with a chargeup time are now handled separately.
entityplayer.setItemInUse(stack, this.getMaxItemUseDuration(stack));
return stack;
}
*/
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(), new SpellModifiers());
IMessage msg = new PacketCastSpell.Message(player.getEntityId(), hand, spell.id(), modifiers);
WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension());
}
if(!player.capabilities.isCreativeMode && !WizardData.get(player).hasSpellBeenDiscovered(spell) && Wizardry.settings.discoveryMode){
player.playSound(SoundEvents.ENTITY_PLAYER_LEVELUP, 1.25f, 1);
if(!player.worldObj.isRemote) player.addChatMessage(new TextComponentTranslation("spell.discover", spell.getNameForTranslationFormatted()));
}
WizardData.get(player).discoverSpell(spell);
// Scrolls are consumed upon successful use in survival mode
if(!player.capabilities.isCreativeMode) stack.stackSize--;
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
}
// Client-inconsistent spell casting. The code inside the else if statement only runs client-side.
// This else if check was bugging me for AGES! I can't believe I didn't compare to ItemWand before.
}else if(!spell.doesSpellRequirePacket()){
// This is all that needs to happen, because everything above works fine on just the server side.
if(spell.cast(world, player, hand, 0, new SpellModifiers())){
// Added in version 1.1.3 to fix the client-side spell discovery not updating for spells with the
// packet optimisation.
if(WizardData.get(player) != null){
WizardData.get(player).discoverSpell(spell);
}
new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
// 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);
}
}
}
@@ -9,6 +9,8 @@ import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.Tier;
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.WizardryAchievements;
@@ -24,7 +26,6 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.EnumAction;
import net.minecraft.item.Item;
@@ -32,10 +33,10 @@ 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.RayTraceResult;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
@@ -79,12 +80,12 @@ public class ItemWand extends Item {
public net.minecraft.client.gui.FontRenderer getFontRenderer(ItemStack stack){
return Wizardry.proxy.getFontRenderer(stack);
}
@Override
@SideOnly(Side.CLIENT)
public void getSubItems(Item parItem, CreativeTabs parTab, List<ItemStack> parListSubItems){
parListSubItems.add(new ItemStack(this, 1));
}
@SideOnly(Side.CLIENT)
public void getSubItems(Item parItem, CreativeTabs parTab, List<ItemStack> parListSubItems){
parListSubItems.add(new ItemStack(this, 1));
}
// Max damage is modifiable with upgrades.
@Override
@@ -95,13 +96,6 @@ public class ItemWand extends Item {
@Override
public void onCreated(ItemStack stack, World par2World, EntityPlayer par3EntityPlayer){
/* Removed because of mana flasks, which would cause a new book to be given each time a mana flask
* is crafted with the wand. Handbook is now given on acquiring a crystal.
ExtendedPlayer properties = ExtendedPlayer.get(par3EntityPlayer);
if(properties != null && !properties.handbookGiven){
par3EntityPlayer.inventory.addItemStackToInventory(new ItemStack(Wizardry.wizardHandbook));
properties.handbookGiven = true;
}*/
par3EntityPlayer.addStat(WizardryAchievements.arcane_initiate);
}
@@ -124,19 +118,19 @@ public class ItemWand extends Item {
((EntityPlayer)entity).addStat(WizardryAchievements.elemental);
}
}
@Override
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...
if(oldStack != null || newStack != null){
// We only care about the situation where we specifically want the animation NOT to play.
if(oldStack.getItem() == newStack.getItem() && !slotChanged
&& oldStack.getItem() instanceof ItemWand && newStack.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(oldStack) == WandHelper.getCurrentSpell(newStack)) return false;
}
return super.shouldCauseReequipAnimation(oldStack, newStack, slotChanged);
}
@@ -174,33 +168,30 @@ public class ItemWand extends Item {
public String getItemStackDisplayName(ItemStack stack){
return (this.element == null ? "" : this.element.getFormattingCode()) + super.getItemStackDisplayName(stack);
}
// Continuous spells use the onUsingItemTick method instead of this one.
/* An important thing to note about this method: it is only called on the server and the client of the player
* holding the item. This means if you spawn particles here they will not show up on other players' screens.
* Instead, this must be done via packets. */
* holding the item (I call this client-inconsistency). This means if you spawn particles here they will not show up
* on other players' screens. Instead, this must be done via packets. */
@Override
public ActionResult<ItemStack> onItemRightClick(ItemStack stack, World world, EntityPlayer player, EnumHand hand){
// Alternate right-click function; overrides spell casting.
if(this.selectMinionTarget(player, world)) return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
if(player.isPotionActive(WizardryPotions.arcane_jammer)) return new ActionResult<ItemStack>(EnumActionResult.FAIL, stack);
Spell spell = WandHelper.getCurrentSpell(stack);
// If a spell is disabled in the config, it will not work.
if(!spell.isEnabled()){
if(!world.isRemote) player.addChatMessage(new TextComponentTranslation("spell.disabled", spell.getNameForTranslationFormatted()));
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 onItemUsingTick method will not fire.
// If the castSpell method then returns false nothing will happen (continuous spells have no EnumAction
// either so setting the item in use has no direct visible effect).
// Edit: Strictly speaking this is not true but the spells that do have an action (shield, shadow ward and levitation)
// will never return false.
// 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
@@ -211,14 +202,13 @@ public class ItemWand extends Item {
// Checks that the spell is not in cooldown or that the player is in creative mode
&& (WandHelper.getCurrentCooldown(stack) == 0 || player.capabilities.isCreativeMode)){
// = Spell modifiers =
SpellModifiers modifiers = this.calculateModifiers(stack, spell);
// 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()){
@@ -230,17 +220,6 @@ public class ItemWand extends Item {
}
player.setActiveHand(hand);
WizardData data = WizardData.get(player);
// = Discovery =
if(data != null && !player.capabilities.isCreativeMode && !data.hasSpellBeenDiscovered(spell) && Wizardry.settings.discoveryMode){
// We are only server side here, so we need to play the sound on the server side to everyone.
world.playSound(null, player.posX, player.posY, player.posZ, SoundEvents.ENTITY_PLAYER_LEVELUP,
SoundCategory.PLAYERS, 1.25f, 1);
player.addChatMessage(new TextComponentTranslation("spell.discover", spell.getNameForTranslationFormatted()));
}
WizardData.get(player).discoverSpell(spell);
// = Cooldown =
// Spells only have a cooldown in survival
@@ -264,53 +243,42 @@ public class ItemWand extends Item {
return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
}
// Client-inconsistent spell casting. The code inside the else if statement only runs client-side.
}else if(!spell.doesSpellRequirePacket()){
// This is all that needs to happen, because everything above works fine on just the server side.
if(spell.cast(world, player, hand, 0, new SpellModifiers())){
// Added in version 1.1.3 to fix the client-side spell discovery not updating for spells with the
// packet optimisation.
if(WizardData.get(player) != null){
WizardData.get(player).discoverSpell(spell);
}
new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
// 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);
}
}
}
return new ActionResult<ItemStack>(EnumActionResult.FAIL, stack);
}
// For continuous spells. The count argument actually decrements by 1 each tick.
@Override
public void onUsingTick(ItemStack stack, EntityLivingBase user, int count){
if(user instanceof EntityPlayer){
EntityPlayer player = (EntityPlayer)user;
Spell spell = WandHelper.getCurrentSpell(stack);
WizardData data = WizardData.get(player);
SpellModifiers modifiers = this.calculateModifiers(stack, spell);
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())){
// = Spell modifiers =
SpellModifiers modifiers = this.calculateModifiers(stack, spell);
if(spell.cast(player.worldObj, player, player.getActiveHand(), stack.getMaxItemUseDuration() - count, modifiers)){
// = Discovery =
if(data != null && !player.capabilities.isCreativeMode && !data.hasSpellBeenDiscovered(spell) && Wizardry.settings.discoveryMode){
player.playSound(SoundEvents.ENTITY_PLAYER_LEVELUP, 1.25f, 1);
if(!player.worldObj.isRemote) player.addChatMessage(new TextComponentTranslation("spell.discover", spell.getNameForTranslationFormatted()));
}
data.discoverSpell(spell);
if(spell.cast(player.worldObj, player, player.getActiveHand(), castingTick, modifiers)){
if(castingTick == 0) MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(player, spell, modifiers, Source.WAND));
// = Mana cost =
// Divides the mana cost over a second appropriately; since damage is an integer it cannot
// just be divided by 20.
@@ -320,57 +288,32 @@ public class ItemWand extends Item {
// 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 armourPieces = getMatchingArmourCount(player, spell);
switch(armourPieces){
case 0: stack.damageItem(spell.cost/5, player);
break;
case 1: if(tickNumber != 17) stack.damageItem(spell.cost/5, player);
break;
case 2: if(tickNumber != 9 && tickNumber != 17) stack.damageItem(spell.cost/5, player);
break;
case 3: if(tickNumber != 5 && tickNumber != 13 && tickNumber != 17) stack.damageItem(spell.cost/5, player);
break;
case 4: if(tickNumber == 1) stack.damageItem(spell.cost/5, player);
break;
}
}
}
}
}
}
/** Returns a SpellModifiers object with the appropriate modifiers applied for the given ItemStack and Spell. */
protected SpellModifiers calculateModifiers(ItemStack stack, Spell spell){
SpellModifiers modifiers = new SpellModifiers();
// Now we only need to add multipliers if they are not 1.
int level = WandHelper.getUpgradeLevel(stack, WizardryItems.range_upgrade);
if(level > 0) modifiers.set(WizardryItems.range_upgrade, 1.0f + level * Constants.RANGE_INCREASE_PER_LEVEL, true);
level = WandHelper.getUpgradeLevel(stack, WizardryItems.duration_upgrade);
if(level > 0) modifiers.set(WizardryItems.duration_upgrade, 1.0f + level * Constants.DURATION_INCREASE_PER_LEVEL, false);
level = WandHelper.getUpgradeLevel(stack, WizardryItems.blast_upgrade);
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.DAMAGE, 1.0f + (this.tier.level + 1) * Constants.DAMAGE_INCREASE_PER_TIER, true);
}
return modifiers;
}
@Override
public boolean itemInteractionForEntity(ItemStack stack, EntityPlayer player, EntityLivingBase entity, EnumHand hand){
@@ -385,36 +328,61 @@ public class ItemWand extends Item {
return false;
}
/** Returns a SpellModifiers object with the appropriate modifiers applied for the given ItemStack and Spell. */
protected SpellModifiers calculateModifiers(ItemStack stack, Spell spell){
SpellModifiers modifiers = new SpellModifiers();
// Now we only need to add multipliers if they are not 1.
int level = WandHelper.getUpgradeLevel(stack, WizardryItems.range_upgrade);
if(level > 0) modifiers.set(WizardryItems.range_upgrade, 1.0f + level * Constants.RANGE_INCREASE_PER_LEVEL, true);
level = WandHelper.getUpgradeLevel(stack, WizardryItems.duration_upgrade);
if(level > 0) modifiers.set(WizardryItems.duration_upgrade, 1.0f + level * Constants.DURATION_INCREASE_PER_LEVEL, false);
level = WandHelper.getUpgradeLevel(stack, WizardryItems.blast_upgrade);
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.DAMAGE, 1.0f + (this.tier.level + 1) * Constants.DAMAGE_INCREASE_PER_TIER, true);
}
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);
if(rayTrace != null && rayTrace.entityHit instanceof EntityLivingBase){
EntityLivingBase entity = (EntityLivingBase)rayTrace.entityHit;
// Sets the selected minion's target to the right-clicked entity
if(player.isSneaking() && WizardData.get(player) != null && WizardData.get(player).selectedMinion != null){
ISummonedCreature minion = WizardData.get(player).selectedMinion.get();
if(minion instanceof EntityLiving && minion != entity){
// There is now only the new AI! (which greatly improves things)
((EntityLiving)minion).setAttackTarget(entity);
@@ -424,7 +392,7 @@ public class ItemWand extends Item {
}
}
}
return false;
}
}
@@ -4,6 +4,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.apache.commons.lang3.ArrayUtils;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
@@ -74,7 +76,7 @@ public class RandomSpell extends LootFunction {
@Override
public ItemStack apply(ItemStack stack, Random random, LootContext context){
if(!(stack.getItem() instanceof ItemSpellBook) || !(stack.getItem() instanceof ItemScroll))
if(!(stack.getItem() instanceof ItemSpellBook) && !(stack.getItem() instanceof ItemScroll))
Wizardry.logger.warn("Applying the random_spell loot function to an item that isn't a spell book or scroll.");
Tier tier;
@@ -96,7 +98,12 @@ public class RandomSpell extends LootFunction {
// Elements aren't weighted
if(elements == null || elements.isEmpty()){
element = Element.values()[random.nextInt(Element.values().length)];
// Element can only be MAGIC if tier is BASIC
if(tier == Tier.BASIC){
element = Element.values()[random.nextInt(Element.values().length)];
}else{
element = ArrayUtils.removeElement(Element.values(), Element.MAGIC)[random.nextInt(Element.values().length)];
}
}else{
// In theory, swapping this line to the commented one should make absolutely no difference.
element = elements.get(random.nextInt(tiers.size()));
@@ -3,7 +3,6 @@ package electroblob.wizardry.packet;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.packet.PacketControlInput.Message;
import electroblob.wizardry.tileentity.ContainerArcaneWorkbench;
import electroblob.wizardry.tileentity.TileEntityArcaneWorkbench;
import electroblob.wizardry.util.WandHelper;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.player.EntityPlayerMP;
@@ -37,8 +36,7 @@ public class PacketControlInput implements IMessageHandler<Message, IMessage> {
case APPLY_BUTTON:
TileEntityArcaneWorkbench tileentity = ((ContainerArcaneWorkbench)player.openContainer).tileEntityArcaneWorkbench;
tileentity.onApplyButtonPressed(player);
((ContainerArcaneWorkbench)player.openContainer).onApplyButtonPressed(player);
break;
case NEXT_SPELL_KEY:
@@ -1,6 +1,10 @@
package electroblob.wizardry.potion;
import net.minecraft.potion.PotionEffect;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
/**
* Interface for potion effects that spawn custom particles instead of (or as well as) the vanilla 'swirly' particles.
@@ -8,6 +12,7 @@ import net.minecraft.world.World;
* @since Wizardry 1.2
*/
// TODO: Backport.
@Mod.EventBusSubscriber
public interface ICustomPotionParticles {
/**
@@ -20,4 +25,21 @@ public interface ICustomPotionParticles {
*/
void spawnCustomParticle(World world, double x, double y, double z);
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
if(event.getEntityLiving().worldObj.isRemote){
// Behold the power of interfaces!
for(PotionEffect effect : event.getEntityLiving().getActivePotionEffects()){
if(effect.getPotion() instanceof ICustomPotionParticles && effect.doesShowParticles()){
double x = event.getEntityLiving().posX + (event.getEntityLiving().worldObj.rand.nextDouble() - 0.5)*event.getEntityLiving().width;
double y = event.getEntityLiving().getEntityBoundingBox().minY + event.getEntityLiving().worldObj.rand.nextDouble()*event.getEntityLiving().height;
double z = event.getEntityLiving().posZ + (event.getEntityLiving().worldObj.rand.nextDouble() - 0.5)*event.getEntityLiving().width;
((ICustomPotionParticles)effect.getPotion()).spawnCustomParticle(event.getEntityLiving().worldObj, x, y, z);
}
}
}
}
}
@@ -1,8 +1,12 @@
package electroblob.wizardry.potion;
import java.util.List;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.entity.construct.EntityDecay;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.potion.Potion;
@@ -32,8 +36,25 @@ public class PotionDecay extends Potion {
}
@Override
public void performEffect(EntityLivingBase entitylivingbase, int strength) {
entitylivingbase.attackEntityFrom(DamageSource.wither, 1);
public void performEffect(EntityLivingBase target, int strength) {
target.attackEntityFrom(DamageSource.wither, 1);
if(target.onGround && target.ticksExisted % Constants.DECAY_SPREAD_INTERVAL == 0){
List<Entity> entities = target.worldObj.getEntitiesWithinAABBExcludingEntity(target, target.getEntityBoundingBox());
boolean flag = true;
for(Entity entity : entities){
if(entity instanceof EntityDecay) flag = false;
}
if(flag){
// The victim spreading the decay is the 'caster' here, so that it can actually wear off, otherwise it just gets infected with its own decay and the effect lasts forever.
target.worldObj.spawnEntityInWorld(new EntityDecay(target.worldObj, target.posX, target.posY, target.posZ, target));
}
}
}
@Override
@@ -2,6 +2,7 @@ package electroblob.wizardry.potion;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.WizardryParticleType;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase;
@@ -10,9 +11,13 @@ import net.minecraft.potion.Potion;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.player.PlayerEvent.BreakSpeed;
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;
@Mod.EventBusSubscriber
public class PotionFrost extends Potion implements ICustomPotionParticles {
private static final ResourceLocation ICON = new ResourceLocation(Wizardry.MODID, "textures/gui/frost_icon.png");
@@ -50,4 +55,12 @@ public class PotionFrost extends Potion implements ICustomPotionParticles {
WizardryUtilities.drawTexturedRect(x + 3, y + 3, 0, 0, 18, 18, 18, 18);
}
@SubscribeEvent
public static void onBreakSpeedEvent(BreakSpeed event){
if(event.getEntityPlayer().isPotionActive(WizardryPotions.frost)){
// Amplifier + 1 because it starts at 0
event.setNewSpeed(event.getOriginalSpeed() * (1 - Constants.FROST_FATIGUE_PER_LEVEL*(event.getEntityPlayer().getActivePotionEffect(WizardryPotions.frost).getAmplifier() + 1)));
}
}
}
@@ -19,7 +19,8 @@ import net.minecraftforge.fml.common.registry.RegistryBuilder;
// stuff during the registry events (or whenever), whilst still having a final field (which is important, not only
// because it makes the text go bold, but also because it stops anyone fiddling with your fields). "Why would I want to
// initialise things within the registry events?", I hear you ask - well, for one, custom registries don't like it if
// you haven't created the registry before you start calling constructors of classes extending IForgeRegistryEntry.Impl.
// you haven't created the registry before you start calling constructors of classes extending IForgeRegistryEntry.Impl,
// and secondly,
@ObjectHolder(Wizardry.MODID)
@Mod.EventBusSubscriber
public final class Spells {
@@ -5,6 +5,7 @@ import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.living.EntityWizard;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.registry.WizardryAchievements;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
@@ -21,7 +22,10 @@ import net.minecraft.util.EnumHand;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class ArcaneJammer extends Spell {
public ArcaneJammer() {
@@ -91,5 +95,11 @@ public class ArcaneJammer extends Spell {
public boolean canBeCastByNPCs(){
return true;
}
@SubscribeEvent
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
// Arcane jammer prevents spell casting.
if(event.getEntityLiving().isPotionActive(WizardryPotions.arcane_jammer)) event.setCanceled(true);
}
}
@@ -5,11 +5,14 @@ import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.packet.PacketClairvoyance;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryParticleType;
import electroblob.wizardry.util.WizardryPathFinder;
import electroblob.wizardry.util.WizardryUtilities;
@@ -18,6 +21,7 @@ import net.minecraft.entity.monster.EntityZombie;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemStack;
import net.minecraft.pathfinding.Path;
import net.minecraft.pathfinding.PathNodeType;
import net.minecraft.pathfinding.PathPoint;
@@ -25,7 +29,11 @@ import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class Clairvoyance extends Spell {
/** The number of ticks it takes each path particle to move from one path point to the next. */
@@ -123,5 +131,33 @@ public class Clairvoyance extends Spell {
Wizardry.proxy.spawnParticle(WizardryParticleType.PATH, world, point.xCoord + 0.5, point.yCoord + 0.5, point.zCoord + 0.5, 0, 0, 0, (int)(1800*durationMultiplier), 1, 1, 1);
}
@SubscribeEvent
public static void onRightClickBlockEvent(PlayerInteractEvent.RightClickBlock event){
if(event.getEntityPlayer().isSneaking()){
// The event now has an ItemStack, which greatly simplifies hand-related stuff.
ItemStack wand = event.getItemStack();
if(wand != null && wand.getItem() instanceof ItemWand && WandHelper.getCurrentSpell(wand) instanceof Clairvoyance){
WizardData properties = WizardData.get(event.getEntityPlayer());
if(properties != null){
// THIS is why BlockPos is a thing - in 1.7.10 this requires a clumsy switch statement.
BlockPos pos = event.getPos().offset(event.getFace());
properties.setClairvoyancePoint(pos, event.getWorld().provider.getDimension());
if(!event.getWorld().isRemote){
event.getEntityPlayer().addChatMessage(new TextComponentTranslation("spell.clairvoyance.confirm", Spells.clairvoyance.getNameForTranslationFormatted()));
}
event.setCanceled(true);
}
}
}
}
}
@@ -6,6 +6,7 @@ import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.IElementalDamage;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryParticleType;
import electroblob.wizardry.util.WizardryUtilities;
@@ -17,7 +18,11 @@ import net.minecraft.util.EnumHand;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class CurseOfSoulbinding extends Spell {
public CurseOfSoulbinding() {
@@ -53,5 +58,17 @@ public class CurseOfSoulbinding extends Spell {
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.ENTITY_WITHER_SPAWN, 1.0F, world.rand.nextFloat() * 0.2F + 1.0F);
return true;
}
@SubscribeEvent
public static void onLivingHurtEvent(LivingHurtEvent event){
if(!event.getEntity().worldObj.isRemote && event.getEntityLiving() instanceof EntityPlayer && !event.getSource().isUnblockable()
&& !(event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).isRetaliatory())){
WizardData data = WizardData.get((EntityPlayer)event.getEntityLiving());
if(data != null){
data.damageAllSoulboundCreatures(event.getAmount());
}
}
}
}
@@ -15,7 +15,7 @@ import net.minecraft.world.World;
public class Flight extends Spell {
public Flight() {
public Flight(){
super(Tier.MASTER, 10, Element.EARTH, "flight", SpellType.UTILITY, 0, EnumAction.NONE, true);
}
@@ -11,6 +11,7 @@ import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryParticleType;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityCreature;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
@@ -24,11 +25,14 @@ import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class Intimidate extends Spell {
/** The NBT tag name for storing the feared entity's UUID in the target's tag compound. Defined here in case
* it changes. */
/** The NBT tag name for storing the feared entity's UUID in the target's tag compound. */
public static final String NBT_KEY = "fearedEntity";
public Intimidate() {
@@ -101,5 +105,24 @@ public class Intimidate extends Spell {
return false;
}
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
if(event.getEntityLiving().isPotionActive(WizardryPotions.fear) && event.getEntityLiving() instanceof EntityCreature){
NBTTagCompound entityNBT = event.getEntityLiving().getEntityData();
EntityCreature creature = (EntityCreature)event.getEntityLiving();
if(entityNBT != null && entityNBT.hasKey(NBT_KEY)){
Entity caster = WizardryUtilities.getEntityByUUID(creature.worldObj, entityNBT.getUniqueId(NBT_KEY));
if(caster instanceof EntityLivingBase){
Intimidate.runAway(creature, (EntityLivingBase)caster);
}
}
}
}
}
@@ -15,7 +15,7 @@ import net.minecraft.world.World;
public class Levitation extends Spell {
public Levitation() {
public Levitation(){
super(Tier.ADVANCED, 10, Element.SORCERY, "levitation", SpellType.UTILITY, 0, EnumAction.BOW, true);
}
@@ -3,11 +3,14 @@ package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.WizardryAchievements;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.effect.EntityLightningBolt;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.passive.EntityPig;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.EnumAction;
import net.minecraft.nbt.NBTTagCompound;
@@ -15,8 +18,15 @@ import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.EntityStruckByLightningEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class LightningBolt extends Spell {
/** The NBT key used to store the UUID of the player that summoned the lightning bolt. Used for achievements. */
public static final String NBT_KEY = "summoningPlayer";
public LightningBolt() {
super(Tier.ADVANCED, 40, Element.LIGHTNING, "lightning_bolt", SpellType.ATTACK, 80, EnumAction.NONE, false);
@@ -46,7 +56,7 @@ public class LightningBolt extends Spell {
// Code for eventhandler recognition; for achievements and such like. Left in for future use.
NBTTagCompound entityNBT = entitylightning.getEntityData();
entityNBT.setUniqueId("summoningPlayer", caster.getUniqueID());
entityNBT.setUniqueId(NBT_KEY, caster.getUniqueID());
}
caster.swingArm(hand);
@@ -87,4 +97,22 @@ public class LightningBolt extends Spell {
public boolean canBeCastByNPCs(){
return true;
}
@SubscribeEvent
public static void onEntityStruckByLightningEvent(EntityStruckByLightningEvent event){
if(event.getLightning().getEntityData() != null && event.getLightning().getEntityData().hasKey(NBT_KEY)){
EntityPlayer player = (EntityPlayer)WizardryUtilities.getEntityByUUID(event.getLightning().worldObj, event.getLightning().getEntityData().getUniqueId("summoningPlayer"));
if(event.getEntity() instanceof EntityCreeper){
player.addStat(WizardryAchievements.charge_creeper);
}
if(event.getEntity() instanceof EntityPig){
player.addStat(WizardryAchievements.frankenstein);
}
}
}
}
@@ -6,7 +6,7 @@ import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.entity.living.EntityWizard;
import electroblob.wizardry.entity.living.EntityEvilWizard;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
@@ -26,7 +26,12 @@ import net.minecraft.util.EnumHand;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent;
import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class MindControl extends Spell {
/** The NBT tag name for storing the controlling entity's UUID in the target's tag compound. Defined here in case
@@ -44,10 +49,10 @@ public class MindControl extends Spell {
if(rayTrace != null && rayTrace.entityHit != null && rayTrace.entityHit instanceof EntityLivingBase){
Entity target = rayTrace.entityHit;
EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit;
if(!world.isRemote){
if(target instanceof EntityPlayer || !target.isNonBoss() || target instanceof INpc){
if(!canControl(target)){
// Adds a message saying that the player/boss entity/wizard resisted mind control
if(!world.isRemote) caster.addChatComponentMessage(new TextComponentTranslation("spell.resist", target.getName(), this.getNameForTranslationFormatted()));
@@ -87,8 +92,7 @@ public class MindControl extends Spell {
if(target != null){
if(!world.isRemote){
if(target instanceof EntityLiving && target.isNonBoss()
&& !(target instanceof EntityWizard)){
if(canControl(target)){
if(!MindControl.findMindControlTarget((EntityLiving)target, caster, world)){
// If no valid target was found, this just acts like mind trick.
@@ -118,12 +122,18 @@ public class MindControl extends Spell {
}
return false;
}
@Override
public boolean canBeCastByNPCs(){
return true;
}
/** Returns true if the given entity can be mind controlled (i.e. is not a player, npc, evil wizard or boss). */
public static boolean canControl(EntityLivingBase target){
return target instanceof EntityLiving && target.isNonBoss() && !(target instanceof INpc)
&& !(target instanceof EntityEvilWizard);
}
/**
* Finds the nearest creature to the given target which it is allowed to attack according to the given caster
* and sets it as the target's attack target. Handles both new and old AI and takes follow range into account.
@@ -165,5 +175,42 @@ public class MindControl extends Spell {
}
@SubscribeEvent
public static void onLivingUpdateEvent(LivingUpdateEvent event){
// This was added because something got changed in the AI classes which means LivingSetAttackTargetEvent doesn't
// get fired when I want it to... so I'm firing it myself.
if(event.getEntityLiving().isPotionActive(WizardryPotions.mind_control) && event.getEntityLiving() instanceof EntityLiving
&& ((EntityLiving)event.getEntityLiving()).getAttackTarget() != null
&& !((EntityLiving)event.getEntityLiving()).getAttackTarget().isEntityAlive())
((EntityLiving)event.getEntityLiving()).setAttackTarget(null); // Causes the event to be fired
}
}
@SubscribeEvent
public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){
if(event.getEntityLiving().isPotionActive(WizardryPotions.mind_control) && MindControl.canControl(event.getEntityLiving())){
NBTTagCompound entityNBT = event.getEntityLiving().getEntityData();
if(entityNBT != null && entityNBT.hasKey(MindControl.NBT_KEY + "Most")){
Entity caster = WizardryUtilities.getEntityByUUID(event.getEntity().worldObj, entityNBT.getUniqueId(MindControl.NBT_KEY));
// If the target that the event tried to set is already a valid mind control target, nothing happens.
if(event.getTarget() != null && WizardryUtilities.isValidTarget(caster, event.getTarget())) return;
if(caster instanceof EntityLivingBase){
if(MindControl.findMindControlTarget((EntityLiving)event.getEntityLiving(), (EntityLivingBase)caster, event.getEntity().worldObj)){
// If it worked, skip setting the target to null.
return;
}
}
}
// If the caster couldn't be found or no valid target was found, this just acts like mind trick.
// If the target is null already, no need to set it to null, or infinite loops will occur.
if(event.getTarget() != null) ((EntityLiving)event.getEntityLiving()).setAttackTarget(null);
}
}
}
@@ -19,7 +19,12 @@ import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class MindTrick extends Spell {
public MindTrick() {
@@ -36,13 +41,13 @@ public class MindTrick extends Spell {
EntityLivingBase target = (EntityLivingBase)rayTrace.entityHit;
if(!world.isRemote){
if(target instanceof EntityPlayer){
target.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, (int)(300*modifiers.get(WizardryItems.duration_upgrade)), 0));
}else if(target instanceof EntityLiving){
((EntityLiving)target).setAttackTarget(null);
target.addPotionEffect(new PotionEffect(WizardryPotions.mind_trick, (int)(300*modifiers.get(WizardryItems.duration_upgrade)), 0));
}
@@ -54,7 +59,7 @@ public class MindTrick extends Spell {
0, 0, 0, 0, 0.8f, 0.2f, 1.0f);
}
}
target.playSound(WizardrySounds.SPELL_DEFLECTION, 0.7F, world.rand.nextFloat() * 0.4F + 0.8F);
caster.swingArm(hand);
return true;
@@ -68,14 +73,14 @@ public class MindTrick extends Spell {
if(target != null){
if(!world.isRemote){
if(target instanceof EntityPlayer){
target.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, (int)(300*modifiers.get(WizardryItems.duration_upgrade)), 0));
}else if(target instanceof EntityLiving){
((EntityLiving)target).setAttackTarget(null);
target.addPotionEffect(new PotionEffect(WizardryPotions.mind_trick, (int)(300*modifiers.get(WizardryItems.duration_upgrade)), 0));
}
}else{
for(int i=0; i<10; i++){
@@ -85,16 +90,38 @@ public class MindTrick extends Spell {
0, 0, 0, 0, 0.8f, 0.2f, 1.0f);
}
}
target.playSound(WizardrySounds.SPELL_DEFLECTION, 0.7F, world.rand.nextFloat() * 0.4F + 0.8F);
caster.swingArm(hand);
return true;
}
return false;
}
@Override
public boolean canBeCastByNPCs() {
return true;
}
@SubscribeEvent
public static void onLivingAttackEvent(LivingAttackEvent event){
if(event.getSource() != null && event.getSource().getEntity() instanceof EntityLivingBase){
// Cancels the mind trick effect if the creature takes damage
// This has been moved to within an (event.getSource().getEntity() instanceof EntityLivingBase) check so it doesn't
// crash the game with a ConcurrentModificationException. If you think about it, mind trick only ought to be
// cancelled if something attacks the entity since potions, drowning, cacti etc. don't affect the targeting.
if(event.getEntityLiving().isPotionActive(WizardryPotions.mind_trick)){
event.getEntityLiving().removePotionEffect(WizardryPotions.mind_trick);
}
}
}
@SubscribeEvent
public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){
// Mind trick
// If the target is null already, no need to set it to null, or infinite loops will occur.
if((event.getEntityLiving().isPotionActive(WizardryPotions.mind_trick) || event.getEntityLiving().isPotionActive(WizardryPotions.fear)) && event.getEntityLiving() instanceof EntityLiving && event.getTarget() != null){
((EntityLiving)event.getEntityLiving()).setAttackTarget(null);
}
}
}
@@ -3,15 +3,26 @@ package electroblob.wizardry.spell;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.util.IElementalDamage;
import electroblob.wizardry.util.MagicDamage;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryUtilities;
import electroblob.wizardry.util.MagicDamage.DamageType;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumHand;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class ShadowWard extends Spell {
public ShadowWard() {
@@ -20,20 +31,37 @@ public class ShadowWard extends Spell {
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers) {
if(world.isRemote){
double dx = -1 + 2*world.rand.nextFloat();
double dy = -1 + world.rand.nextFloat();
double dz = -1 + 2*world.rand.nextFloat();
world.spawnParticle(EnumParticleTypes.PORTAL, caster.posX, WizardryUtilities.getPlayerEyesPos(caster), caster.posZ, dx, dy, dz);
}
if(ticksInUse % 50 == 0){
WizardryUtilities.playSoundAtPlayer(caster, SoundEvents.BLOCK_PORTAL_AMBIENT, 0.6f, 1.5f);
}
return true;
}
@SubscribeEvent
public static void onLivingAttackEvent(LivingAttackEvent event){
if(event.getSource() != null && event.getSource().getEntity() instanceof EntityLivingBase){
// There used to be a check that the target was a player here, but I don't see any reason for it.
ItemStack wand = event.getEntityLiving().getActiveItemStack();
if(wand != null && wand.getItemDamage() < wand.getMaxDamage() && wand.getItem() instanceof ItemWand
&& WandHelper.getCurrentSpell(wand) instanceof ShadowWard && !event.getSource().isUnblockable()
&& !(event.getSource() instanceof IElementalDamage && ((IElementalDamage)event.getSource()).isRetaliatory())){
event.setCanceled(true);
// Now we can preserve the original daage source (sort of) as long as we make it retaliatory.
event.getEntityLiving().attackEntityFrom(MagicDamage.causeDirectMagicDamage(event.getSource().getEntity(), DamageType.MAGIC, true), event.getAmount()/2);
((EntityLivingBase)event.getSource().getEntity()).attackEntityFrom(MagicDamage.causeDirectMagicDamage(event.getEntityLiving(), DamageType.MAGIC, true), event.getAmount()/2);
}
}
}
}
@@ -8,13 +8,19 @@ import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.registry.WizardrySounds;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.MobEffects;
import net.minecraft.item.EnumAction;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.EnumHand;
import net.minecraft.world.World;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Mod.EventBusSubscriber
public class Transience extends Spell {
public Transience() {
@@ -28,7 +34,7 @@ public class Transience extends Spell {
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers) {
if(!caster.isPotionActive(WizardryPotions.transience)){
if(!world.isRemote){
caster.addPotionEffect(new PotionEffect(WizardryPotions.transience, (int)(400*modifiers.get(WizardryItems.duration_upgrade)), 0));
@@ -40,5 +46,37 @@ public class Transience extends Spell {
return false;
}
@SubscribeEvent
public static void onLivingAttackEvent(LivingAttackEvent event){
if(event.getSource() != null){
// Prevents all blockable damage while transience is active
if(event.getEntityLiving().isPotionActive(WizardryPotions.transience) && !event.getSource().isUnblockable()){
event.setCanceled(true);
}
// Prevents transient entities from causing any damage
if(event.getSource().getEntity() instanceof EntityLivingBase
&& ((EntityLivingBase)event.getSource().getEntity()).isPotionActive(WizardryPotions.transience)){
event.setCanceled(true);
}
}
}
@SubscribeEvent
public static void onBlockPlaceEvent(BlockEvent.PlaceEvent event){
// Prevents transient players from placing blocks
if(event.getPlayer().isPotionActive(WizardryPotions.transience)){
event.setCanceled(true);
return;
}
}
@SubscribeEvent
public static void onBlockBreakEvent(BlockEvent.BreakEvent event){
// Prevents transient players from breaking blocks
if(event.getPlayer().isPotionActive(WizardryPotions.transience)){
event.setCanceled(true);
return;
}
}
}
@@ -3,25 +3,36 @@ package electroblob.wizardry.tileentity;
import java.util.HashSet;
import java.util.Set;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.event.SpellBindEvent;
import electroblob.wizardry.item.ItemArcaneTome;
import electroblob.wizardry.item.ItemArmourUpgrade;
import electroblob.wizardry.item.ItemSpellBook;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.item.ItemWizardArmour;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryAchievements;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.MinecraftForge;
public class ContainerArcaneWorkbench extends Container {
public TileEntityArcaneWorkbench tileEntityArcaneWorkbench;
/** The arcane workbench tile entity associated with this container. */
public TileEntityArcaneWorkbench tileentity;
public static final ResourceLocation EMPTY_SLOT_CRYSTAL = new ResourceLocation(Wizardry.MODID, "gui/empty_slot_crystal");
public static final ResourceLocation EMPTY_SLOT_UPGRADE = new ResourceLocation(Wizardry.MODID, "gui/empty_slot_upgrade");
@@ -29,6 +40,7 @@ public class ContainerArcaneWorkbench extends Container {
public static final int CRYSTAL_SLOT = 8;
public static final int WAND_SLOT = 9;
public static final int UPGRADE_SLOT = 10;
private static final int[][][] SPELL_BOOK_SLOT_COORDS = {
{{80, 22}, {121, 51}, {106, 98}, {54, 98}, {39, 51}, {-999, -999}, {-999, -999}, {-999, -999}},
{{80, 22}, {117, 43}, {117, 85}, {80, 106}, {43, 85}, {43, 43}, {-999, -999}, {-999, -999}},
@@ -38,7 +50,7 @@ public class ContainerArcaneWorkbench extends Container {
public ContainerArcaneWorkbench(IInventory inventory, TileEntityArcaneWorkbench tileentity){
this.tileEntityArcaneWorkbench = tileentity;
this.tileentity = tileentity;
ItemStack wand = tileentity.getStackInSlot(WAND_SLOT);
@@ -47,16 +59,16 @@ public class ContainerArcaneWorkbench extends Container {
}
this.addSlotToContainer(new SlotItemList(tileentity, CRYSTAL_SLOT, 8, 88, 64, WizardryItems.magic_crystal))
.setBackgroundName(EMPTY_SLOT_CRYSTAL.toString());
.setBackgroundName(EMPTY_SLOT_CRYSTAL.toString());
this.addSlotToContainer(new SlotWandArmour(tileentity, WAND_SLOT, 80, 64, this));
Set<Item> upgrades = new HashSet<Item>(WandHelper.getSpecialUpgrades());
Set<Item> upgrades = new HashSet<Item>(WandHelper.getSpecialUpgrades()); // Can't be done statically.
upgrades.add(WizardryItems.arcane_tome);
upgrades.add(WizardryItems.armour_upgrade);
this.addSlotToContainer(new SlotItemList(tileentity, UPGRADE_SLOT, 8, 106, 1, upgrades.toArray(new Item[0])))
.setBackgroundName(EMPTY_SLOT_UPGRADE.toString());
.setBackgroundName(EMPTY_SLOT_UPGRADE.toString());
for(int x = 0; x < 9; x++){
this.addSlotToContainer(new Slot(inventory, x, 8 + x * 18, 196));
@@ -72,17 +84,14 @@ public class ContainerArcaneWorkbench extends Container {
}
@Override
public boolean canInteractWith(EntityPlayer par1EntityPlayer)
{
return this.tileEntityArcaneWorkbench.isUseableByPlayer(par1EntityPlayer);
public boolean canInteractWith(EntityPlayer player){
return this.tileentity.isUseableByPlayer(player);
}
/**
* Called from the central wand/armour slot when its item is changed or removed.
*/
// I wrote this!
/** Called from the central wand/armour slot when its item is changed or removed. */
// In case I forget again and think it should have @Override: I wrote this!
public void onSlotChanged(int slotNumber, ItemStack stack, EntityPlayer player){
if(slotNumber == WAND_SLOT){
if(stack == null || (!(stack.getItem() instanceof ItemWand) && stack.getItem() != WizardryItems.blank_scroll)){
@@ -110,8 +119,8 @@ public class ContainerArcaneWorkbench extends Container {
if(stack.getItem() == WizardryItems.blank_scroll){
// If a blank scroll is added
// The first slot is shown
this.getSlot(0).xDisplayPosition = ContainerArcaneWorkbench.SPELL_BOOK_SLOT_COORDS[0][0][0];
this.getSlot(0).yDisplayPosition = ContainerArcaneWorkbench.SPELL_BOOK_SLOT_COORDS[0][0][1];
this.getSlot(0).xDisplayPosition = SPELL_BOOK_SLOT_COORDS[0][0][0];
this.getSlot(0).yDisplayPosition = SPELL_BOOK_SLOT_COORDS[0][0][1];
// The rest of the slots are hidden
for(int i=1; i<CRYSTAL_SLOT; i++){
@@ -138,7 +147,7 @@ public class ContainerArcaneWorkbench extends Container {
for(int i=0; i<CRYSTAL_SLOT; i++){
int n = WandHelper.getUpgradeLevel(stack, WizardryItems.attunement_upgrade);
int[] coords = ContainerArcaneWorkbench.SPELL_BOOK_SLOT_COORDS[n][i];
int[] coords = SPELL_BOOK_SLOT_COORDS[n][i];
Slot slot1 = this.getSlot(i);
// Puts the slot back in the correct position
@@ -165,56 +174,49 @@ public class ContainerArcaneWorkbench extends Container {
// FIXME: It only seems to be syncing correctly when a stack is put into the slot, not taken out.
// Was this broken in 1.7.10 as well?
this.tileEntityArcaneWorkbench.sync();
this.tileentity.sync();
}
@Override
public ItemStack transferStackInSlot(EntityPlayer par1EntityPlayer, int clickedSlotId)
public ItemStack transferStackInSlot(EntityPlayer player, int clickedSlotId)
{
ItemStack remainder = null;
Slot slot = (Slot)this.inventorySlots.get(clickedSlotId);
if (slot != null && slot.getHasStack())
{
ItemStack itemstack = slot.getStack(); // The stack that was there originally
remainder = itemstack.copy(); // A copy of that stack
ItemStack stack = slot.getStack(); // The stack that was there originally
remainder = stack.copy(); // A copy of that stack
// Workbench -> inventory
if(clickedSlotId <= UPGRADE_SLOT){
// Tries to move the stack into the player's inventory. If this fails...
if (!this.mergeItemStack(itemstack, UPGRADE_SLOT + 1, this.inventorySlots.size(), true)){
if (!this.mergeItemStack(stack, UPGRADE_SLOT + 1, this.inventorySlots.size(), true)){
return null; // ...nothing else happens.
}
// Inventory -> workbench
}else{
}
// Inventory -> workbench
else{
// The following logic prevents shift-clicking transferring the items to the wrong slot.
int minSlotId = 0;
int maxSlotId = UPGRADE_SLOT;
if(itemstack.getItem() instanceof ItemSpellBook){
if(stack.getItem() instanceof ItemSpellBook){
minSlotId = 0;
maxSlotId = CRYSTAL_SLOT-1;
}
else if(itemstack.getItem() == WizardryItems.magic_crystal){
else if(stack.getItem() == WizardryItems.magic_crystal){
minSlotId = CRYSTAL_SLOT;
maxSlotId = CRYSTAL_SLOT;
}
else if(itemstack.getItem() instanceof ItemWand || itemstack.getItem() instanceof ItemWizardArmour
|| itemstack.getItem() == WizardryItems.blank_scroll){
else if(stack.getItem() instanceof ItemWand || stack.getItem() instanceof ItemWizardArmour
|| stack.getItem() == WizardryItems.blank_scroll){
minSlotId = WAND_SLOT;
maxSlotId = WAND_SLOT;
}
else if(itemstack.getItem() instanceof ItemArcaneTome
|| itemstack.getItem() instanceof ItemArmourUpgrade
|| itemstack.getItem() == WizardryItems.condenser_upgrade
|| itemstack.getItem() == WizardryItems.siphon_upgrade
|| itemstack.getItem() == WizardryItems.range_upgrade
|| itemstack.getItem() == WizardryItems.cooldown_upgrade
|| itemstack.getItem() == WizardryItems.duration_upgrade
|| itemstack.getItem() == WizardryItems.storage_upgrade
|| itemstack.getItem() == WizardryItems.blast_upgrade
|| itemstack.getItem() == WizardryItems.attunement_upgrade){
else if(stack.getItem() instanceof ItemArcaneTome
|| stack.getItem() instanceof ItemArmourUpgrade
|| WandHelper.isWandUpgrade(stack.getItem())){
minSlotId = UPGRADE_SLOT;
maxSlotId = UPGRADE_SLOT;
}
@@ -222,13 +224,13 @@ public class ContainerArcaneWorkbench extends Container {
return null; // If none of the above cases were true, then the item won't fit in the workbench.
}
if(!this.mergeItemStack(itemstack, minSlotId, maxSlotId + 1, false))
if(!this.mergeItemStack(stack, minSlotId, maxSlotId + 1, false))
{
return null;
}
}
if (itemstack.stackSize == 0)
if (stack.stackSize == 0)
{
slot.putStack((ItemStack)null);
}
@@ -237,12 +239,12 @@ public class ContainerArcaneWorkbench extends Container {
slot.onSlotChanged();
}
if (itemstack.stackSize == remainder.stackSize)
if (stack.stackSize == remainder.stackSize)
{
return null;
}
slot.onPickupFromSlot(par1EntityPlayer, itemstack);
slot.onPickupFromSlot(player, stack);
}
return remainder;
@@ -262,4 +264,198 @@ public class ContainerArcaneWorkbench extends Container {
return false;
}
/** Called (via {@link electroblob.wizardry.packet.PacketControlInput PacketControlInput}) when the apply button
* in the arcane workbench GUI is pressed. */
// All operations on the items contained in the inventory simply call the corresponding methods in the tileentity.
// As of 2.1, for the sake of events and neatness of code, this was moved here from TileEntityArcaneWorkbench.
public void onApplyButtonPressed(EntityPlayer player){
ItemStack wand = this.getSlot(WAND_SLOT).getStack();
ItemStack[] spellBooks = new ItemStack[CRYSTAL_SLOT];
for(int i=0; i<spellBooks.length; i++){
spellBooks[i] = this.getSlot(i).getStack();
}
ItemStack crystals = this.getSlot(CRYSTAL_SLOT).getStack();
ItemStack upgrade = this.getSlot(UPGRADE_SLOT).getStack();
if(MinecraftForge.EVENT_BUS.post(new SpellBindEvent(player, this))) return;
// Since the workbench now accepts armour as well as wands, this check is needed.
if(wand != null && wand.getItem() instanceof ItemWand){
// Upgrades wand if necessary. Damage is copied, preserving remaining durability,
// and also the entire NBT tag compound.
if(upgrade != null){
if(upgrade.getItem() == WizardryItems.arcane_tome){
ItemStack newWand;
switch(Tier.values()[upgrade.getItemDamage()]){
case APPRENTICE:
if(((ItemWand)wand.getItem()).tier == Tier.BASIC){
newWand = new ItemStack(WizardryUtilities.getWand(Tier.values()[upgrade.getItemDamage()], ((ItemWand)wand.getItem()).element));
newWand.setTagCompound(wand.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() - (wand.getMaxDamage() - wand.getItemDamage()));
this.putStackInSlot(WAND_SLOT, newWand);
this.putStackInSlot(UPGRADE_SLOT, null);
player.addStat(WizardryAchievements.apprentice, 1);
}
break;
case ADVANCED:
if(((ItemWand)wand.getItem()).tier == Tier.APPRENTICE){
newWand = new ItemStack(WizardryUtilities.getWand(Tier.values()[upgrade.getItemDamage()], ((ItemWand)wand.getItem()).element));
newWand.setTagCompound(wand.getTagCompound());
newWand.setItemDamage(newWand.getMaxDamage() - (wand.getMaxDamage() - wand.getItemDamage()));
this.putStackInSlot(WAND_SLOT, newWand);
this.putStackInSlot(UPGRADE_SLOT, null);
}
break;
case MASTER:
if(((ItemWand)wand.getItem()).tier == Tier.ADVANCED){
newWand = new ItemStack(WizardryUtilities.getWand(Tier.values()[upgrade.getItemDamage()], ((ItemWand)wand.getItem()).element));
newWand.setTagCompound(wand.getTagCompound());
newWand.setItemDamage(newWand.getMaxDamage() - (wand.getMaxDamage() - wand.getItemDamage()));
this.putStackInSlot(WAND_SLOT, newWand);
this.putStackInSlot(UPGRADE_SLOT, null);
player.addStat(WizardryAchievements.master, 1);
}
break;
default:
break;
}
// This needs to happen so the charging works on the new wand, not the old one.
wand = this.getSlot(WAND_SLOT).getStack();
}else{
// Special upgrades
// Used to preserve existing mana when upgrading storage rather than creating free mana.
int prevMana = wand.getMaxDamage() - wand.getItemDamage();
if(WandHelper.getTotalUpgrades(wand) < ((ItemWand)wand.getItem()).tier.upgradeLimit
&& WandHelper.getUpgradeLevel(wand, upgrade.getItem()) < Constants.UPGRADE_STACK_LIMIT){
WandHelper.applyUpgrade(wand, upgrade.getItem());
// Special behaviours for specific upgrades
if(upgrade.getItem() == WizardryItems.storage_upgrade){
wand.setItemDamage(wand.getMaxDamage() - prevMana);
}
if(upgrade.getItem() == WizardryItems.attunement_upgrade){
Spell[] spells = WandHelper.getSpells(wand);
Spell[] newSpells = new Spell[5 + WandHelper.getUpgradeLevel(wand, WizardryItems.attunement_upgrade)];
for(int i=0; i<newSpells.length; i++){
// Prevents both NPEs and AIOOBEs
newSpells[i] = i < spells.length && spells[i] != null ? spells[i] : Spells.none;
}
WandHelper.setSpells(wand, newSpells);
int[] cooldown = WandHelper.getCooldowns(wand);
int[] newCooldown = new int[5 + WandHelper.getUpgradeLevel(wand, WizardryItems.attunement_upgrade)];
if(cooldown.length > 0){
for(int i=0; i<cooldown.length; i++){
newCooldown[i] = cooldown[i];
}
}
WandHelper.setCooldowns(wand, newCooldown);
}
this.getSlot(UPGRADE_SLOT).decrStackSize(1);
player.addStat(WizardryAchievements.special_upgrade, 1);
if(WandHelper.getTotalUpgrades(wand) == Tier.MASTER.upgradeLimit){
player.addStat(WizardryAchievements.max_out_wand, 1);
}
}
}
}
// Reads NBT spell id 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
// be bound as normal, along with any upgrades and crystals.
Spell[] spells = WandHelper.getSpells(wand);
if(spells.length <= 0){
// 5 here because if the spell array doesn't exist, the wand can't possibly have attunement upgrades
spells = new Spell[5];
}
for(int i=0; i<spells.length; i++){
if(spellBooks[i] != null && !(Spell.get(spellBooks[i].getItemDamage()).tier.level > ((ItemWand)wand.getItem()).tier.level)){
spells[i] = Spell.get(spellBooks[i].getItemDamage());
}
}
WandHelper.setSpells(wand, spells);
// Charges wand by appropriate amount
if(crystals != null){
int chargeDepleted = wand.getItemDamage();
//System.out.println("Charge depleted: " + chargeDepleted);
//System.out.println("Crystals found: " + crystals.stackSize);
if(crystals.stackSize * Constants.MANA_PER_CRYSTAL < chargeDepleted){
//System.out.println("charging");
wand.setItemDamage(chargeDepleted - crystals.stackSize * Constants.MANA_PER_CRYSTAL);
this.getSlot(CRYSTAL_SLOT).decrStackSize(crystals.stackSize);
}else if(chargeDepleted != 0){
//System.out.println((int)Math.ceil(((double)chargeDepleted)/50));
this.getSlot(CRYSTAL_SLOT).decrStackSize((int)Math.ceil(((double)chargeDepleted)/Constants.MANA_PER_CRYSTAL));
wand.setItemDamage(0);
}
}
}
// Armour
else if(wand != null && wand.getItem() instanceof ItemWizardArmour){
// Applies legendary upgrade
if(upgrade != null && upgrade.getItem() == WizardryItems.armour_upgrade){
if(!wand.hasTagCompound()){
wand.setTagCompound(new NBTTagCompound());
}
if(!wand.getTagCompound().hasKey("legendary")){
wand.getTagCompound().setBoolean("legendary", true);
this.putStackInSlot(UPGRADE_SLOT, null);
player.addStat(WizardryAchievements.legendary);
}
}
// Charges armour by appropriate amount
if(crystals != null){
int chargeDepleted = wand.getItemDamage();
if(crystals.stackSize * Constants.MANA_PER_CRYSTAL < chargeDepleted){
wand.setItemDamage(chargeDepleted - crystals.stackSize * Constants.MANA_PER_CRYSTAL);
this.getSlot(CRYSTAL_SLOT).decrStackSize(crystals.stackSize);
}else if(chargeDepleted != 0){
this.getSlot(CRYSTAL_SLOT).decrStackSize((int)Math.ceil(((double)chargeDepleted)/Constants.MANA_PER_CRYSTAL));
wand.setItemDamage(0);
}
}
}
// Scrolls
else if(wand != null && wand.getItem() == WizardryItems.blank_scroll){
// 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(spellBooks[0] != null && (player.capabilities.isCreativeMode || (WizardData.get(player) != null
&& WizardData.get(player).hasSpellBeenDiscovered(Spell.get(spellBooks[0].getItemDamage()))))
&& crystals != null && crystals.stackSize * Constants.MANA_PER_CRYSTAL > Spell.get(spellBooks[0].getItemDamage()).cost){
this.getSlot(CRYSTAL_SLOT).decrStackSize((int)Math.ceil(((double)Spell.get(spellBooks[0].getItemDamage()).cost)/Constants.MANA_PER_CRYSTAL));
this.putStackInSlot(WAND_SLOT, new ItemStack(WizardryItems.scroll, 1, spellBooks[0].getItemDamage()));
}
}
}
}
@@ -3,18 +3,11 @@ package electroblob.wizardry.tileentity;
import java.util.HashSet;
import java.util.Set;
import electroblob.wizardry.WizardData;
import electroblob.wizardry.constants.Constants;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.item.ItemWizardArmour;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryAchievements;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.WandHelper;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
@@ -286,196 +279,4 @@ public class TileEntityArcaneWorkbench extends TileEntity implements IInventory,
}
}
/** Called (via {@link electroblob.wizardry.packet.PacketControlInput PacketControlInput}) when the apply button
* in the arcane workbench GUI is pressed. */
// IDEA: Perhaps this should be in the container class?
public void onApplyButtonPressed(EntityPlayer player){
ItemStack wand = this.getStackInSlot(ContainerArcaneWorkbench.WAND_SLOT);
ItemStack[] spellBooks = new ItemStack[ContainerArcaneWorkbench.CRYSTAL_SLOT];
for(int i=0; i<spellBooks.length; i++){
spellBooks[i] = this.getStackInSlot(i);
}
ItemStack crystals = this.getStackInSlot(ContainerArcaneWorkbench.CRYSTAL_SLOT);
ItemStack upgrade = this.getStackInSlot(ContainerArcaneWorkbench.UPGRADE_SLOT);
// TODO: Implement this
//if(MinecraftForge.EVENT_BUS.post(new SpellBindEvent(player))) return;
// Since the workbench now accepts armour as well as wands, this check is needed.
if(wand != null && wand.getItem() instanceof ItemWand){
// Upgrades wand if necessary. Damage is copied, preserving remaining durability,
// and also the entire NBT tag compound.
if(upgrade != null){
if(upgrade.getItem() == WizardryItems.arcane_tome){
ItemStack newWand;
switch(Tier.values()[upgrade.getItemDamage()]){
case APPRENTICE:
if(((ItemWand)wand.getItem()).tier == Tier.BASIC){
newWand = new ItemStack(WizardryUtilities.getWand(Tier.values()[upgrade.getItemDamage()], ((ItemWand)wand.getItem()).element));
newWand.setTagCompound(wand.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() - (wand.getMaxDamage() - wand.getItemDamage()));
this.setInventorySlotContents(ContainerArcaneWorkbench.WAND_SLOT, newWand);
this.setInventorySlotContents(ContainerArcaneWorkbench.UPGRADE_SLOT, null);
player.addStat(WizardryAchievements.apprentice, 1);
}
break;
case ADVANCED:
if(((ItemWand)wand.getItem()).tier == Tier.APPRENTICE){
newWand = new ItemStack(WizardryUtilities.getWand(Tier.values()[upgrade.getItemDamage()], ((ItemWand)wand.getItem()).element));
newWand.setTagCompound(wand.getTagCompound());
newWand.setItemDamage(newWand.getMaxDamage() - (wand.getMaxDamage() - wand.getItemDamage()));
this.setInventorySlotContents(ContainerArcaneWorkbench.WAND_SLOT, newWand);
this.setInventorySlotContents(ContainerArcaneWorkbench.UPGRADE_SLOT, null);
}
break;
case MASTER:
if(((ItemWand)wand.getItem()).tier == Tier.ADVANCED){
newWand = new ItemStack(WizardryUtilities.getWand(Tier.values()[upgrade.getItemDamage()], ((ItemWand)wand.getItem()).element));
newWand.setTagCompound(wand.getTagCompound());
newWand.setItemDamage(newWand.getMaxDamage() - (wand.getMaxDamage() - wand.getItemDamage()));
this.setInventorySlotContents(ContainerArcaneWorkbench.WAND_SLOT, newWand);
this.setInventorySlotContents(ContainerArcaneWorkbench.UPGRADE_SLOT, null);
player.addStat(WizardryAchievements.master, 1);
}
break;
default:
break;
}
// This needs to happen so the charging works on the new wand, not the old one.
wand = this.getStackInSlot(ContainerArcaneWorkbench.WAND_SLOT);
}else{
// Special upgrades
// Used to preserve existing mana when upgrading storage rather than creating free mana.
int prevMana = wand.getMaxDamage() - wand.getItemDamage();
if(WandHelper.getTotalUpgrades(wand) < ((ItemWand)wand.getItem()).tier.upgradeLimit
&& WandHelper.getUpgradeLevel(wand, upgrade.getItem()) < Constants.UPGRADE_STACK_LIMIT){
WandHelper.applyUpgrade(wand, upgrade.getItem());
// Special behaviours for specific upgrades
if(upgrade.getItem() == WizardryItems.storage_upgrade){
wand.setItemDamage(wand.getMaxDamage() - prevMana);
}
if(upgrade.getItem() == WizardryItems.attunement_upgrade){
Spell[] spells = WandHelper.getSpells(wand);
Spell[] newSpells = new Spell[5 + WandHelper.getUpgradeLevel(wand, WizardryItems.attunement_upgrade)];
for(int i=0; i<newSpells.length; i++){
// Prevents both NPEs and AIOOBEs
newSpells[i] = i < spells.length && spells[i] != null ? spells[i] : Spells.none;
}
WandHelper.setSpells(wand, newSpells);
int[] cooldown = WandHelper.getCooldowns(wand);
int[] newCooldown = new int[5 + WandHelper.getUpgradeLevel(wand, WizardryItems.attunement_upgrade)];
if(cooldown.length > 0){
for(int i=0; i<cooldown.length; i++){
newCooldown[i] = cooldown[i];
}
}
WandHelper.setCooldowns(wand, newCooldown);
}
this.decrStackSize(ContainerArcaneWorkbench.UPGRADE_SLOT, 1);
player.addStat(WizardryAchievements.special_upgrade, 1);
if(WandHelper.getTotalUpgrades(wand) == Tier.MASTER.upgradeLimit){
player.addStat(WizardryAchievements.max_out_wand, 1);
}
}
}
}
// Reads NBT spell id 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
// be bound as normal, along with any upgrades and crystals.
Spell[] spells = WandHelper.getSpells(wand);
if(spells.length <= 0){
// 5 here because if the spell array doesn't exist, the wand can't possibly have attunement upgrades
spells = new Spell[5];
}
for(int i=0; i<spells.length; i++){
if(spellBooks[i] != null && !(Spell.get(spellBooks[i].getItemDamage()).tier.level > ((ItemWand)wand.getItem()).tier.level)){
spells[i] = Spell.get(spellBooks[i].getItemDamage());
}
}
WandHelper.setSpells(wand, spells);
// Charges wand by appropriate amount
if(crystals != null){
int chargeDepleted = wand.getItemDamage();
//System.out.println("Charge depleted: " + chargeDepleted);
//System.out.println("Crystals found: " + crystals.stackSize);
if(crystals.stackSize * Constants.MANA_PER_CRYSTAL < chargeDepleted){
//System.out.println("charging");
wand.setItemDamage(chargeDepleted - crystals.stackSize * Constants.MANA_PER_CRYSTAL);
this.decrStackSize(ContainerArcaneWorkbench.CRYSTAL_SLOT, crystals.stackSize);
}else if(chargeDepleted != 0){
//System.out.println((int)Math.ceil(((double)chargeDepleted)/50));
this.decrStackSize(ContainerArcaneWorkbench.CRYSTAL_SLOT, (int)Math.ceil(((double)chargeDepleted)/Constants.MANA_PER_CRYSTAL));
wand.setItemDamage(0);
}
}
// Armour
}else if(wand != null && wand.getItem() instanceof ItemWizardArmour){
// Applies legendary upgrade
if(upgrade != null && upgrade.getItem() == WizardryItems.armour_upgrade){
if(!wand.hasTagCompound()){
wand.setTagCompound(new NBTTagCompound());
}
if(!wand.getTagCompound().hasKey("legendary")){
wand.getTagCompound().setBoolean("legendary", true);
this.setInventorySlotContents(ContainerArcaneWorkbench.UPGRADE_SLOT, null);
player.addStat(WizardryAchievements.legendary);
}
}
// Charges armour by appropriate amount
if(crystals != null){
int chargeDepleted = wand.getItemDamage();
if(crystals.stackSize * Constants.MANA_PER_CRYSTAL < chargeDepleted){
wand.setItemDamage(chargeDepleted - crystals.stackSize * Constants.MANA_PER_CRYSTAL);
this.decrStackSize(ContainerArcaneWorkbench.CRYSTAL_SLOT, crystals.stackSize);
}else if(chargeDepleted != 0){
this.decrStackSize(ContainerArcaneWorkbench.CRYSTAL_SLOT, (int)Math.ceil(((double)chargeDepleted)/Constants.MANA_PER_CRYSTAL));
wand.setItemDamage(0);
}
}
// Scrolls
}else if(wand != null && wand.getItem() == WizardryItems.blank_scroll){
// 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(spellBooks[0] != null && (player.capabilities.isCreativeMode || (WizardData.get(player) != null
&& WizardData.get(player).hasSpellBeenDiscovered(Spell.get(spellBooks[0].getItemDamage()))))
&& crystals != null && crystals.stackSize * Constants.MANA_PER_CRYSTAL > Spell.get(spellBooks[0].getItemDamage()).cost){
this.decrStackSize(ContainerArcaneWorkbench.CRYSTAL_SLOT, (int)Math.ceil(((double)Spell.get(spellBooks[0].getItemDamage()).cost)/Constants.MANA_PER_CRYSTAL));
this.setInventorySlotContents(ContainerArcaneWorkbench.WAND_SLOT, new ItemStack(WizardryItems.scroll, 1, spellBooks[0].getItemDamage()));
}
}
}
}
@@ -1,6 +1,12 @@
package electroblob.wizardry.util;
import electroblob.wizardry.registry.WizardryAchievements;
import electroblob.wizardry.util.MagicDamage.DamageType;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
/** This interface allows {@link MagicDamage} and {@link IndirectMagicDamage} to both be treated as instances of a
* single type so that the damage type field can be accessed, rather than having to deal with each of them separately,
@@ -8,10 +14,34 @@ import electroblob.wizardry.util.MagicDamage.DamageType;
* need to extend different subclasses of {@link net.minecraft.util.DamageSource DamageSource}).
* @since Wizardry 1.1
* @author Electroblob */
@Mod.EventBusSubscriber
public interface IElementalDamage {
DamageType getType();
boolean isRetaliatory();
@SubscribeEvent
public static void onLivingAttackEvent(LivingAttackEvent event){
if(event.getSource() instanceof IElementalDamage){
if(MagicDamage.isEntityImmune(((IElementalDamage)event.getSource()).getType(), event.getEntity())){
event.setCanceled(true);
// I would have liked to have done the 'resist' chat message here, but I overlooked the fact that I
// would need an instance of the spell to get its display name!
return;
}
// One convenient side effect of the new damage type system is that I can get rid of all the places where
// creepers are charged and just put them here under shock damage - this is precisely the sort of
// repetitive code I was trying to get rid of, since errors can (and did!) occur.
if(event.getEntityLiving() instanceof EntityCreeper && !((EntityCreeper)event.getEntityLiving()).getPowered()
&& ((IElementalDamage)event.getSource()).getType() == DamageType.SHOCK){
// Charges creepers when they are hit by shock damage
WizardryUtilities.chargeCreeper((EntityCreeper)event.getEntityLiving());
// Gives the player that caused the shock damage the 'It's Gonna Blow' achievement
if(event.getSource().getEntity() instanceof EntityPlayer){
((EntityPlayer)event.getSource().getEntity()).addStat(WizardryAchievements.charge_creeper);
}
}
}
}
}
@@ -5,22 +5,24 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import electroblob.wizardry.event.SpellCastEvent;
import io.netty.buffer.ByteBuf;
import net.minecraft.item.Item;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.fml.common.network.ByteBufUtils;
/**
* Object that wraps any number of spell multipliers into one, allowing for expandability within the Spell#cast
* Object that wraps any number of spell modifiers into one, allowing for expandability within the Spell#cast
* methods. This class is essentially a glorified {@link Map} which can be written to and read from a {@link ByteBuf}.
* It is possible to calculate spell multipliers from wand NBT within the cast methods, but this is cumbersome and does
* not allow the multipliers to be sent to the client, which is sometimes necessary (for example, detonate needs to know
* about range multipliers on the client side or the particles wouldn't show outside of the base range).
* It is possible to calculate spell modifiers from wand NBT within the cast methods, but this is cumbersome and does
* not allow the modifiers to be sent to the client, which is sometimes necessary (for example, detonate needs to know
* about range modifiers on the client side or the particles wouldn't show outside of the base range).
* <p>
* Most external interaction with SpellModifiers objects will be in SpellCastEvent.Pre, where you can add additional
* multipliers to them if desired for use with your own spells, or modify the existing ones. If you have added a wand
* Most external interaction with SpellModifiers objects will be in {@link SpellCastEvent.Pre}, where you can add additional
* modifiers to them if desired for use with your own spells, or modify the existing ones. If you have added a wand
* upgrade, this is <b>not</b> done automatically for you; you will have to do it yourself (for the simple reason that
* not all wand upgrades affect spells).
* not all wand upgrades affect spells). SpellModifiers objects are <i>mutable</i>, so you can simply change the values
* they contain to modify the spell.
* <p>
* To use a SpellModifiers object within the <code>Spell.cast</code> methods, simply retrieve the desired multiplier
* using {@link SpellModifiers#get(Item)} for wand upgrades, or {@link SpellModifiers#get(String)} if the multiplier
@@ -29,13 +31,13 @@ import net.minecraftforge.fml.common.network.ByteBufUtils;
* @since Wizardry 1.2
* @see WandHelper
*/
// I have made the decision that the USERS of this class must decide whether the multipliers need syncing or not, on a
// I have made the decision that the USERS of this class must decide whether the modifiers need syncing or not, on a
// case-by-case basis. Why? Because assigning keys to either sync or not sync would be unnecessarily restrictive, and
// would mean they have to be registered, and part of the point of SpellModifiers is that they can be added to on the
// fly.
public final class SpellModifiers {
/** Constant string identifier for the damage multiplier. All the other multipliers in Wizardry have items. */
/** Constant string identifier for the damage modifier. All the other modifiers in Wizardry have items. */
public static final String DAMAGE = "damage";
private Map<String, Float> multiplierMap;
@@ -53,7 +55,7 @@ public final class SpellModifiers {
* upgrade item was registered with.
* @throws IllegalArgumentException if the given item is not a registered special wand upgrade.
* @param upgrade The upgrade item the multiplier corresponds to.
* @param multiplier The multiplier value, with 1 being default. Usage of multipliers is up to individual spells to
* @param multiplier The multiplier value, with 1 being default. Usage of modifiers is up to individual spells to
* implement.
* @param needsSyncing Whether this multiplier should be synchronised with the client via packets. <i>Only set this
* to true if particles will be spawned which need to know the value of the multiplier.</i>
@@ -69,7 +71,7 @@ public final class SpellModifiers {
* multiplier will correspond to a wand upgrade, in which case use {@link SpellModifiers#set(Item, float, boolean)}
* instead.
* @param key The key used to identify the multiplier.
* @param multiplier The multiplier value, with 1 being default. Usage of multipliers is up to individual spells to
* @param multiplier The multiplier value, with 1 being default. Usage of modifiers is up to individual spells to
* implement.
* @param needsSyncing Whether this multiplier should be synchronised with the client via packets. <i>Only set this
* to true if particles will be spawned which depend on the multiplier.</i>
@@ -23,7 +23,9 @@ import net.minecraft.nbt.NBTTagCompound;
* unlikely case that this is necessary.
* <p>
* Also note that none of the methods in this class actually check that the given ItemStack contains an ItemWand; you
* can, for example, pass in a stack of snowballs without causing problems, but that is of course pointless!
* can, for example, pass in a stack of snowballs without causing problems, but that is of course pointless! However,
* if you have your own spell casting item (which doesn't extend ItemWand), this setup means you can still use this class
* to manage its NBT structure.
* <p>
* All <b>get</b> methods in this class return some kind of default if the passed-in wand stack has no nbt data.
* See individual method descriptions for more details.<br>
@@ -6,6 +6,7 @@ import javax.annotation.Nullable;
import com.google.common.collect.Sets;
import electroblob.wizardry.spell.Clairvoyance;
import net.minecraft.entity.EntityLiving;
import net.minecraft.pathfinding.NodeProcessor;
import net.minecraft.pathfinding.Path;
@@ -14,7 +15,8 @@ import net.minecraft.pathfinding.PathPoint;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
/** Minecraft's pathfinder refused to play nicely, so I 'borrowed' its code and fiddled with it. */
/** Minecraft's pathfinder refused to play nicely, so I 'borrowed' its code and fiddled with it. Currently this is only
* used for the {@link Clairvoyance} spell. */
public class WizardryPathFinder {
/** The path being generated */
@@ -56,6 +56,14 @@ import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
/** This class contains some useful static methods for use anywhere - items, entities, spells, events, blocks, etc.
* Broadly speaking, these fall into the following categories:
* <p>
* - In-world utilities (position calculating, retrieving entities, etc.)<br>
* - Raytracing<br>
* - Drawing utilities (client-only)<br>
* - NBT and data storage utilities<br>
* - Interaction with the ally designation system<br>
* - Loot and weighting utilities
* @see CommonProxy
* @see WandHelper
* @since Wizardry 1.0