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:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user