That's one heck of a commit you've got there...

I may have got a bit behind with version control. A lot behind, in fact. Maybe I'll go back and split this sometime - then again, I probably won't. But hey, at least it's here!
This commit is contained in:
Electroblob77
2019-08-18 00:04:18 +01:00
parent 2680d02304
commit f37812be3e
1564 changed files with 47652 additions and 12984 deletions
@@ -0,0 +1,234 @@
package electroblob.wizardry.util;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.spell.MindControl;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.IEntityOwnable;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
/**
* Contains some useful static methods for interacting with the ally designation system. Also handles the friendly fire
* setting. This was split off from {@link WizardryUtilities} as of wizardry 4.2 in an effort to make the code easier to
* navigate.
*/
@Mod.EventBusSubscriber
public final class AllyDesignationSystem {
private AllyDesignationSystem(){} // No instances!
/** Set of constants for each of the four friendly fire settings. */
public enum FriendlyFire {
ALL("All", false, false),
ONLY_PLAYERS("Only players", false, true),
ONLY_OWNED("Only summoned/tamed creatures", true, false),
NONE("None", true, true);
/** Constant array storing the names of each of the constants, in the order they are declared. */
public static final String[] names;
static {
names = new String[values().length];
for(FriendlyFire setting : values()){
names[setting.ordinal()] = setting.name;
}
}
/** The readable name for this friendly fire setting that will be displayed on the button in the config GUI. */
public final String name;
public final boolean blockPlayers;
public final boolean blockOwned;
FriendlyFire(String name, boolean blockPlayers, boolean blockOwned){
this.name = name;
this.blockPlayers = blockPlayers;
this.blockOwned = blockOwned;
}
/**
* Gets a friendly fire setting from its string name (ignoring case), or ALL if the given name is not a valid
* setting.
*/
public static FriendlyFire fromName(String name){
for(FriendlyFire setting : values()){
if(setting.name.equalsIgnoreCase(name)) return setting;
}
Wizardry.logger.info("Invalid string for the friendly fire setting. Using default (all) instead.");
return ALL;
}
}
/**
* 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></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></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 AllyDesignationSystem 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></p>
* - The target is null
* <p></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></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></p>
* - The target is a creature that was summoned/controlled by the attacker or by an ally of the attacker.
* <p></p>
* - The target is a creature that was tamed by the attacker or by an ally of the attacker
* (see {@link net.minecraft.entity.IEntityOwnable}).
* <p></p>
* <i>As of wizardry 4.1.2, this method now returns <b>true</b> instead of false if the attacker is null. This
* is because in the vast majority of cases, it makes more sense this way: if a construct has no caster, it
* should affect all entities; if a minion has no caster it should target all entities; etc.</i>
*/
public static boolean isValidTarget(Entity attacker, Entity target){
// Always return true if the attacker is null
if(attacker == null) return true;
// Always return false if the target is null
if(target == null) return false;
// Tests whether the target is the attacker
if(target == attacker) return false;
// I really shouldn't need to do this, but fake players seem to break stuff...
if(target instanceof FakePlayer) 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 summoned/tamed (or is otherwise owned) by the attacker
if(target instanceof IEntityOwnable && ((IEntityOwnable)target).getOwner() == 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.hasUniqueId(MindControl.NBT_KEY)){
if(attacker == WizardryUtilities.getEntityByUUID(target.world,
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 IEntityOwnable){
// Tests whether the target is a creature that was summoned/tamed by an ally of the attacker
if(isOwnerAlly((EntityPlayer)attacker, (IEntityOwnable)target));
}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.world, entityNBT.getUniqueId(MindControl.NBT_KEY));
if(controller instanceof EntityPlayer && WizardData.get((EntityPlayer)attacker).isPlayerAlly((EntityPlayer)controller)){
return false;
}
}
}
}
return true;
}
/** Umbrella method that covers both {@link AllyDesignationSystem#isPlayerAlly(EntityPlayer, EntityPlayer)} and
* {@link AllyDesignationSystem#isOwnerAlly(EntityPlayer, IEntityOwnable)}, returning true if the given
* {@link EntityLivingBase} is either owned by the given player, an ally of the given player or owned by an ally
* of the given player. This is generally used to determine targets for healing or other group buffs. */
public static boolean isAllied(EntityPlayer allyOf, EntityLivingBase possibleAlly){
return (possibleAlly instanceof EntityPlayer && isPlayerAlly(allyOf, (EntityPlayer)possibleAlly))
|| (possibleAlly instanceof IEntityOwnable && (((IEntityOwnable)possibleAlly).getOwner() == allyOf
|| isOwnerAlly(allyOf, (IEntityOwnable)possibleAlly)));
}
/** Helper method for testing if the second player is an ally of the first player. Makes the code neater.
* @see AllyDesignationSystem#isOwnerAlly(EntityPlayer, IEntityOwnable) */
public static boolean isPlayerAlly(EntityPlayer allyOf, EntityPlayer possibleAlly){
WizardData data = WizardData.get(allyOf);
return data != null && data.isPlayerAlly(possibleAlly);
}
/** Helper method for testing if the given {@link net.minecraft.entity.IEntityOwnable}'s owner is an ally of the
* given player. This works even when the owner is not logged in, though it may not correctly respect teams when
* that is the case. */
public static boolean isOwnerAlly(EntityPlayer allyOf, IEntityOwnable ownable){
WizardData data = WizardData.get(allyOf);
if(data == null) return false;
Entity owner = ownable.getOwner();
return owner instanceof EntityPlayer ? data.isPlayerAlly((EntityPlayer)owner) : data.isPlayerAlly(ownable.getOwnerId());
}
@SubscribeEvent
public static void onLivingAttackEvent(LivingAttackEvent event){
if(event.getSource() != null && event.getSource().getTrueSource() instanceof EntityPlayer
&& event.getSource() instanceof IElementalDamage){
if(event.getEntity() instanceof EntityPlayer){
// Prevents any magic damage to allied players if friendly fire is disabled for players
if(Wizardry.settings.friendlyFire.blockPlayers && isPlayerAlly((EntityPlayer)event.getSource().getTrueSource(), (EntityPlayer)event.getEntity())){
event.setCanceled(true);
}
}else{
// Prevents any magic damage to entities owned by allied players if friendly fire is disabled for owned creatures
// Since we're dealing with players separately we might as well just use isAllied
if(Wizardry.settings.friendlyFire.blockOwned && isAllied((EntityPlayer)event.getSource().getTrueSource(), event.getEntityLiving())){
event.setCanceled(true);
}
}
}
}
}
@@ -1,82 +0,0 @@
package electroblob.wizardry.util;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import electroblob.wizardry.Wizardry;
import net.minecraft.advancements.ICriterionInstance;
import net.minecraft.advancements.ICriterionTrigger;
import net.minecraft.advancements.PlayerAdvancements;
import net.minecraft.advancements.critereon.AbstractCriterionInstance;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.ResourceLocation;
/**
* This class implements a generic custom advancement trigger that can be fired from any point in
* the code. This replaces the achievement system in instances where the JSON advancement descriptions
* cannot properly capture the advancement-worthy events. Where possible, advancement conditions
* should be triggered by JSON descriptions and vanilla advancement triggers.
*
* @author 12foo
* @since 4.1.0
*/
public class CustomAdvancementTrigger implements ICriterionTrigger<CustomAdvancementTrigger.Instance> {
private final ResourceLocation id;
private final SetMultimap<PlayerAdvancements, Listener<? extends ICriterionInstance>> listeners = HashMultimap.create();
/**
* This is a dummy criterion instance that does nothing on its own (but it is bound to this
* trigger, and via listeners to the player). We later fire this manually when we want the
* advancement to happen.
*/
public static class Instance extends AbstractCriterionInstance {
public Instance(ResourceLocation triggerId) {
super(triggerId);
}
}
public CustomAdvancementTrigger(String name) {
super();
id = new ResourceLocation(Wizardry.MODID, name);
}
@Override
public ResourceLocation getId() {
return id;
}
@Override
public void addListener(PlayerAdvancements playerAdvancementsIn, Listener<Instance> listener) {
listeners.put(playerAdvancementsIn, listener);
}
@Override
public void removeListener(PlayerAdvancements playerAdvancementsIn, Listener<Instance> listener) {
listeners.remove(playerAdvancementsIn, listener);
}
@Override
public void removeAllListeners(PlayerAdvancements playerAdvancementsIn) {
listeners.removeAll(playerAdvancementsIn);
}
@Override
public Instance deserializeInstance(JsonObject json, JsonDeserializationContext context) {
// Every time a trigger with this name is deserialized from the JSON, we just return a new
// dummy criterion instance.
return new CustomAdvancementTrigger.Instance(id);
}
public void triggerFor(EntityPlayer player) {
// Fire our dummy criterion manually on all advancements of the player, thereby granting
// the ones that match it.
if (player instanceof EntityPlayerMP) {
final PlayerAdvancements advances = ((EntityPlayerMP) player).getAdvancements();
listeners.get(advances).forEach((listener) -> listener.grantCriterion(advances));
}
}
}
@@ -0,0 +1,86 @@
package electroblob.wizardry.util;
import com.google.common.collect.Maps;
import net.minecraft.util.SoundCategory;
import net.minecraftforge.common.util.EnumHelper;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.util.Map;
/**
* Add a new CONSTANT and reference name to net.minecraft.util.SoundCategory
*
* This allows the display of a volume control in the "Music & Sound Options" dialog.
* Unfortunately the GuiScreenOptionsSounds dialog does not auto size
* properly and move the Done button lower on the screen.
*
* To initialize the class create an instance during FMLPreInitializationEvent in
* the file with the @Mod annotation or your common proxy class.
*
* Usage example: static final SoundCategory SC_MXTUNE = MODSoundCategory.add("MXTUNE");
*
* The language file key is "soundCategory.mxtune"
* The game settings "options.txt" key is "soundCategory_mxtune"
*
* To use the MXTUNE enum constant in code it must be referenced by name because
* SoundCategory.MXTUNE does not exist at compile time.
* e.g. SoundCategory.getByName("mxtune");
*
* @author Paul Boese aka Aeronica (modified for 1.12.2 and for conciseness/clarity by Electroblob)
* @see <a href=http://www.minecraftforge.net/forum/topic/42439-adding-additional-soundcategorys/>
* www.minecraftforge.net/forum/topic/42439-adding-additional-soundcategorys/</a>
*/
public final class CustomSoundCategory {
private static final String SRG_soundLevels = "field_186714_aM";
private static final String SRG_SOUND_CATEGORIES = "field_187961_k";
// >> Electroblob: Don't know why this was instantiated at all, surely it's a static helper class?
private CustomSoundCategory(){}
/**
* Adds a new custom sound category, performing the necessary changes to GameSettings and
*
* @param name A unique name for the sound category
* @return The resulting SoundCategory object
* @throws IllegalArgumentException if name is not unique
*/
public static SoundCategory add(String name){
Map<String, SoundCategory> SOUND_CATEGORIES;
String constantName;
String referenceName;
SoundCategory soundCategory;
// >> Electroblob: Constructors were unnecessary since strings are immutable
constantName = name.toUpperCase().replace(" ", "");
referenceName = constantName.toLowerCase();
// >> Electroblob: Removed array surrounding varargs argument
soundCategory = EnumHelper.addEnum(SoundCategory.class , constantName, new Class[]{String.class}, referenceName);
SOUND_CATEGORIES = ObfuscationReflectionHelper.getPrivateValue(SoundCategory.class, SoundCategory.VOICE ,"SOUND_CATEGORIES", SRG_SOUND_CATEGORIES);
if (SOUND_CATEGORIES.containsKey(referenceName))
// >> Electroblob: changed from Error to IllegalArgumentException
throw new IllegalArgumentException("Clash in Sound Category name pools! Cannot insert " + constantName);
SOUND_CATEGORIES.put(referenceName, soundCategory);
if (FMLLaunchHandler.side() == Side.CLIENT) setSoundLevels();
return soundCategory;
}
/** Game sound level options settings only exist on the client side */
@SideOnly(Side.CLIENT)
private static void setSoundLevels(){
// SoundCategory now contains 'name' sound category so build a new map
// >> Electroblob: Converted to local variable
Map<SoundCategory, Float> soundLevels = Maps.newEnumMap(SoundCategory.class);
// Replace the map in the GameSettings.class
// >> Electroblob: Fully qualified names, because this class gets loaded on both sides
ObfuscationReflectionHelper.setPrivateValue(net.minecraft.client.settings.GameSettings.class,
net.minecraft.client.Minecraft.getMinecraft().gameSettings, soundLevels,
"soundLevels", SRG_soundLevels);
}
}
@@ -1,9 +1,7 @@
package electroblob.wizardry.util;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.util.MagicDamage.DamageType;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@@ -43,10 +41,6 @@ public interface IElementalDamage {
&& ((IElementalDamage)event.getSource()).getType() == DamageType.SHOCK){
// Charges creepers when they are hit by shock damage
WizardryUtilities.chargeCreeper((EntityCreeper)event.getEntityLiving());
// Gives the player that caused the shock damage the 'It's Gonna Blow' achievement
if(event.getSource().getTrueSource() instanceof EntityPlayer){
WizardryAdvancementTriggers.charge_creeper.triggerFor((EntityPlayer)event.getSource().getTrueSource());
}
}
}
}
@@ -0,0 +1,47 @@
package electroblob.wizardry.util;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.util.math.BlockPos;
import javax.annotation.concurrent.Immutable;
/** Simple wrapper class that stores a {@link BlockPos} and an integer dimension ID. */
@Immutable
public class Location {
public final BlockPos pos;
public final int dimension;
public Location(BlockPos pos, int dimension){
this.pos = pos;
this.dimension = dimension;
}
/** Returns true if the given location refers to the same coordinates and dimension as this one. */
@Override
public boolean equals(Object that){
if(this == that) return true;
if(that instanceof Location){
return this.pos.equals(((Location)that).pos) && this.dimension == ((Location)that).dimension;
}
return false;
}
/** Creates and returns an {@link NBTTagCompound} representing this location. The returned compound tag is the
* same as that returned by {@link NBTUtil#createPosTag(BlockPos)}, but with an extra "dimension" key. */
public NBTTagCompound toNBT(){
NBTTagCompound nbt = NBTUtil.createPosTag(pos);
nbt.setInteger("dimension", dimension);
return nbt;
}
/** Creates a new {@code Location} from the given {@link NBTTagCompound}. The given compound tag should be the
* same as that returned by {@link NBTUtil#createPosTag(BlockPos)}, but with an extra "dimension" key. */
public static Location fromNBT(NBTTagCompound nbt){
return new Location(NBTUtil.getPosFromTag(nbt), nbt.getInteger("dimension"));
}
}
@@ -1,37 +1,15 @@
package electroblob.wizardry.util;
import java.util.ArrayList;
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 electroblob.wizardry.entity.living.*;
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.EntityWitherSkeleton;
import net.minecraft.entity.monster.EntityZombie;
import net.minecraft.entity.monster.*;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EntityDamageSource;
import java.util.*;
// 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. getTrueSource() will return
@@ -47,12 +25,12 @@ import net.minecraft.util.EntityDamageSource;
/**
* <i>"Ouch, that hurt!"</i>
* <p>
* <p></p>
* 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>
* <p></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
@@ -71,7 +49,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage
/** The name of the damagesource for indirect magic damage from the wizardry mod. */
public static final String INDIRECT_MAGIC_DAMAGE = "indirect_wizardry_magic";
// 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 static final Map<Class<? extends Entity>, DamageType[]> immunityMapping = new HashMap<>();
private final DamageType type;
private final boolean isRetaliatory;
@@ -82,29 +60,29 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage
* them.
*/
public enum DamageType {
/** Generic magic damage from the wizardry mod. Like vanilla magic damage, except it doesn't bypass armour. */
/** Generic magic damage from wizardry. 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
/** Fire damage from wizardry. 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, and is affected by the fire protection
* enchantment. */
FIRE,
/** Frost (ice) damage from the wizardry mod. Snow golems, ice wraiths and ice giants are immune. */
/** Frost (ice) damage from wizardry. 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 (lightning) damage from wizardry. Lightning wraiths and storm elementals are immune. */
SHOCK,
/** Wither damage from the wizardry mod. Withers, wither skeletons and shadow wraiths are immune. */
/** Wither damage from wizardry. Withers, wither skeletons and shadow wraiths are immune. */
WITHER,
/** Poison damage from the wizardry mod. Spiders, cave spiders and undead mobs are immune. */
/** Poison damage from wizardry. Spiders, cave spiders and undead mobs are immune. */
POISON,
/** Force damage from the wizardry mod. */ // Insubstantial creatures (ghast, shadow wraith, etc.) are immune?
/** Force damage from wizardry. */ // Insubstantial creatures (ghast, shadow wraith, etc.) are immune?
FORCE,
/** Blast damage from the wizardry mod. Affected by the blast protection enchantment. */
/** Blast damage from wizardry. Affected by the blast protection enchantment. */
BLAST,
/** Radiant damage from the wizardry mod. */
RADIANT;
/** Radiant damage from wizardry. */
RADIANT
}
static{
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.
@@ -118,6 +96,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage
setEntityImmunities(EntityStormElemental.class, DamageType.FIRE, DamageType.SHOCK);
setEntityImmunities(EntityWither.class, DamageType.FIRE, DamageType.WITHER);
setEntityImmunities(EntitySnowman.class, DamageType.FROST);
setEntityImmunities(EntityPolarBear.class, DamageType.FROST);
setEntityImmunities(EntityIceWraith.class, DamageType.FROST);
setEntityImmunities(EntityIceGiant.class, DamageType.FROST);
setEntityImmunities(EntityLightningWraith.class, DamageType.SHOCK);
@@ -165,7 +144,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage
/** 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 = immunityMapping.get(entityType) == null ? new ArrayList<DamageType>()
List<DamageType> immunities = immunityMapping.get(entityType) == null ? new ArrayList<>()
: Arrays.asList(immunityMapping.get(entityType));
immunities.add(immunity);
// Apparently putting 0 here works just fine.
@@ -178,7 +157,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage
* 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>
* <p></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.
*
@@ -186,7 +165,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage
* @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).
* might reasonably affect creatures that are usually immune to wither effects).
* @return A damagesource object of type EntityDamageSource
*/
public static DamageSource causeDirectMagicDamage(Entity caster, DamageType type){
@@ -198,7 +177,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage
* 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>
* <p></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.
*
@@ -206,7 +185,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage
* @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).
* might reasonably affect creatures that are usually 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
@@ -221,7 +200,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage
* 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>
* <p></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.
*
@@ -230,7 +209,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage
* @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).
* might reasonably affect creatures that are usually immune to wither effects).
* @return A damagesource object of type EntityDamageSourceIndirect
*/
public static DamageSource causeIndirectMagicDamage(Entity magic, Entity caster, DamageType type){
@@ -242,7 +221,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage
* 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>
* <p></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.
*
@@ -251,7 +230,7 @@ public class MagicDamage extends EntityDamageSource implements IElementalDamage
* @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).
* might reasonably affect creatures that are usually 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
@@ -0,0 +1,231 @@
package electroblob.wizardry.util;
import electroblob.wizardry.Wizardry;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import java.util.*;
import java.util.function.Function;
/**
* Contains a number of useful static methods for interacting with NBT data, particularly involving collections.
* This was split off from {@link WizardryUtilities} as of wizardry 4.2 in an effort to make the code easier to navigate.
*
* @author Electroblob
* @since Wizardry 4.2
*/
public final class NBTExtras {
private NBTExtras(){} // No instances!
/**
* 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></p>
* <code>properties.setTag("conjuredItems", WizardryUtilities.mapToNBT(this.conjuredItemDurations,
* item -> new NBTTagInt(Item.getIdFromItem((Item)item)), NBTTagInt::new));</code>
* <p></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(Map.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 NBTExtras#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 NBTExtras#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<>();
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 NBTExtras#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");
}
/**
* Stores the given {@link Collection} to an {@link NBTTagList} and returns it, converting the elements in the
* collection to NBT tags (subclasses of {@link NBTBase}) according to the supplied mapper function.
*
* @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 mapper A function that converts the elements in the collection to NBT objects that can be stored.
* @return An {@code NBTTagList} that represents the given collection.
*/
public static <E, T extends NBTBase> NBTTagList listToNBT(Collection<E> list, Function<E, T> mapper){
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(mapper.apply(element));
}
return tagList;
}
/**
* Reads a {@link Collection} from the given {@link NBTTagList}, given a function that converts the element tag
* types to the element types in the returned collection. The given {@code NBTTagList} remains unchanged after
* calling this method. Unless the target variable for this method is of type {@code Collection}, 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)}).
* <p></p>
* <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.</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<>();
// The original tag list should remain unchanged, hence the copy.
NBTTagList tagList2 = tagList.copy();
while(!tagList2.isEmpty()){
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;
}
/**
* Removes the UUID with the given key from the given NBT tag, if any. Why this doesn't exist in vanilla I have
* no idea.
* <p></p>
* <i>Usage note: this method complements {@link NBTTagCompound#setUniqueId(String, UUID)} and
* {@link NBTTagCompound#getUniqueId(String)}, which store UUIDs by appending "Most" and "Least" to the given
* key to store the most and least significant UUID bits respectively. It will not work for the UUID methods in
* {@link net.minecraft.nbt.NBTUtil}, which store the long values under "M" and "L" in their own compound tag.</i>
*/
public static void removeUniqueId(NBTTagCompound tag, String key){
tag.removeTag(key + "Most");
tag.removeTag(key + "Least");
}
/**
* Returns an NBTTagCompound which contains only the given UUID, stored using
* {@link NBTTagCompound#setUniqueId(String, UUID)}. Allows for neater storage to NBTTagLists.
* @deprecated Use {@link net.minecraft.nbt.NBTUtil#createUUIDTag(UUID)}. Note that this will break backwards
* compatibility because it uses "M" and "L" instead of "uuidMost" and "uuidLeast".
*/
@Deprecated
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 NBTExtras#UUIDtoTagCompound(UUID)}.
* @deprecated Use {@link net.minecraft.nbt.NBTUtil#getUUIDFromTag(NBTTagCompound)}. Note that this will break
* backwards compatibility because it uses "M" and "L" instead of "uuidMost" and "uuidLeast".
*/
@Deprecated
public static UUID tagCompoundToUUID(NBTTagCompound tag){
return tag.getUniqueId("uuid");
}
}
@@ -1,45 +1,51 @@
package electroblob.wizardry.util;
import java.util.Random;
import electroblob.wizardry.Wizardry;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import java.util.Random;
/**
* <i>"Don't waste time spawning particles manually - let {@code ParticleBuilder} do the work for you!"</i>
* <p>
* Singleton class that builds wizardry particles. This is an alternative (and neater, I think) solution to using varargs.
* All building methods are chainable, so particles can be created using only one line of code, similar to the
* {@code BufferBuilder} system. The number of different combinations of parameters now required for the various particle
* types in wizardry made the method overloads in the proxies very cumbersome and inevitably resulted in redundant
* parameters, which made the code messy and hard to read. Those methods have now been removed.
* <p>
* <p></p>
* Singleton class that builds wizardry particles. This is an alternative (and neater, I think) solution to vanilla's
* varargs-based system. All building methods are chainable, so particles can be created using only one line of code,
* similar to how {@code BufferBuilder} is used for drawing vertices. This class replaces the particle spawning methods
* in wizardry's proxies.
* <p></p>
* {@link ParticleBuilder#instance} retrieves the static instance of the particle builder. Use
* {@link ParticleBuilder#particle(Type)} to start building a particle, or alternatively use the static
* convenience version {@link ParticleBuilder#create(Type)}. Use {@link ParticleBuilder#spawn(World)}
* {@link ParticleBuilder#particle(ResourceLocation)} to start building a particle, or alternatively use the static
* convenience version {@link ParticleBuilder#create(ResourceLocation)}. Use {@link ParticleBuilder#spawn(World)}
* to finish building and spawn the particle. Between these two, a variety of parameters can be set using the various
* setter methods (see individual method descriptions for more details). These, along with {@code ParticleBuilder.particle(...)},
* return the particle builder instance, allowing them to be chained together to spawn particles using a single line of code.
* If any parameters are unspecified these will default to certain values, which may or may not depend on the particle type.
* Not all parameters affect all particles. Again, see individual method descriptions for more details.
* <p>
* <p></p>
* For example, a typical call to the particle builder might look something like this:
* <p>
* <p></p>
* <code>ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(vx, vy, vz).clr(r, g, b).spawn(world);</code>
* <p>
* <p></p>
* It also goes without saying that <b>this class should only ever be used client-side</b>. Attempting to spawn particles
* on the server side will not work and will print a warning to the console.
* @author Electroblob
* @since Wizardry 4.2
*/
/* This isn't strictly a builder class in the traditional sense, because rather than returning the built object at the
* end, it sends it to be processed instead and returns nothing. It's also lazy, see the comment about builder variables
* below. */
// The number of different combinations of parameters now required for the various particle
// types in wizardry made the method overloads in the proxies very cumbersome and inevitably resulted in redundant
// parameters, which made the code messy and hard to read. Those methods have now been removed.
// Strictly speaking, this isn't a builder class in the traditional sense, because rather than returning the built
// object at the end, it sends it to be processed instead and returns nothing. Additionally, unlike most builders
// it's a singleton, because it's likely to be called very frequently and there's no point making a new instance
// every time and clogging the heap with objects. It's also lazy, see the comment about builder variables below.
public final class ParticleBuilder {
/** The static instance of the particle builder. */
@@ -51,7 +57,7 @@ public final class ParticleBuilder {
// Builder variables
// We can't just store a particle and set its parameters in the builder methods, because the server won't like having
// a field of a client-only type
private Type type;
private ResourceLocation type;
private double x, y, z;
private double vx, vy, vz;
private float r, g, b;
@@ -66,36 +72,66 @@ public final class ParticleBuilder {
private Entity entity;
private float yaw, pitch;
private double tx, ty, tz;
private double tvx, tvy, tvz;
private Entity target;
private long seed;
private double length;
/** Enum constants representing the different types of particle added by wizardry. As of 4.2.0, this has been moved
* from its own file {@code WizardryParticleType} to inside {@link ParticleBuilder}. This allowed its name to be
* shortened to simply {@code Type}, making most references more concise. References in classes where another
* {@code Type} is also used can simply refer to the full name, {@code ParticleBuilder.Type}, which is no more verbose
* than before.
* <p>
/**
* {@link ResourceLocation} constants representing the different types of particle added by wizardry. These
* effectively replace the enum {@code WizardryParticleType} from previous versions.
* <p></p>
* Individual constants have comments detailing their corresponding default parameters. A range of values indicates
* randomness. */
public enum Type {
/** 3D-rendered light-beam particle.<p><b>Defaults:</b><p>Lifetime: 1 tick<br> Colour: white */ BEAM,
/** Helical animated 'buffing' particle.<p><b>Defaults:</b><p>Lifetime: 15 ticks
* <br>Velocity: (0, 0.27, 0)<br>Colour: white */ BUFF,
/** Spiral particle, like potions.<p><b>Defaults:</b><p>Lifetime: 8-40 ticks<br>Colour: white */ DARK_MAGIC,
/** Single pixel particle.<p><b>Defaults:</b><p>Lifetime: 16-80 ticks<br>Colour: white */ DUST,
/** Rapid flash, like fireworks.<p><b>Defaults:</b><p>Lifetime: 6 ticks<br>Colour: white */ FLASH,
/** Small shard of ice.<p><b>Defaults:</b><p>Lifetime: 8-40 ticks<br>Gravity: true */ ICE,
/** Single leaf.<p><b>Defaults:</b><p>Lifetime: 10-15 ticks<br>Velocity: (0, -0.03, 0)
* <br>Colour: green/brown */ LEAF,
/** 3D-rendered lightning particle.<p><b>Defaults:</b><p>Lifetime: 3 ticks<br> Colour: blue */ LIGHTNING,
/** 2D lightning effect, normally on the ground.<p><b>Defaults:</b><p>Lifetime: 7 ticks
* <br>Facing: up */ LIGHTNING_PULSE,
/** Bubble that doesn't burst in air.<p><b>Defaults:</b><p>Lifetime: 8-40 ticks */ MAGIC_BUBBLE,
/** Scaleable, moving flame.<p><b>Defaults:</b><p>Lifetime: 8-40 ticks<br> */ MAGIC_FIRE,
/** Soft-edged round particle.<p><b>Defaults:</b><p>Lifetime: 8-40 ticks<br>Colour: white */ PATH,
/** Scorch mark.<p><b>Defaults:</b><p>Lifetime: 100-140 ticks<br>Colour: black<br>Fade: black */ SCORCH,
/** Snowflake particle.<p><b>Defaults:</b><p>Lifetime: 40-50 ticks<br>Velocity: (0, -0.02, 0) */ SNOW,
/** Animated lightning particle.<p><b>Defaults:</b><p>Lifetime: 3 ticks */ SPARK,
/** Animated sparkle particle.<p><b>Defaults:</b><p>Lifetime: 48-60 ticks<br>Colour: white */ SPARKLE
* randomness.
* <p></p>
* To register your own particle types, use {@link electroblob.wizardry.client.particle.ParticleWizardry#registerParticle(
* ResourceLocation, electroblob.wizardry.client.particle.ParticleWizardry.IWizardryParticleFactory)
* ParticleWizardry.registerParticle(ResourceLocation, IWizardryParticleFactory)}.
*/
// This was originally an enum, but I think having 'Type' explicitly declared is quite nice so I've left it as a
// nested class.
public static class Type {
/** 3D-rendered light-beam particle.<p></p><b>Defaults:</b><br>Lifetime: 1 tick<br> Colour: white */
public static final ResourceLocation BEAM = new ResourceLocation(Wizardry.MODID,"beam");
/** Helical animated 'buffing' particle.<p></p><b>Defaults:</b><br>Lifetime: 15 ticks
* <br>Velocity: (0, 0.27, 0)<br>Colour: white */
public static final ResourceLocation BUFF = new ResourceLocation(Wizardry.MODID,"buff");
/** Spiral particle, like potions.<p></p><b>Defaults:</b><br>Lifetime: 8-40 ticks<br>Colour: white */
public static final ResourceLocation DARK_MAGIC = new ResourceLocation(Wizardry.MODID,"dark_magic");
/** Single pixel particle.<p></p><b>Defaults:</b><br>Lifetime: 16-80 ticks<br>Colour: white */
public static final ResourceLocation DUST = new ResourceLocation(Wizardry.MODID,"dust");
/** Rapid flash, like fireworks.<p></p><b>Defaults:</b><br>Lifetime: 6 ticks<br>Colour: white */
public static final ResourceLocation FLASH = new ResourceLocation(Wizardry.MODID,"flash");
/** Small shard of ice.<p></p><b>Defaults:</b><br>Lifetime: 8-40 ticks<br>Gravity: true */
public static final ResourceLocation ICE = new ResourceLocation(Wizardry.MODID,"ice");
/** Single leaf.<p></p><b>Defaults:</b><br>Lifetime: 10-15 ticks<br>Velocity: (0, -0.03, 0)
* <br>Colour: green/brown */
public static final ResourceLocation LEAF = new ResourceLocation(Wizardry.MODID,"leaf");
/** 3D-rendered lightning particle.<p></p><b>Defaults:</b><br>Lifetime: 3 ticks<br> Colour: blue */
public static final ResourceLocation LIGHTNING = new ResourceLocation(Wizardry.MODID,"lightning");
/** 2D lightning effect, normally on the ground.<p></p><b>Defaults:</b><br>Lifetime: 7 ticks
* <br>Facing: up */
public static final ResourceLocation LIGHTNING_PULSE = new ResourceLocation(Wizardry.MODID,"lightning_pulse");
/** Bubble that doesn't burst in air.<p></p><b>Defaults:</b><br>Lifetime: 8-40 ticks */
public static final ResourceLocation MAGIC_BUBBLE = new ResourceLocation(Wizardry.MODID,"magic_bubble");
/** Animated flame.<p></p><b>Defaults:</b><br>Lifetime: 12-16 ticks<br> */
public static final ResourceLocation MAGIC_FIRE = new ResourceLocation(Wizardry.MODID,"magic_fire");
/** Soft-edged round particle.<p></p><b>Defaults:</b><br>Lifetime: 8-40 ticks<br>Colour: white */
public static final ResourceLocation PATH = new ResourceLocation(Wizardry.MODID,"path");
/** Scorch mark.<p></p><b>Defaults:</b><br>Lifetime: 100-140 ticks<br>Colour: black<br>Fade: black */
public static final ResourceLocation SCORCH = new ResourceLocation(Wizardry.MODID,"scorch");
/** Snowflake particle.<p></p><b>Defaults:</b><br>Lifetime: 40-50 ticks<br>Velocity: (0, -0.02, 0) */
public static final ResourceLocation SNOW = new ResourceLocation(Wizardry.MODID,"snow");
/** Animated lightning particle.<p></p><b>Defaults:</b><br>Lifetime: 3 ticks */
public static final ResourceLocation SPARK = new ResourceLocation(Wizardry.MODID,"spark");
/** Animated sparkle particle.<p></p><b>Defaults:</b><<br>Lifetime: 48-60 ticks<br>Colour: white */
public static final ResourceLocation SPARKLE = new ResourceLocation(Wizardry.MODID,"sparkle");
/** 3D-rendered expanding sphere.<p></p><b>Defaults:</b><<br>Lifetime: 6 ticks<br>Colour: white */
public static final ResourceLocation SPHERE = new ResourceLocation(Wizardry.MODID,"sphere");
/** Wrapped animated 'summoning' particle.<p></p><b>Defaults:</b><<br>Lifetime: 15 ticks */
public static final ResourceLocation SUMMON = new ResourceLocation(Wizardry.MODID,"summon");
/** 3D-rendered vine particle.<p></p><b>Defaults:</b><br>Lifetime: 1 tick<br> Colour: green */
public static final ResourceLocation VINE = new ResourceLocation(Wizardry.MODID,"vine");
}
private ParticleBuilder(){
@@ -106,12 +142,12 @@ public final class ParticleBuilder {
/**
* Starts building a particle of the given type. Static convenience version of
* {@link ParticleBuilder#particle(Type)}; makes code more concise.
* {@link ParticleBuilder#particle(ResourceLocation)}; makes code more concise.
* @param type The type of particle to build
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is already building.
*/
public static ParticleBuilder create(Type type){
public static ParticleBuilder create(ResourceLocation type){
return ParticleBuilder.instance.particle(type);
}
@@ -121,8 +157,8 @@ public final class ParticleBuilder {
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is already building.
*/
public ParticleBuilder particle(Type type){
if(building) throw new IllegalStateException("Already Building! Particle being built: " + getCurrentParticleString());
public ParticleBuilder particle(ResourceLocation type){
if(building) throw new IllegalStateException("Already building! Particle being built: " + getCurrentParticleString());
this.type = type;
this.building = true;
return this;
@@ -131,7 +167,7 @@ public final class ParticleBuilder {
/** Gets a readable string representation of the current builder parameters; used in error messages. */
private String getCurrentParticleString(){
return String.format("[ Type: %s, Position: (%s, %s, %s), Velocity: (%s, %s, %s), Colour: (%s, %s, %s), "
+ "Fade Colour: (%s, %s, %s), Radius: %s, Revs/tick: %s, Lifetime: %s, Gravity: %s, Shaded: %s,"
+ "Fade Colour: (%s, %s, %s), Radius: %s, Revs/tick: %s, Lifetime: %s, Gravity: %s, Shaded: %s, "
+ "Scale: %s, Entity: %s ]",
type, x, y, z, vx, vy, vz, r, g, b, fr, fg, fb, radius, rpt, lifetime, gravity, shaded, scale, entity);
}
@@ -139,7 +175,7 @@ public final class ParticleBuilder {
/**
* Sets the position of the particle being built. If unspecified, this defaults to the origin (0, 0, 0). If an entity
* is specified using {@link ParticleBuilder#entity(Entity)}, this will be <i>relative to</i> that entity's position.
* <p>
* <p></p>
* <b>Affects:</b> All particle types
* @param x The x coordinate to set
* @param y The y coordinate to set
@@ -158,7 +194,7 @@ public final class ParticleBuilder {
/**
* Sets the position of the particle being built. This is a vector-based alternative to {@link ParticleBuilder#pos(
* double, double, double)}, allowing for even more concise code when a vector is available.
* <p>
* <p></p>
* <b>Affects:</b> All particle types
* @param pos A vector representing the coordinates of the particle to be built.
* @return The particle builder instance, allowing other methods to be chained onto this one
@@ -171,8 +207,8 @@ public final class ParticleBuilder {
/**
* Sets the velocity of the particle being built. If unspecified, this defaults to the particle's default velocity,
* specified within its constructor.
* <p>
* <b>Affects:</b> All particle types except {@link Type#DUST DUST}
* <p></p>
* <b>Affects:</b> All particle types
* @param vx The x velocity to set
* @param vy The y velocity to set
* @param vz The z velocity to set
@@ -190,8 +226,8 @@ public final class ParticleBuilder {
/**
* Sets the velocity of the particle being built. This is a vector-based alternative to {@link ParticleBuilder#vel(
* double, double, double)}, allowing for even more concise code when a vector is available.
* <p>
* <b>Affects:</b> All particle types
* <p></p>
* <b>Affects:</b> All particle types except
* @param vel A vector representing the velocity of the particle to be built.
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is not yet building.
@@ -202,10 +238,11 @@ public final class ParticleBuilder {
/**
* Sets the colour of the particle being built. If unspecified, this defaults to the particle's default colour,
* specified within its constructor.
* <p>
* <b>Affects:</b> {@link Type#DARK_MAGIC DARK_MAGIC}, {@link Type#DUST DUST}, {@link Type#FLASH FLASH},
* {@link Type#LEAF LEAF}, {@link Type#PATH PATH}, {@link Type#SPARKLE SPARKLE}
* specified within its constructor. <i>If all colour components are 0 or 1, at least one must have the float suffix
* ({@code f} or {@code F}) or the integer overload will be used instead, causing the particle to appear black!</i>
* <p></p>
* <b>Affects:</b> All particle types except {@link Type#ICE ICE}, {@link Type#MAGIC_BUBBLE MAGIC_BUBBLE}
* and {@link Type#MAGIC_FIRE MAGIC_FIRE}
* @param r The red colour component to set; will be clamped to between 0 and 1
* @param g The green colour component to set; will be clamped to between 0 and 1
* @param b The blue colour component to set; will be clamped to between 0 and 1
@@ -219,13 +256,47 @@ public final class ParticleBuilder {
this.b = MathHelper.clamp(b, 0, 1);
return this;
}
/**
* Sets the colour of the particle being built. This is an 8-bit (0-255) integer version of
* {@link ParticleBuilder#clr(float, float, float)}.
* <p></p>
* <b>Affects:</b> All particle types except {@link Type#ICE ICE}, {@link Type#MAGIC_BUBBLE MAGIC_BUBBLE}
* and {@link Type#MAGIC_FIRE MAGIC_FIRE}
* @param r The red colour component to set; will be clamped to between 0 and 255
* @param g The green colour component to set; will be clamped to between 0 and 255
* @param b The blue colour component to set; will be clamped to between 0 and 255
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is not yet building.
*/
public ParticleBuilder clr(int r, int g, int b){
return this.clr(r/255f, g/255f, b/255f); // Yes, 255 is correct and not 256, or else we can't have pure white
}
/**
* Sets the colour of the particle being built. This is a 6-digit hex colour version of
* {@link ParticleBuilder#clr(float, float, float)}.
* <p></p>
* <b>Affects:</b> All particle types except {@link Type#ICE ICE}, {@link Type#MAGIC_BUBBLE MAGIC_BUBBLE}
* and {@link Type#MAGIC_FIRE MAGIC_FIRE}
* @param hex The colour to be set, as a packed 6-digit hex integer (e.g. 0xff0000).
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is not yet building.
*/
public ParticleBuilder clr(int hex){
int r = (hex & 0xFF0000) >> 16;
int g = (hex & 0xFF00) >> 8;
int b = (hex & 0xFF);
return this.clr(r, g, b);
}
/**
* Sets the fade colour of the particle being built. If unspecified, this defaults to the whatever the particle's base
* colour is.
* <p>
* <b>Affects:</b> {@link Type#DARK_MAGIC DARK_MAGIC}, {@link Type#DUST DUST}, {@link Type#FLASH FLASH},
* {@link Type#LEAF LEAF}, {@link Type#PATH PATH}, {@link Type#SPARKLE SPARKLE}
* colour is. <i>If all colour components are 0 or 1, at least one must have the float suffix
* ({@code f} or {@code F}) or the integer overload will be used instead, causing the particle to appear black!</i>
* <p></p>
* <b>Affects:</b> All particle types except {@link Type#ICE ICE}, {@link Type#MAGIC_BUBBLE MAGIC_BUBBLE}
* and {@link Type#MAGIC_FIRE MAGIC_FIRE}
* @param r The red colour component to set; will be clamped to between 0 and 1
* @param g The green colour component to set; will be clamped to between 0 and 1
* @param b The blue colour component to set; will be clamped to between 0 and 1
@@ -239,10 +310,43 @@ public final class ParticleBuilder {
this.fb = MathHelper.clamp(b, 0, 1);
return this;
}
/**
* Sets the fade colour of the particle being built. This is an 8-bit (0-255) integer version of
* {@link ParticleBuilder#fade(float, float, float)}.
* <p></p>
* <b>Affects:</b> All particle types except {@link Type#ICE ICE}, {@link Type#MAGIC_BUBBLE MAGIC_BUBBLE}
* and {@link Type#MAGIC_FIRE MAGIC_FIRE}
* @param r The red colour component to set; will be clamped to between 0 and 255
* @param g The green colour component to set; will be clamped to between 0 and 255
* @param b The blue colour component to set; will be clamped to between 0 and 255
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is not yet building.
*/
public ParticleBuilder fade(int r, int g, int b){
return this.clr(r/255f, g/255f, b/255f); // Yes, 255 is correct and not 256, or else we can't have pure white
}
/**
* Sets the fade colour of the particle being built. This is a 6-digit hex colour version of
* {@link ParticleBuilder#fade(float, float, float)}.
* <p></p>
* <b>Affects:</b> All particle types except {@link Type#ICE ICE}, {@link Type#MAGIC_BUBBLE MAGIC_BUBBLE}
* and {@link Type#MAGIC_FIRE MAGIC_FIRE}
* @param hex The colour to be set, as a packed 6-digit hex integer (e.g. 0xff0000).
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is not yet building.
*/
public ParticleBuilder fade(int hex){
int r = (hex & 0xFF0000) >> 16;
int g = (hex & 0xFF00) >> 8;
int b = (hex & 0xFF);
return this.clr(r, g, b);
}
/**
* Sets the scale of the particle being built. If unspecified, this defaults to 1.
* <p>
* <p></p>
* <b>Affects:</b> All particle types
* @param scale The scale to set, as a multiple of the particle's default scale
* @return The particle builder instance, allowing other methods to be chained onto this one
@@ -257,7 +361,7 @@ public final class ParticleBuilder {
/**
* Sets the lifetime of the particle being built. If unspecified, this defaults to the particle's default lifetime,
* specified within its constructor.
* <p>
* <p></p>
* <b>Affects:</b> All particle types
* @param lifetime The lifetime to set in ticks
* @return The particle builder instance, allowing other methods to be chained onto this one
@@ -268,10 +372,28 @@ public final class ParticleBuilder {
this.lifetime = lifetime;
return this;
}
/**
* Sets the seed of the particle being built. If unspecified, this defaults to the particle's default seed,
* specified within its constructor (this is normally chosen at random).
* <p></p>
* <i>Pro tip: to get a particle to stay the same while a continuous spell is in use (but change between casts),
* use {@code .seed(world.getTotalWorldTime() - ticksInUse)}.</i>
* <p></p>
* <b>Affects:</b> All particle types
* @param seed The seed to set
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is not yet building.
*/
public ParticleBuilder seed(long seed){
if(!building) throw new IllegalStateException("Not building yet!");
this.seed = seed;
return this;
}
/**
* Sets the spin parameters of the particle being built. If unspecified, these both default to 0.
* <p>
* <p></p>
* <b>Affects:</b> All particle types
* @param radius The rotation radius to set
* @param speed The rotation speed to set, in revolutions per tick
@@ -284,11 +406,12 @@ public final class ParticleBuilder {
this.rpt = speed;
return this;
}
// Used to say Affects: {@link Type#ICE ICE}, {@link Type#SPARKLE SPARKLE} - not sure that's true any more
/**
* Sets the gravity of the particle being built. If unspecified, this defaults to false.
* <p>
* <b>Affects:</b> {@link Type#ICE ICE}, {@link Type#SPARKLE SPARKLE}
* <p></p>
* <b>Affects:</b> All particle types
* @param gravity True to enable gravity for the particle, false to disable
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is not yet building.
@@ -301,7 +424,7 @@ public final class ParticleBuilder {
/**
* Sets the shading of the particle being built. If unspecified, this defaults to false.
* <p>
* <p></p>
* <b>Affects:</b> All particle types
* @param shaded True to enable shading for the particle, false for full brightness
* @return The particle builder instance, allowing other methods to be chained onto this one
@@ -315,7 +438,7 @@ public final class ParticleBuilder {
/**
* Sets the collisions of the particle being built. If unspecified, this defaults to false.
* <p>
* <p></p>
* <b>Affects:</b> All particle types
* @param collide True to enable block collisions for the particle, false to disable
* @return The particle builder instance, allowing other methods to be chained onto this one
@@ -331,7 +454,7 @@ public final class ParticleBuilder {
* Sets the entity of the particle being built. This will cause the particle to move with the given entity, and will
* make the position specified using {@link ParticleBuilder#pos(double, double, double)} <i>relative to</i> that
* entity's position.
* <p>
* <p></p>
* <b>Affects:</b> All particle types
* @param entity The entity to set (passing in null will do nothing but will not cause any problems, so for the sake
* of conciseness it is not necessary to perform a null check on the passed-in argument)
@@ -347,7 +470,7 @@ public final class ParticleBuilder {
/**
* Sets the rotation of the particle being built. If unspecified, the particle will use the default behaviour and
* rotate to face the viewer.
* <p>
* <p></p>
* <b>Affects:</b> All particle types
* @param yaw The yaw angle to set in degrees, where 0 is south.
* @param pitch The pitch angle to set in degrees, where 0 is horizontal.
@@ -365,7 +488,8 @@ public final class ParticleBuilder {
* Sets the rotation of the particle being built. This is an {@code EnumFacing}-based alternative to {@link
* ParticleBuilder#face(float, float)} which sets the yaw and pitch to the appropriate angles for the given facing.
* For example, if the given facing is {@code NORTH}, the particle will render parallel to the north face of blocks.
* <p>
* If unspecified, the particle will use the default behaviour and rotate to face the viewer.
* <p></p>
* <b>Affects:</b> All particle types
* @param direction The {@code EnumFacing} direction to set.
* @return The particle builder instance, allowing other methods to be chained onto this one
@@ -374,11 +498,13 @@ public final class ParticleBuilder {
public ParticleBuilder face(EnumFacing direction){
return face(direction.getHorizontalAngle(), direction.getAxis().isVertical() ? direction.getAxisDirection().getOffset() * 90 : 0);
}
// ============================================= Targeted-only methods =============================================
/**
* Sets the target of the particle being built. This will cause the particle to stretch to touch the given position.
* <p>
* <b>Affects:</b>
* <p></p>
* <b>Affects:</b> Targeted particles, namely {@link Type#BEAM BEAM}, {@link Type#LIGHTNING LIGHTNING} and {@link Type#VINE VINE}
* @param x The target x-coordinate to set
* @param y The target y-coordinate to set
* @param z The target z-coordinate to set
@@ -394,10 +520,11 @@ public final class ParticleBuilder {
}
/**
* Sets the target of the particle being built. This is a vector-based alternative to {@link ParticleBuilder#
* target(double, double, double)}, allowing for even more concise code when a vector is available.
* <p>
* <b>Affects:</b> All particle types
* Sets the target of the particle being built. This is a vector-based alternative to
* {@link ParticleBuilder#target(double, double, double)}, allowing for even more concise code when a vector is
* available.
* <p></p>
* <b>Affects:</b> Targeted particles, namely {@link Type#BEAM BEAM}, {@link Type#LIGHTNING LIGHTNING} and {@link Type#VINE VINE}
* @param pos A vector representing the target position of the particle to be built.
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is not yet building.
@@ -405,11 +532,59 @@ public final class ParticleBuilder {
public ParticleBuilder target(Vec3d pos){
return target(pos.x, pos.y, pos.z);
}
/**
* Sets the target point velocity of the particle being built. This will cause the position it stretches to touch to move
* at the given velocity. Has no effect unless {@link ParticleBuilder#target(double, double, double)} or one of its
* overloads is also set. <p></p>
* <b>Affects:</b> Targeted particles, namely {@link Type#BEAM BEAM}, {@link Type#LIGHTNING LIGHTNING} and {@link Type#VINE VINE}
* @param vx The target point x velocity to set
* @param vy The target point y velocity to set
* @param vz The target point z velocity to set
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is not yet building.
*/
public ParticleBuilder tvel(double vx, double vy, double vz){
if(!building) throw new IllegalStateException("Not building yet!");
this.tvx = vx;
this.tvy = vy;
this.tvz = vz;
return this;
}
/**
* Sets the target point velocity of the particle being built. This is a vector-based alternative to
* {@link ParticleBuilder#tvel(double, double, double)}, allowing for even more concise code when a vector is
* available.
* <p></p>
* <b>Affects:</b> Targeted particles, namely {@link Type#BEAM BEAM}, {@link Type#LIGHTNING LIGHTNING} and {@link Type#VINE VINE}
* @param vel A vector representing the target point velocity of the particle to be built.
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is not yet building.
*/
public ParticleBuilder tvel(Vec3d vel){
return tvel(vel.x, vel.y, vel.z);
}
/**
* Sets the target and target velocity of the particle being built. This method takes an origin entity and a
* position and estimates the position of the target point based on the given entity's rotational velocities and its
* distance from the given position.
* <p></p>
* <b>Affects:</b> Targeted particles, namely {@link Type#BEAM BEAM}, {@link Type#LIGHTNING LIGHTNING} and {@link Type#VINE VINE}
* @param length The length of the particle being built.
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is not yet building.
*/
public ParticleBuilder length(double length){
this.length = length;
return this;
}
/**
* Sets the target of the particle being built. This will cause the particle to stretch to touch the given entity.
* <p>
* <b>Affects:</b>
* <p></p>
* <b>Affects:</b> Targeted particles, namely {@link Type#BEAM BEAM}, {@link Type#LIGHTNING LIGHTNING} and {@link Type#VINE VINE}
* @param target The entity to set
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is not yet building.
@@ -429,7 +604,7 @@ public final class ParticleBuilder {
if(!building) throw new IllegalStateException("Not building yet!");
if(y < 0 && entity == null) Wizardry.logger.warn("Spawning particle below y = 0 - are you sure the position/entity"
if(y < 0 && entity == null) Wizardry.logger.warn("Spawning particle below y = 0 - are you sure the position/entity "
+ "has been set correctly?");
if(!world.isRemote){
@@ -443,22 +618,27 @@ public final class ParticleBuilder {
electroblob.wizardry.client.particle.ParticleWizardry particle = Wizardry.proxy.createParticle(type, world, x, y, z);
if(particle == null){
// No need to display a warning here, we already did it in the client proxy
reset();
return;
}
// Anything with an if statement here allows default values to be set in particle constructors
if(!Double.isNaN(vx) && !Double.isNaN(vy) && !Double.isNaN(vz)) particle.setVelocity(vx, vy, vz);
if(r >= 0 && g >= 0 && b >= 0) particle.setRBGColorF(r, g, b);
if(fr >= 0 && fg >= 0 && fb >= 0) particle.setFadeColour(fr, fg, fb);
if(lifetime >= 0) particle.setMaxAge(lifetime);
if(radius > 0) particle.setSpin(radius, rpt);
if(!Float.isNaN(yaw) && !Float.isNaN(pitch)) particle.setFacing(yaw, pitch);
if(seed != 0) particle.setSeed(seed);
if(!Double.isNaN(tvx) && !Double.isNaN(tvy) && !Double.isNaN(tvz)) particle.setTargetVelocity(tvx, tvy, tvz);
if(length > 0) particle.setLength(length);
particle.multipleParticleScaleBy(scale);
if(!Double.isNaN(vx) && !Double.isNaN(vy) && !Double.isNaN(vz)) particle.setVelocity(vx, vy, vz);
if(r >= 0 && g >= 0 && b >= 0) particle.setRBGColorF(r, g, b);
if(fr >= 0 && fg >= 0 && fb >= 0)particle.setFadeColour(fr, fg, fb);
if(lifetime >= 0) particle.setMaxAge(lifetime);
particle.setGravity(gravity);
particle.setShaded(shaded);
particle.setCollisions(collide);
if(radius > 0) particle.setSpin(radius, rpt);
particle.setEntity(entity);
if(!Float.isNaN(yaw) && !Float.isNaN(pitch)) particle.setFacing(yaw, pitch);
particle.setTargetPosition(tx, ty, tz);
particle.setTargetEntity(target);
@@ -498,7 +678,12 @@ public final class ParticleBuilder {
tx = Double.NaN;
ty = Double.NaN;
tz = Double.NaN;
tvx = Double.NaN;
tvy = Double.NaN;
tvz = Double.NaN;
target = null;
seed = 0;
length = -1;
}
// ============================================== Convenience methods ==============================================
@@ -513,14 +698,14 @@ public final class ParticleBuilder {
* Equivalent to calling {@code ParticleBuilder.create(type).pos(...)}; users should chain any additional builder
* methods onto this one and finish with {@code .spawn(world)} as normal.
* Used extensively with summoned creatures; makes code much neater and more concise.
* <p>
* <p></p>
* <i>N.B. this does <b>not</b> cause the particle to move with the given entity.</i>
* @param type The type of particle to build
* @param entity The entity to position the particle at
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is already building.
*/
public static ParticleBuilder create(Type type, Entity entity){
public static ParticleBuilder create(ResourceLocation type, Entity entity){
double x = entity.posX + (entity.world.rand.nextDouble() - 0.5D) * (double)entity.width;
double y = entity.posY + entity.world.rand.nextDouble() * (double)entity.height;
@@ -545,7 +730,7 @@ public final class ParticleBuilder {
* @return The particle builder instance, allowing other methods to be chained onto this one
* @throws IllegalStateException if the particle builder is already building.
*/
public static ParticleBuilder create(Type type, Random random, double x, double y, double z, double radius, boolean move){
public static ParticleBuilder create(ResourceLocation type, Random random, double x, double y, double z, double radius, boolean move){
double px = x + (random.nextDouble()*2 - 1) * radius;
double py = y + (random.nextDouble()*2 - 1) * radius;
@@ -560,7 +745,9 @@ public final class ParticleBuilder {
/** Spawns spark and large smoke particles (8 of each) within a 1x1x1 volume centred on the given position. */
public static void spawnShockParticles(World world, double x, double y, double z) {
double px, py, pz;
for(int i=0; i<8; i++){
px = x + world.rand.nextDouble() - 0.5;
py = y + world.rand.nextDouble() - 0.5;
@@ -572,5 +759,19 @@ public final class ParticleBuilder {
world.spawnParticle(EnumParticleTypes.SMOKE_LARGE, px, py, pz, 0, 0, 0);
}
}
/** Spawns golden-yellow sparkle particles around the given entity's head and a golden-yellow buff particle around
* its entire body. */
public static void spawnHealParticles(World world, EntityLivingBase entity){
for(int i = 0; i < 10; i++){
double x = entity.posX + world.rand.nextDouble() * 2 - 1;
double y = entity.getEntityBoundingBox().minY + entity.getEyeHeight() - 0.5 + world.rand.nextDouble();
double z = entity.posZ + world.rand.nextDouble() * 2 - 1;
ParticleBuilder.create(Type.SPARKLE).pos(x, y, z).vel(0, 0.1, 0).clr(1, 1, 0.3f).spawn(world);
}
ParticleBuilder.create(Type.BUFF).entity(entity).clr(1, 1, 0.3f).spawn(world);
}
}
@@ -0,0 +1,207 @@
package electroblob.wizardry.util;
import electroblob.wizardry.entity.ICustomHitbox;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.List;
import java.util.function.Predicate;
/**
* Contains a number of static methods that perform raytracing and related functions. This was split off from
* {@link WizardryUtilities} as of wizardry 4.2 in an effort to make the code easier to navigate.
*
* @author Electroblob
* @since Wizardry 4.2
*/
public final class RayTracer {
private RayTracer(){} // No instances!
/**
* Helper method which performs a ray trace for <b>blocks only</b> from an entity's eye position in the direction
* they are looking, over a specified range, using {@link World#rayTraceBlocks(Vec3d, Vec3d, boolean, boolean, boolean)}.
*
* @param world The world in which to perform the ray trace.
* @param entity The entity from which to perform the ray trace. The ray trace will start from this entity's eye
* position and proceed in the direction the entity is looking.
* @param range The distance over which the ray trace will be performed.
* @param hitLiquids True to return hits on the surfaces of liquids, false to ignore liquid blocks as if they were
* not there.
* @return A {@link RayTraceResult} representing the object that was hit, which may be either a block or nothing.
* Returns {@code null} only if the origin and endpoint are within the same block.
*/
@Nullable
public static RayTraceResult standardBlockRayTrace(World world, EntityLivingBase entity, double range, boolean hitLiquids,
boolean ignoreUncollidables, boolean returnLastUncollidable){
// This method does not apply an offset like ray spells do, since it is not desirable in most other use cases.
Vec3d origin = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ);
Vec3d endpoint = origin.add(entity.getLookVec().scale(range));
return world.rayTraceBlocks(origin, endpoint, hitLiquids, ignoreUncollidables, returnLastUncollidable);
}
/**
* Helper method which performs a ray trace for <b>blocks only</b> from an entity's eye position in the direction
* they are looking, over a specified range. This is a shorthand for
* {@link #standardBlockRayTrace(World, EntityLivingBase, double, boolean, boolean, boolean)}; ignoreUncollidables
* and returnLastUncollidable default to false.
*/
@Nullable
public static RayTraceResult standardBlockRayTrace(World world, EntityLivingBase entity, double range, boolean hitLiquids){
return standardBlockRayTrace(world, entity, range, hitLiquids, false, false);
}
/**
* Helper method which performs a ray trace for blocks and entities from an entity's eye position in the direction
* they are looking, over a specified range, using {@link RayTracer#rayTrace(World, Vec3d, Vec3d, float, boolean, boolean, boolean, Class, Predicate)}. Aim assist is zero, the entity type is simply {@code Entity} (all entities), and the
* filter removes the given entity and any dying entities and allows all others.
*
* @param world The world in which to perform the ray trace.
* @param entity The entity from which to perform the ray trace. The ray trace will start from this entity's eye
* position and proceed in the direction the entity is looking. This entity will be ignored when ray tracing.
* @param range The distance over which the ray trace will be performed.
* @param hitLiquids True to return hits on the surfaces of liquids, false to ignore liquid blocks as if they were
* not there.
* @return A {@link RayTraceResult} representing the object that was hit, which may be an entity, a block or
* nothing. Returns {@code null} only if the origin and endpoint are within the same block and no entity was hit.
*/
@Nullable
public static RayTraceResult standardEntityRayTrace(World world, Entity entity, double range, boolean hitLiquids){
// This method does not apply an offset like ray spells do, since it is not desirable in most other use cases.
Vec3d origin = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ);
Vec3d endpoint = origin.add(entity.getLookVec().scale(range));
return rayTrace(world, origin, endpoint, 0, hitLiquids, false, false, Entity.class, ignoreEntityFilter(entity));
}
/**
* Helper method for use with {@link RayTracer#rayTrace(World, Vec3d, Vec3d, float, boolean, boolean, boolean, Class, Predicate)}
* which returns a {@link Predicate} that returns true for the given entity, plus any entities that have zero health
* or less (i.e. are in the process of dying). This is a commonly used filter in spells.
*
* @param entity The entity that the returned predicate should return true for.
* @return A {@link Predicate} that returns true for the given entity and any entities that are in the process of
* dying, false for all other entities.
*/
public static Predicate<Entity> ignoreEntityFilter(Entity entity){
return e -> e == entity || (e instanceof EntityLivingBase && ((EntityLivingBase)e).getHealth() <= 0);
}
/**
* Performs a ray trace for blocks and entities, starting at the given origin and finishing at the given endpoint.
* As of wizardry 4.2, the ray tracing methods have been rewritten to be more user-friendly and implement proper
* aim assist.
* <p></p>
* <i>N.B. It is possible to ignore entities entirely by passing in a {@code Predicate} that is always false;
* however, in this specific case it is more efficient to use
* {@link World#rayTraceBlocks(Vec3d, Vec3d, boolean, boolean, boolean)} or one of its overloads.</i>
*
* @param world The world in which to perform the ray trace.
* @param origin A vector representing the coordinates of the start point of the ray trace.
* @param endpoint A vector representing the coordinates of the finish point of the ray trace.
* @param aimAssist In addition to direct hits, the ray trace will also hit entities that are up to this distance
* from its path. For a normal ray trace, this should be 0. Values greater than 0 will give an 'aim assist' effect.
* @param hitLiquids Whether liquids should be ignored when ray tracing blocks
* @param ignoreUncollidables Whether blocks with no collisions should be ignored
* @param returnLastUncollidable If blocks with no collisions are ignored, whether to return the last one (useful if,
* for example, you want to replace snow layers or tall grass)
* @param entityType The class of entities to include; all other entities will be ignored.
* @param filter A {@link Predicate} which filters out entities that can be ignored; often used to exclude the
* player that is performing the ray trace.
*
* @return A {@link RayTraceResult} representing the object that was hit, which may be an entity, a block or
* nothing. Returns {@code null} only if the origin and endpoint are within the same block and no entity was hit.
*
* @see RayTracer#standardEntityRayTrace(World, Entity, double, boolean)
* @see RayTracer#standardBlockRayTrace(World, EntityLivingBase, double, boolean)
*/
// Interestingly enough, aimAssist can be negative, which means hits have to be in the middle of entities!
@Nullable
public static RayTraceResult rayTrace(World world, Vec3d origin, Vec3d endpoint, float aimAssist,
boolean hitLiquids, boolean ignoreUncollidables, boolean returnLastUncollidable, Class<? extends Entity> entityType, Predicate<? super Entity> filter){
// 1 is the standard amount of extra search volume, and aim assist needs to increase this further as well as
// expanding the entities' bounding boxes.
float borderSize = 1 + aimAssist;
// The AxisAlignedBB constructor accepts min/max coords in either order.
AxisAlignedBB searchVolume = new AxisAlignedBB(origin.x, origin.y, origin.z, endpoint.x, endpoint.y, endpoint.z)
.grow(borderSize, borderSize, borderSize);
// Gets all of the entities in the bounding box that could be collided with.
List<Entity> entities = world.getEntitiesWithinAABB(entityType, searchVolume);
// Applies the given filter to remove entities that should be ignored.
entities.removeIf(filter);
// Finds the first block hit by the ray trace, if any.
RayTraceResult result = world.rayTraceBlocks(origin, endpoint, hitLiquids, ignoreUncollidables, returnLastUncollidable);
// Clips the entity search range to the part of the ray trace before the block hit, if it hit a block.
if(result != null){
endpoint = result.hitVec;
}
// Search variables
Entity closestHitEntity = null;
Vec3d closestHitPosition = endpoint;
AxisAlignedBB entityBounds;
Vec3d intercept = null;
// Iterates through all the entities
for(Entity entity : entities){
// I'd like to add the following line so we can, for example, use greater telekinesis through a
// ring of fire, but doing so will stop forcefields blocking particles
//if(!entity.canBeCollidedWith()) continue;
float fuzziness = WizardryUtilities.isLiving(entity) ? aimAssist : 0; // Only living entities have aim assist
if(entity instanceof ICustomHitbox){ // Custom hitboxes
intercept = ((ICustomHitbox)entity).calculateIntercept(origin, endpoint, fuzziness);
}else{ // Normal hit detection
entityBounds = entity.getEntityBoundingBox();
if(entityBounds != null){
// This is zero for everything except fireballs...
float entityBorderSize = entity.getCollisionBorderSize();
// ... meaning the following line does nothing in all other cases.
// -> Added the non-zero check to prevent unnecessary AABB object creation.
if(entityBorderSize != 0)
entityBounds = entityBounds.grow(entityBorderSize, entityBorderSize, entityBorderSize);
// Aim assist expands the bounding box to hit entities within the specified distance of the ray trace.
if(fuzziness != 0) entityBounds = entityBounds.grow(fuzziness, fuzziness, fuzziness);
// Finds the first point at which the ray trace intercepts the entity's bounding box, if any.
RayTraceResult hit = entityBounds.calculateIntercept(origin, endpoint);
if(hit != null) intercept = hit.hitVec;
}
}
// If the ray trace hit the entity...
if(intercept != null){
// Decides whether the entity that was hit is the closest so far, and if so, overwrites the old one.
float currentHitDistance = (float)intercept.distanceTo(origin);
float closestHitDistance = (float)closestHitPosition.distanceTo(origin);
if(currentHitDistance < closestHitDistance){
closestHitEntity = entity;
closestHitPosition = intercept;
}
}
}
// If the ray trace hit an entity, return that entity; otherwise return the result of the block ray trace.
if(closestHitEntity != null){
result = new RayTraceResult(closestHitEntity, closestHitPosition);
}
return result;
}
}
@@ -0,0 +1,42 @@
package electroblob.wizardry.util;
import net.minecraft.entity.Entity;
import net.minecraft.util.EnumFacing;
/**
* Like {@link EnumFacing}, but relative!
*/
public enum RelativeFacing {
DOWN("down", -1),
UP("up", -1),
FRONT("front", 0),
BACK("back", 2),
LEFT("left", 3),
RIGHT("right", 1);
public final String name;
private final int horizontalIndex;
private static final RelativeFacing[] HORIZONTALS = new RelativeFacing[4];
RelativeFacing(String name, int horizontalIndex){
this.name = name;
this.horizontalIndex = horizontalIndex;
}
static {
for(RelativeFacing facing : values()){
if(facing.horizontalIndex > -1) HORIZONTALS[facing.horizontalIndex] = facing;
}
}
public static RelativeFacing relativise(EnumFacing absolute, Entity relativeTo){
if(absolute == EnumFacing.DOWN) return DOWN;
if(absolute == EnumFacing.UP) return UP;
EnumFacing look = relativeTo.getAdjustedHorizontalFacing();
int relativeIndex = absolute.getHorizontalIndex() - look.getHorizontalIndex();
if(relativeIndex < 0) relativeIndex += 4;
return HORIZONTALS[relativeIndex];
}
}
@@ -1,31 +1,28 @@
package electroblob.wizardry.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import electroblob.wizardry.event.SpellCastEvent;
import io.netty.buffer.ByteBuf;
import net.minecraft.item.Item;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.fml.common.network.ByteBufUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* <i>"{@code SpellModifiers} - modify all the things!"</i>
* <p>
* <p></p>
* Object that wraps any number of spell modifiers into one, allowing for expandability within the Spell#cast methods.
* This class is essentially a glorified {@link Map} which can be written to and read from a {@link ByteBuf}. It is
* possible to calculate spell modifiers from wand NBT within the cast methods, but this is cumbersome and does not
* allow the modifiers to be sent to the client, which is sometimes necessary (for example, detonate needs to know about
* range modifiers on the client side or the particles wouldn't show outside of the base range).
* <p>
* This class is essentially a glorified {@link Map} which can be written to and read from a {@link ByteBuf}.
* <p></p>
* Most external interaction with SpellModifiers objects will be in {@link SpellCastEvent.Pre}, where you can add
* additional modifiers to them if desired for use with your own spells, or modify the existing ones. If you have added
* a wand upgrade, this is <b>not</b> done automatically for you; you will have to do it yourself (for the simple reason
* that not all wand upgrades affect spells). SpellModifiers objects are <i>mutable</i>, so you can simply change the
* values they contain to modify the spell.
* <p>
* <p></p>
* To use a SpellModifiers object within the <code>Spell.cast</code> methods, simply retrieve the desired modifier
* using {@link SpellModifiers#get(Item)} for wand upgrades, or {@link SpellModifiers#get(String)} if the modifier is
* not from a wand upgrade.
@@ -40,21 +37,40 @@ import net.minecraftforge.fml.common.network.ByteBufUtils;
// fly.
public final class SpellModifiers {
/** Constant string identifier for the potency modifier. All the other modifiers in Wizardry have items. */
/** Constant string identifier for the potency modifier. */
public static final String POTENCY = "potency";
/** Constant string identifier for the mana cost modifier. */
public static final String COST = "cost";
/** Constant string identifier for the wand progression modifier. */
public static final String PROGRESSION = "progression";
private Map<String, Float> multiplierMap;
private Map<String, Float> syncedMultiplierMap;
private final Map<String, Float> multiplierMap;
private final 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>();
multiplierMap = new HashMap<>();
syncedMultiplierMap = new HashMap<>();
}
// /** Returns a deep copy of this {@code SpellModifiers} object. */
// @Override
// public SpellModifiers clone(){
// SpellModifiers clone;
// try {
// clone = (SpellModifiers)super.clone();
// }catch(CloneNotSupportedException e){
// Wizardry.logger.error("Whaaaaat?!", e);
// return null;
// }
// clone.multiplierMap = new HashMap<>(this.multiplierMap);
// clone.syncedMultiplierMap = new HashMap<>(this.syncedMultiplierMap);
// return clone;
// }
/**
* Adds the given multiplier to this SpellModifiers object, using the string identifier that the given wand upgrade
* item was registered with.
@@ -109,6 +125,31 @@ public final class SpellModifiers {
return value == null ? 1 : value;
}
// Not sure this really makes sense with the current system, it may just be better to keep it how it is
// /**
// * Returns the <i>level</i> of upgrade (i.e. number of upgrades or wand tier) that would be required to
// * generate a modifier with the given key. <i>This does not necessarily mean that was how this modifier was
// * applied; and the returned value may not be a whole number if commands were involved.</i>
// */
// public float level(String key){
// return 0;
// }
/**
* Returns an amplified version of the multiplier corresponding to the given string key. An <i>amplified</i>
* modifier is the original modifier <i>scaled about 1</i> - for example, amplifying by 2 would produce the
* following results:<br>
* 1.3 -> 1.6<br>
* 2 -> 3<br>
* 0.7 -> 0.4<br>
* 1 -> 1<br>
* (In other words, the modifier is decreased by 1, multiplied by the scalar and then increased by 1 again.)<br>
* <b>N.B. This does not change the stored modifier.</b>
*/
public float amplified(String key, float scalar){
return (get(key) - 1) * scalar + 1;
}
/**
* Returns an unmodifiable map of the modifiers stored in this SpellModifiers object. Useful for iterating through
* the modifiers.
@@ -146,10 +187,10 @@ public final class SpellModifiers {
/**
* 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>
* command syntax) represents a SpellModifiers object with a damage modifier of 1.5 and a range modifier of 2:
* <p></p>
* <code>{damage:1.5, range:2}</code>
* <p>
* <p></p>
* Note that needsSyncing is set to true for all returned modifiers.
*/
public static SpellModifiers fromNBT(NBTTagCompound nbt){
@@ -159,5 +200,22 @@ public final class SpellModifiers {
}
return modifiers;
}
/**
* Creates a new NBTTagCompound for this SpellModifiers object. The NBTTagCompound will have a float tags for each
* modifier, which will be stored using the modifier names as keys. For example, the following NBT tag (in
* command syntax) represents a SpellModifiers object with a damage modifier of 1.5 and a range modifier of 2:
* <p></p>
* <code>{damage:1.5, range:2}</code>
* <p></p>
* Note that information about syncing of modifiers is discarded.
*/
public NBTTagCompound toNBT(){
NBTTagCompound nbt = new NBTTagCompound();
for(Entry<String, Float> entry : multiplierMap.entrySet()){
nbt.setFloat(entry.getKey(), entry.getValue());
}
return nbt;
}
}
@@ -0,0 +1,361 @@
package electroblob.wizardry.util;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.SpellType;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.spell.Spell;
import io.netty.buffer.ByteBuf;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.util.*;
import java.util.stream.Collectors;
/**
* Object that stores base properties associated with spells. Each spell has a single instance of this class which
* stores its base properties and other data. This class also handles loading of the properties from JSON.
* <p></p>
* All the fields in this class are final and are assigned during object creation. This is because the intent is that a
* new SpellProperties object is created on load and synced with each client on player login. Having final fields
* therefore guarantees that the properties are always synced whenever necessary, but cannot otherwise be fiddled
* with programmatically.
* <p></p>
* Generally, users need not worry about this class; it is intended that the various property getters in Spell be used
* rather than querying this class directly <i>(in fact, you can't do that without reflection anyway)</i>.
* <p></p>
* @author Electroblob
* @since Wizardry 4.2
*/
// There is not a particular semantic reason to separate this from the Spell class itself. However, doing so means that
// everything related to the JSON spell system is kept in one place and doesn't clutter the (already long) Spell class.
// Additionally, it allows SpellProperties objects to be passed around during loading and syncing.
public final class SpellProperties {
private static final Gson gson = new Gson();
/** Set of enum constants representing contexts in which a spell can be enabled/disabled. */
public enum Context {
/** Disabling this context will make a spell's book unobtainable and unusable. */ BOOK("book"),
/** Disabling this context will make a spell's scroll unobtainable and unusable. */ SCROLL("scroll"),
/** Disabling this context will prevent a spell from being cast using a wand. */ WANDS("wands"),
/** Disabling this context will prevent NPCs from casting or dropping a spell. */ NPCS("npcs"),
/** Disabling this context will prevent dispensers from casting a spell. */ DISPENSERS("dispensers"),
/** Disabling this context will prevent a spell from being cast using commands. */ COMMANDS("commands"),
/** Disabling this context will prevent a spell's book or scroll generating in chests. */ TREASURE("treasure"),
/** Disabling this context will prevent a spell's book or scroll from being sold by NPCs.*/ TRADES("trades"),
/** Disabling this context will prevent a spell's book or scroll being dropped by mobs. */ LOOTING("looting");
/** The JSON identifier for this context. */
public final String name;
Context(String name){
this.name = name;
}
}
/** A map storing whether each context is enabled for this spell. */
private final Map<Context, Boolean> enabledContexts;
/** A map storing the base values for this spell. These values are defined by the spell class and cannot be
* changed. */
// We're using Number here because it makes implementors think about what they convert it to.
// If we did what attributes do and just use doubles, people (myself included!) might plug them into calculations
// without thinking. However, with Number you can't just do that, you have to convert and therefore you have to
// decide how to do the conversion. Internally they're handled as floats though.
private final Map<String, Number> baseValues;
/** The tier this spell belongs to. */
public final Tier tier;
/** The element this spell belongs to. */
public final Element element;
/** The type of spell this is classified as. */
public final SpellType type;
/** Mana cost of the spell. If it is a continuous spell the cost is per second. */
public final int cost;
/** The charge-up time of the spell, in ticks. */
public final int chargeup;
/** The cooldown time of the spell, in ticks. */
public final int cooldown;
// Sometimes it just makes more sense to do the JSON parsing in the constructor
// It's the only way we're gonna keep the fields final!
/**
* Parses the given JSON object and constructs a new {@code SpellProperties} from it, setting all the relevant
* fields and references.
*
* @param json A JSON object representing the spell properties to be constructed.
* @param spell The spell that this {@code SpellProperties} object is for.
* @throws JsonSyntaxException if at any point the JSON object is found to be invalid.
*/
private SpellProperties(JsonObject json, Spell spell){
String[] baseValueNames = spell.getPropertyKeys();
enabledContexts = new EnumMap<>(Context.class);
baseValues = new HashMap<>();
JsonObject enabled = JsonUtils.getJsonObject(json, "enabled");
// This time we know the exact set of properties so we can iterate over them instead of the json object
// In fact, we actually want to throw an exception if any of them are missing
for(Context context : Context.values()){
enabledContexts.put(context, JsonUtils.getBoolean(enabled, context.name));
}
try {
tier = Tier.fromName(JsonUtils.getString(json, "tier"));
element = Element.fromName(JsonUtils.getString(json, "element"));
type = SpellType.fromName(JsonUtils.getString(json, "type"));
}catch(IllegalArgumentException e){
throw new JsonSyntaxException("Incorrect spell property value", e);
}
cost = JsonUtils.getInt(json, "cost");
chargeup = JsonUtils.getInt(json, "chargeup");
cooldown = JsonUtils.getInt(json, "cooldown");
// There's not much point specifying the classes of the numbers here because the json getter methods just
// perform conversion to the requested type anyway. It therefore makes very little difference whether the
// conversion is done during JSON parsing or when we actually use the value - and at least in the latter case,
// individual subclasses have control over how it is converted.
// My case in point: summoning 2.5 spiders is obviously nonsense, but what happens when we cast that with a
// modifier of 2? Should we round the base value down to 2 and then apply the x2 modifier to get 4 spiders?
// Should we round it up instead? Or should we apply the modifier first and then do the rounding, so with no
// modifier we still get 2 spiders but with the x2 modifier we get 5?
// The most pragmatic solution is to let the spell class decide for itself.
// (Of course, we can only hope that the users aren't jerks and don't try to summon 2 and a half spiders...)
JsonObject baseValueObject = JsonUtils.getJsonObject(json, "base_properties");
// If the code requests more values than the JSON file contains, that will cause a JsonSyntaxException here anyway.
// If there are redundant values in the JSON file, chances are that a user has misunderstood the system and tried
// to add properties that aren't implemented. However, redundant values will also be found if a programmer has
// forgotten to call addProperties in their spell constructor (I know I have!), potentially causing a crash at
// some random point in the future. Since redundant values aren't a problem by themselves, we shouldn't throw an
// exception, but a warning is appropriate.
int redundantKeys = baseValueObject.size() - baseValueNames.length;
if(redundantKeys > 0) Wizardry.logger.warn("Spell " + spell.getRegistryName() + " has " + redundantKeys +
" redundant spell property key(s) defined in its JSON file. Extra values will have no effect! (Modders:" +
" make sure you have called addProperties(...) during spell construction)");
if(baseValueNames.length > 0){
for(String baseValueName : baseValueNames){
baseValues.put(baseValueName, JsonUtils.getFloat(baseValueObject, baseValueName));
}
}
}
/** Constructs a new SpellProperties object for the given spell, reading its values from the given ByteBuf. */
public SpellProperties(Spell spell, ByteBuf buf){
enabledContexts = new EnumMap<>(Context.class);
baseValues = new HashMap<>();
for(Context context : Context.values()){
// Enum maps have a guaranteed iteration order so this works fine
enabledContexts.put(context, buf.readBoolean());
}
tier = Tier.values()[buf.readShort()];
element = Element.values()[buf.readShort()];
type = SpellType.values()[buf.readShort()];
cost = buf.readInt();
chargeup = buf.readInt();
cooldown = buf.readInt();
List<String> keys = Arrays.asList(spell.getPropertyKeys());
Collections.sort(keys); // Should be the same list of keys in the same order they were written to the ByteBuf
for(String key : keys){
baseValues.put(key, buf.readFloat());
}
}
/** Writes this SpellProperties object to the given ByteBuf so it can be sent via packets. */
public void write(ByteBuf buf){
for(Context context : Context.values()){
// Enum maps have a guaranteed iteration order so this works fine
buf.writeBoolean(enabledContexts.get(context));
}
buf.writeShort(tier.ordinal());
buf.writeShort(element.ordinal());
buf.writeShort(type.ordinal());
buf.writeInt(cost);
buf.writeInt(chargeup);
buf.writeInt(cooldown);
List<String> keys = new ArrayList<>(baseValues.keySet());
Collections.sort(keys); // Sort alphabetically (as long as the order is consistent it doesn't matter)
for(String key : keys){
buf.writeFloat(baseValues.get(key).floatValue());
}
}
/**
* Returns whether the spell is enabled in any of the given contexts.
* @param contexts The context in which to check if the spell is enabled.
* @return True if the spell is enabled in any of the given contexts, false if not.
*/
public boolean isEnabled(Context... contexts){
return enabledContexts.entrySet().stream().anyMatch(e -> e.getValue() && Arrays.asList(contexts).contains(e.getKey()));
}
/**
* Returns the base value for this spell that corresponds to the given identifier.
* @param identifier The string identifier to fetch the base value for.
* @return The base value, as a {@code Number}.
* @throws IllegalArgumentException if no base value was defined with the given identifier.
*/
public Number getBaseValue(String identifier){
if(!baseValues.containsKey(identifier)){
throw new IllegalArgumentException("Base value with identifier '" + identifier + "' is not defined.");
}
return baseValues.get(identifier);
}
/**
* Called from preInit() in the main mod class to initialise the spell property system.
*/
// For some reason I had this called from a method in CommonProxy which was overridden to do nothing in
// ClientProxy, but that method was never called and instead this one was called directly from the main mod class.
// I *think* I decided against the proxy thing and just forgot to delete the methods (they're gone now), but if
// things don't work as expected then that may be why - pretty sure it's fine though since the properties get
// wiped client-side on each login anyway.
public static void init(){
// Collecting to a set should give us one of each mod ID
Set<String> modIDs = Spell.getSpells(Spell.allSpells).stream().map(s -> s.getRegistryName().getNamespace()).collect(Collectors.toSet());
boolean flag = true;
for(String modID : modIDs){
flag &= loadSpellProperties(modID); // Don't short-circuit, or mods later on won't get loaded!
}
if(!flag) Wizardry.logger.warn("Some spell property files did not load correctly; this will likely cause problems later!");
}
// Sooooooo I just realised that resource packs - you know, that famously client-side thing - can now define
// stuff that should be specified by the server. No wonder it all got moved to data packs in 1.13...
// Anyway, for the time being we're in 1.12 so we're gonna have to do this instead.
// For crafting recipes, Forge does some stuff behind the scenes to load recipe JSON files from mods' namespaces.
// This leverages the same methods.
private static boolean loadSpellProperties(String modID){
// Yes, I know you're not supposed to do orElse(null). But... meh.
ModContainer mod = Loader.instance().getModList().stream().filter(m -> m.getModId().equals(modID)).findFirst().orElse(null);
if(mod == null){
Wizardry.logger.warn("Tried to load spell properties for mod with ID '" + modID + "', but no such mod was loaded");
return false; // Failed!
}
// Spells will be removed from this list as their properties are set
// If everything works properly, it should be empty by the end
List<Spell> spells = Spell.getSpells(s -> s.getRegistryName().getNamespace().equals(modID));
if(modID.equals(Wizardry.MODID)) spells.add(Spells.none); // In this particular case we do need the none spell
Wizardry.logger.info("Loading spell properties for " + spells.size() + " spells in mod " + modID);
// This method is used by Forge to load mod recipes and advancements, so it's a fair bet it's the right one
// In the absence of Javadoc, here's what the non-obvious parameters do:
// - preprocessor is called once with just the root directory, allowing any global index files to be processed
// - processor is called once for each file in the directory so processing can be done
// - defaultUnfoundRoot is the default value to return if the root specified isn't found
// - visitAllFiles determines whether the method short-circuits; in other words, if the processor returns false
// at any point and visitAllFiles is false, the method returns immediately.
boolean success = CraftingHelper.findFiles(mod, "assets/" + modID + "/spells", null,
(root, file) -> {
String relative = root.relativize(file).toString();
if(!"json".equals(FilenameUtils.getExtension(file.toString())) || relative.startsWith("_"))
return true; // True or it'll look like it failed just because it found a non-JSON file
String name = FilenameUtils.removeExtension(relative).replaceAll("\\\\", "/");
ResourceLocation key = new ResourceLocation(modID, name);
Spell spell = Spell.registry.getValue(key);
// If no spell matches a particular file, log it and just ignore the file
if(spell == null){
Wizardry.logger.info("Spell properties file " + name + ".json does not match any registered spells; ensure the filename is spelled correctly.");
return true;
}
BufferedReader reader = null;
// We want to do this regardless of whether the JSON file got read properly, because that prints its
// own separate warning
if(!spells.remove(spell)) Wizardry.logger.warn("What's going on?!");
try{
reader = Files.newBufferedReader(file);
JsonObject json = JsonUtils.fromJson(gson, reader, JsonObject.class);
SpellProperties properties = new SpellProperties(json, spell);
spell.setProperties(properties);
}catch(JsonParseException jsonparseexception){
Wizardry.logger.error("Parsing error loading spell property file for " + key, jsonparseexception);
return false;
}catch(IOException ioexception){
Wizardry.logger.error("Couldn't read spell property file for " + key, ioexception);
return false;
}finally{
IOUtils.closeQuietly(reader);
}
return true;
},
true, true);
// If a spell is missing its file, log an error
if(!spells.isEmpty()){
if(spells.size() <= 15){
spells.forEach(s -> Wizardry.logger.error("Spell " + s.getRegistryName() + " is missing a properties file!"));
}else{
// If there are more than 15 don't bother logging them all, chances are they're all missing
Wizardry.logger.error("Mod " + modID + " has " + spells.size() + " spells that are missing properties files!");
}
}
return success;
}
}
// We probably could have used the attribute system for all of this, but I am reluctant to do so for a number of
// reasons:
// - It's a mess.
// - Unlike entities and itemstacks, spells don't have a separate instance for each time they are cast, which might
// prove problematic.
// - I'm loading my base properties once and not touching them again, so they're more like block materials than anything
// else.
// - Some of the properties aren't numerical, and some of them can't have modifiers applied. In fact, most of them can't!
// So even if we were to use attributes, we'd still need this class.
@@ -1,9 +1,5 @@
package electroblob.wizardry.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
import electroblob.wizardry.item.ItemWand;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
@@ -12,25 +8,29 @@ import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
/**
* <i>"Never fear, {@code WandHelper} is here!"</i>
* <p>
* <p></p>
* 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>
* <p></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>
* <p></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! However, if
* you have your own spell casting item (which doesn't extend ItemWand), this setup means you can still use this class
* to manage its NBT structure.
* <p>
* <p></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
@@ -46,11 +46,13 @@ public final class WandHelper {
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 MAX_COOLDOWN_ARRAY_KEY = "maxCooldown";
public static final String UPGRADES_KEY = "upgrades";
public static final String PROGRESSION_KEY = "progression";
private static final HashMap<Item, String> upgradeMap = new HashMap<Item, String>();
static{
static {
upgradeMap.put(WizardryItems.condenser_upgrade, "condenser");
upgradeMap.put(WizardryItems.storage_upgrade, "storage");
upgradeMap.put(WizardryItems.siphon_upgrade, "siphon");
@@ -59,8 +61,11 @@ public final class WandHelper {
upgradeMap.put(WizardryItems.cooldown_upgrade, "cooldown");
upgradeMap.put(WizardryItems.blast_upgrade, "blast");
upgradeMap.put(WizardryItems.attunement_upgrade, "attunement");
upgradeMap.put(WizardryItems.melee_upgrade, "melee");
}
// =================================================== Spells ===================================================
/**
* 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,
@@ -77,7 +82,7 @@ public final class WandHelper {
spells = new Spell[spellIDs.length];
for(int i = 0; i < spellIDs.length; i++){
spells[i] = Spell.get(spellIDs[i]);
spells[i] = Spell.byMetadata(spellIDs[i]);
}
}
@@ -95,7 +100,7 @@ public final class WandHelper {
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();
spellIDs[i] = spells[i] != null ? spells[i].metadata() : Spells.none.metadata();
}
wand.getTagCompound().setIntArray(SPELL_ARRAY_KEY, spellIDs);
@@ -123,9 +128,10 @@ public final class WandHelper {
public static Spell getNextSpell(ItemStack wand){
Spell[] spells = getSpells(wand);
int index = getNextSpellIndex(wand);
if(wand.getTagCompound() != null){
return spells[getNextSpellIndex(wand)];
if(index >= 0 && index < spells.length){
return spells[index];
}
return Spells.none;
@@ -136,9 +142,10 @@ public final class WandHelper {
public static Spell getPreviousSpell(ItemStack wand){
Spell[] spells = getSpells(wand);
int index = getPreviousSpellIndex(wand);
if(wand.getTagCompound() != null){
return spells[getPreviousSpellIndex(wand)];
if(index >= 0 && index < spells.length){
return spells[index];
}
return Spells.none;
@@ -165,6 +172,8 @@ public final class WandHelper {
}
private static int getNextSpellIndex(ItemStack wand){
if(wand.getTagCompound() == null) wand.setTagCompound(new NBTTagCompound());
int numberOfSpells = getSpells(wand).length;
int spellIndex = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY);
@@ -181,6 +190,8 @@ public final class WandHelper {
}
private static int getPreviousSpellIndex(ItemStack wand){
if(wand.getTagCompound() == null) wand.setTagCompound(new NBTTagCompound());
int numberOfSpells = getSpells(wand).length;
int spellIndex = wand.getTagCompound().getInteger(SELECTED_SPELL_KEY);
@@ -194,6 +205,8 @@ public final class WandHelper {
return spellIndex;
}
// ================================================== Cooldowns ==================================================
/**
* 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,
@@ -211,7 +224,8 @@ public final class WandHelper {
return cooldowns;
}
/** Sets the given wand's cooldown array. The array can be anywhere between 5 and 8 (inclusive) in length. */
/** Sets the given wand's cooldown array. The array can be anywhere between 5 and 8 (inclusive) in length.
* Unlike {@link WandHelper#setCurrentCooldown(ItemStack, int)}, this will <b>not</b> set the max cooldowns. */
public static void setCooldowns(ItemStack wand, int[] cooldowns){
if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound()));
@@ -266,7 +280,7 @@ public final class WandHelper {
return cooldowns[getPreviousSpellIndex(wand)];
}
/** Sets the given wand's cooldown for the currently selected spell. */
/** Sets the given wand's cooldown for the currently selected spell. Will also set the maximum cooldown. */
public static void setCurrentCooldown(ItemStack wand, int cooldown){
if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound()));
@@ -280,8 +294,52 @@ public final class WandHelper {
cooldowns[wand.getTagCompound().getInteger(SELECTED_SPELL_KEY)] = cooldown;
setCooldowns(wand, cooldowns);
int[] maxCooldowns = getMaxCooldowns(wand);
if(maxCooldowns.length == 0) maxCooldowns = new int[getSpells(wand).length];
maxCooldowns[wand.getTagCompound().getInteger(SELECTED_SPELL_KEY)] = cooldown;
setMaxCooldowns(wand, maxCooldowns);
}
/**
* Returns an array of the max cooldowns for each spell bound to the given wand. If the wand has no cooldown data,
* returns an array of length 0.
*/
public static int[] getMaxCooldowns(ItemStack wand){
int[] cooldowns = new int[0];
if(wand.getTagCompound() != null){
return wand.getTagCompound().getIntArray(MAX_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 setMaxCooldowns(ItemStack wand, int[] cooldowns){
if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound()));
wand.getTagCompound().setIntArray(MAX_COOLDOWN_ARRAY_KEY, cooldowns);
}
/** Returns the given wand's max cooldown for the currently selected spell, or 0 if the wand has no cooldown data. */
public static int getCurrentMaxCooldown(ItemStack wand){
int[] cooldowns = getMaxCooldowns(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)];
}
// ================================================== Upgrades ==================================================
/**
* 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.
@@ -371,4 +429,28 @@ public final class WandHelper {
throw new IllegalArgumentException("Duplicate wand upgrade identifier: " + identifier);
upgradeMap.put(upgrade, identifier);
}
// ================================================= Progression =================================================
/** Sets the given wand's progression to the given value. */
public static void setProgression(ItemStack wand, int progression){
if(wand.getTagCompound() == null) wand.setTagCompound((new NBTTagCompound()));
wand.getTagCompound().setInteger(PROGRESSION_KEY, progression);
}
/** Returns the progression value for the given wand, or 0 if the wand has no data. */
public static int getProgression(ItemStack wand){
if(wand.getTagCompound() == null) return 0;
return wand.getTagCompound().getInteger(PROGRESSION_KEY);
}
/** Adds the given amount of progression to this wand's progression value. */
public static void addProgression(ItemStack wand, int progression){
setProgression(wand, getProgression(wand) + progression);
}
}
@@ -1,57 +0,0 @@
package electroblob.wizardry.util;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.village.MerchantRecipe;
import net.minecraft.village.MerchantRecipeList;
import net.minecraftforge.oredict.OreDictionary;
/** Custom version of {@link MerchantRecipeList} which allows wildcard recipes (i.e. trades which accept items with any
* damage value). Function is otherwise identical. For some reason this feature was removed in 1.11.
* @author Electroblob
* @since Wizardry 4.1 */
@SuppressWarnings("serial")
public class WildcardTradeList extends MerchantRecipeList {
public WildcardTradeList(){
super();
}
public WildcardTradeList(NBTTagCompound tag){
super(tag);
}
@Override
public MerchantRecipe canRecipeBeUsed(ItemStack offer1, ItemStack offer2, int index){
if(index > 0 && index < this.size()){
MerchantRecipe merchantrecipe1 = (MerchantRecipe)this.get(index);
return !this.areItemStacksExactlyEqual(offer1, merchantrecipe1.getItemToBuy()) || (!offer2.isEmpty() || merchantrecipe1.hasSecondItemToBuy()) && (!merchantrecipe1.hasSecondItemToBuy() || !this.areItemStacksExactlyEqual(offer2, merchantrecipe1.getSecondItemToBuy())) || offer1.getCount() < merchantrecipe1.getItemToBuy().getCount() || merchantrecipe1.hasSecondItemToBuy() && offer2.getCount() < merchantrecipe1.getSecondItemToBuy().getCount() ? null : merchantrecipe1;
}else{
for(int i = 0; i < this.size(); ++i){
MerchantRecipe merchantrecipe = (MerchantRecipe)this.get(i);
if (this.areItemStacksExactlyEqual(offer1, merchantrecipe.getItemToBuy()) && offer1.getCount() >= merchantrecipe.getItemToBuy().getCount() && (!merchantrecipe.hasSecondItemToBuy() && offer2.isEmpty() || merchantrecipe.hasSecondItemToBuy() && this.areItemStacksExactlyEqual(offer2, merchantrecipe.getSecondItemToBuy()) && offer2.getCount() >= merchantrecipe.getSecondItemToBuy().getCount())){
return merchantrecipe;
}
}
return null;
}
}
private boolean areItemStacksExactlyEqual(ItemStack stack1, ItemStack stack2){
// Added to allow wildcards; this line is the only actual change.
if((stack1.getItemDamage() == OreDictionary.WILDCARD_VALUE || stack2.getItemDamage() == OreDictionary.WILDCARD_VALUE)
// Can't use ItemStack.areItemsEqualIgnoreDurability because that only works for items with durability, not subtypes.
&& stack1.getItem() == stack2.getItem()) return true;
return ItemStack.areItemsEqual(stack1, stack2) && (!stack2.hasTagCompound() || stack1.hasTagCompound() && NBTUtil.areNBTEquals(stack2.getTagCompound(), stack1.getTagCompound(), false));
}
}
@@ -1,142 +0,0 @@
package electroblob.wizardry.util;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.collect.Sets;
import electroblob.wizardry.spell.Clairvoyance;
import net.minecraft.entity.EntityLiving;
import net.minecraft.pathfinding.NodeProcessor;
import net.minecraft.pathfinding.Path;
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. Currently this is only
* used for the {@link Clairvoyance} spell.
*/
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.init(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);
}
}
File diff suppressed because it is too large Load Diff