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:
@@ -1,11 +1,6 @@
|
||||
package electroblob.wizardry.spell;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.constants.Element;
|
||||
import electroblob.wizardry.constants.SpellType;
|
||||
import electroblob.wizardry.constants.Tier;
|
||||
import electroblob.wizardry.entity.living.ISpellCaster;
|
||||
import electroblob.wizardry.entity.projectile.EntityBomb;
|
||||
import electroblob.wizardry.entity.projectile.EntityMagicProjectile;
|
||||
@@ -16,90 +11,109 @@ import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.EnumAction;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.Vec3i;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Generic superclass for all spells which launch non-directed projectiles (i.e. instances of {@link EntityMagicProjectile}).
|
||||
* This allows all the relevant code to be centralised, since these spells all work in the same way. Usually, a simple
|
||||
* instantiation of this class is sufficient to create a projectile spell; if something extra needs to be done, such as
|
||||
* particle spawning, then methods can be overridden (perhaps using an anonymous class) to add the required functionality.
|
||||
* It is encouraged, however, to put extra functionality in the projectile class instead whenever possible.
|
||||
* <p>
|
||||
* <p></p>
|
||||
* Properties added by this type of spell: {@link Spell#RANGE}
|
||||
* <p></p>
|
||||
* By default, this type of spell can be cast by NPCs. {@link Spell#canBeCastByNPCs()}
|
||||
* <p>
|
||||
* By default, this type of spell does not require a packet to be sent. {@link Spell#doesSpellRequirePacket()}
|
||||
* <p></p>
|
||||
* By default, this type of spell can be cast by dispensers. {@link Spell#canBeCastByDispensers()}
|
||||
* <p></p>
|
||||
* By default, this type of spell does not require a packet to be sent. {@link Spell#requiresPacket()}
|
||||
*
|
||||
* @author Electroblob
|
||||
* @since Wizardry 4.2
|
||||
*/
|
||||
public class SpellProjectile extends Spell {
|
||||
public class SpellProjectile<T extends EntityMagicProjectile> extends Spell {
|
||||
|
||||
private static final float DISPENSER_INACCURACY = 1; // This is the same as for players
|
||||
private static final float FALLBACK_VELOCITY = 1.5f; // 1.5 seems to be a pretty standard value
|
||||
|
||||
// The general contract for these spell subtypes is that any required parameters are set via the constructor and are
|
||||
// final, whereas any non-critical parameters are set via chainable setters with sensible defaults if not. For example,
|
||||
// the actual sound to play is required, but it makes sense for its volume and pitch to default to 1 if unspecified.
|
||||
|
||||
/** A factory that creates projectile entities. */
|
||||
protected final Function<World, EntityMagicProjectile> projectileFactory;
|
||||
/** The base range (projectile speed) of this spell. */
|
||||
protected final float baseRange;
|
||||
/** The sound that gets played when this spell is cast. */
|
||||
protected final SoundEvent sound;
|
||||
protected final Function<World, T> projectileFactory;
|
||||
|
||||
/** The volume of the sound played when this spell is cast. Defaults to 1. */
|
||||
protected float volume = 1;
|
||||
/** The pitch of the sound played when this spell is cast. Defaults to 1. */
|
||||
protected float pitch = 1;
|
||||
/** The pitch variation of the sound played when this spell is cast. Defaults to 0. */
|
||||
protected float pitchVariation = 0;
|
||||
|
||||
public SpellProjectile(String name, Tier tier, Element element, int cost, int cooldown, Function<World, EntityMagicProjectile> projectileFactory, float baseRange, SoundEvent sound) {
|
||||
this(Wizardry.MODID, name, tier, element, cost, cooldown, projectileFactory, baseRange, sound);
|
||||
public SpellProjectile(String name, Function<World, T> projectileFactory) {
|
||||
this(Wizardry.MODID, name, projectileFactory);
|
||||
}
|
||||
|
||||
public SpellProjectile(String modID, String name, Tier tier, Element element, int cost, int cooldown, Function<World, EntityMagicProjectile> projectileFactory, float baseRange, SoundEvent sound){
|
||||
super(modID, name, tier, element, SpellType.PROJECTILE, cost, cooldown, EnumAction.NONE, false);
|
||||
public SpellProjectile(String modID, String name, Function<World, T> projectileFactory){
|
||||
super(modID, name, EnumAction.NONE, false);
|
||||
this.projectileFactory = projectileFactory;
|
||||
this.baseRange = baseRange;
|
||||
this.sound = sound;
|
||||
addProperties(RANGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sound parameters for this spell.
|
||||
* @param volume
|
||||
* @param pitch
|
||||
* @param pitchVariation
|
||||
* @return The spell instance, allowing this method to be chained onto the constructor.
|
||||
*/
|
||||
public SpellProjectile soundValues(float volume, float pitch, float pitchVariation) {
|
||||
this.volume = volume;
|
||||
this.pitch = pitch;
|
||||
this.pitchVariation = pitchVariation;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override public boolean doesSpellRequirePacket(){ return false; }
|
||||
@Override public boolean requiresPacket(){ return false; }
|
||||
|
||||
@Override public boolean canBeCastByNPCs(){ return true; }
|
||||
|
||||
@Override public boolean canBeCastByDispensers() { return true; }
|
||||
|
||||
/** Computes the velocity the projectile should be launched at to achieve the required range. */
|
||||
// Long story short, it doesn't make much sense to me to have the JSON file specify the velocity - even less so if
|
||||
// the velocity is masquerading under the tag 'range' - so we'll let the code do the heavy lifting so people can
|
||||
// input something meaningful.
|
||||
protected float calculateVelocity(EntityMagicProjectile projectile, SpellModifiers modifiers, float launchHeight){
|
||||
// The required range
|
||||
float range = getProperty(RANGE).floatValue() * modifiers.get(WizardryItems.range_upgrade);
|
||||
|
||||
if(projectile.hasNoGravity()){
|
||||
// No sensible spell will do this - range is meaningless if the projectile has no gravity or lifetime
|
||||
if(projectile.getLifetime() <= 0) return FALLBACK_VELOCITY;
|
||||
// Speed = distance/time (trivial, I know, but I've put it here for the sake of completeness)
|
||||
return range / projectile.getLifetime();
|
||||
}else{
|
||||
// It seems that in Minecraft, g is usually* 0.03 - the getter method is protected unfortunately
|
||||
// * Potions and xp bottles seem to have more gravity (because that makes sense...)
|
||||
float g = 0.03f;
|
||||
// Assume horizontal projection
|
||||
return range / MathHelper.sqrt(2 * launchHeight/g);
|
||||
}
|
||||
}
|
||||
|
||||
// Previously we assumed the base range specified refers to the absolute maximum possible range
|
||||
// when launched at the ideal angle of projection. See the following:
|
||||
// https://math.stackexchange.com/questions/127300/maximum-range-of-a-projectile-launched-from-an-elevation
|
||||
// The above link gives the formula: Rmax = u/g * sqrt(u^2 + 2gH), so u = sqrt(sqrt(g^2*(h^2+r^2)) - gh)
|
||||
// This is probably overkill on the accuracy front, but... hey, I'm a perfectionist, what can I say?
|
||||
//return MathHelper.sqrt(MathHelper.sqrt(g*g * (launchHeight*launchHeight + range*range)) - g*launchHeight);
|
||||
|
||||
@Override
|
||||
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
|
||||
|
||||
if(!world.isRemote){
|
||||
// Creates a projectile from the supplied factory
|
||||
EntityMagicProjectile projectile = projectileFactory.apply(world);
|
||||
T projectile = projectileFactory.apply(world);
|
||||
// Sets the necessary parameters
|
||||
projectile.aim(caster, baseRange * modifiers.get(WizardryItems.range_upgrade));
|
||||
projectile.aim(caster, calculateVelocity(projectile, modifiers, caster.getEyeHeight()
|
||||
- (float)EntityMagicProjectile.LAUNCH_Y_OFFSET));
|
||||
projectile.damageMultiplier = modifiers.get(SpellModifiers.POTENCY);
|
||||
if(projectile instanceof EntityBomb) ((EntityBomb)projectile).blastMultiplier = modifiers.get(WizardryItems.blast_upgrade);
|
||||
addProjectileExtras(projectile, caster, modifiers);
|
||||
// Spawns the projectile in the world
|
||||
world.spawnEntity(projectile);
|
||||
}
|
||||
|
||||
caster.swingArm(hand);
|
||||
|
||||
WizardryUtilities.playSoundAtPlayer(caster, sound, volume, pitch + pitchVariation * (world.rand.nextFloat() - 0.5f));
|
||||
this.playSound(world, caster, ticksInUse, -1, modifiers);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -111,20 +125,22 @@ public class SpellProjectile extends Spell {
|
||||
|
||||
if(!world.isRemote){
|
||||
// Creates a projectile from the supplied factory
|
||||
EntityMagicProjectile projectile = projectileFactory.apply(world);
|
||||
T projectile = projectileFactory.apply(world);
|
||||
// Sets the necessary parameters
|
||||
int aimingError = caster instanceof ISpellCaster ? ((ISpellCaster)caster).getAimingError(world.getDifficulty())
|
||||
: WizardryUtilities.getDefaultAimingError(world.getDifficulty());
|
||||
projectile.aim(caster, target, baseRange * modifiers.get(WizardryItems.range_upgrade), aimingError);
|
||||
projectile.aim(caster, target, calculateVelocity(projectile, modifiers, caster.getEyeHeight()
|
||||
- (float)EntityMagicProjectile.LAUNCH_Y_OFFSET), aimingError);
|
||||
projectile.damageMultiplier = modifiers.get(SpellModifiers.POTENCY);
|
||||
if(projectile instanceof EntityBomb) ((EntityBomb)projectile).blastMultiplier = modifiers.get(WizardryItems.blast_upgrade);
|
||||
addProjectileExtras(projectile, caster, modifiers);
|
||||
// Spawns the projectile in the world
|
||||
world.spawnEntity(projectile);
|
||||
}
|
||||
|
||||
caster.swingArm(hand);
|
||||
|
||||
caster.playSound(sound, volume, pitch + pitchVariation * (world.rand.nextFloat() - 0.5f));
|
||||
this.playSound(world, caster, ticksInUse, -1, modifiers);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -132,4 +148,38 @@ public class SpellProjectile extends Spell {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cast(World world, double x, double y, double z, EnumFacing direction, int ticksInUse, int duration, SpellModifiers modifiers){
|
||||
|
||||
if(!world.isRemote){
|
||||
// Creates a projectile from the supplied factory
|
||||
T projectile = projectileFactory.apply(world);
|
||||
// Sets the necessary parameters
|
||||
projectile.setPosition(x, y, z);
|
||||
Vec3i vec = direction.getDirectionVec();
|
||||
projectile.shoot(vec.getX(), vec.getY(), vec.getZ(), calculateVelocity(projectile, modifiers,
|
||||
0.375f), DISPENSER_INACCURACY); // 0.375 is the height of the hole in a dispenser
|
||||
projectile.damageMultiplier = modifiers.get(SpellModifiers.POTENCY);
|
||||
if(projectile instanceof EntityBomb) ((EntityBomb)projectile).blastMultiplier = modifiers.get(WizardryItems.blast_upgrade);
|
||||
addProjectileExtras(projectile, null, modifiers);
|
||||
// Spawns the projectile in the world
|
||||
world.spawnEntity(projectile);
|
||||
}
|
||||
// This MUST be the coordinates of the actual dispenser, so we need to offset it
|
||||
this.playSound(world, x - direction.getXOffset(), y - direction.getYOffset(), z - direction.getZOffset(), ticksInUse, duration, modifiers);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called just before the projectile is spawned. Does nothing by default, but subclasses can override to call extra
|
||||
* methods on the spawned projectile. This method is only called server-side so cannot be used to spawn particles directly.
|
||||
* @param projectile The entity being spawned.
|
||||
* @param caster The caster of this spell, or null if it was cast by a dispenser.
|
||||
* @param modifiers The modifiers this spell was cast with.
|
||||
*/
|
||||
protected void addProjectileExtras(T projectile, @Nullable EntityLivingBase caster, SpellModifiers modifiers){
|
||||
// Subclasses can put spell-specific stuff here
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user