715 lines
29 KiB
Java
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
|
|
}
|
|
}
|
|
|
|
}
|