Files
Wizardry/src/main/java/electroblob/wizardry/spell/Possession.java
T

715 lines
29 KiB
Java

package electroblob.wizardry.spell;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.data.IVariable;
import electroblob.wizardry.data.IVariable.Variable;
import electroblob.wizardry.data.Persistence;
import electroblob.wizardry.data.WizardData;
import electroblob.wizardry.entity.living.*;
import electroblob.wizardry.entity.projectile.*;
import electroblob.wizardry.integration.DamageSafetyChecker;
import electroblob.wizardry.packet.PacketControlInput;
import electroblob.wizardry.packet.PacketPossession;
import electroblob.wizardry.packet.WizardryPacketHandler;
import electroblob.wizardry.registry.Spells;
import electroblob.wizardry.registry.WizardryItems;
import electroblob.wizardry.util.ParticleBuilder;
import electroblob.wizardry.util.ParticleBuilder.Type;
import electroblob.wizardry.util.SpellModifiers;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.*;
import net.minecraft.entity.ai.EntityAIAttackMelee;
import net.minecraft.entity.ai.attributes.AttributeModifier;
import net.minecraft.entity.ai.attributes.IAttribute;
import net.minecraft.entity.ai.attributes.IAttributeInstance;
import net.minecraft.entity.monster.*;
import net.minecraft.entity.passive.EntityChicken;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.projectile.EntityPotion;
import net.minecraft.entity.projectile.EntitySnowball;
import net.minecraft.init.Enchantments;
import net.minecraft.init.Items;
import net.minecraft.init.PotionTypes;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.EnumAction;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.potion.PotionEffect;
import net.minecraft.potion.PotionUtils;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants.NBT;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingDamageEvent;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.entity.player.EntityItemPickupEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Function;
@Mod.EventBusSubscriber
public class Possession extends SpellRay {
/** A {@code ResourceLocation} representing the shader file used when possessing an entity. */
public static final ResourceLocation SHADER = new ResourceLocation(Wizardry.MODID, "shaders/post/possession.json");
/** The NBT tag name for storing the possessing entity's UUID in the target's tag compound. */
public static final String NBT_KEY = "possessor";
/** The NBT tag name for storing the possessor's previous inventory in their tag compound. */
public static final String INVENTORY_NBT_KEY = "prevInventory";
/** The health (in half-hearts) below or equal to which the possessor will automatically stop possessing. */
public static final String CRITICAL_HEALTH = "critical_health";
private static final int PROJECTILE_COOLDOWN = 30;
public static final IVariable<Integer> TIMER_KEY = new Variable<Integer>(Persistence.DIMENSION_CHANGE).withTicker(Possession::update);
public static final IVariable<EntityLiving> POSSESSEE_KEY = new Variable<>(Persistence.DIMENSION_CHANGE);
public static final IVariable<Integer> SHOOT_COOLDOWN_KEY = new Variable<Integer>(Persistence.DIMENSION_CHANGE).withTicker((p, n) -> Math.max(n-1, 0));
private static final Multimap<Class<? extends EntityLiving>, BiConsumer<?, EntityPlayer>> abilities = HashMultimap.create();
private static final Map<Class<? extends EntityLiving>, Function<World, ? extends IProjectile>> projectiles = new HashMap<>();
private static final Map<IAttribute, UUID> INHERITED_ATTRIBUTES;
static {
INHERITED_ATTRIBUTES = ImmutableMap.of(
SharedMonsterAttributes.MOVEMENT_SPEED, UUID.fromString("f65cfcaf-e7ec-4dfb-aa6c-711735d007e3"),
SharedMonsterAttributes.ATTACK_DAMAGE, UUID.fromString("ab67c89e-74a5-4e27-9621-40bffb4f7a03"),
SharedMonsterAttributes.KNOCKBACK_RESISTANCE, UUID.fromString("05529535-9bcf-42bb-8822-45f5ce6a8f08"));
addAbility(EntitySpider.class, (spider, player) -> { if(player.collidedHorizontally) player.motionY = 0.2; });
addAbility(EntityChicken.class, (chicken, player) -> { if(!player.onGround && player.motionY < 0) player.motionY *= 0.6D; });
addAbility(EntityLiving.class, (entity, player) -> { if(!entity.isImmuneToFire() && player.isBurning()) player.extinguish(); });
addProjectile(EntitySnowman.class, EntitySnowball::new); // Woooo snowballs!
addProjectile(EntityBlaze.class, EntityMagicFireball::new); // Ugh normal fireballs don't fit so let's just use mine!
addProjectile(EntityGhast.class, EntityLargeMagicFireball::new);
addProjectile(EntityIceWraith.class, EntityIceShard::new);
addProjectile(EntityShadowWraith.class, EntityDarknessOrb::new);
addProjectile(EntityStormElemental.class, EntityLightningDisc::new);
addProjectile(EntityWitch.class, EntityPotion::new);
}
public Possession(){
super("possession", false, EnumAction.NONE);
addProperties(EFFECT_DURATION, CRITICAL_HEALTH);
}
@Override public boolean canBeCastByNPCs() { return false; }
@Override public boolean canBeCastByDispensers() { return false; }
@Override
public boolean requiresPacket(){
return false; // Has its own packet
}
@Override
protected SoundEvent[] createSounds(){
return createSoundsWithSuffixes("possess", "end");
}
@Override
public boolean cast(World world, EntityPlayer caster, EnumHand hand, int ticksInUse, SpellModifiers modifiers){
Vec3d look = caster.getLookVec();
Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight() - Y_OFFSET, caster.posZ);
if(!shootSpell(world, origin, look, caster, ticksInUse, modifiers)) return false;
if(casterSwingsArm(world, caster, hand, ticksInUse, modifiers)) caster.swingArm(hand);
this.playSound(world, caster, ticksInUse, -1, modifiers, "possess"); // TODO: There must be a better way...
return true;
}
@Override
protected boolean onEntityHit(World world, Entity target, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse,
SpellModifiers modifiers){
if(target instanceof EntityLiving && caster instanceof EntityPlayer && !isPossessing((EntityPlayer)caster)){
EntityPlayer player = (EntityPlayer)caster;
if(!player.isCreative() && player.getHealth() <= getProperty(CRITICAL_HEALTH).floatValue()){
player.sendStatusMessage(new TextComponentTranslation(
"spell." + this.getRegistryName() + ".insufficienthealth"), true);
return false;
}
if(!world.isRemote){
int duration = (int)(getProperty(EFFECT_DURATION).floatValue() * modifiers.get(WizardryItems.duration_upgrade));
if(possess(player, (EntityLiving)target, duration)){
return true;
}
}
}
return false;
}
@Override
protected boolean onBlockHit(World world, BlockPos pos, EnumFacing side, Vec3d hit, EntityLivingBase caster, Vec3d origin, int ticksInUse,
SpellModifiers modifiers){
return false;
}
@Override
protected boolean onMiss(World world, EntityLivingBase caster, Vec3d origin, Vec3d direction, int ticksInUse, SpellModifiers modifiers){
return false;
}
// ================================================ Helper methods ================================================
/**
* Causes the given player to start possessing the given target for the given duration, and sets all relevant data
* for both entities accordingly. Also takes care of sending packets to update clients.
* @param possessor The player doing the possessing.
* @param target The entity being possessed.
* @param duration The number of ticks for which the possession should last. Pass in a negative integer to make the
* possession last indefinitely (until manually ended with the dismount key).
* @return True if the possession succeeded, false if for some reason it did not (only happens if the player's
* {@link WizardData} is null).
*/
public boolean possess(EntityPlayer possessor, EntityLiving target, int duration){
if(WizardData.get(possessor) != null){
WizardData.get(possessor).setVariable(POSSESSEE_KEY, target);
WizardData.get(possessor).setVariable(TIMER_KEY, duration);
possessor.setPositionAndRotation(target.posX, target.posY, target.posZ, target.rotationYaw, target.rotationPitch);
possessor.eyeHeight = target.getEyeHeight();
setSize(possessor, target.width, target.height);
target.setDead();
target.setNoAI(true);
target.setAttackTarget(null);
// Attributes
if(target instanceof EntityFlying || target instanceof net.minecraft.entity.passive.EntityFlying){
possessor.capabilities.allowFlying = true;
possessor.capabilities.isFlying = true;
}
// Apply attribute modifiers which change the player's attribute value to the target's value
// Uses predefined UUIDs so we can easily remove them later
attributes:
for(IAttribute attribute : INHERITED_ATTRIBUTES.keySet()){
IAttributeInstance instance = target.getAttributeMap().getAttributeInstance(attribute);
if(instance != null){
double targetValue = instance.getAttributeValue();
double currentValue = possessor.getAttributeMap().getAttributeInstance(attribute).getAttributeValue();
// Don't ask me why, but the player's base movement speed seems to be 0.1
if(attribute == SharedMonsterAttributes.MOVEMENT_SPEED) currentValue /= possessor.capabilities.getWalkSpeed();
for(EntityEquipmentSlot slot : EntityEquipmentSlot.values()){
if(target.getItemStackFromSlot(slot).getAttributeModifiers(slot).containsKey(attribute.getName())){
// If the mob has equipment, use the modifiers for that equipment instead of the mob's normal ones
// Not doing this results in the player being able to one-hit most mobs when possessing a zombie pigman!
continue attributes;
}
}
possessor.getAttributeMap().getAttributeInstance(attribute).applyModifier(new AttributeModifier(
INHERITED_ATTRIBUTES.get(attribute), "possessionModifier", targetValue / currentValue,
WizardryUtilities.Operations.MULTIPLY_FLAT));
}
}
if(possessor.world.isRemote && possessor == net.minecraft.client.Minecraft.getMinecraft().player){
// Shaders and effects
if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER);
electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); // Looks quite nice...
}else{
// Targeting
for(EntityLiving creature : WizardryUtilities.getEntitiesWithinRadius(16, possessor.posX,
possessor.posY, possessor.posZ, possessor.world, EntityLiving.class)){
// Mobs are dumb, if a player possesses something they're like "Huh?! Where'd you go?"
// Of course, this won't last long if the player attacks them, since they'll revenge-target them
if(creature.getAttackTarget() == possessor && !creature.canAttackClass(target.getClass()))
creature.setAttackTarget(null);
}
// Inventory and items
if(possessor.getEntityData() != null){
possessor.getEntityData().setTag(INVENTORY_NBT_KEY, possessor.inventory.writeToNBT(new NBTTagList()));
}
possessor.inventory.clear();
possessor.inventoryContainer.detectAndSendChanges();
ItemStack stack = target.getHeldItemMainhand().copy();
if(target instanceof EntityEnderman && ((EntityEnderman)target).getHeldBlockState() != null){
stack = new ItemStack(((EntityEnderman)target).getHeldBlockState().getBlock());
}else if(stack.getItem() instanceof ItemBow){
Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(stack);
enchantments.put(Enchantments.INFINITY, 1);
EnchantmentHelper.setEnchantments(enchantments, stack);
ItemStack arrow = new ItemStack(Items.ARROW);
if(target instanceof EntityStray || target instanceof EntityStrayMinion){
arrow = new ItemStack(Items.TIPPED_ARROW);
PotionUtils.addPotionToItemStack(arrow, PotionTypes.SLOWNESS);
}
possessor.setHeldItem(EnumHand.OFF_HAND, arrow);
}
possessor.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, stack);
// Packets
WizardryPacketHandler.net.sendToAllTracking(new PacketPossession.Message(possessor, target, duration), possessor);
if(possessor instanceof EntityPlayerMP){
WizardryPacketHandler.net.sendTo(new PacketPossession.Message(possessor, target, duration), (EntityPlayerMP)possessor);
}
}
return true;
}
return false;
}
/** Causes the given player to stop possessing their current possessee, if any, and resets all relevant data for
* both entities. Also takes care of sending packets to update clients. */
public void endPossession(EntityPlayer player){
// Reverts the possessed entity back to normal
EntityLiving victim = getPossessee(player);
if(victim != null){
victim.isDead = false;
victim.setNoAI(false);
victim.setPosition(player.posX, player.posY, player.posZ);
if(!player.world.isRemote) player.world.spawnEntity(victim);
for(PotionEffect effect : player.getActivePotionEffects()){
victim.addPotionEffect(effect);
}
}
// Reverts the player back to normal
player.clearActivePotions();
player.eyeHeight = player.getDefaultEyeHeight(); // How convenient!
if(WizardData.get(player) != null){
WizardData.get(player).setVariable(TIMER_KEY, 0);
WizardData.get(player).setVariable(POSSESSEE_KEY, null);
}
if(!player.capabilities.isCreativeMode){
player.capabilities.allowFlying = false;
player.capabilities.isFlying = false;
}
if(player.world.isRemote && player == net.minecraft.client.Minecraft.getMinecraft().player){
net.minecraft.client.Minecraft.getMinecraft().entityRenderer.stopUseShader();
electroblob.wizardry.client.WizardryClientEventHandler.playBlinkEffect(); // Looks quite nice...
}
for(IAttribute attribute : INHERITED_ATTRIBUTES.keySet()){
player.getAttributeMap().getAttributeInstance(attribute).removeModifier(INHERITED_ATTRIBUTES.get(attribute));
}
if(player instanceof EntityPlayerMP){
player.inventory.clear();
if(player.getEntityData() != null){
player.inventory.readFromNBT(player.getEntityData().getTagList(INVENTORY_NBT_KEY, NBT.TAG_COMPOUND));
}
player.inventoryContainer.detectAndSendChanges();
}
this.playSound(player.world, player, 0, -1, null, "end");
if(!player.world.isRemote && player instanceof EntityPlayerMP){
WizardryPacketHandler.net.sendToAllTracking(new PacketPossession.Message(player, null, 0), player);
WizardryPacketHandler.net.sendTo(new PacketPossession.Message(player, null, 0), (EntityPlayerMP)player);
}
}
/** Returns the {@code EntityLiving} that is currently being possessed by the given player, or null if the player is
* not currently possessing an entity. */
@Nullable
public static EntityLiving getPossessee(EntityPlayer player){
return WizardData.get(player) == null ? null : WizardData.get(player).getVariable(POSSESSEE_KEY);
}
/** Returns true if the given player is currently possessing an entity, false otherwise. Just a shortcut for
* {@code Possession.getPossessee(player) != null}. */
public static boolean isPossessing(EntityPlayer player){
return getPossessee(player) != null;
}
private static int update(EntityPlayer player, Integer possessionTimer){
if(possessionTimer == null) possessionTimer = 0;
if(possessionTimer > 0){
if(isPossessing(player) && !player.isSneaking()){
possessionTimer--;
if(player.world.isRemote){
ParticleBuilder.create(Type.DARK_MAGIC, player).clr(0.1f, 0, 0.3f).spawn(player.world);
// TODO: This (and a few other similar uses) needs ClientProxy-ing.
if(!net.minecraft.client.Minecraft.getMinecraft().entityRenderer.isShaderActive())
if(Wizardry.settings.useShaders) net.minecraft.client.Minecraft.getMinecraft().entityRenderer.loadShader(SHADER);
}
}else{
((Possession)Spells.possession).endPossession(player);
return 0;
}
}else if(isPossessing(player)){
((Possession)Spells.possession).endPossession(player);
}
return possessionTimer;
}
/** Adds the given {@link BiConsumer} to the list of abilities. An <i>ability</i> is an entity-specific action or
* effect that happens when a certain type of entity is possessed. For example, spiders can climb walls, endermen
* can pick up blocks, creepers explode, etc. Other mods may use this method to */
public static <T extends EntityLiving> void addAbility(Class<T> entityType, BiConsumer<T, EntityPlayer> ability){
abilities.put(entityType, ability);
}
@SuppressWarnings("unchecked") // Guess what? Type erasure again!
private static <T extends EntityLiving> void performAbilities(EntityLiving entity, EntityPlayer player){
// Now we have a type parameter T to work with we can ram the entity into the consumer without a compiler error
for(Class<? extends EntityLiving> entityType : abilities.keySet()){
if(entityType.isAssignableFrom(entity.getClass())){
abilities.get(entityType).forEach(a -> ((BiConsumer<T, EntityPlayer>)a).accept((T)entity, player));
}
}
}
/** Adds the given factory to the list of projectiles. When a player right-clicks while possessing an entity of the
* given type, the given projectile factory will be invoked to create a projectile, which is then aimed and spawned. */
public static <T extends Entity & IProjectile> void addProjectile(Class<? extends EntityLiving> entityType, Function<World, T> factory){
projectiles.put(entityType, factory);
}
/** Copied from Entity#setSize, with the call to move(...) removed. This is presumably also better than reflecting
* into Entity#setSize, which is protected. */
private static void setSize(Entity entity, float width, float height){
if(width != entity.width || height != entity.height){
entity.width = width;
entity.height = height;
double halfWidth = (double)width / 2.0D;
entity.setEntityBoundingBox(new AxisAlignedBB(entity.posX - halfWidth, entity.posY, entity.posZ - halfWidth, entity.posX + halfWidth, entity.posY + (double)entity.height, entity.posZ + halfWidth));
}
}
// ================================================ Event Handlers ================================================
// We got every kind of event handler goin', folks!
@SubscribeEvent
public static void onPlayerTickEvent(TickEvent.PlayerTickEvent event){
if(event.phase == TickEvent.Phase.START){
EntityLiving possessee = getPossessee(event.player);
if(possessee != null){
// Updating these to the player's variables won't have an effect on player movement, but it will
// affect various bits of mob-specific logic
possessee.setPosition(event.player.posX, event.player.posY, event.player.posZ);
possessee.motionX = event.player.motionX;
possessee.motionY = event.player.motionY;
possessee.motionZ = event.player.motionZ;
possessee.onGround = event.player.onGround;
possessee.onUpdate(); // Event though it's not in the world, it still needs updating
possessee.ticksExisted++; // Normally gets updated from World
if(possessee.getHealth() <= 0){
((Possession)Spells.possession).endPossession(event.player);
}
performAbilities(possessee, event.player);
}
}
// Right at the end of EntityPlayer#onUpdate() it calls EntityPlayer#updateSize(), which resets the player's
// size (and is also where this event is fired from, oddly enough) ... but not on my watch!
if(event.phase == TickEvent.Phase.END){
EntityLiving possessee = getPossessee(event.player);
if(possessee != null){
setSize(event.player, possessee.width, possessee.height);
}
}
}
// When possessing, attacks are diverted to the possessed entity for armour, immunity, resistance calculations
// and so on, then when the damage is actually applied, the player is also damaged via onLivingDamageEvent below
@SubscribeEvent(priority = EventPriority.HIGH)
public static void onLivingAttackEvent(LivingAttackEvent event){
if(event.getEntity() instanceof EntityPlayer && event.getSource() != DamageSource.OUT_OF_WORLD){
EntityLiving possessee = getPossessee((EntityPlayer)event.getEntity());
if(possessee != null){
DamageSafetyChecker.attackEntitySafely(possessee, event.getSource(), event.getAmount(), event.getSource().getDamageType());
event.setCanceled(true);
}
}
}
// LivingDamageEvent used in preference to LivingHurtEvent because the player is 'inside' the possessed entity, so
// any damage should come through that entity (and any armour, potions, enchantments etc. it has) first.
@SubscribeEvent
public static void onLivingDamageEvent(LivingDamageEvent event){
for(EntityPlayer player : event.getEntity().world.playerEntities){
EntityLiving possessee = getPossessee(player);
if(possessee == event.getEntity()){
// Possessors take half of all damage taken by the entity they are possessing. If the possessor receives
// fatal/critical damage (i.e. damage that takes them to half a heart or less), their health is reset to half
// a heart and the possession ends.
if(!player.capabilities.isCreativeMode){
// TODO: Make this a proper DamageSource?
DamageSafetyChecker.attackEntitySafely(player, DamageSource.OUT_OF_WORLD, event.getAmount() / 2,
DamageSource.OUT_OF_WORLD.getDamageType());
}
if(player.getHealth() <= Spells.possession.getProperty(CRITICAL_HEALTH).floatValue()){
player.setHealth(Spells.possession.getProperty(CRITICAL_HEALTH).floatValue());
((Possession)Spells.possession).endPossession(player);
}
}
}
}
// Prevents possessing players from interacting with blocks and controls projectile shooting
@SubscribeEvent
public static void onPlayerInteractEvent(PlayerInteractEvent event){
if(event instanceof PlayerInteractEvent.RightClickItem) return; // Can always do this
EntityLiving possessee = getPossessee(event.getEntityPlayer());
if(possessee != null){
// Let endermen interact with blocks
if(possessee instanceof EntityEnderman && (
(event instanceof PlayerInteractEvent.RightClickBlock && ((EntityEnderman)possessee).getHeldBlockState() != null)
|| (event instanceof PlayerInteractEvent.LeftClickBlock && ((EntityEnderman)possessee).getHeldBlockState() == null)))
return;
if(WizardData.get(event.getEntityPlayer()) != null && event.getWorld().isRemote
&& (event instanceof PlayerInteractEvent.RightClickEmpty
|| event instanceof PlayerInteractEvent.EntityInteract
|| event instanceof PlayerInteractEvent.RightClickBlock)){
Integer cooldown = WizardData.get(event.getEntityPlayer()).getVariable(SHOOT_COOLDOWN_KEY);
if(cooldown == null || cooldown == 0){
WizardryPacketHandler.net.sendToServer(new PacketControlInput.Message(PacketControlInput.ControlType.POSSESSION_PROJECTILE));
WizardData.get(event.getEntityPlayer()).setVariable(SHOOT_COOLDOWN_KEY, PROJECTILE_COOLDOWN);
if(possessee instanceof EntityLightningWraith){
Spells.arc.cast(event.getWorld(), event.getEntityPlayer(), EnumHand.MAIN_HAND, 0, new SpellModifiers());
}
if(possessee instanceof EntityCreeper){
((EntityCreeper)possessee).ignite();
}
}
}
if(event.isCancelable()) event.setCanceled(true);
}
}
/** Called via packets to shoot a projectile if the entity currently possessed by the given player can do so. */
public static void shootProjectile(EntityPlayer possessor){
if(WizardData.get(possessor) != null){
Integer cooldown = WizardData.get(possessor).getVariable(SHOOT_COOLDOWN_KEY);
if(cooldown == null || cooldown == 0){
EntityLiving possessee = getPossessee(possessor);
if(possessee != null){
if(possessee instanceof EntityLightningWraith){
Spells.arc.cast(possessor.world, possessor, EnumHand.MAIN_HAND, 0, new SpellModifiers());
}
if(possessee instanceof EntityCreeper){
((Possession)Spells.possession).endPossession(possessor);
((EntityCreeper)possessee).ignite(); // Aaaaaaand.... RUN!
}
Function<World, ? extends IProjectile> factory = projectiles.get(possessee.getClass());
if(factory != null){
IProjectile projectile = factory.apply(possessor.world);
Vec3d look = possessor.getLookVec();
((Entity)projectile).setPosition(possessor.posX + look.x, possessor.posY + possessor.getEyeHeight() + look.y, possessor.posZ + look.z);
projectile.shoot(look.x, look.y, look.z, 1.6f, WizardryUtilities.getDefaultAimingError(possessor.world.getDifficulty()));
if(projectile instanceof EntityMagicProjectile) ((EntityMagicProjectile)projectile).setCaster(possessor);
else if(projectile instanceof EntityMagicArrow) ((EntityMagicArrow)projectile).setCaster(possessor);
possessor.world.spawnEntity((Entity)projectile);
}
WizardData.get(possessor).setVariable(SHOOT_COOLDOWN_KEY, PROJECTILE_COOLDOWN);
}
}
}
}
@SubscribeEvent
public static void onLivingSetAttackTargetEvent(LivingSetAttackTargetEvent event){ // Not fired for revenge-targeting
if(event.getTarget() instanceof EntityPlayer && event.getEntityLiving() instanceof EntityLiving){
EntityLiving possessee = getPossessee((EntityPlayer)event.getTarget());
EntityLiving attacker = (EntityLiving)event.getEntityLiving();
if(possessee != null && !attacker.canAttackClass(possessee.getClass())){
((EntityLiving)event.getEntityLiving()).setAttackTarget(null); // Mobs can't target a player possessing an entity they don't normally attack
}
}
}
// With these two methods I'm pretty sure it's watertight
@SubscribeEvent
public static void onLivingDeathEvent(LivingDeathEvent event){
if(event.getEntity() instanceof EntityPlayer && isPossessing((EntityPlayer)event.getEntity())){
((Possession)Spells.possession).endPossession((EntityPlayer)event.getEntity()); // Just in case, to make sure the player drops their items
}
}
@SubscribeEvent
public static void onPlayerLoggedOutEvent(PlayerEvent.PlayerLoggedOutEvent event){
if(isPossessing(event.player)) ((Possession)Spells.possession).endPossession(event.player);
}
@SubscribeEvent
public static void onBlockBreakEvent(BlockEvent.BreakEvent event){
EntityLiving possessee = getPossessee(event.getPlayer());
if(possessee instanceof EntityEnderman){
if(((EntityEnderman)possessee).getHeldBlockState() == null){
((EntityEnderman)possessee).setHeldBlockState(event.getState());
event.getPlayer().setHeldItem(EnumHand.MAIN_HAND, new ItemStack(event.getState().getBlock()));
event.setExpToDrop(0);
event.getWorld().setBlockToAir(event.getPos()); // Remove block before it can drop
}else{
event.setCanceled(true);
}
}
}
@SubscribeEvent
public static void onBlockPlaceEvent(BlockEvent.PlaceEvent event){
EntityLiving possessee = getPossessee(event.getPlayer());
if(possessee instanceof EntityEnderman){
if(((EntityEnderman)possessee).getHeldBlockState() == event.getState()){
((EntityEnderman)possessee).setHeldBlockState(null);
}else{
event.setCanceled(true);
}
}
}
@SubscribeEvent
public static void onEntityItemPickupEvent(EntityItemPickupEvent event){ // Why are there two item pickup events?
EntityLiving possessee = getPossessee(event.getEntityPlayer());
if(possessee != null){
if(possessee.canPickUpLoot() && possessee.getHeldItemMainhand().isEmpty()){
possessee.setHeldItem(EnumHand.MAIN_HAND, event.getItem().getItem());
}else{
event.setCanceled(true);
}
}
}
@SubscribeEvent
static void onItemTossEvent(ItemTossEvent event){
if(isPossessing(event.getPlayer())){ // Can't drop items while possessing
event.setCanceled(true);
event.getPlayer().inventory.addItemStackToInventory(event.getEntityItem().getItem());
}
}
@SubscribeEvent
public static void onAttackEntityEvent(AttackEntityEvent event){
EntityLiving possessee = getPossessee(event.getEntityPlayer());
if(possessee == null) return;
if(possessee instanceof EntityCreeper){
event.setCanceled(true); // Why do creepers have a melee AI?!
}else if(possessee.tasks.taskEntries.stream().noneMatch(t -> t.action instanceof EntityAIAttackMelee)){
event.setCanceled(true); // Can't melee with a non-melee mob
}
}
}