Initial commit
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
package electroblob.wizardry.util;
|
||||
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
|
||||
/** 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,
|
||||
* which would be inefficient and cumbersome (the latter of those classes cannot extend the former because they both
|
||||
* need to extend different subclasses of {@link net.minecraft.util.DamageSource DamageSource}).
|
||||
* @since Wizardry 1.1
|
||||
* @author Electroblob */
|
||||
public interface IElementalDamage {
|
||||
|
||||
DamageType getType();
|
||||
|
||||
boolean isRetaliatory();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package electroblob.wizardry.util;
|
||||
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.EntityDamageSourceIndirect;
|
||||
|
||||
public class IndirectMagicDamage extends EntityDamageSourceIndirect implements IElementalDamage {
|
||||
|
||||
private final DamageType type;
|
||||
private final boolean isRetaliatory;
|
||||
|
||||
public IndirectMagicDamage(String name, Entity magic, Entity caster, DamageType type, boolean isRetaliatory){
|
||||
super(name, magic, caster);
|
||||
this.type = type;
|
||||
this.isRetaliatory = isRetaliatory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DamageType getType(){
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRetaliatory(){
|
||||
return isRetaliatory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package electroblob.wizardry.util;
|
||||
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
|
||||
/**
|
||||
* DamageSource specifically for summoned creatures. This is for ranged attacks and works exactly the same way as a
|
||||
* normal indirect damage source, except that it takes the minion as an additional parameter. This is for the sole
|
||||
* purpose of displaying the correct death message. <i>The event handler deals with all damage dealt by summoned
|
||||
* creatures, so it's very unlikely that this will be needed anywhere else. If for some reason it is, note that
|
||||
* knockback has to be removed and re-applied after the damage is dealt.</i>
|
||||
* @author Electroblob
|
||||
* @since Wizardry 1.2
|
||||
* @see MinionDamage
|
||||
*/
|
||||
public class IndirectMinionDamage extends IndirectMagicDamage {
|
||||
|
||||
private Entity minion;
|
||||
|
||||
public IndirectMinionDamage(String name, Entity projectile, Entity minion, Entity caster, DamageType type, boolean isRetaliatory) {
|
||||
super(name, projectile, caster, type, isRetaliatory);
|
||||
this.minion = minion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDeathMessage(EntityLivingBase victim) {
|
||||
ITextComponent itextcomponent = this.minion.getDisplayName();
|
||||
String key = "death.attack." + this.damageType;
|
||||
return new TextComponentTranslation(key, victim.getDisplayName(), itextcomponent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package electroblob.wizardry.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import electroblob.wizardry.entity.living.EntityBlazeMinion;
|
||||
import electroblob.wizardry.entity.living.EntityIceGiant;
|
||||
import electroblob.wizardry.entity.living.EntityIceWraith;
|
||||
import electroblob.wizardry.entity.living.EntityLightningWraith;
|
||||
import electroblob.wizardry.entity.living.EntityPhoenix;
|
||||
import electroblob.wizardry.entity.living.EntityShadowWraith;
|
||||
import electroblob.wizardry.entity.living.EntitySkeletonMinion;
|
||||
import electroblob.wizardry.entity.living.EntitySpiderMinion;
|
||||
import electroblob.wizardry.entity.living.EntityStormElemental;
|
||||
import electroblob.wizardry.entity.living.EntityZombieMinion;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.boss.EntityDragon;
|
||||
import net.minecraft.entity.boss.EntityWither;
|
||||
import net.minecraft.entity.monster.EntityBlaze;
|
||||
import net.minecraft.entity.monster.EntityCaveSpider;
|
||||
import net.minecraft.entity.monster.EntityGhast;
|
||||
import net.minecraft.entity.monster.EntityMagmaCube;
|
||||
import net.minecraft.entity.monster.EntityPigZombie;
|
||||
import net.minecraft.entity.monster.EntitySkeleton;
|
||||
import net.minecraft.entity.monster.EntitySnowman;
|
||||
import net.minecraft.entity.monster.EntitySpider;
|
||||
import net.minecraft.entity.monster.EntityZombie;
|
||||
import net.minecraft.entity.monster.SkeletonType;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EntityDamageSource;
|
||||
|
||||
// A note on the use of the vanilla damagesources:
|
||||
// When using indirect damage sources, the SECOND argument is the original entity (i.e. the caster), and the
|
||||
// FIRST argument is the actual projectile or whatever that does the damage. getEntity() will return
|
||||
// the original entity, and getSourceOfDamage() will return the projectile.
|
||||
|
||||
// The vanilla approach to damage types is inconsistent, to say the least. Poison is simply 'magic', and relies on
|
||||
// EntityLivingBase.isPotionApplicable to determine whether an entity is affected or not. Wither, on the other hand, is
|
||||
// its own damage type, but again relies on the potion to determine what is immune (which in vanilla is nothing). Fire
|
||||
// has a proper implementation of course, with both block-based and projectile-based types, and any other damagesource
|
||||
// can also be designated as fire damage with setFireDamage(). Likewise, projectile and explosion damage can also be
|
||||
// applied to any damagesource, but these aren't considered types in their own right; rather, they seem to be damage
|
||||
// type 'attributes'. This is my attempt to collect all of these into a reasonably coherent system.
|
||||
|
||||
/** As of wizardry 1.1, this class has replaced the damagesource-related methods in WizardryUtilities, allowing a
|
||||
* {@link DamageType} to be specified with the damage. The main reason for this is so that damage sources can fit with the
|
||||
* vanilla behaviour on armour enchantments and such like whilst still being classified as wizardry damage for the purposes
|
||||
* of friendly fire, etc.
|
||||
* <p><i>
|
||||
* In the future, there is scope for an entirely standalone mod based on this idea. Perhaps 'Elemental Damage' could be
|
||||
* a config-only mod which allows its users to define any number of specific damage types, then give any creature a
|
||||
* resistance, immunity or vulnerability to each, as well as being able to specify the sources for each type, such as
|
||||
* enchantments, potions, specific items, certain entities, even particular situations and string damagesource names
|
||||
* from other mods.
|
||||
* </i>
|
||||
* @see IndirectMagicDamage
|
||||
* @see IElementalDamage
|
||||
* @since Wizardry 1.1
|
||||
* @author Electroblob */
|
||||
public class MagicDamage extends EntityDamageSource implements IElementalDamage {
|
||||
|
||||
/** The name of the damagesource for direct magic damage from the wizardry mod. */
|
||||
public static final String DIRECT_MAGIC_DAMAGE = "wizardryMagic";
|
||||
/** The name of the damagesource for indirect magic damage from the wizardry mod. */
|
||||
public static final String INDIRECT_MAGIC_DAMAGE = "indirectWizardryMagic";
|
||||
// Technically, I don't need to specify that the classes in this map must extend entity, but it's good practice to.
|
||||
private static final Map<Class<? extends Entity>, DamageType[]> immunityMapping = new HashMap<Class<? extends Entity>, DamageType[]>();
|
||||
|
||||
private final DamageType type;
|
||||
private final boolean isRetaliatory;
|
||||
|
||||
/** A simple set of constants for the types of damage. The names are deliberately different to those in EnumElement
|
||||
* to avoid confusion. All types are classified as magic damage in the vanilla system, so witches are resistant to them. */
|
||||
public enum DamageType {
|
||||
/** Generic magic damage from the wizardry mod. Like vanilla magic damage, except it doesn't bypass armour. */
|
||||
MAGIC,
|
||||
/** Fire damage from the wizardry mod. Counts as fire damage in the vanilla system, so is blocked by any mobs that
|
||||
* are immune to fire and entities with the fire resistance effect. */
|
||||
FIRE,
|
||||
/** Frost (ice) damage from the wizardry mod. Snow golems, ice wraiths and ice giants are immune. */
|
||||
FROST,
|
||||
/** Shock (lightning) damage from the wizardry mod. Lightning wraiths and storm elementals are immune. */
|
||||
SHOCK,
|
||||
/** Wither damage from the wizardry mod. Withers, wither skeletons and shadow wraiths are immune. */
|
||||
WITHER,
|
||||
/** Poison damage from the wizardry mod. Spiders, cave spiders and undead mobs are immune. */
|
||||
POISON,
|
||||
/** [NYI] Force damage from the wizardry mod. Insubstantial creatures (ghast, shadow wraith, etc.) are immune. */
|
||||
FORCE,
|
||||
/** Blast damage from the wizardry mod. Affected by the blast protection enchantment. */
|
||||
BLAST,
|
||||
/** [NYI] Radiant damage from the wizardry mod. */
|
||||
RADIANT;
|
||||
}
|
||||
|
||||
static {
|
||||
// Of course, the entities that are immune to fire already are since there's a vanilla system for that, but
|
||||
// they're included here anyway for completeness and in case anyone wants to check if an entity is immune to
|
||||
// an unspecified element for reasons other than dealing damage.
|
||||
setEntityImmunities(EntityPhoenix.class, DamageType.FIRE);
|
||||
setEntityImmunities(EntityBlaze.class, DamageType.FIRE);
|
||||
setEntityImmunities(EntityBlazeMinion.class, DamageType.FIRE);
|
||||
setEntityImmunities(EntityPigZombie.class, DamageType.FIRE, DamageType.POISON);
|
||||
setEntityImmunities(EntityMagmaCube.class, DamageType.FIRE);
|
||||
setEntityImmunities(EntityGhast.class, DamageType.FIRE);
|
||||
setEntityImmunities(EntityDragon.class, DamageType.FIRE);
|
||||
setEntityImmunities(EntityStormElemental.class, DamageType.FIRE, DamageType.SHOCK);
|
||||
setEntityImmunities(EntityWither.class, DamageType.FIRE, DamageType.WITHER);
|
||||
setEntityImmunities(EntitySnowman.class, DamageType.FROST);
|
||||
setEntityImmunities(EntityIceWraith.class, DamageType.FROST);
|
||||
setEntityImmunities(EntityIceGiant.class, DamageType.FROST);
|
||||
setEntityImmunities(EntityLightningWraith.class, DamageType.SHOCK);
|
||||
setEntityImmunities(EntityShadowWraith.class, DamageType.WITHER);
|
||||
setEntityImmunities(EntitySpider.class, DamageType.POISON);
|
||||
setEntityImmunities(EntitySpiderMinion.class, DamageType.POISON);
|
||||
setEntityImmunities(EntityCaveSpider.class, DamageType.POISON);
|
||||
setEntityImmunities(EntityZombie.class, DamageType.POISON);
|
||||
setEntityImmunities(EntitySkeleton.class, DamageType.POISON);
|
||||
setEntityImmunities(EntityZombieMinion.class, DamageType.POISON);
|
||||
setEntityImmunities(EntitySkeletonMinion.class, DamageType.POISON);
|
||||
}
|
||||
|
||||
public MagicDamage(String name, Entity caster, DamageType type, boolean isRetaliatory){
|
||||
super(name, caster);
|
||||
this.type = type;
|
||||
this.isRetaliatory = isRetaliatory;
|
||||
this.setMagicDamage();
|
||||
if(type == DamageType.FIRE) this.setFireDamage();
|
||||
if(type == DamageType.BLAST) this.setExplosion();
|
||||
}
|
||||
|
||||
/** Returns true if the given entity is immune to the given damage type according to the entity immunity mappings,
|
||||
* false otherwise. When you want to check for resistances, check this method rather than just checking the result
|
||||
* of attackEntityFrom, since that could return false for all sorts of reasons besides immunities. However, if you
|
||||
* don't need to know whether the damage succeeded or not, there's no point in checking this method. A common use
|
||||
* of this method is to check for immunity and if so display the "[mob] resisted [spell]" chat message. See
|
||||
* {@link electroblob.wizardry.spell.Arc Arc} for a good example of this, and also of when not to use it. */
|
||||
public static boolean isEntityImmune(DamageType type, Entity entity){
|
||||
// Because Mojang, in their infinite wisdom, did not make wither skeletons their own separate class (despite the
|
||||
// fact that cave spiders are), I have to test for this manually. Realistically, no mod author would be stupid
|
||||
// enough to put two entities in one class, so this should be the only time I ever have to do this.
|
||||
if(type == DamageType.WITHER && entity instanceof EntitySkeleton
|
||||
// Don't need to check EntitySkeletonMinion separately any more because it now extends EntitySkeleton.
|
||||
&& ((EntitySkeleton)entity).getSkeletonType() == SkeletonType.WITHER){
|
||||
return true;
|
||||
}
|
||||
|
||||
if(type == DamageType.FIRE && entity.isImmuneToFire()) return true;
|
||||
|
||||
DamageType[] immunities = MagicDamage.immunityMapping.get(entity.getClass());
|
||||
|
||||
return immunities != null && Arrays.asList(immunities).contains(type);
|
||||
}
|
||||
|
||||
/** Registers the given type of entity as immune to all of the passed in damage types. */
|
||||
public static void setEntityImmunities(Class<? extends Entity> entityType, DamageType... immunities){
|
||||
immunityMapping.put(entityType, immunities);
|
||||
}
|
||||
|
||||
/** Adds the passed in damage type to the list of damage types to which the given entity is immune. */
|
||||
public static void addEntityImmunity(Class<? extends Entity> entityType, DamageType immunity){
|
||||
List<DamageType> immunities = Arrays.asList(immunityMapping.get(entityType));
|
||||
immunities.add(immunity);
|
||||
// Apparently putting 0 here works just fine.
|
||||
immunityMapping.put(entityType, immunities.toArray(new DamageType[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DamageSource called "wizardryMagic" with the given entity as the caster. Use in preference to vanilla
|
||||
* types to allow things to distinguish between magic and regular melee/swords. Unlike DamageSource.magic, it does
|
||||
* not bypass armour and has a player as the source (rather than nothing). It is still classed as magic damage (for
|
||||
* the record, all this does in vanilla is make witches 85% resistant to it - but that seems kinda right anyway).
|
||||
* isRetaliatory defaults to false.
|
||||
* <p>
|
||||
* Now that this is its own class, this static method is largely redundant, but it's not worth refactoring the entire
|
||||
* mod just to get rid of this method and use the constructor instead.
|
||||
* @param caster The player or other living entity causing the damage
|
||||
* @param type The type that this damage belongs to; used for resistances and wand perks. Use
|
||||
* {@link DamageType#MAGIC} for regular, non-elemental magic damage (sometimes you might not want an element
|
||||
* even though the spell has one - for example, not all necromancy spells are 'withery', so some of them might
|
||||
* reasonably affect creatures that are ususally immune to wither effects).
|
||||
* @return A damagesource object of type EntityDamageSource
|
||||
*/
|
||||
public static DamageSource causeDirectMagicDamage(Entity caster, DamageType type){
|
||||
return causeDirectMagicDamage(caster, type, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DamageSource called "wizardryMagic" with the given entity as the caster. Use in preference to vanilla
|
||||
* types to allow things to distinguish between magic and regular melee/swords. Unlike DamageSource.magic, it does
|
||||
* not bypass armour and has a player as the source (rather than nothing). It is still classed as magic damage (for
|
||||
* the record, all this does in vanilla is make witches 85% resistant to it - but that seems kinda right anyway).
|
||||
* <p>
|
||||
* Now that this is its own class, this static method is largely redundant, but it's not worth refactoring the entire
|
||||
* mod just to get rid of this method and use the constructor instead.
|
||||
* @param caster The player or other living entity causing the damage
|
||||
* @param type The type that this damage belongs to; used for resistances and wand perks. Use
|
||||
* {@link DamageType#MAGIC} for regular, non-elemental magic damage (sometimes you might not want an element
|
||||
* even though the spell has one - for example, not all necromancy spells are 'withery', so some of them might
|
||||
* reasonably affect creatures that are ususally immune to wither effects).
|
||||
* @param isRetaliatory whether this damage source came from a retaliatory attack; prevents infinite loops
|
||||
* occurring when two entities damage each other with retaliatory effects.
|
||||
* @return A damagesource object of type EntityDamageSource
|
||||
*/
|
||||
public static DamageSource causeDirectMagicDamage(Entity caster, DamageType type, boolean isRetaliatory){
|
||||
return new MagicDamage(DIRECT_MAGIC_DAMAGE, caster, type, isRetaliatory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DamageSource called "indirectWizardryMagic" with the player as the caster. Use in preference to vanilla
|
||||
* types to allow things to distinguish between magic and regular arrows/throwables. Unlike
|
||||
* DamageSource.causeIndirectMagicDamage, it does not bypass armour. It is still classed as magic damage (for the
|
||||
* record, all this does in vanilla is make witches 85% resistant to it - but that seems kinda right anyway).
|
||||
* isRetaliatory defaults to false.
|
||||
* <p>
|
||||
* Now that this is its own class, this static method is largely redundant, but it's not worth refactoring the entire
|
||||
* mod just to get rid of this method and use the constructor instead.
|
||||
* @param magic The entity that actually caused the damage
|
||||
* @param caster The player or other living entity that cast the spell originally
|
||||
* @param type The type that this damage belongs to; used for resistances and wand perks. Use
|
||||
* {@link DamageType#MAGIC} for regular, non-elemental magic damage (sometimes you might not want an element
|
||||
* even though the spell has one - for example, not all necromancy spells are 'withery', so some of them might
|
||||
* reasonably affect creatures that are ususally immune to wither effects).
|
||||
* @return A damagesource object of type EntityDamageSourceIndirect
|
||||
*/
|
||||
public static DamageSource causeIndirectMagicDamage(Entity magic, Entity caster, DamageType type){
|
||||
return causeIndirectMagicDamage(magic, caster, type, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DamageSource called "indirectWizardryMagic" with the player as the caster. Use in preference to vanilla
|
||||
* types to allow things to distinguish between magic and regular arrows/throwables. Unlike
|
||||
* DamageSource.causeIndirectMagicDamage, it does not bypass armour. It is still classed as magic damage (for the
|
||||
* record, all this does in vanilla is make witches 85% resistant to it - but that seems kinda right anyway).
|
||||
* <p>
|
||||
* Now that this is its own class, this static method is largely redundant, but it's not worth refactoring the entire
|
||||
* mod just to get rid of this method and use the constructor instead.
|
||||
* @param magic The entity that actually caused the damage
|
||||
* @param caster The player or other living entity that cast the spell originally
|
||||
* @param type The type that this damage belongs to; used for resistances and wand perks. Use
|
||||
* {@link DamageType#MAGIC} for regular, non-elemental magic damage (sometimes you might not want an element
|
||||
* even though the spell has one - for example, not all necromancy spells are 'withery', so some of them might
|
||||
* reasonably affect creatures that are ususally immune to wither effects).
|
||||
* @param isRetaliatory whether this damage source came from a retaliatory attack; prevents infinite loops
|
||||
* occurring when two entities damage each other with retaliatory effects.
|
||||
* @return A damagesource object of type EntityDamageSourceIndirect
|
||||
*/
|
||||
public static DamageSource causeIndirectMagicDamage(Entity magic, Entity caster, DamageType type, boolean isRetaliatory){
|
||||
return new IndirectMagicDamage(INDIRECT_MAGIC_DAMAGE, magic, caster, type, isRetaliatory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DamageType getType(){
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRetaliatory(){
|
||||
return isRetaliatory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package electroblob.wizardry.util;
|
||||
|
||||
import electroblob.wizardry.util.MagicDamage.DamageType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
|
||||
/**
|
||||
* DamageSource specifically for summoned creatures. Despite being for melee attacks, this is actually an indirect
|
||||
* damage source, because it allows the minion to be the 'projectile'. This class also overrides getDeathMessage to
|
||||
* display the minion's name rather than the caster's. <i>The event handler deals with all damage dealt by summoned
|
||||
* creatures, so it's very unlikely that this will be needed anywhere else. If for some reason it is, note that
|
||||
* knockback has to be removed and re-applied after the damage is dealt.</i>
|
||||
* @author Electroblob
|
||||
* @since Wizardry 1.2
|
||||
* @see IndirectMinionDamage
|
||||
*/
|
||||
public class MinionDamage extends IndirectMagicDamage {
|
||||
|
||||
public MinionDamage(String name, Entity minion, Entity caster, DamageType type, boolean isRetaliatory) {
|
||||
super(name, minion, caster, type, isRetaliatory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextComponent getDeathMessage(EntityLivingBase victim) {
|
||||
ITextComponent itextcomponent = this.damageSourceEntity.getDisplayName();
|
||||
String key = "death.attack." + this.damageType;
|
||||
return new TextComponentTranslation(key, victim.getDisplayName(), itextcomponent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package electroblob.wizardry.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
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
|
||||
* 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).
|
||||
* <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
|
||||
* 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).
|
||||
* <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
|
||||
* is not from a wand upgrade.
|
||||
* @author Electroblob
|
||||
* @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
|
||||
// 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. */
|
||||
public static final String DAMAGE = "damage";
|
||||
|
||||
private Map<String, Float> multiplierMap;
|
||||
private Map<String, Float> syncedMultiplierMap;
|
||||
|
||||
/** Creates an empty SpellModifiers object. All calls to <code>get(...)</code> on an empty SpellModifiers object
|
||||
* will return a value of 1. */
|
||||
public SpellModifiers(){
|
||||
multiplierMap = new HashMap<String, Float>();
|
||||
syncedMultiplierMap = new HashMap<String, Float>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given multiplier to this SpellModifiers object, using the string identifier that the given wand
|
||||
* 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
|
||||
* 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>
|
||||
* @return The SpellModifiers object, allowing this method to be chained onto the constructor.
|
||||
*/
|
||||
public SpellModifiers set(Item upgrade, float multiplier, boolean needsSyncing){
|
||||
this.set(WandHelper.getIdentifier(upgrade), multiplier, needsSyncing);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given multiplier to this SpellModifiers object, using the given string key. In most cases, the
|
||||
* 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
|
||||
* 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>
|
||||
* @return The SpellModifiers object, allowing this method to be chained onto the constructor.
|
||||
*/
|
||||
public SpellModifiers set(String key, float multiplier, boolean needsSyncing){
|
||||
multiplierMap.put(key, multiplier);
|
||||
if(needsSyncing) syncedMultiplierMap.put(key, multiplier);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the multiplier corresponding to the given wand upgrade item, or 1 if no multiplier was stored.
|
||||
* @throws IllegalArgumentException if the given item is not a registered special wand upgrade. */
|
||||
public float get(Item upgrade){
|
||||
return get(WandHelper.getIdentifier(upgrade));
|
||||
}
|
||||
|
||||
/** Returns the multiplier corresponding to the given string key, or 1 if no multiplier was stored. In most cases,
|
||||
* the multiplier will correspond to a wand upgrade, in which case use {@link SpellModifiers#get(Item)}
|
||||
* instead. */
|
||||
public float get(String key){
|
||||
Float value = multiplierMap.get(key);
|
||||
// Must check for null before unboxing, and if it is null, return the default 1.
|
||||
return value == null ? 1 : value;
|
||||
}
|
||||
|
||||
/** Returns an unmodifiable map of the modifiers stored in this SpellModifiers object. Useful for iterating through
|
||||
* the modifiers. */
|
||||
public Map<String, Float> getModifiers(){
|
||||
return Collections.unmodifiableMap(this.multiplierMap);
|
||||
}
|
||||
|
||||
/** Removes all modifiers from this SpellModifiers object, effectively resetting them all to 1. */
|
||||
public void reset(){
|
||||
this.multiplierMap.clear();
|
||||
this.syncedMultiplierMap.clear();
|
||||
}
|
||||
|
||||
/** Reads this SpellModifiers object from the given ByteBuf. */
|
||||
public void read(ByteBuf buf){
|
||||
int entryCount = buf.readInt();
|
||||
for(int i=0; i<entryCount; i++){
|
||||
this.set(ByteBufUtils.readUTF8String(buf), buf.readFloat(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/** Writes this SpellModifiers object to the given ByteBuf so it can be sent via packets. */
|
||||
public void write(ByteBuf buf){
|
||||
buf.writeInt(syncedMultiplierMap.size());
|
||||
for(Entry<String, Float> entry : syncedMultiplierMap.entrySet()){
|
||||
ByteBufUtils.writeUTF8String(buf, entry.getKey());
|
||||
buf.writeFloat(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a new SpellModifiers object from the given NBTTagCompound. The NBTTagCompound should have 1 or more
|
||||
* float tags, which will be stored as modifiers under the same name as the tag. For example, the following NBT
|
||||
* tag (in command syntax) will create a SpellModifiers object with a damage modifier of 1.5 and a range modifier of
|
||||
* 2:<p><code>{damage:1.5, range:2}</code><p>
|
||||
* Note that needsSyncing is set to true for all returned modifiers. */
|
||||
public static SpellModifiers fromNBT(NBTTagCompound nbt){
|
||||
SpellModifiers modifiers = new SpellModifiers();
|
||||
for(String key : nbt.getKeySet()){
|
||||
modifiers.set(key, nbt.getFloat(key), true);
|
||||
}
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
package electroblob.wizardry.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
import electroblob.wizardry.registry.Spells;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
/** Much like {@link net.minecraft.enchantment.EnchantmentHelper EnchantmentHelper}, this class has some static methods
|
||||
* which allow cleaner and more concise interaction with the wand NBT data, which is quite a complex structure. Such
|
||||
* interaction previously resulted in rather verbose and repetitive code which was hard to read and even harder to debug!
|
||||
* For example, this class allowed {@link electroblob.wizardry.item.ItemWand ItemWand} to be shortened by about 80 lines.
|
||||
* In addition, by having all the various null checks and array size checks in one place, the chance of accidental errors
|
||||
* due to forgetting to check these things is greatly reduced.
|
||||
* <p>
|
||||
* Note that these methods contain no game logic at all; they are purely for interacting with the NBT data. Conversely,
|
||||
* you should never need to access the wand's NBT data directly when using this class, but the keys are public in the
|
||||
* 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!
|
||||
* <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>
|
||||
* All <b>set</b> methods in this class create a new nbt data for the passed-in wand if it has none, before doing
|
||||
* whatever else they do.
|
||||
* @see electroblob.wizardry.item.ItemWand ItemWand
|
||||
* @see electroblob.wizardry.packet.PacketControlInput PacketControlInput
|
||||
* @since Wizardry 1.1 */
|
||||
public final class WandHelper {
|
||||
|
||||
// NBT tag keys
|
||||
public static final String SPELL_ARRAY_KEY = "spells";
|
||||
public static final String SELECTED_SPELL_KEY = "selectedSpell";
|
||||
public static final String COOLDOWN_ARRAY_KEY = "cooldown";
|
||||
public static final String UPGRADES_KEY = "upgrades";
|
||||
|
||||
private static final HashMap<Item, String> upgradeMap = new HashMap<Item, String>();
|
||||
|
||||
static {
|
||||
upgradeMap.put(WizardryItems.condenser_upgrade, "condenser");
|
||||
upgradeMap.put(WizardryItems.storage_upgrade, "storage");
|
||||
upgradeMap.put(WizardryItems.siphon_upgrade, "siphon");
|
||||
upgradeMap.put(WizardryItems.range_upgrade, "range");
|
||||
upgradeMap.put(WizardryItems.duration_upgrade, "duration");
|
||||
upgradeMap.put(WizardryItems.cooldown_upgrade, "cooldown");
|
||||
upgradeMap.put(WizardryItems.blast_upgrade, "blast");
|
||||
upgradeMap.put(WizardryItems.attunement_upgrade, "attunement");
|
||||
}
|
||||
|
||||
/** Returns an array containing the spells currently bound to the given wand. As of Wizardry 1.1, this array is not
|
||||
* always the same size; it can be anywhere between 5 and 8 (inclusive) in length. If the wand has no spell data,
|
||||
* returns an array of length 0. */
|
||||
public static Spell[] getSpells(ItemStack wand){
|
||||
|
||||
Spell[] spells = new Spell[0];
|
||||
|
||||
if(wand.getTagCompound() != null){
|
||||
|
||||
int[] spellIDs = wand.getTagCompound().getIntArray(SPELL_ARRAY_KEY);
|
||||
|
||||
spells = new Spell[spellIDs.length];
|
||||
|
||||
for(int i=0; i<spellIDs.length; i++){
|
||||
spells[i] = Spell.get(spellIDs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return spells;
|
||||
}
|
||||
|
||||
/** Binds the given array of spells to the given wand. The array can be anywhere between 5 and 8 (inclusive) in length. */
|
||||
public static void setSpells(ItemStack wand, Spell[] spells){
|
||||
|
||||
if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound()));
|
||||
|
||||
int[] spellIDs = new int[spells.length];
|
||||
|
||||
for(int i=0; i<spells.length; i++){
|
||||
spellIDs[i] = spells[i] != null ? spells[i].id() : Spells.none.id();
|
||||
}
|
||||
|
||||
wand.getTagCompound().setIntArray(SPELL_ARRAY_KEY, spellIDs);
|
||||
}
|
||||
|
||||
/** Returns the currently selected spell for the given wand, or the 'none' spell if the wand has no spell data. */
|
||||
public static Spell getCurrentSpell(ItemStack wand){
|
||||
|
||||
Spell[] spells = getSpells(wand);
|
||||
|
||||
if(wand.getTagCompound() != null){
|
||||
|
||||
int selectedSpell = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY);
|
||||
|
||||
if(selectedSpell < spells.length){
|
||||
return spells[selectedSpell];
|
||||
}
|
||||
}
|
||||
|
||||
return Spells.none;
|
||||
}
|
||||
|
||||
/** Selects the next spell in this wand's list of spells. */
|
||||
public static void selectNextSpell(ItemStack wand){
|
||||
// 5 here because if the spell array doesn't exist, the wand can't possibly have attunement upgrades
|
||||
if(getSpells(wand).length < 0) setSpells(wand, new Spell[5]);
|
||||
|
||||
if(wand.getTagCompound() != null){
|
||||
|
||||
int numberOfSpells = getSpells(wand).length;
|
||||
int selectedSpell = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY);
|
||||
|
||||
// Greater than or equal to so that if attunement upgrades are somehow removed by NBT modification it just
|
||||
// resets.
|
||||
if(selectedSpell >= numberOfSpells - 1){
|
||||
selectedSpell = 0;
|
||||
}else{
|
||||
selectedSpell++;
|
||||
}
|
||||
|
||||
wand.getTagCompound().setInteger(SELECTED_SPELL_KEY, selectedSpell);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Selects the previous spell in this wand's list of spells. */
|
||||
public static void selectPreviousSpell(ItemStack wand){
|
||||
|
||||
// 5 here because if the spell array doesn't exist, the wand can't possibly have attunement upgrades
|
||||
if(getSpells(wand).length < 0) setSpells(wand, new Spell[5]);
|
||||
// This cannot possibly be null here, and yet I am getting an NPE...
|
||||
if(wand.getTagCompound() != null){
|
||||
|
||||
int numberOfSpells = getSpells(wand).length;
|
||||
int selectedSpell = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY);
|
||||
|
||||
if(selectedSpell <= 0){
|
||||
selectedSpell = numberOfSpells - 1;
|
||||
}else{
|
||||
selectedSpell--;
|
||||
}
|
||||
|
||||
wand.getTagCompound().setInteger(SELECTED_SPELL_KEY, selectedSpell);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns an array of the cooldowns for each spell bound to the given wand. As of Wizardry 1.1, this array is not
|
||||
* always the same size; it can be anywhere between 5 and 8 (inclusive) in length. If the wand has no cooldown data,
|
||||
* returns an array of length 0. */
|
||||
public static int[] getCooldowns(ItemStack wand){
|
||||
|
||||
int[] cooldowns = new int[0];
|
||||
|
||||
if(wand.getTagCompound() != null){
|
||||
|
||||
return wand.getTagCompound().getIntArray(COOLDOWN_ARRAY_KEY);
|
||||
}
|
||||
|
||||
return cooldowns;
|
||||
}
|
||||
|
||||
/** Sets the given wand's cooldown array. The array can be anywhere between 5 and 8 (inclusive) in length. */
|
||||
public static void setCooldowns(ItemStack wand, int[] cooldowns){
|
||||
|
||||
if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound()));
|
||||
|
||||
wand.getTagCompound().setIntArray(COOLDOWN_ARRAY_KEY, cooldowns);
|
||||
}
|
||||
|
||||
/** Decrements the cooldown for each spell bound to the given wand by 1, if that cooldown is greater than 0. */
|
||||
public static void decrementCooldowns(ItemStack wand){
|
||||
|
||||
int[] cooldowns = getCooldowns(wand);
|
||||
|
||||
// If there are no cooldowns, it is assumed that they are all zero and therefore nothing needs to be done.
|
||||
if(cooldowns.length == 0) return;
|
||||
|
||||
for(int i=0; i<cooldowns.length; i++){
|
||||
if(cooldowns[i] > 0) cooldowns[i]--;
|
||||
}
|
||||
|
||||
setCooldowns(wand, cooldowns);
|
||||
}
|
||||
|
||||
/** Returns the given wand's cooldown for the currently selected spell, or 0 if the wand has no cooldown data. */
|
||||
public static int getCurrentCooldown(ItemStack wand){
|
||||
|
||||
int[] cooldowns = getCooldowns(wand);
|
||||
|
||||
if(cooldowns.length == 0) return 0;
|
||||
// Don't need to check if the tag compound is null since the above check is equivalent.
|
||||
return cooldowns[wand.getTagCompound().getInteger(SELECTED_SPELL_KEY)];
|
||||
}
|
||||
|
||||
/** Sets the given wand's cooldown for the currently selected spell. */
|
||||
public static void setCurrentCooldown(ItemStack wand, int cooldown){
|
||||
|
||||
if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound()));
|
||||
|
||||
int[] cooldowns = getCooldowns(wand);
|
||||
|
||||
// The length of the spells array must be greater than 0 since this method can only be called if a spell is
|
||||
// cast, which is impossible if there are no spells.
|
||||
if(cooldowns.length == 0) cooldowns = new int[getSpells(wand).length];
|
||||
|
||||
cooldowns[wand.getTagCompound().getInteger(SELECTED_SPELL_KEY)] = cooldown;
|
||||
|
||||
setCooldowns(wand, cooldowns);
|
||||
}
|
||||
|
||||
/** Returns the number of upgrades of the given type that have been applied to the given wand, or 0 if the wand has
|
||||
* no upgrade data or the given item is not a valid wand upgrade. */
|
||||
public static int getUpgradeLevel(ItemStack wand, Item upgrade){
|
||||
|
||||
String key = upgradeMap.get(upgrade);
|
||||
|
||||
if(wand.getTagCompound() != null && wand.getTagCompound().hasKey(UPGRADES_KEY) && key != null){
|
||||
return wand.getTagCompound().getCompoundTag(UPGRADES_KEY).getInteger(key);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/** Returns the total number of upgrades that have been applied to the given wand, or 0 if the wand has no upgrade
|
||||
* data. */
|
||||
public static int getTotalUpgrades(ItemStack wand){
|
||||
|
||||
int totalUpgrades = 0;
|
||||
|
||||
for(Item item : upgradeMap.keySet()){
|
||||
totalUpgrades += getUpgradeLevel(wand, item);
|
||||
}
|
||||
|
||||
return totalUpgrades;
|
||||
}
|
||||
|
||||
/** Applies the given upgrade to the given wand, or in other words increases the level for that upgrade by 1. This
|
||||
* does <b>not</b> account for the individual or total upgrade stack limits. */
|
||||
public static void applyUpgrade(ItemStack wand, Item upgrade){
|
||||
|
||||
if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound()));
|
||||
|
||||
if(!wand.getTagCompound().hasKey(UPGRADES_KEY)) wand.getTagCompound().setTag(UPGRADES_KEY, new NBTTagCompound());
|
||||
|
||||
NBTTagCompound upgrades = wand.getTagCompound().getCompoundTag(UPGRADES_KEY);
|
||||
|
||||
String key = upgradeMap.get(upgrade);
|
||||
|
||||
if(key != null) upgrades.setInteger(key, upgrades.getInteger(key) + 1);
|
||||
|
||||
wand.getTagCompound().setTag(UPGRADES_KEY, upgrades);
|
||||
}
|
||||
|
||||
/** Returns true if the given item is a valid special wand upgrade. */
|
||||
public static boolean isWandUpgrade(Item upgrade){
|
||||
return upgradeMap.containsKey(upgrade);
|
||||
}
|
||||
|
||||
/** Returns an unmodifiable set of all the items which are valid special wand upgrades. */
|
||||
public static Set<Item> getSpecialUpgrades(){
|
||||
return Collections.unmodifiableSet(WandHelper.upgradeMap.keySet());
|
||||
}
|
||||
|
||||
/** Package-protected getter for the identifier that corresponds to the given item, used only in the
|
||||
* {@link SpellModifiers} class. Internal to Wizardry.
|
||||
* @throws IllegalArgumentException if the given item is not a registered special wand upgrade.*/
|
||||
static String getIdentifier(Item upgrade){
|
||||
if(!isWandUpgrade(upgrade)) throw new IllegalArgumentException("Tried to get a wand upgrade key for an item"
|
||||
+ "that is not a registered special wand upgrade.");
|
||||
return upgradeMap.get(upgrade);
|
||||
}
|
||||
|
||||
/** Registers a special upgrade with wizardry. Not used in the base mod, but I've put it here to make it easy
|
||||
* for add-ons to add new wand upgrades.
|
||||
* @param upgrade The wand upgrade item
|
||||
* @param identifier A unique string, used as a key for wand nbt tags
|
||||
* @throws IllegalArgumentException if the passed in identifier is already used for another wand upgrade */
|
||||
public static void registerSpecialUpgrade(Item upgrade, String identifier){
|
||||
// Throwing an exception is the best thing to do here, since if a duplicate was allowed weird things would
|
||||
// happen later with wand NBT.
|
||||
if(upgradeMap.containsValue(identifier)) throw new IllegalArgumentException("Duplicate wand upgrade identifier: " + identifier);
|
||||
upgradeMap.put(upgrade, identifier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package electroblob.wizardry.util;
|
||||
|
||||
/** Enum constants representing the different types of particle added by wizardry. This was renamed from the previous
|
||||
* EnumParticleType in the 1.10.2 port to avoid confusion with the vanilla version, EnumParticleTypes. */
|
||||
public enum WizardryParticleType {
|
||||
BLIZZARD,
|
||||
BRIGHT_DUST,
|
||||
DARK_MAGIC,
|
||||
DUST,
|
||||
ICE,
|
||||
LEAF,
|
||||
MAGIC_BUBBLE,
|
||||
MAGIC_FIRE,
|
||||
PATH,
|
||||
SNOW,
|
||||
SPARK,
|
||||
SPARKLE,
|
||||
SPARKLE_ROTATING
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package electroblob.wizardry.util;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.pathfinding.NodeProcessor;
|
||||
import net.minecraft.pathfinding.Path;
|
||||
import net.minecraft.pathfinding.PathHeap;
|
||||
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. */
|
||||
public class WizardryPathFinder {
|
||||
|
||||
/** The path being generated */
|
||||
private final PathHeap path = new PathHeap();
|
||||
private final Set<PathPoint> closedSet = Sets.<PathPoint>newHashSet();
|
||||
/** Selection of path points to add to the path */
|
||||
private final PathPoint[] pathOptions = new PathPoint[32];
|
||||
private final NodeProcessor nodeProcessor;
|
||||
|
||||
public WizardryPathFinder(NodeProcessor processor){
|
||||
this.nodeProcessor = processor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Path findPath(IBlockAccess world, EntityLiving entity, BlockPos destination, float range){
|
||||
return this.findPath(world, entity, (double)((float)destination.getX() + 0.5F), (double)((float)destination.getY() + 0.5F), (double)((float)destination.getZ() + 0.5F), range);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Path findPath(IBlockAccess world, EntityLiving entity, double x, double y, double z, float range){
|
||||
this.path.clearPath();
|
||||
this.nodeProcessor.initProcessor(world, entity);
|
||||
PathPoint pathpoint = this.nodeProcessor.getStart();
|
||||
PathPoint pathpoint1 = this.nodeProcessor.getPathPointToCoords(x, y, z);
|
||||
Path path = this.findPath(pathpoint, pathpoint1, range);
|
||||
this.nodeProcessor.postProcess();
|
||||
return path;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Path findPath(PathPoint start, PathPoint end, float range){
|
||||
|
||||
start.totalPathDistance = 0.0F;
|
||||
start.distanceToNext = start.distanceManhattan(end);
|
||||
start.distanceToTarget = start.distanceToNext;
|
||||
this.path.clearPath();
|
||||
this.closedSet.clear();
|
||||
this.path.addPoint(start);
|
||||
PathPoint pathpoint = start;
|
||||
int i = 0;
|
||||
|
||||
while(!this.path.isPathEmpty()){
|
||||
|
||||
++i;
|
||||
|
||||
// This is the offending line - timeout limit changed from 200 to 5000.
|
||||
if(i >= 5000){
|
||||
break;
|
||||
}
|
||||
|
||||
PathPoint pathpoint1 = this.path.dequeue();
|
||||
|
||||
if(pathpoint1.equals(end)){
|
||||
pathpoint = end;
|
||||
break;
|
||||
}
|
||||
|
||||
if(pathpoint1.distanceManhattan(end) < pathpoint.distanceManhattan(end)){
|
||||
pathpoint = pathpoint1;
|
||||
}
|
||||
|
||||
pathpoint1.visited = true;
|
||||
int j = this.nodeProcessor.findPathOptions(this.pathOptions, pathpoint1, end, range);
|
||||
|
||||
for(int k = 0; k < j; ++k){
|
||||
|
||||
PathPoint pathpoint2 = this.pathOptions[k];
|
||||
float f = pathpoint1.distanceManhattan(pathpoint2);
|
||||
pathpoint2.distanceFromOrigin = pathpoint1.distanceFromOrigin + f;
|
||||
pathpoint2.cost = f + pathpoint2.costMalus;
|
||||
float f1 = pathpoint1.totalPathDistance + pathpoint2.cost;
|
||||
|
||||
if(pathpoint2.distanceFromOrigin < range && (!pathpoint2.isAssigned() || f1 < pathpoint2.totalPathDistance)){
|
||||
|
||||
pathpoint2.previous = pathpoint1;
|
||||
pathpoint2.totalPathDistance = f1;
|
||||
pathpoint2.distanceToNext = pathpoint2.distanceManhattan(end) + pathpoint2.costMalus;
|
||||
|
||||
if(pathpoint2.isAssigned()){
|
||||
this.path.changeDistance(pathpoint2, pathpoint2.totalPathDistance + pathpoint2.distanceToNext);
|
||||
}else{
|
||||
pathpoint2.distanceToTarget = pathpoint2.totalPathDistance + pathpoint2.distanceToNext;
|
||||
this.path.addPoint(pathpoint2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(pathpoint == start){
|
||||
return null;
|
||||
}else{
|
||||
Path path = this.createEntityPath(start, pathpoint);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new PathEntity for a given start and end point
|
||||
*/
|
||||
private Path createEntityPath(PathPoint start, PathPoint end){
|
||||
|
||||
int i = 1;
|
||||
|
||||
for(PathPoint pathpoint = end; pathpoint.previous != null; pathpoint = pathpoint.previous){
|
||||
++i;
|
||||
}
|
||||
|
||||
PathPoint[] points = new PathPoint[i];
|
||||
PathPoint pathpoint1 = end;
|
||||
--i;
|
||||
|
||||
for(points[i] = end; pathpoint1.previous != null; points[i] = pathpoint1){
|
||||
pathpoint1 = pathpoint1.previous;
|
||||
--i;
|
||||
}
|
||||
|
||||
return new Path(points);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,967 @@
|
||||
package electroblob.wizardry.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
|
||||
import electroblob.wizardry.CommonProxy;
|
||||
import electroblob.wizardry.WizardData;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.constants.Element;
|
||||
import electroblob.wizardry.constants.Tier;
|
||||
import electroblob.wizardry.entity.living.ISummonedCreature;
|
||||
import electroblob.wizardry.registry.WizardryItems;
|
||||
import electroblob.wizardry.registry.WizardryPotions;
|
||||
import electroblob.wizardry.spell.MindControl;
|
||||
import electroblob.wizardry.spell.Spell;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.monster.EntityCreeper;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.inventory.EntityEquipmentSlot;
|
||||
import net.minecraft.inventory.EntityEquipmentSlot.Type;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.network.datasync.DataParameter;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.DamageSource;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.SoundCategory;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.ReflectionHelper;
|
||||
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.
|
||||
* @see CommonProxy
|
||||
* @see WandHelper
|
||||
* @since Wizardry 1.0
|
||||
* @author Electroblob */
|
||||
public final class WizardryUtilities {
|
||||
|
||||
/** Constant which is simply an array of the four armour slots. (Could've sworn this exists somewhere in
|
||||
* vanilla, but I can't find it anywhere...) */
|
||||
public static final EntityEquipmentSlot[] ARMOUR_SLOTS;
|
||||
/** Changed to a constant in wizardry 2.1, since this is a lot more efficient. */
|
||||
private static final DataParameter<Boolean> POWERED;
|
||||
|
||||
static {
|
||||
// The list of slots needs to be mutable.
|
||||
List<EntityEquipmentSlot> slots = new ArrayList<EntityEquipmentSlot>(Arrays.asList(EntityEquipmentSlot.values()));
|
||||
slots.removeIf(slot -> slot.getSlotType() != Type.ARMOR);
|
||||
ARMOUR_SLOTS = slots.toArray(new EntityEquipmentSlot[0]);
|
||||
|
||||
// Null is passed in deliberately since POWERED is a static field.
|
||||
POWERED = ReflectionHelper.getPrivateValue(EntityCreeper.class, null, "POWERED", "field_184714_b");
|
||||
}
|
||||
|
||||
// SECTION Block/Entity/World Utilities
|
||||
// ===============================================================================================================
|
||||
|
||||
/**
|
||||
* Returns whether the block at the given coordinates can be replaced by another one (works as if a block is being placed by a player).
|
||||
* True for air, liquids, vines, tall grass and snow layers but not for flowers, signs etc.
|
||||
* This is a shortcut for <code>world.getBlockState(pos).getMaterial().isReplaceable()</code>.
|
||||
* @see WizardryUtilities#canBlockBeReplacedB(World, BlockPos)
|
||||
*/
|
||||
public static boolean canBlockBeReplaced(World world, BlockPos pos){
|
||||
return world.isAirBlock(new BlockPos(pos)) || world.getBlockState(pos).getMaterial().isReplaceable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the block at the given coordinates can be replaced by another one (works as if a block is being placed by a player)
|
||||
* and is not a liquid.
|
||||
* True for air, vines, tall grass and snow layers but not for flowers, signs etc. or any liquids.
|
||||
* @see WizardryUtilities#canBlockBeReplaced(World, BlockPos)
|
||||
*/
|
||||
public static boolean canBlockBeReplacedB(World world, BlockPos pos){
|
||||
return canBlockBeReplaced(world, pos) && !world.getBlockState(pos).getMaterial().isLiquid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the block at the given coordinates is unbreakable in survival mode. In vanilla this is true for
|
||||
* bedrock and end portal frame, for example.
|
||||
* This is a shortcut for world.getBlockState(pos).getBlockHardness(world, pos) == -1.0f.
|
||||
* Not much of a shortcut any more, since block ids have been phased out.
|
||||
*/
|
||||
public static boolean isBlockUnbreakable(World world, BlockPos pos){
|
||||
return world.isAirBlock(new BlockPos(pos)) ? false : world.getBlockState(pos).getBlockHardness(world, pos) == -1.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the nearest floor level to the given y coord within the range specified at the given x and z coords.
|
||||
* Liquids and other blocks that cannot be built on top of do not count, but stuff like signs does.
|
||||
* (Technically any block is allowed to be the floor according to the code, but seeing as it searches upwards and
|
||||
* non-solid blocks usually need a supporting block, the floor is likely to always be solid).
|
||||
* @param world
|
||||
* @param x The x coordinate to search in
|
||||
* @param y The y coordinate to search from
|
||||
* @param z The z coordinate to search in
|
||||
* @param range The maximum distance from the given y coordinate to search.
|
||||
* @return The y coordinate of the closest floor level, or -1 if there is none. Returns the actual level of the floor
|
||||
* as would be seen in the debug screen when the player is standing on it.
|
||||
* @see WizardryUtilities#getNearestFloorLevelB(World, BlockPos, int)
|
||||
*/
|
||||
public static int getNearestFloorLevel(World world, BlockPos pos, int range){
|
||||
|
||||
int yCoord = -2;
|
||||
for(int i = -range; i <= range; i++){
|
||||
// The last bit determines whether the block found to be a suitable floor is closer than the previous one found.
|
||||
if(world.isSideSolid(pos.up(i), EnumFacing.UP) && (world.isAirBlock(pos.up(i+1)) || !world.isSideSolid(pos.up(i+1), EnumFacing.UP))
|
||||
&& (i < yCoord-pos.getY() || yCoord == -2)){
|
||||
yCoord = pos.getY() + i;
|
||||
}
|
||||
}
|
||||
return yCoord + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the nearest floor level to the given y coord within the range specified at the given x and z coords.
|
||||
* Only works if the block above the floor is actually air and the floor is solid or a liquid.
|
||||
* @param world
|
||||
* @param x The x coordinate to search in
|
||||
* @param y The y coordinate to search from
|
||||
* @param z The z coordinate to search in
|
||||
* @param range The maximum distance from the given y coordinate to search.
|
||||
* @return The y coordinate of the closest floor level, or -1 if there is none. Returns the actual level of the floor
|
||||
* as would be seen in the debug screen when the player is standing on it.
|
||||
* @see WizardryUtilities#getNearestFloorLevel(World, BlockPos, int)
|
||||
*/
|
||||
public static int getNearestFloorLevelB(World world, BlockPos pos, int range){
|
||||
int yCoord = -2;
|
||||
for(int i = -range; i <= range; i++){
|
||||
if(world.isAirBlock(new BlockPos(pos.up(i+1))) && (world.getBlockState(pos.up(i)).getMaterial().isLiquid() || world.isSideSolid(pos.up(i), EnumFacing.UP))
|
||||
&& (i < yCoord-pos.getY() || yCoord == -2)){
|
||||
// The last bit determines whether the block found to be a suitable floor is closer than the previous one found.
|
||||
yCoord = pos.getY() + i;
|
||||
}
|
||||
}
|
||||
return yCoord + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the nearest floor level to the given y coord within the range specified at the given x and z coords.
|
||||
* Everything that is not air is treated as floor, even stuff that can't be walked on.
|
||||
* @param world
|
||||
* @param x The x coordinate to search in
|
||||
* @param y The y coordinate to search from
|
||||
* @param z The z coordinate to search in
|
||||
* @param range The maximum distance from the given y coordinate to search.
|
||||
* @return The y coordinate of the closest floor level, or -1 if there is none. Returns the actual level of the floor
|
||||
* as would be seen in the debug screen when the player is standing on it.
|
||||
* @see WizardryUtilities#getNearestFloorLevel(World, BlockPos, int)
|
||||
*/
|
||||
public static int getNearestFloorLevelC(World world, BlockPos pos, int range){
|
||||
int yCoord = -2;
|
||||
for(int i = -range; i <= range; i++){
|
||||
if(world.isAirBlock(new BlockPos(pos.up(i+1))) && (i < yCoord-pos.getY() || yCoord == -2)){
|
||||
// The last bit determines whether the block found to be a suitable floor is closer than the previous one found.
|
||||
yCoord = pos.getY() + i;
|
||||
}
|
||||
}
|
||||
return yCoord + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a random position on the ground near the player within the specified horizontal and vertical ranges. Used
|
||||
* to find a position to spawn entities in summoning spells.
|
||||
* @param entity The entity around which to search
|
||||
* @param horizontalRange The maximum number of blocks on the x or z axis the returned position can be from the
|
||||
* given entity. <i>The number of operations performed by this method is proportional to the square of this
|
||||
* parameter, so for performance reasons it is recommended that it does not exceed around 10.</i>
|
||||
* @param verticalRange The maximum number of blocks on the y axis the returned position can be from the given
|
||||
* entity
|
||||
* @return A BlockPos with the coordinates of the block directly above the ground at the position found, or null
|
||||
* if none were found within range. Importantly, since this method checks <i>all possible</i> positions within
|
||||
* range (i.e. randomness only occurs when deciding between the possible positions), if it returns null once
|
||||
* then it will always return null given the same circumstances and parameters. What this means is that you can
|
||||
* (and should) immediately stop trying to cast a summoning spell if this returns null.
|
||||
*/
|
||||
@Nullable
|
||||
public static BlockPos findNearbyFloorSpace(Entity entity, int horizontalRange, int verticalRange){
|
||||
|
||||
World world = entity.worldObj;
|
||||
List<BlockPos> possibleLocations = new ArrayList<BlockPos>();
|
||||
BlockPos origin = new BlockPos(entity);
|
||||
|
||||
for(int x = -horizontalRange; x <= horizontalRange; x++){
|
||||
for(int z = -horizontalRange; z <= horizontalRange; z++){
|
||||
int y = WizardryUtilities.getNearestFloorLevel(world, origin.add(x, 0, z), verticalRange);
|
||||
if(y > -1) possibleLocations.add(new BlockPos(origin.getX() + x, y, origin.getZ() + z));
|
||||
}
|
||||
}
|
||||
|
||||
if(possibleLocations.isEmpty()){
|
||||
return null;
|
||||
}else{
|
||||
return possibleLocations.get(world.rand.nextInt(possibleLocations.size()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the blockstate of the block the specified entity is standing on. Uses {@link MathHelper#floor_double(double)} because casting to int will not
|
||||
* return the correct coordinate when x or z is negative.
|
||||
*/
|
||||
public static IBlockState getBlockEntityIsStandingOn(Entity entity){
|
||||
BlockPos pos = new BlockPos(MathHelper.floor_double(entity.posX), (int)entity.getEntityBoundingBox().minY-1, MathHelper.floor_double(entity.posZ));
|
||||
return entity.worldObj.getBlockState(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for {@link WizardryUtilities#getEntitiesWithinRadius(double, double, double, double, World, Class)}
|
||||
* with EntityLivingBase as the entity type. This is by far the most common use for that method, which is why this
|
||||
* shorthand exists.
|
||||
* @param radius The search radius
|
||||
* @param x The x coordinate to search around
|
||||
* @param y The y coordinate to search around
|
||||
* @param z The z coordinate to search around
|
||||
* @param world The world to search in
|
||||
*/
|
||||
public static List<EntityLivingBase> getEntitiesWithinRadius(double radius, double x, double y, double z, World world){
|
||||
return getEntitiesWithinRadius(radius, x, y, z, world, EntityLivingBase.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all entities of the specified type within the specified radius of the given coordinates. This is different to using
|
||||
* a raw AABB because a raw AABB will search in a cube volume rather than a sphere.
|
||||
* Note that this does not exclude any entities; if any specific entities are to be excluded this must be
|
||||
* checked when iterating through the list.
|
||||
* @see {@link WizardryUtilities#getEntitiesWithinRadius(double, double, double, double, World)}
|
||||
* @param radius The search radius
|
||||
* @param x The x coordinate to search around
|
||||
* @param y The y coordinate to search around
|
||||
* @param z The z coordinate to search around
|
||||
* @param world The world to search in
|
||||
* @param entityType The class of entity to search for; pass in Entity.class for all entities
|
||||
*/
|
||||
public static <T extends Entity> List<T> getEntitiesWithinRadius(double radius, double x, double y, double z, World world, Class<T> entityType){
|
||||
AxisAlignedBB aabb = new AxisAlignedBB(x - radius, y - radius, z - radius, x + radius, y + radius, z + radius);
|
||||
List<T> entityList = world.getEntitiesWithinAABB(entityType, aabb);
|
||||
for(int i=0;i<entityList.size();i++){
|
||||
if(entityList.get(i).getDistance(x, y, z) > radius){
|
||||
entityList.remove(i);
|
||||
}
|
||||
}
|
||||
return entityList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an entity from its UUID. Note that you should check this isn't null. If the UUID is known to belong
|
||||
* to an EntityPlayer, use the more efficient {@link World#getPlayerEntityByUUID(UUID)} instead.
|
||||
* @param world The world the entity is in
|
||||
* @param id The entity's UUID
|
||||
* @return The Entity that has the given UUID, or null if no such entity exists in the specified world.
|
||||
*/
|
||||
@Nullable
|
||||
public static Entity getEntityByUUID(World world, UUID id){
|
||||
|
||||
for(Entity entity : world.loadedEntityList){
|
||||
// This is a perfect example of where you need to use .equals() and not ==. For most applications,
|
||||
// this was unnoticeable until world reload because the UUID instance or entity instance is stored.
|
||||
// Fixed now though.
|
||||
if(entity.getUniqueID().equals(id)){
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// No point allowing anything other than players for these methods since other entities can use Entity#playSound.
|
||||
|
||||
/** Shortcut for {@link World#playSound(EntityPlayer, double, double, double, SoundEvent, SoundCategory, float, float)}
|
||||
* where the player is null but the x, y and z coordinates are those of the passed in player. Use in preference to
|
||||
* {@link EntityPlayer#playSound(SoundEvent, float, float)} if there are client-server discrepancies. */
|
||||
public static void playSoundAtPlayer(EntityPlayer player, SoundEvent sound, SoundCategory category, float volume, float pitch){
|
||||
player.worldObj.playSound(null, player.posX, player.posY, player.posZ, sound, category, volume, pitch);
|
||||
}
|
||||
|
||||
/** See {@link WizardryUtilities#playSoundAtPlayer(EntityPlayer, SoundEvent, SoundCategory, float, float)}. Category
|
||||
* defaults to {@link SoundCategory#PLAYERS}. */
|
||||
public static void playSoundAtPlayer(EntityPlayer player, SoundEvent sound, float volume, float pitch){
|
||||
player.worldObj.playSound(null, player.posX, player.posY, player.posZ, sound, SoundCategory.PLAYERS, volume, pitch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity riding the given entity, or null if there is none. Allows for neater code now that
|
||||
* entities have a list of passengers, because it is necessary to check that the list is not null or empty first.
|
||||
*/
|
||||
@Nullable
|
||||
public static Entity getRider(Entity entity){
|
||||
return entity.getPassengers() != null && !entity.getPassengers().isEmpty() ? entity.getPassengers().get(0) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attacks the given entity with the given damage source and amount, but preserving the entity's original velocity
|
||||
* instead of applying knockback, as would happen with {@link EntityLivingBase#attackEntityFrom(DamageSource, float)}
|
||||
* <i>(More accurately, calls that method as normal and then resets the entity's velocity to what it was before).</i>
|
||||
* Handy for when you need to damage an entity repeatedly in a short space of time.
|
||||
* @param entity The entity to attack
|
||||
* @param source The source of the damage
|
||||
* @param amount The amount of damage to apply
|
||||
* @return True if the attack succeeded, false if not.
|
||||
*/
|
||||
public static boolean attackEntityWithoutKnockback(Entity entity, DamageSource source, float amount){
|
||||
double vx = entity.motionX; double vy = entity.motionY; double vz = entity.motionZ;
|
||||
boolean succeeded = entity.attackEntityFrom(source, amount);
|
||||
entity.motionX = vx; entity.motionY = vy; entity.motionZ = vz;
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the standard (non-enchanted) amount of knockback to the given target, using the same calculation as
|
||||
* {@link EntityLivingBase#attackEntityFrom(DamageSource, float)}. Use in conjunction with
|
||||
* {@link WizardryUtilities#attackEntityWithoutKnockback(Entity, DamageSource, float)} to change the source of
|
||||
* knockback for an attack.
|
||||
* @param attacker The entity that caused the knockback; the target will be pushed away from this entity.
|
||||
* @param target The entity to be knocked back.
|
||||
*/
|
||||
public static void applyStandardKnockback(Entity attacker, EntityLivingBase target){
|
||||
double dx = attacker.posX - target.posX;
|
||||
double dz;
|
||||
for(dz = attacker.posZ - target.posZ; dx * dx + dz * dz < 1.0E-4D; dz = (Math.random() - Math.random()) * 0.01D){
|
||||
dx = (Math.random() - Math.random()) * 0.01D;
|
||||
}
|
||||
// The first argument is never used.
|
||||
target.knockBack(null, 0.4f, dx, dz);
|
||||
}
|
||||
|
||||
// Just what benefit does having posY be the eye position on the first person client actually give?
|
||||
|
||||
/**
|
||||
* Gets the y coordinate of the given player's eyes. This is to cover an inconsistency between the value of
|
||||
* EntityPlayer.posY on the first person client and everywhere else; in first person (i.e. when
|
||||
* Minecraft.getMinecraft().thePlayer == player) player.posY is the eye position, but everywhere else it is the
|
||||
* feet position. This is intended for use when spawning particles, since this is the only situation where the
|
||||
* discrepancy is likely to matter.
|
||||
* <p>
|
||||
* As of Wizardry 1.2, this is just a shorthand for:<p>
|
||||
* <code><center>player.getEntityBoundingBox().minY + player.getEyeHeight()</code></center>
|
||||
*/
|
||||
public static double getPlayerEyesPos(EntityPlayer player){
|
||||
return player.getEntityBoundingBox().minY + player.getEyeHeight();
|
||||
}
|
||||
|
||||
/** Returns a list of the itemstacks in the given player's hotbar. Defined here for convenience and to
|
||||
* centralise the (unfortunately unavoidable) use of hardcoded numbers to reference the inventory slots. The
|
||||
* returned list is a modifiable copy of part of the player's inventory stack list; as such, changes to the list
|
||||
* are <b>not</b> written through to the player's inventory. However, the ItemStack instances themselves are not
|
||||
* copied, so changes to any of their fields (size, metadata...) will change those in the player's inventory.
|
||||
* @since Wizardry 1.2 */
|
||||
public static List<ItemStack> getHotbar(EntityPlayer player){
|
||||
return new ArrayList<ItemStack>(Arrays.asList(player.inventory.mainInventory).subList(0, 9));
|
||||
}
|
||||
|
||||
/** Returns a list of the itemstacks in the given player's hotbar and offhand, sorted into the following order:
|
||||
* main hand, offhand, rest of hotbar left-to-right. The returned list is a modifiable copy of part of the player's
|
||||
* inventory stack list; as such, changes to the list are <b>not</b> written through to the player's inventory.
|
||||
* However, the ItemStack instances themselves are not copied, so changes to any of their fields (size, metadata...)
|
||||
* will change those in the player's inventory.
|
||||
* @since Wizardry 1.2 */
|
||||
public static List<ItemStack> getPrioritisedHotbarAndOffhand(EntityPlayer player){
|
||||
List<ItemStack> hotbar = WizardryUtilities.getHotbar(player);
|
||||
// Adds the offhand item to the beginning of the list so it is processed before the hotbar
|
||||
hotbar.add(0, player.getHeldItemOffhand());
|
||||
// Moves the item in the main hand to the beginning of the list so it is processed first
|
||||
hotbar.remove(player.getHeldItemMainhand());
|
||||
hotbar.add(0, player.getHeldItemMainhand());
|
||||
return hotbar;
|
||||
}
|
||||
|
||||
/** Tests whether the specified player has any of the specified item in their entire inventory, including armour
|
||||
* slots and offhand. */
|
||||
public static boolean doesPlayerHaveItem(EntityPlayer player, Item item){
|
||||
|
||||
for(ItemStack stack : player.inventory.mainInventory){
|
||||
if(stack != null && stack.getItem() == item){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for(ItemStack stack : player.inventory.armorInventory){
|
||||
if(stack != null && stack.getItem() == item){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for(ItemStack stack : player.inventory.offHandInventory){
|
||||
if(stack != null && stack.getItem() == item){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Checks if the given player is opped on the given server. If the server is a singleplayer or LAN server, this
|
||||
* means they have cheats enabled. */
|
||||
public static boolean isPlayerOp(EntityPlayer player, MinecraftServer server){
|
||||
return server.getPlayerList().getOppedPlayers().getEntry(player.getGameProfile()) != null;
|
||||
}
|
||||
|
||||
/** Turns the given creeper into a charged creeper. In 1.10, this requires reflection since the DataManager keys
|
||||
* are private. (You <i>could</i> call {@link EntityCreeper#onStruckByLightning(...)} and then heal it and
|
||||
* extinguish it, but that's a bit awkward.) */
|
||||
public static void chargeCreeper(EntityCreeper creeper){
|
||||
creeper.getDataManager().set(POWERED, true);
|
||||
}
|
||||
|
||||
// SECTION Raytracing
|
||||
// ===============================================================================================================
|
||||
|
||||
/**
|
||||
* Does a block ray trace (NOT entities) from an entity's eyes (i.e. properly...)
|
||||
*/
|
||||
@Nullable
|
||||
public static RayTraceResult rayTrace(double range, World world, EntityLivingBase entity, boolean hitLiquids){
|
||||
|
||||
Vec3d start = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ);
|
||||
Vec3d look = entity.getLookVec();
|
||||
Vec3d end = start.addVector(look.xCoord * range, look.yCoord * range, look.zCoord * range);
|
||||
return world.rayTraceBlocks(start, end, hitLiquids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method which does a rayTrace for entities from an entity's eye level in the direction they are looking
|
||||
* with a specified range, using the tracePath method. Tidies up the code a bit. Border size defaults to 1.
|
||||
*
|
||||
* @param world
|
||||
* @param entity
|
||||
* @param range
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public static RayTraceResult standardEntityRayTrace(World world, EntityLivingBase entity, double range){
|
||||
double dx = entity.getLookVec().xCoord * range;
|
||||
double dy = entity.getLookVec().yCoord * range;
|
||||
double dz = entity.getLookVec().zCoord * range;
|
||||
HashSet<Entity> hashset = new HashSet<Entity>(1);
|
||||
hashset.add(entity);
|
||||
return WizardryUtilities.tracePath(world, (float)entity.posX, (float)(entity.getEntityBoundingBox().minY + entity.getEyeHeight()), (float)entity.posZ, (float)(entity.posX + dx), (float)(entity.posY + entity.getEyeHeight() + dy), (float)(entity.posZ + dz), 1.0f, hashset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method which does a rayTrace for entities from a entity's eye level in the direction they are looking
|
||||
* with a specified range and radius, using the tracePath method. Tidies up the code a bit.
|
||||
*
|
||||
* @param world
|
||||
* @param entity
|
||||
* @param range
|
||||
* @param borderSize
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public static RayTraceResult standardEntityRayTrace(World world, EntityLivingBase entity, double range, float borderSize){
|
||||
double dx = entity.getLookVec().xCoord * range;
|
||||
double dy = entity.getLookVec().yCoord * range;
|
||||
double dz = entity.getLookVec().zCoord * range;
|
||||
HashSet<Entity> hashset = new HashSet<Entity>(1);
|
||||
hashset.add(entity);
|
||||
return WizardryUtilities.tracePath(world, (float)entity.posX, (float)(entity.getEntityBoundingBox().minY + entity.getEyeHeight()), (float)entity.posZ, (float)(entity.posX + dx), (float)(entity.posY + entity.getEyeHeight() + dy), (float)(entity.posZ + dz), borderSize, hashset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for ray tracing entities (the useless default method doesn't work, despite EnumHitType having an ENTITY field...)
|
||||
* You can also use this for seeking.
|
||||
*
|
||||
* @param world
|
||||
* @param x startX
|
||||
* @param y startY
|
||||
* @param z startZ
|
||||
* @param tx endX
|
||||
* @param ty endY
|
||||
* @param tz endZ
|
||||
* @param borderSize extra area to examine around line for entities
|
||||
* @param excluded any excluded entities (the player, etc)
|
||||
* @return a RayTraceResult of either the block hit (no entity hit), the entity hit (hit an entity), or null for nothing hit
|
||||
*/
|
||||
@Nullable
|
||||
public static RayTraceResult tracePath(World world, float x, float y, float z, float tx, float ty, float tz, float borderSize, HashSet<Entity> excluded, boolean collideablesOnly){
|
||||
|
||||
Vec3d startVec = new Vec3d(x, y, z);
|
||||
//Vec3d lookVec = new Vec3d(tx-x, ty-y, tz-z);
|
||||
Vec3d endVec = new Vec3d(tx, ty, tz);
|
||||
float minX = x < tx ? x : tx;
|
||||
float minY = y < ty ? y : ty;
|
||||
float minZ = z < tz ? z : tz;
|
||||
float maxX = x > tx ? x : tx;
|
||||
float maxY = y > ty ? y : ty;
|
||||
float maxZ = z > tz ? z : tz;
|
||||
AxisAlignedBB bb = new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ).expand(borderSize, borderSize, borderSize);
|
||||
List<Entity> allEntities = world.getEntitiesWithinAABBExcludingEntity(null, bb);
|
||||
RayTraceResult blockHit = world.rayTraceBlocks(startVec, endVec);
|
||||
startVec = new Vec3d(x, y, z);
|
||||
endVec = new Vec3d(tx, ty, tz);
|
||||
float maxDistance = (float) endVec.distanceTo(startVec);
|
||||
if(blockHit!=null)
|
||||
{
|
||||
maxDistance = (float) blockHit.hitVec.distanceTo(startVec);
|
||||
}
|
||||
Entity closestHitEntity = null;
|
||||
float closestHit = maxDistance;
|
||||
float currentHit = 0.f;
|
||||
AxisAlignedBB entityBb;// = ent.getBoundingBox();
|
||||
RayTraceResult intercept;
|
||||
for(Entity ent : allEntities)
|
||||
{
|
||||
if((ent.canBeCollidedWith() || !collideablesOnly) && ((excluded != null && !excluded.contains(ent)) || excluded == null))
|
||||
{
|
||||
float entBorder = ent.getCollisionBorderSize();
|
||||
entityBb = ent.getEntityBoundingBox();
|
||||
if(entityBb!=null)
|
||||
{
|
||||
entityBb = entityBb.expand(entBorder, entBorder, entBorder);
|
||||
intercept = entityBb.calculateIntercept(startVec, endVec);
|
||||
if(intercept!=null)
|
||||
{
|
||||
currentHit = (float) intercept.hitVec.distanceTo(startVec);
|
||||
if(currentHit < closestHit || currentHit==0)
|
||||
{
|
||||
closestHit = currentHit;
|
||||
closestHitEntity = ent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(closestHitEntity!=null)
|
||||
{
|
||||
blockHit = new RayTraceResult(closestHitEntity);
|
||||
}
|
||||
return blockHit;
|
||||
}
|
||||
|
||||
// SECTION Rendering and GUIs
|
||||
// ===============================================================================================================
|
||||
|
||||
// Doesn't seem right to put this in the proxies since it should only ever be called from client-side code, and I'm
|
||||
// not about to make a whole separate utilities class just for one method. Fully qualified names it is!
|
||||
/**
|
||||
* <b>[Client-side only]</b> Draws a textured rectangle, taking the size of the image and the bit needed into
|
||||
* account, unlike {@link net.minecraft.client.gui.Gui#drawTexturedModalRect(int, int, int, int, int, int)
|
||||
* Gui.drawTexturedModalRect(int, int, int, int, int, int)}, which is harcoded for only 256x256 textures. Also
|
||||
* handy for custom potion icons.
|
||||
* @param x The x position of the rectangle
|
||||
* @param y The y position of the rectangle
|
||||
* @param u The x position of the top left corner of the section of the image wanted
|
||||
* @param v The y position of the top left corner of the section of the image wanted
|
||||
* @param width The width of the section
|
||||
* @param height The height of the section
|
||||
* @param textureWidth The width of the actual image.
|
||||
* @param textureHeight The height of the actual image.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public static void drawTexturedRect(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight){
|
||||
|
||||
float f = 1F / (float)textureWidth;
|
||||
float f1 = 1F / (float)textureHeight;
|
||||
|
||||
// Essentially the same as getting the tessellator. For most code, you'll want the tessellator AND the vertexbuffer
|
||||
// stored in local variables.
|
||||
net.minecraft.client.renderer.VertexBuffer buffer = net.minecraft.client.renderer.Tessellator.getInstance().getBuffer();
|
||||
// Equivalent of tessellator.startDrawingQuads()
|
||||
buffer.begin(org.lwjgl.opengl.GL11.GL_QUADS, net.minecraft.client.renderer.vertex.DefaultVertexFormats.POSITION_TEX);
|
||||
// Equivalent of tessellator.addVertex()
|
||||
buffer.pos((double)(x), (double)(y + height), 0).tex((double)((float)(u) * f), (double)((float)(v + height) * f1)).endVertex();
|
||||
buffer.pos((double)(x + width), (double)(y + height), 0).tex((double)((float)(u + width) * f), (double)((float)(v + height) * f1)).endVertex();
|
||||
buffer.pos((double)(x + width), (double)(y), 0).tex((double)((float)(u + width) * f), (double)((float)(v) * f1)).endVertex();
|
||||
buffer.pos((double)(x), (double)(y), 0).tex((double)((float)(u) * f), (double)((float)(v) * f1)).endVertex();
|
||||
// Exactly the same as before.
|
||||
net.minecraft.client.renderer.Tessellator.getInstance().draw();
|
||||
}
|
||||
|
||||
/** Shorthand for {@link WizardryUtilities#drawTexturedRect(int, int, int, int, int, int, int, int)} which draws
|
||||
* the entire texture (u and v are set to 0 and textureWidth and textureHeight are the same as width and height). */
|
||||
@SideOnly(Side.CLIENT)
|
||||
public static void drawTexturedRect(int x, int y, int width, int height){
|
||||
drawTexturedRect(x, y, 0, 0, width, height, width, height);
|
||||
}
|
||||
|
||||
// SECTION NBT and Data Storage
|
||||
// ===============================================================================================================
|
||||
|
||||
/**
|
||||
* Verifies that the given string is a valid string representation of a UUID. More specifically, returns true if and
|
||||
* only if the given string is not null and matches the regular expression:<p>
|
||||
* <center><code>/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/<p></code></center>
|
||||
* which is the regex equivalent of the standard string representation of a UUID as described in {@link UUID#toString()}.
|
||||
* This method is intended to be used as a check to prevent an {@link IllegalArgumentException} from occurring when
|
||||
* calling {@link UUID#fromString(String)}.
|
||||
* @param string The string to be checked
|
||||
* @return Whether the given string is a valid string representation of a UUID
|
||||
* @deprecated UUIDs can now be stored in NBT directly; use that in preference to storing them as strings.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean verifyUUIDString(String string){
|
||||
return string != null && string.matches("/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/");
|
||||
}
|
||||
|
||||
/** Generic method that stores any Map to an NBTTagList, given two functions that convert the key and value
|
||||
* types in that map to subclasses of NBTBase. For what it's worth, there is very little point in using this unless
|
||||
* you can use something more concise than an anonymous class to do the conversion. A lambda expression, or better,
|
||||
* a method reference, would fit nicely. For example, take ExtendedPlayer's use of this to store conjured item
|
||||
* durations:
|
||||
* <p>
|
||||
* <code>properties.setTag("conjuredItems", WizardryUtilities.mapToNBT(this.conjuredItemDurations,
|
||||
* item -> new NBTTagInt(Item.getIdFromItem((Item)item)), NBTTagInt::new));</code>
|
||||
* <p>
|
||||
* This is a lot nicer than simply iterating through the map, because for that you need to use the entry list,
|
||||
* which introduces local variables that aren't really necessary. Notice that, since the values V in the map are
|
||||
* simply Integer objects, a simple constructor reference to NBTTagInt::new can be used instead of a lambda
|
||||
* expression (the Integer is auto-unboxed to int).
|
||||
*
|
||||
* @param <K> The type of key stored in the given Map.
|
||||
* @param <V> The type of value stored in the given Map.
|
||||
* @param <L> The subtype of NBTBase that the keys (of type K) will be converted to.
|
||||
* @param <W> The subtype of NBTBase that the values (of type V) will be converted to.
|
||||
* @param map The Map to be stored.
|
||||
* @param keyFunction A Function that converts the keys in the map to NBT objects that can be stored.
|
||||
* @param valueFunction A Function that converts the values in the map to NBT objects that can be stored.
|
||||
* @param keyTagName The tag name to use for the key tags.
|
||||
* @param valueTagName The tag name to use for the value tags.
|
||||
* @return An NBTTagList that represents the given Map.
|
||||
*/
|
||||
public static <K, V, L extends NBTBase, W extends NBTBase> NBTTagList mapToNBT(Map<K, V> map, Function<K, L> keyFunction, Function<V, W> valueFunction, String keyTagName, String valueTagName){
|
||||
|
||||
NBTTagList tagList = new NBTTagList();
|
||||
|
||||
for(Entry<K, V> entry : map.entrySet()){
|
||||
NBTTagCompound mapping = new NBTTagCompound();
|
||||
mapping.setTag(keyTagName, keyFunction.apply(entry.getKey()));
|
||||
mapping.setTag(valueTagName, valueFunction.apply(entry.getValue()));
|
||||
tagList.appendTag(mapping);
|
||||
}
|
||||
|
||||
return tagList;
|
||||
}
|
||||
|
||||
/** See {@link WizardryUtilities#mapToNBT(Map, Function, Function, String, String)}; this version is for when the
|
||||
* names of the individual key/value tags are unimportant (they default to "key" and "value" respectively). */
|
||||
public static <K, V, L extends NBTBase, W extends NBTBase> NBTTagList mapToNBT(Map<K, V> map, Function<K, L> keyFunction, Function<V, W> valueFunction){
|
||||
return mapToNBT(map, keyFunction, valueFunction, "key", "value");
|
||||
}
|
||||
|
||||
/** Generic method that reads a Map from an NBTTagList, given two functions that convert the key and value tag types
|
||||
* into the key and value types in the returned map. The given NBTTagList remains unchanged after calling this method.
|
||||
*
|
||||
* @param <K> The type of key stored in the returned Map.
|
||||
* @param <V> The type of value stored in the returned Map.
|
||||
* @param <L> The subtype of NBTBase that the keys are stored as.
|
||||
* @param <W> The subtype of NBTBase that the values are stored as.
|
||||
* @param tagList The NBTTagList to be converted. This <b>must</b> be a list of compound tags.
|
||||
* @param keyFunction A Function that converts the generic NBTBase tags in the list to keys of type K for the map.
|
||||
* @param valueFunction A Function that converts the generic NBTBase tags in the list to values of type V for the map.
|
||||
* @param keyTagName The tag name used for the key tags.
|
||||
* @param valueTagName The tag name used for the value tags.
|
||||
* @return A Map containing the keys and values stored in the given NBTTagList. Can be empty, but not null.
|
||||
* @throws ClassCastException If the tags are not of the expected type.
|
||||
* @see WizardryUtilities#mapToNBT(Map, Function, Function, String, String)
|
||||
*/
|
||||
@SuppressWarnings("unchecked") // Intentional, because throwing an exception is appropriate here.
|
||||
public static <K, V, L extends NBTBase, W extends NBTBase> Map<K, V> NBTToMap(NBTTagList tagList, Function<L, K> keyFunction, Function<W, V> valueFunction, String keyTagName, String valueTagName){
|
||||
|
||||
Map<K, V> map = new HashMap<K, V>();
|
||||
|
||||
for(int i=0; i<tagList.tagCount(); i++){
|
||||
NBTTagCompound mapping = tagList.getCompoundTagAt(i);
|
||||
NBTBase keyTag = mapping.getTag(keyTagName);
|
||||
NBTBase valueTag = mapping.getTag(valueTagName);
|
||||
K key = null;
|
||||
try { key = keyFunction.apply((L)keyTag); } catch (ClassCastException e) {
|
||||
Wizardry.logger.error("Error when reading map from NBT: unexpected tag type " + NBTBase.NBT_TYPES[keyTag.getId()], e);
|
||||
}
|
||||
V value = null;
|
||||
try { value = valueFunction.apply((W)valueTag); } catch (ClassCastException e) {
|
||||
Wizardry.logger.error("Error when reading map from NBT: unexpected tag type " + NBTBase.NBT_TYPES[valueTag.getId()], e);
|
||||
}
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/** See {@link WizardryUtilities#NBTToMap(NBTTagList, Function, Function, String, String)}; this version is for when
|
||||
* the names of the individual key/value tags are unimportant (they default to "key" and "value" respectively). */
|
||||
public static <K, V, L extends NBTBase, W extends NBTBase> Map<K, V> NBTToMap(NBTTagList tagList, Function<L, K> keyFunction, Function<W, V> valueFunction){
|
||||
return NBTToMap(tagList, keyFunction, valueFunction, "key", "value");
|
||||
}
|
||||
|
||||
/** Generic method that stores any Collection to an NBTTagList, given a function that converts the elements in
|
||||
* that collection to subclasses of NBTBase. For what it's worth, there is very little point in using this unless
|
||||
* you can use something more concise than an anonymous class to do the conversion. A lambda expression, or better,
|
||||
* a method reference, would fit nicely.
|
||||
*
|
||||
* @param <E> The type of element stored in the given Collection.
|
||||
* @param <T> The NBT tag type that the elements will be converted to.
|
||||
* @param list The Collection to be stored.
|
||||
* @param function A Function that converts the elements in the collection to NBT objects that can be stored.
|
||||
* @return An NBTTagList that represents the given Collection.
|
||||
*/
|
||||
public static <E, T extends NBTBase> NBTTagList listToNBT(Collection<E> list, Function<E, T> function){
|
||||
|
||||
NBTTagList tagList = new NBTTagList();
|
||||
// If the collection is ordered, it will preserve the order, even though we don't know what type it is yet.
|
||||
for(E element : list){
|
||||
tagList.appendTag(function.apply(element));
|
||||
}
|
||||
|
||||
return tagList;
|
||||
}
|
||||
|
||||
/** Generic method that reads a Collection from an NBTTagList, given a function that converts the element tag
|
||||
* types to the element types in the returned collection. The given NBTTagList remains unchanged after calling this
|
||||
* method. Unless the target variable for this method is of type Collection<E>, you will need to create a new
|
||||
* collection containing the elements in the returned collection via that collection's constructor (e.g. {@code new
|
||||
* HashSet<E>(collection)}). <i>Although this method returns a Collection rather than any of its subtypes, it
|
||||
* uses an ArrayList internally to guarantee the order of the elements in the returned collection is the same
|
||||
* as the order in which they were stored. As such, you may safely cast to List should you wish.</i>
|
||||
*
|
||||
* @param <E> The type of element stored in the returned Collection.
|
||||
* @param <T> The subtype of NBTBase that the elements are stored as.
|
||||
* @param tagList The NBTTagList to be converted.
|
||||
* @param function A Function that converts the generic NBTBase tags in the list to elements for the collection.
|
||||
* Chances are you will need to cast the NBTBase tag to whichever NBT tag type you are expecting in order to
|
||||
* access the appropriate getter method.
|
||||
* @return A Collection containing the elements stored in the given NBTTagList. Can be empty, but not null.
|
||||
* @throws ClassCastException If the tags are not of the expected type.
|
||||
*/
|
||||
@SuppressWarnings("unchecked") // Intentional, because throwing an exception is appropriate here.
|
||||
public static <E, T extends NBTBase> Collection<E> NBTToList(NBTTagList tagList, Function<T, E> function){
|
||||
// Uses an ArrayList to guarantee iteration order, and also to permit duplicate elements (which are
|
||||
// perfectly reasonable in this context).
|
||||
Collection<E> list = new ArrayList<E>();
|
||||
// The original tag list should remain unchanged, hence the copy.
|
||||
NBTTagList tagList2 = (NBTTagList) tagList.copy();
|
||||
|
||||
while(!tagList2.hasNoTags()){
|
||||
NBTBase tag = tagList2.removeTag(0);
|
||||
// Why oh why is NBTTagList not parametrised? It even has a tagType field, so it must know!
|
||||
try { list.add(function.apply((T)tag)); } catch (ClassCastException e){
|
||||
Wizardry.logger.error("Error when reading list from NBT: unexpected tag type " + NBTBase.NBT_TYPES[tag.getId()], e);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
|
||||
}
|
||||
|
||||
// TODO: Backport: It has recently become apparent that storing UUIDs as strings is not good practice, so backport these two
|
||||
// methods to 1.7.10 and replace tag.setUniqueId and tag.getUniqueId with their respective contents from 1.10.2.
|
||||
|
||||
/** Returns an NBTTagCompound which contains only the given UUID, stored using
|
||||
* {@link NBTTagCompound#setUniqueId(String, UUID)}. Allows for neater storage to NBTTagLists. */
|
||||
public static NBTTagCompound UUIDtoTagCompound(UUID id){
|
||||
NBTTagCompound tag = new NBTTagCompound();
|
||||
tag.setUniqueId("uuid", id);
|
||||
return tag;
|
||||
}
|
||||
|
||||
/** Wrapper for {@link NBTTagCompound#getUniqueId(String)} which converts an NBTTagCompound directly to a UUID.
|
||||
* Intended to be used as the inverse of {@link WizardryUtilities#UUIDtoTagCompound(UUID)}.*/
|
||||
public static UUID tagCompoundToUUID(NBTTagCompound tag){
|
||||
return tag.getUniqueId("uuid");
|
||||
}
|
||||
|
||||
// SECTION Ally Designation System
|
||||
// ===============================================================================================================
|
||||
|
||||
/**
|
||||
* Returns whether the given target can be attacked by the given attacker. It is up to the caller of this method
|
||||
* to work out what this means; it doesn't necessarily mean the target is completely immune (for example, revenge
|
||||
* targeting might reasonably bypass this). This method is intended for use where the damage is indirect and/or
|
||||
* unavoidable; direct attacks should not check this method. Currently this means the following situations check
|
||||
* this method:
|
||||
* <p>
|
||||
* - AI targeting for summoned creatures<br>
|
||||
* - AI targeting for mind-controlled creatures<br>
|
||||
* - Constructs with an area of effect<br>
|
||||
* - Instantaneous spells with an area of effect around the caster (e.g. forest's curse, thunderstorm)<br>
|
||||
* - Any lightning chaining effects<br>
|
||||
* - Any projectiles which seek targets
|
||||
* <p>
|
||||
* Also note that the friendly fire option is dealt with
|
||||
* in the event handler. This method acts as a sort of wrapper for all the ADS stuff in {@link WizardData}; more
|
||||
* details about the ally designation system can be found there.
|
||||
*
|
||||
* @param attacker The entity that cast the spell originally
|
||||
* @param target The entity being attacked
|
||||
*
|
||||
* @return False under any of the following circumstances, true otherwise:
|
||||
* <p>
|
||||
* - Either entity is null
|
||||
* <p>
|
||||
* - The target is the attacker (this isn't as stupid as it sounds - anything with an AoE might cause this to be
|
||||
* true, as can summoned creatures)
|
||||
* <p>
|
||||
* - The target and the attacker are both players and the target is an ally of the attacker (but the
|
||||
* attacker need not be an ally of the target)
|
||||
* <p>
|
||||
* - The target is a creature that was summoned/controlled by the attacker or by an ally of the attacker.
|
||||
*/
|
||||
public static boolean isValidTarget(Entity attacker, Entity target){
|
||||
|
||||
if(attacker == null || target == null) return false;
|
||||
|
||||
// Tests whether the target is the attacker
|
||||
if(target == attacker) return false;
|
||||
|
||||
// Tests whether the target is a creature that was summoned by the attacker
|
||||
if(target instanceof ISummonedCreature && ((ISummonedCreature)target).getCaster() == attacker){
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tests whether the target is a creature that was mind controlled by the attacker
|
||||
if(target instanceof EntityLiving && ((EntityLivingBase)target).isPotionActive(WizardryPotions.mind_control)){
|
||||
|
||||
NBTTagCompound entityNBT = target.getEntityData();
|
||||
|
||||
if(entityNBT != null && entityNBT.hasKey(MindControl.NBT_KEY)){
|
||||
if(attacker == WizardryUtilities.getEntityByUUID(target.worldObj, entityNBT.getUniqueId(MindControl.NBT_KEY))){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ally section
|
||||
if(attacker instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker) != null){
|
||||
|
||||
if(target instanceof EntityPlayer){
|
||||
// Tests whether the target is an ally of the attacker
|
||||
if(WizardData.get((EntityPlayer)attacker).isPlayerAlly((EntityPlayer)target)){
|
||||
return false;
|
||||
}
|
||||
|
||||
}else if(target instanceof ISummonedCreature){
|
||||
// Tests whether the target is a creature that was summoned by an ally of the attacker
|
||||
if(((ISummonedCreature)target).getCaster() instanceof EntityPlayer
|
||||
&& WizardData.get((EntityPlayer)attacker).isPlayerAlly((EntityPlayer)((ISummonedCreature)target).getCaster())){
|
||||
return false;
|
||||
}
|
||||
|
||||
}else if(target instanceof EntityLiving && ((EntityLivingBase)target).isPotionActive(WizardryPotions.mind_control)){
|
||||
// Tests whether the target is a creature that was mind controlled by an ally of the attacker
|
||||
NBTTagCompound entityNBT = target.getEntityData();
|
||||
|
||||
if(entityNBT != null && entityNBT.hasKey(MindControl.NBT_KEY)){
|
||||
|
||||
Entity controller = WizardryUtilities.getEntityByUUID(target.worldObj, entityNBT.getUniqueId(MindControl.NBT_KEY));
|
||||
|
||||
if(controller instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker).isPlayerAlly((EntityPlayer)controller)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Helper method for testing if the second player is an ally of the first player. Makes the code neater. */
|
||||
public static boolean isPlayerAlly(EntityPlayer allyOf, EntityPlayer possibleAlly) {
|
||||
|
||||
WizardData properties = WizardData.get(allyOf);
|
||||
|
||||
if(properties != null && properties.isPlayerAlly(possibleAlly)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// SECTION Loot and Weighting
|
||||
// ===============================================================================================================
|
||||
|
||||
/**
|
||||
* See {@link WizardryUtilities#getStandardWeightedRandomSpellId(Random, boolean)}. nonContinuous defaults to false.
|
||||
*/
|
||||
public static int getStandardWeightedRandomSpellId(Random random){
|
||||
return getStandardWeightedRandomSpellId(random, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method which gets a spell id according to the standard weighting. The tier is a weighted random value;
|
||||
* the actual spell within that tier is completely random. Will not return the id of a spell which has been
|
||||
* disabled in the config. This is for simple stuff like chests and drops; more complex generators like wizard
|
||||
* trades don't use this method.
|
||||
* <p>
|
||||
* For reference, the standard weighting is as follows:
|
||||
* Basic: 60%, Apprentice: 25%, Advanced: 10%, Master: 5%
|
||||
*
|
||||
* @param random An instance of {@link Random} to use for RNG
|
||||
* @param nonContinuous Whether the spells must be non-continuous (used for scrolls)
|
||||
* @return A random spell id number
|
||||
*/
|
||||
public static int getStandardWeightedRandomSpellId(Random random, boolean nonContinuous){
|
||||
|
||||
Tier tier = Tier.getWeightedRandomTier(random);
|
||||
|
||||
List<Spell> spells = Spell.getSpells(new Spell.TierElementFilter(tier, null));
|
||||
if(nonContinuous) spells.retainAll(Spell.getSpells(Spell.nonContinuousSpells));
|
||||
|
||||
// Ensures the tier chosen actually has spells in it, and if not uses BASIC instead.
|
||||
if(spells.isEmpty()){
|
||||
spells = Spell.getSpells(new Spell.TierElementFilter(Tier.BASIC, null));
|
||||
if(nonContinuous) spells.retainAll(Spell.getSpells(Spell.nonContinuousSpells));
|
||||
}
|
||||
|
||||
// Finds a random spell in the list and returns its id.
|
||||
return spells.get(random.nextInt(spells.size())).id();
|
||||
}
|
||||
|
||||
// TODO: These methods need a rethink. What are we trying to achieve with them? Should each use case look in the same
|
||||
// pool of items? For example, might we (or someone else) want to have a wand which can generate in chests, but is
|
||||
// not used by wizards?
|
||||
|
||||
// I reckon this should be strictly for cases where we only ever want the standard wand set, i.e. wizards' gear, etc.
|
||||
|
||||
/**
|
||||
* Helper method to return the appropriate armour item based on element and slot. As of Wizardry 2.1, this uses
|
||||
* the immutable map stored in {@link WizardryItems#ARMOUR_MAP}.
|
||||
* Currently used to iterate through armour for registering charging recipes and for chest generation.
|
||||
* @param element The EnumElement of the armour required. Null will be converted to {@link Element#MAGIC}.
|
||||
* @param slot EntityEquipmentSlot of the armour piece required
|
||||
* @return The armour item which corresponds to the given element and slot, or null if no such item exists.
|
||||
* @throws IllegalArgumentException if the given slot is not an armour slot.
|
||||
*/
|
||||
public static Item getArmour(Element element, EntityEquipmentSlot slot){
|
||||
if(slot == null || slot.getSlotType() != Type.ARMOR) throw new IllegalArgumentException("Must be a valid armour slot");
|
||||
if(element == null) element = Element.MAGIC;
|
||||
return WizardryItems.ARMOUR_MAP.get(ImmutablePair.of(slot, element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to return the appropriate wand based on tier and element.As of Wizardry 2.1, this uses
|
||||
* the immutable map stored in {@link WizardryItems#WAND_MAP}.
|
||||
* Currently used in the packet handler for upgrading wands, for chest generation and to iterate through
|
||||
* wands for charging recipes.
|
||||
* @param tier The tier of the wand required.
|
||||
* @param element The element of the wand required. Null will be converted to {@link Element#MAGIC}.
|
||||
* @return The wand item which corresponds to the given element and slot, or null if no such item exists.
|
||||
* @throws NullPointerException if the given tier is null.
|
||||
*/
|
||||
public static Item getWand(Tier tier, Element element){
|
||||
if(tier == null) throw new NullPointerException("The given tier cannot be null.");
|
||||
return WizardryItems.WAND_MAP.get(ImmutablePair.of(tier, element));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user