Miscellaneous setup
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user