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,105 @@
package electroblob.wizardry.misc;
import electroblob.wizardry.data.DispenserCastingData;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.event.SpellCastEvent.Source;
import electroblob.wizardry.item.ItemScroll;
import electroblob.wizardry.packet.PacketDispenserCastSpell;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.spell.Spell;
import electroblob.wizardry.util.SpellModifiers;
import net.minecraft.block.BlockDispenser;
import net.minecraft.dispenser.IBlockSource;
import net.minecraft.dispenser.IPosition;
import net.minecraft.init.Bootstrap.BehaviorDispenseOptional;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
/**
* Dispenser behaviour for casting spells from dispensers based on the metadata of the dispensed item. This class, along
* with the capability {@link DispenserCastingData DispenserCastingData}, forms the dispenser
* equivalent of {@link electroblob.wizardry.entity.living.EntityAIAttackSpell EntityAIAttackSpell}, which handles spell
* casting for NPCs.
*
* @author Electroblob
* @since Wizardry 4.2
*/
/*
* The dispenser and NPC casting systems are somewhat comparable in structure:
*
| Dispensers | NPCs |
| -------------------------|---------------------------|
| BehaviourSpellDispense | EntityAIAttackSpell | Handles the actual spell casting. These are slightly different
| | | | | in that EntityAIAttackSpell handles ticking for NPC spells,
| | | | | whereas for dispensers this is handled in DispenserCastingData.
| V | V |
| DispenserCastingData | ISpellCaster | Deals with data storage for spell casting. Of course, being an
| | | interface, ISpellCaster doesn't actually store the data itself.
*
*/
public class BehaviourSpellDispense extends BehaviorDispenseOptional {
public BehaviourSpellDispense(){}
@Override
protected ItemStack dispenseStack(IBlockSource source, ItemStack stack){
// This is only ever called server-side.
successful = false;
World world = source.getWorld();
// This returns a position that is 0.2 blocks away from the middle of the front face of the dispenser
IPosition position = BlockDispenser.getDispensePosition(source);
EnumFacing direction = source.getBlockState().getValue(BlockDispenser.FACING);
Spell spell = Spell.byMetadata(stack.getMetadata());
// If there's a block in the way, nothing happens
if(world.isSideSolid(source.getBlockPos().offset(direction), direction.getOpposite())) return stack;
// If the scroll can never be cast by a dispenser, it should be dispensed as an item.
if(!spell.canBeCastByDispensers()) return super.dispenseStack(source, stack);
SpellModifiers modifiers = new SpellModifiers();
double x = position.getX();
double y = position.getY();
double z = position.getZ();
// For horizontal dispensers, the position is lowered by 0.125 so it actually lines up with the hole.
if(direction.getAxis().isHorizontal()) y -= 0.125;
// If the scroll can be cast by a dispenser, it should be cast. If this fails, then the scroll should stay in
// the dispenser.
if(MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Pre(Source.DISPENSER, spell, world, x, y, z, direction, modifiers)))
return stack;
successful = spell.cast(world, x, y, z, direction, 0, -1, modifiers);
if(successful){
MinecraftForge.EVENT_BUS.post(new SpellCastEvent.Post(Source.DISPENSER, spell, world, x, y, z, direction, modifiers));
stack.shrink(1);
if(spell.isContinuous || spell.requiresPacket()){
// Sends a packet to all players in dimension to tell them to spawn particles.
IMessage msg = new PacketDispenserCastSpell.Message(x, y, z, direction, source.getBlockPos(), spell,
spell.isContinuous ? ItemScroll.CASTING_TIME : 0, modifiers); // Non-continuous spells ignore duration
WizardryPacketHandler.net.sendToDimension(msg, world.provider.getDimension());
}
if(spell.isContinuous){
DispenserCastingData data = DispenserCastingData.get(source.getBlockTileEntity());
data.startCasting(spell, x, y, z, ItemScroll.CASTING_TIME, modifiers);
}
}
return stack;
}
}
@@ -0,0 +1,487 @@
package electroblob.wizardry.misc;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.constants.Tier;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.entity.EntityMeteor;
import electroblob.wizardry.entity.construct.*;
import electroblob.wizardry.entity.living.*;
import electroblob.wizardry.entity.projectile.EntityFirebomb;
import electroblob.wizardry.entity.projectile.EntityMagicFireball;
import electroblob.wizardry.event.SpellCastEvent;
import electroblob.wizardry.item.ItemArtefact;
import electroblob.wizardry.registry.*;
import electroblob.wizardry.spell.Banish;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.entity.effect.EntityLightningBolt;
import net.minecraft.entity.item.EntityFallingBlock;
import net.minecraft.entity.passive.EntitySquid;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.MobEffects;
import net.minecraft.item.ItemStack;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.common.IPlantable;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.BiConsumer;
/**
* A {@code Forfeit} object represents a negative effect that may happen when a player attempts to cast an
* undiscovered spell. The nature and severity of the forfeit depends on the element and tier of the spell that was
* attempted.<br>
* <br>
* Adding a new forfeit is as simple as calling {@link Forfeit#add(Tier, Element, Forfeit)} and supplying the
* {@code Forfeit} instance along with a tier and element to associate it with. To create a forfeit, you may extend
* this class and instantiate it, or use {@link Forfeit#create(ResourceLocation, BiConsumer)} to concisely define the
* behaviour (this method is provided since most forfeits' behaviour code is fairly brief and would otherwise result in
* a large number of trivial 'stub' classes - in fact, many of wizardry's forfeits are defined in a single line).<br>
* <br>
* This class also handles the (event-driven) selection of forfeits and determines when one should be applied.
*
* @author Electroblob
* @since Wizardry 4.2
*/
// With the exception of sound events, everything forfeit-related is done right here. How about that for modularity? :P
@Mod.EventBusSubscriber
public abstract class Forfeit {
private static final ListMultimap<Pair<Tier, Element>, Forfeit> forfeits = ArrayListMultimap.create();
private static final float TIER_CHANGE_CHANCE = 0.2f;
private final ResourceLocation name;
protected final SoundEvent sound;
public Forfeit(ResourceLocation name){
this.name = name;
this.sound = WizardrySounds.createSound("forfeit." + name.getPath());
}
public abstract void apply(World world, EntityPlayer player);
/**
* Returns an {@link ITextComponent} for the message displayed when this forfeit is activated.
* @param implementName An {@code ITextComponent} for the name of the implement being used. This is usually
* something generic like 'wand' or 'scroll'.
* @return An {@code ITextComponent} representing this forfeit's message, for use in chat messages.
* @see Forfeit#getMessageForWand()
* @see Forfeit#getMessageForScroll()
*/
public ITextComponent getMessage(ITextComponent implementName){
return new TextComponentTranslation("forfeit." + name.toString(), implementName);
}
/** Wrapper for {@link Forfeit#getMessage(ITextComponent)} with {@code implementName} set to the lang file key
* {@code item.ebwizardry:wand.generic} */
public ITextComponent getMessageForWand(){
return getMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":wand.generic"));
}
/** Wrapper for {@link Forfeit#getMessage(ITextComponent)} with {@code implementName} set to the lang file key
* {@code item.ebwizardry:scroll.generic} */
public ITextComponent getMessageForScroll(){
return getMessage(new TextComponentTranslation("item." + Wizardry.MODID + ":scroll.generic"));
}
/** Returns the {@link SoundEvent} played when this forfeit is activated. */
public SoundEvent getSound(){
return sound;
}
public static void add(Tier tier, Element element, Forfeit forfeit){
forfeits.put(Pair.of(tier, element), forfeit);
}
public static Forfeit getRandomForfeit(Random random, Tier tier, Element element){
float f = random.nextFloat();
if(f < TIER_CHANGE_CHANCE && tier.ordinal() > 0) tier = Tier.values()[tier.ordinal() - 1];
else if(f > 1 - TIER_CHANGE_CHANCE && tier.ordinal() < Tier.values().length-1) tier = Tier.values()[tier.ordinal() + 1];
List<Forfeit> matches = forfeits.get(Pair.of(tier, element));
if(matches.isEmpty()){
Wizardry.logger.warn("No forfeits with tier {} and element {}!", tier, element);
return null;
}
return matches.get(random.nextInt(matches.size()));
}
public static Collection<Forfeit> getForfeits(){
return Collections.unmodifiableCollection(forfeits.values());
}
/** Static helper method that creates a {@code Forfeit} with the given name and an effect specified by the given
* consumer. This allows code to use a neater lambda expression rather than an anonymous class. */
public static Forfeit create(ResourceLocation name, BiConsumer<World, EntityPlayer> effect){
return new Forfeit(name){
@Override
public void apply(World world, EntityPlayer player){
effect.accept(world, player);
}
};
}
/** Internal wrapper for {@link Forfeit#create(ResourceLocation, BiConsumer)} so I don't have to put wizardry's
* mod ID in every time. */
private static Forfeit create(String name, BiConsumer<World, EntityPlayer> effect){
return create(new ResourceLocation(Wizardry.MODID, name), effect);
}
@SubscribeEvent(priority = EventPriority.NORMAL) // Forfeits come after spell disabling but before modifiers
public static void onSpellCastPreEvent(SpellCastEvent.Pre event){
if(!Wizardry.settings.discoveryMode) return;
if(event.getCaster() instanceof EntityPlayer && !((EntityPlayer)event.getCaster()).isCreative()
&& (event.getSource() == SpellCastEvent.Source.WAND || event.getSource() == SpellCastEvent.Source.SCROLL)){
EntityPlayer player = (EntityPlayer)event.getCaster();
WizardData data = WizardData.get(player);
float chance = (float)Wizardry.settings.forfeitChance;
if(ItemArtefact.isArtefactActive(player, WizardryItems.amulet_wisdom)) chance *= 0.5;
// Use the synchronised random to ensure the same outcome on client- and server-side
if(data.synchronisedRandom.nextFloat() < chance && !data.hasSpellBeenDiscovered(event.getSpell())){
event.setCanceled(true);
Forfeit forfeit = getRandomForfeit(event.getWorld().rand, event.getSpell().getTier(), event.getSpell().getElement());
if(forfeit == null){ // Should never happen, but just in case...
if(!event.getWorld().isRemote) player.sendMessage(new TextComponentTranslation("forfeit.ebwizardry:do_nothing"));
return;
}
forfeit.apply(event.getWorld(), player);
WizardryAdvancementTriggers.spell_failure.triggerFor(player);
WizardryUtilities.playSoundAtPlayer(player, forfeit.getSound(), WizardrySounds.SPELLS, 1, 1);
if(!event.getWorld().isRemote) player.sendMessage(
event.getSource() == SpellCastEvent.Source.WAND ? forfeit.getMessageForWand() : forfeit.getMessageForScroll());
}
}
}
/** Called from the preInit method in the main mod class to set up all the forfeits. */
public static void register(){
add(Tier.NOVICE, Element.FIRE, create("burn_self", (w, p) -> p.setFire(5)));
add(Tier.APPRENTICE, Element.FIRE, create("fireball", (w, p) -> {
if(!w.isRemote){
EntityMagicFireball fireball = new EntityMagicFireball(w);
Vec3d vec = p.getPositionEyes(0).add(p.getLookVec().scale(6));
fireball.setPosition(vec.x, vec.y, vec.z);
fireball.shoot(p.posX, p.posY + p.getEyeHeight(), p.posZ, 1.5f, 1);
w.spawnEntity(fireball);
}
}));
add(Tier.APPRENTICE, Element.FIRE, create("firebomb", (w, p) -> {
if(!w.isRemote){
EntityFirebomb firebomb = new EntityFirebomb(w);
firebomb.setPosition(p.posX, p.posY + 5, p.posZ);
w.spawnEntity(firebomb);
}
}));
add(Tier.ADVANCED, Element.FIRE, create("explode", (w, p) -> w.createExplosion(null, p.posX, p.posY, p.posZ, 1, false)));
add(Tier.ADVANCED, Element.FIRE, create("blazes", (w, p) -> {
if(!w.isRemote){
for(int i = 0; i < 3; i++){
BlockPos pos = WizardryUtilities.findNearbyFloorSpace(p, 4, 2);
if(pos == null) break;
EntityBlazeMinion blaze = new EntityBlazeMinion(w);
blaze.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5);
w.spawnEntity(blaze);
}
}
}));
add(Tier.MASTER, Element.FIRE, create("burn_surroundings", (w, p) -> {
if(!w.isRemote){
List<BlockPos> sphere = WizardryUtilities.getBlockSphere(p.getPosition(), 6);
for(BlockPos pos : sphere){
if(w.rand.nextBoolean() && w.isAirBlock(pos)) w.setBlockState(pos, Blocks.FIRE.getDefaultState());
}
}
}));
add(Tier.MASTER, Element.FIRE, create("meteors", (w, p) -> {
if(!w.isRemote) for(int i=0; i<5; i++) w.spawnEntity(new EntityMeteor(w, p.posX + w.rand.nextDouble() * 16 - 8,
p.posY + 40 + w.rand.nextDouble() * 30, p.posZ + w.rand.nextDouble() * 16 - 8,
1, WizardryUtilities.canDamageBlocks(p, w)));
}));
add(Tier.NOVICE, Element.ICE, create("freeze_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.frost, 200))));
add(Tier.APPRENTICE, Element.ICE, create("freeze_self_2", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.frost, 300, 1))));
add(Tier.APPRENTICE, Element.ICE, create("ice_spikes", (w, p) -> {
if(!w.isRemote){
for(int i = 0; i < 5; i++){
EntityIceSpike iceSpike = new EntityIceSpike(w);
double x = p.posX + 2 - w.rand.nextFloat() * 4;
double z = p.posZ + 2 - w.rand.nextFloat() * 4;
Integer y = WizardryUtilities.getNearestSurface(w, new BlockPos(x, p.posY, z), EnumFacing.UP, 2, true,
WizardryUtilities.SurfaceCriteria.basedOn(World::isBlockFullCube));
if(y == null) break;
iceSpike.setFacing(EnumFacing.UP);
iceSpike.setPosition(x, y, z);
w.spawnEntity(iceSpike);
}
}
}));
add(Tier.ADVANCED, Element.ICE, create("blizzard", (w, p) -> {
if(!w.isRemote){
EntityBlizzard blizzard = new EntityBlizzard(w);
blizzard.setPosition(p.posX, p.posY, p.posZ);
w.spawnEntity(blizzard);
}
}));
add(Tier.ADVANCED, Element.ICE, create("ice_wraiths", (w, p) -> {
if(!w.isRemote){
for(int i = 0; i < 3; i++){
BlockPos pos = WizardryUtilities.findNearbyFloorSpace(p, 4, 2);
if(pos == null) break;
EntityIceWraith iceWraith = new EntityIceWraith(w);
iceWraith.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5);
w.spawnEntity(iceWraith);
}
}
}));
add(Tier.MASTER, Element.ICE, create("hailstorm", (w, p) -> {
if(!w.isRemote){
EntityHailstorm hailstorm = new EntityHailstorm(w);
hailstorm.setPosition(p.posX, p.posY + 5, p.posZ - 3); // Subtract 3 from z because it's facing south (yaw 0)
w.spawnEntity(hailstorm);
}
}));
add(Tier.MASTER, Element.ICE, create("ice_giant", (w, p) -> {
if(!w.isRemote){
EntityIceGiant iceGiant = new EntityIceGiant(w);
iceGiant.setPosition(p.posX + p.getLookVec().x * 4, p.posY, p.posZ + p.getLookVec().z * 4);
w.spawnEntity(iceGiant);
}
}));
add(Tier.NOVICE, Element.LIGHTNING, create("thunder", (w, p) -> {
p.addVelocity(-p.getLookVec().x, 0, -p.getLookVec().z);
if(w.isRemote) w.spawnParticle(EnumParticleTypes.EXPLOSION_LARGE, p.posX, p.posY, p.posZ, 0, 0, 0);
}));
add(Tier.APPRENTICE, Element.LIGHTNING, create("storm", (w, p) -> {
int standardWeatherTime = (300 + (new Random()).nextInt(600)) * 20;
w.getWorldInfo().setRaining(true);
w.getWorldInfo().setRainTime(standardWeatherTime);
w.getWorldInfo().setThundering(true);
w.getWorldInfo().setThunderTime(standardWeatherTime);
}));
add(Tier.APPRENTICE, Element.LIGHTNING, create("lightning_sigils", (w, p) -> {
if(!w.isRemote){
for(EnumFacing direction : EnumFacing.HORIZONTALS){
BlockPos pos = p.getPosition().offset(direction, 2);
Integer y = WizardryUtilities.getNearestFloor(w, pos, 2);
if(y == null) continue;
EntityLightningSigil sigil = new EntityLightningSigil(w);
sigil.setPosition(pos.getX() + 0.5, y, pos.getZ() + 0.5);
w.spawnEntity(sigil);
}
}
}));
add(Tier.ADVANCED, Element.LIGHTNING, create("lightning", (w, p) -> w.addWeatherEffect(new EntityLightningBolt(w, p.posX, p.posY, p.posZ, false))));
add(Tier.ADVANCED, Element.LIGHTNING, create("paralyse_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.paralysis, 200))));
add(Tier.ADVANCED, Element.LIGHTNING, create("lightning_wraiths", (w, p) -> {
if(!w.isRemote){
for(int i = 0; i < 3; i++){
BlockPos pos = WizardryUtilities.findNearbyFloorSpace(p, 4, 2);
if(pos == null) break;
EntityLightningWraith lightningWraith = new EntityLightningWraith(w);
lightningWraith.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5);
w.spawnEntity(lightningWraith);
}
}
}));
add(Tier.MASTER, Element.LIGHTNING, create("storm_elementals", (w, p) -> {
if(!w.isRemote){
for(EnumFacing direction : EnumFacing.HORIZONTALS){
BlockPos pos = p.getPosition().offset(direction, 3);
EntityStormElemental stormElemental = new EntityStormElemental(w);
stormElemental.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5);
w.spawnEntity(stormElemental);
}
}
}));
add(Tier.NOVICE, Element.NECROMANCY, create("nausea", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.NAUSEA, 400))));
add(Tier.APPRENTICE, Element.NECROMANCY, create("zombie_horde", (w, p) -> {
if(!w.isRemote){
for(int i = 0; i < 3; i++){
BlockPos pos = WizardryUtilities.findNearbyFloorSpace(p, 4, 2);
if(pos == null) break;
EntityZombieMinion zombie = new EntityZombieMinion(w);
zombie.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5);
w.spawnEntity(zombie);
}
}
}));
add(Tier.ADVANCED, Element.NECROMANCY, create("wither_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.WITHER, 400))));
add(Tier.MASTER, Element.NECROMANCY, create("cripple_self", (w, p) -> p.attackEntityFrom(DamageSource.MAGIC, p.getHealth() - 1)));
add(Tier.MASTER, Element.NECROMANCY, create("shadow_wraiths", (w, p) -> {
if(!w.isRemote){
for(EnumFacing direction : EnumFacing.HORIZONTALS){
BlockPos pos = p.getPosition().offset(direction, 3);
EntityShadowWraith wraith = new EntityShadowWraith(w);
wraith.setPosition(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5);
w.spawnEntity(wraith);
}
}
}));
add(Tier.NOVICE, Element.EARTH, create("snares", (w, p) -> {
if(!w.isRemote){
for(EnumFacing direction : EnumFacing.HORIZONTALS){
BlockPos pos = p.getPosition().offset(direction);
w.setBlockState(pos, WizardryBlocks.snare.getDefaultState());
}
}
}));
add(Tier.NOVICE, Element.EARTH, create("squid", (w, p) -> {
if(!w.isRemote){
EntitySquid squid = new EntitySquid(w);
squid.setPosition(p.posX, p.posY + 3, p.posZ);
w.spawnEntity(squid);
}
}));
add(Tier.APPRENTICE, Element.EARTH, create("uproot_plants", (w, p) -> {
if(!w.isRemote){
List<BlockPos> sphere = WizardryUtilities.getBlockSphere(p.getPosition(), 5);
sphere.removeIf(pos -> !(w.getBlockState(pos).getBlock() instanceof IPlantable));
sphere.forEach(pos -> w.destroyBlock(pos, true));
}
}));
add(Tier.APPRENTICE, Element.EARTH, create("poison_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.POISON, 400, 1))));
add(Tier.ADVANCED, Element.EARTH, create("flood", (w, p) -> {
if(!w.isRemote){
List<BlockPos> sphere = WizardryUtilities.getBlockSphere(p.getPosition().up(), 2);
sphere.removeIf(pos -> !WizardryUtilities.canBlockBeReplaced(w, pos, true));
sphere.forEach(pos -> w.setBlockState(pos, Blocks.WATER.getDefaultState()));
}
}));
add(Tier.MASTER, Element.EARTH, create("bury_self", (w, p) -> {
if(!w.isRemote){
List<BlockPos> sphere = WizardryUtilities.getBlockSphere(p.getPosition(), 4);
sphere.removeIf(pos -> !w.getBlockState(pos).isFullCube());
sphere.forEach(pos -> {
EntityFallingBlock block = new EntityFallingBlock(w, pos.getX() + 0.5, pos.getY() + 0.5,
pos.getZ() + 0.5, w.getBlockState(pos));
block.motionY = 0.3 * (4 - (p.getPosition().getY() - pos.getY()));
w.spawnEntity(block);
});
}
}));
add(Tier.NOVICE, Element.SORCERY, create("spill_inventory", (w, p) -> {
for(int i = 0; i < p.inventory.mainInventory.size(); i++){
ItemStack stack = p.inventory.mainInventory.get(i);
if(!stack.isEmpty()){
p.dropItem(stack, true, false);
p.inventory.mainInventory.set(i, ItemStack.EMPTY);
}
}
}));
add(Tier.APPRENTICE, Element.SORCERY, create("teleport_self", (w, p) -> ((Banish)Spells.banish).teleport(p, w, 8 + w.rand.nextDouble() * 8)));
add(Tier.ADVANCED, Element.SORCERY, create("levitate_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.LEVITATION, 200))));
add(Tier.ADVANCED, Element.SORCERY, create("vex_horde", (w, p) -> {
if(!w.isRemote){
for(int i = 0; i < 4; i++){
BlockPos pos = WizardryUtilities.findNearbyFloorSpace(p, 4, 2);
if(pos == null) break;
EntityVexMinion vex = new EntityVexMinion(w);
vex.setPosition(pos.getX() + 0.5, pos.getY() + 1, pos.getZ() + 0.5);
w.spawnEntity(vex);
}
}
}));
add(Tier.MASTER, Element.SORCERY, create("black_hole", (w, p) -> {
EntityBlackHole blackHole = new EntityBlackHole(w);
Vec3d vec = p.getPositionEyes(1).add(p.getLookVec().scale(4));
blackHole.setPosition(vec.x, vec.y, vec.z);
w.spawnEntity(blackHole);
}));
add(Tier.MASTER, Element.SORCERY, create("arrow_rain", (w, p) -> {
if(!w.isRemote){
EntityArrowRain arrowRain = new EntityArrowRain(w);
arrowRain.setPosition(p.posX, p.posY + 5, p.posZ - 3); // Subtract 3 from z because it's facing south (yaw 0)
w.spawnEntity(arrowRain);
}
}));
add(Tier.NOVICE, Element.HEALING, create("damage_self", (w, p) -> p.attackEntityFrom(DamageSource.MAGIC, 4)));
add(Tier.NOVICE, Element.HEALING, create("spill_armour", (w, p) -> {
for(int i = 0; i < p.inventory.armorInventory.size(); i++){
ItemStack stack = p.inventory.armorInventory.get(i);
if(!stack.isEmpty()){
p.dropItem(stack, true, false);
p.inventory.armorInventory.set(i, ItemStack.EMPTY);
}
}
}));
add(Tier.APPRENTICE, Element.HEALING, create("hunger", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.HUNGER, 400, 4))));
add(Tier.APPRENTICE, Element.HEALING, create("blind_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.BLINDNESS, 200))));
add(Tier.ADVANCED, Element.HEALING, create("weaken_self", (w, p) -> p.addPotionEffect(new PotionEffect(MobEffects.WEAKNESS, 600, 3))));
add(Tier.ADVANCED, Element.HEALING, create("jam_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.arcane_jammer, 300))));
add(Tier.MASTER, Element.HEALING, create("curse_self", (w, p) -> p.addPotionEffect(new PotionEffect(WizardryPotions.curse_of_undeath, Integer.MAX_VALUE))));
}
}
@@ -0,0 +1,57 @@
package electroblob.wizardry.misc;
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));
}
}
@@ -0,0 +1,140 @@
package electroblob.wizardry.misc;
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;
import javax.annotation.Nullable;
import java.util.Set;
/**
* 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);
}
}