/**
* This class was created by <Vazkii>. It's distributed as
* part of the Botania Mod. Get the Source Code in github:
* https://github.com/Vazkii/Botania
*
* Botania is Open Source and distributed under the
* Botania License: http://botaniamod.net/license.php
*
* File Created @ [Jul 12, 2014, 3:47:45 PM (GMT)]
*/
package vazkii.botania.common.entity;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import net.minecraft.entity.monster.EntityWitherSkeleton;
import net.minecraft.item.Item;
import org.lwjgl.opengl.ARBShaderObjects;
import com.google.common.base.Optional;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.EntityAISwimming;
import net.minecraft.entity.ai.EntityAIWatchClosest;
import net.minecraft.entity.monster.EntitySkeleton;
import net.minecraft.entity.monster.EntityWitch;
import net.minecraft.entity.monster.EntityZombie;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.init.MobEffects;
import net.minecraft.init.SoundEvents;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.ItemRecord;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.network.play.server.SPacketRemoveEntityEffect;
import net.minecraft.potion.Potion;
import net.minecraft.potion.PotionEffect;
import net.minecraft.tileentity.TileEntityBeacon;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.world.BossInfo;
import net.minecraft.world.BossInfoServer;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import vazkii.botania.api.boss.IBotaniaBoss;
import vazkii.botania.api.internal.ShaderCallback;
import vazkii.botania.api.lexicon.multiblock.Multiblock;
import vazkii.botania.api.lexicon.multiblock.MultiblockSet;
import vazkii.botania.api.lexicon.multiblock.component.MultiblockComponent;
import vazkii.botania.api.sound.BotaniaSoundEvents;
import vazkii.botania.api.state.BotaniaStateProps;
import vazkii.botania.api.state.enums.PylonVariant;
import vazkii.botania.client.core.handler.BossBarHandler;
import vazkii.botania.client.core.helper.ShaderHelper;
import vazkii.botania.common.Botania;
import vazkii.botania.common.achievement.ModAchievements;
import vazkii.botania.common.block.ModBlocks;
import vazkii.botania.common.core.helper.Vector3;
import vazkii.botania.common.item.ModItems;
import vazkii.botania.common.lib.LibEntityNames;
import vazkii.botania.common.lib.LibMisc;
import vazkii.botania.common.network.PacketBotaniaEffect;
import vazkii.botania.common.network.PacketHandler;
public class EntityDoppleganger extends EntityLiving implements IBotaniaBoss {
public static final float ARENA_RANGE = 12F;
private static final int SPAWN_TICKS = 160;
private static final float MAX_HP = 320F;
private static final int MOB_SPAWN_START_TICKS = 20;
private static final int MOB_SPAWN_END_TICKS = 80;
private static final int MOB_SPAWN_BASE_TICKS = 800;
private static final int MOB_SPAWN_TICKS = MOB_SPAWN_BASE_TICKS + MOB_SPAWN_START_TICKS + MOB_SPAWN_END_TICKS;
private static final int MOB_SPAWN_WAVES = 10;
private static final int MOB_SPAWN_WAVE_TIME = MOB_SPAWN_BASE_TICKS / MOB_SPAWN_WAVES;
private static final String TAG_INVUL_TIME = "invulTime";
private static final String TAG_AGGRO = "aggro";
private static final String TAG_SOURCE_X = "sourceX";
private static final String TAG_SOURCE_Y = "sourceY";
private static final String TAG_SOURCE_Z = "sourcesZ";
private static final String TAG_MOB_SPAWN_TICKS = "mobSpawnTicks";
private static final String TAG_HARD_MODE = "hardMode";
private static final String TAG_PLAYER_COUNT = "playerCount";
private static final DataParameter<Integer> INVUL_TIME = EntityDataManager.createKey(EntityDoppleganger.class, DataSerializers.VARINT);
private static final DataParameter<Integer> PLAYER_COUNT = EntityDataManager.createKey(EntityDoppleganger.class, DataSerializers.VARINT);
private static final DataParameter<Boolean> HARD_MODE = EntityDataManager.createKey(EntityDoppleganger.class, DataSerializers.BOOLEAN);
private static final DataParameter<BlockPos> SOURCE = EntityDataManager.createKey(EntityDoppleganger.class, DataSerializers.BLOCK_POS);
private static final DataParameter<Optional<UUID>> BOSSINFO_ID = EntityDataManager.createKey(EntityDoppleganger.class, DataSerializers.OPTIONAL_UNIQUE_ID);
private static final BlockPos[] PYLON_LOCATIONS = {
new BlockPos(4, 1, 4),
new BlockPos(4, 1, -4),
new BlockPos(-4, 1, 4),
new BlockPos(-4, 1, -4)
};
private static final List<String> CHEATY_BLOCKS = Arrays.asList("OpenBlocks:beartrap",
"ThaumicTinkerer:magnet");
private boolean isPlayingMusic = false;
private boolean spawnLandmines = false;
private boolean spawnPixies = false;
private boolean anyWithArmor = false;
private boolean aggro = false;
private int tpDelay = 0;
private int mobSpawnTicks = 0;
private final List<UUID> playersWhoAttacked = new ArrayList<>();
private final BossInfoServer bossInfo = (BossInfoServer) new BossInfoServer(new TextComponentTranslation("entity." + LibEntityNames.DOPPLEGANGER_REGISTRY + ".name"), BossInfo.Color.PINK, BossInfo.Overlay.PROGRESS).setCreateFog(true);;
public EntityPlayer trueKiller = null;
public EntityDoppleganger(World world) {
super(world);
setSize(0.6F, 1.8F);
isImmuneToFire = true;
experienceValue = 825;
Botania.proxy.addBoss(this);
}
public static MultiblockSet makeMultiblockSet() {
Multiblock mb = new Multiblock();
for(BlockPos p : PYLON_LOCATIONS)
mb.addComponent(p.up(), ModBlocks.pylon.getDefaultState().withProperty(BotaniaStateProps.PYLON_VARIANT, PylonVariant.GAIA));
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
mb.addComponent(new BeaconComponent(new BlockPos(i - 1, 0, j - 1)));
mb.addComponent(new BeaconBeamComponent(new BlockPos(0, 1, 0)));
mb.setRenderOffset(new BlockPos(0, -1, 0));
return mb.makeSet();
}
public static boolean spawn(EntityPlayer player, ItemStack stack, World world, BlockPos pos, boolean hard) {
if(world.getTileEntity(pos) instanceof TileEntityBeacon && isTruePlayer(player)) {
if(world.getDifficulty() == EnumDifficulty.PEACEFUL) {
if(!world.isRemote)
player.sendMessage(new TextComponentTranslation("botaniamisc.peacefulNoob").setStyle(new Style().setColor(TextFormatting.RED)));
return false;
}
for(BlockPos coords : PYLON_LOCATIONS) {
BlockPos pos_ = pos.add(coords);
IBlockState state = world.getBlockState(pos_);
Block blockat = state.getBlock();
if(blockat != ModBlocks.pylon || state.getValue(BotaniaStateProps.PYLON_VARIANT) != PylonVariant.GAIA) {
if(!world.isRemote)
player.sendMessage(new TextComponentTranslation("botaniamisc.needsCatalysts").setStyle(new Style().setColor(TextFormatting.RED)));
return false;
}
}
if(!hasProperArena(world, pos)) {
if(!world.isRemote) {
PacketHandler.sendTo((EntityPlayerMP) player,
new PacketBotaniaEffect(PacketBotaniaEffect.EffectType.ARENA_INDICATOR, pos.getX(), pos.getY(), pos.getZ()));
player.sendMessage(new TextComponentTranslation("botaniamisc.badArena").setStyle(new Style().setColor(TextFormatting.RED)));
}
return false;
}
int guardians = getGaiaGuardiansAround(world, pos);
if(guardians > 0)
return false;
if(world.isRemote)
return true;
stack.shrink(1);
EntityDoppleganger e = new EntityDoppleganger(world);
e.setPosition(pos.getX() + 0.5, pos.getY() + 3, pos.getZ() + 0.5);
e.setInvulTime(SPAWN_TICKS);
e.setHealth(1F);
e.setSource(pos);
e.mobSpawnTicks = MOB_SPAWN_TICKS;
e.setHardMode(hard);
int playerCount = (int) e.getPlayersAround().stream().filter(EntityDoppleganger::isTruePlayer).count();
e.setPlayerCount(playerCount);
e.getAttributeMap().getAttributeInstance(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(MAX_HP * playerCount);
if (hard)
e.getAttributeMap().getAttributeInstance(SharedMonsterAttributes.ARMOR).setBaseValue(15);
e.playSound(SoundEvents.ENTITY_ENDERDRAGON_GROWL, 10F, 0.1F);
e.onInitialSpawn(world.getDifficultyForLocation(new BlockPos(e)), null);
world.spawnEntity(e);
return true;
}
return false;
}
private static boolean hasProperArena(World world, BlockPos startPos) {
List<BlockPos> trippedPositions = new ArrayList();
boolean tripped = false;
int heightCheck = 3;
int heightMin = 2;
int range = (int) Math.ceil(ARENA_RANGE);
for(int i = -range; i < range + 1; i++)
for(int j = -range; j < range + 1; j++) {
if(Math.abs(i) == 4 && Math.abs(j) == 4 || vazkii.botania.common.core.helper.MathHelper.pointDistancePlane(i, j, 0, 0) > ARENA_RANGE)
continue; // Ignore pylons and out of circle
int air = 0;
yCheck: {
BlockPos pos = null;
int trippedColumn = 0;
for(int k = heightCheck + heightMin; k >= -heightCheck; k--) {
pos = startPos.add(i, k, j);
boolean isAir = world.getBlockState(pos).getCollisionBoundingBox(world, pos) == null;
if(isAir)
air++;
else {
if(air >= 2)
break yCheck;
else if(trippedColumn < 2) {
trippedPositions.add(pos);
trippedColumn++;
}
air = 0;
}
}
if(trippedColumn == 0)
trippedPositions.add(pos);
tripped = true;
}
}
if(tripped) {
Botania.proxy.setWispFXDepthTest(false);
for(BlockPos pos : trippedPositions) {
System.out.println(world.isRemote);
Botania.proxy.wispFX(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, 1F, 0.2F, 0.2F, 0.5F, 0F, 8);
}
Botania.proxy.setWispFXDepthTest(true);
return false;
}
return true;
}
@Override
protected void initEntityAI() {
tasks.addTask(0, new EntityAISwimming(this));
tasks.addTask(1, new EntityAIWatchClosest(this, EntityPlayer.class, ARENA_RANGE * 1.5F));
}
@Override
protected void entityInit() {
super.entityInit();
dataManager.register(INVUL_TIME, 0);
dataManager.register(SOURCE, BlockPos.ORIGIN);
dataManager.register(HARD_MODE, false);
dataManager.register(PLAYER_COUNT, 0);
dataManager.register(BOSSINFO_ID, Optional.absent());
}
public int getInvulTime() {
return dataManager.get(INVUL_TIME);
}
public BlockPos getSource() {
return dataManager.get(SOURCE);
}
public boolean isHardMode() {
return dataManager.get(HARD_MODE);
}
public int getPlayerCount() {
return dataManager.get(PLAYER_COUNT);
}
public void setInvulTime(int time) {
dataManager.set(INVUL_TIME, time);
}
public void setSource(BlockPos pos) {
dataManager.set(SOURCE, pos);
}
public void setHardMode(boolean hardMode) {
dataManager.set(HARD_MODE, hardMode);
}
public void setPlayerCount(int count) {
dataManager.set(PLAYER_COUNT, count);
}
@Override
public void writeEntityToNBT(NBTTagCompound par1nbtTagCompound) {
super.writeEntityToNBT(par1nbtTagCompound);
par1nbtTagCompound.setInteger(TAG_INVUL_TIME, getInvulTime());
par1nbtTagCompound.setBoolean(TAG_AGGRO, aggro);
par1nbtTagCompound.setInteger(TAG_MOB_SPAWN_TICKS, mobSpawnTicks);
BlockPos source = getSource();
par1nbtTagCompound.setInteger(TAG_SOURCE_X, source.getX());
par1nbtTagCompound.setInteger(TAG_SOURCE_Y, source.getY());
par1nbtTagCompound.setInteger(TAG_SOURCE_Z, source.getZ());
par1nbtTagCompound.setBoolean(TAG_HARD_MODE, isHardMode());
par1nbtTagCompound.setInteger(TAG_PLAYER_COUNT, getPlayerCount());
}
@Override
public void readEntityFromNBT(NBTTagCompound par1nbtTagCompound) {
super.readEntityFromNBT(par1nbtTagCompound);
setInvulTime(par1nbtTagCompound.getInteger(TAG_INVUL_TIME));
aggro = par1nbtTagCompound.getBoolean(TAG_AGGRO);
mobSpawnTicks = par1nbtTagCompound.getInteger(TAG_MOB_SPAWN_TICKS);
int x = par1nbtTagCompound.getInteger(TAG_SOURCE_X);
int y = par1nbtTagCompound.getInteger(TAG_SOURCE_Y);
int z = par1nbtTagCompound.getInteger(TAG_SOURCE_Z);
setSource(new BlockPos(x, y, z));
setHardMode(par1nbtTagCompound.getBoolean(TAG_HARD_MODE));
if(par1nbtTagCompound.hasKey(TAG_PLAYER_COUNT))
setPlayerCount(par1nbtTagCompound.getInteger(TAG_PLAYER_COUNT));
else setPlayerCount(1);
if (this.hasCustomName()) {
this.bossInfo.setName(this.getDisplayName());
}
}
@Override
public void setCustomNameTag(@Nonnull String name) {
super.setCustomNameTag(name);
this.bossInfo.setName(this.getDisplayName());
}
@Override
public boolean attackEntityFrom(@Nonnull DamageSource source, float par2) {
Entity e = source.getEntity();
if (e instanceof EntityPlayer && isTruePlayer(e) && getInvulTime() == 0) {
EntityPlayer player = (EntityPlayer) e;
if(!playersWhoAttacked.contains(player.getUniqueID()))
playersWhoAttacked.add(player.getUniqueID());
player.isOnLadder();
player.isInWater();
player.isPotionActive(MobEffects.BLINDNESS);
player.isRiding();
int cap = 25;
return super.attackEntityFrom(source, Math.min(cap, par2));
}
return false;
}
private static final Pattern FAKE_PLAYER_PATTERN = Pattern.compile("^(?:\\[.*\\])|(?:ComputerCraft)$");
public static boolean isTruePlayer(Entity e) {
if(!(e instanceof EntityPlayer))
return false;
EntityPlayer player = (EntityPlayer) e;
String name = player.getName();
return !(player instanceof FakePlayer || FAKE_PLAYER_PATTERN.matcher(name).matches());
}
@Override
protected void damageEntity(@Nonnull DamageSource par1DamageSource, float par2) {
super.damageEntity(par1DamageSource, par2);
Entity attacker = par1DamageSource.getEntity();
if(attacker != null) {
Vector3 thisVector = Vector3.fromEntityCenter(this);
Vector3 playerVector = Vector3.fromEntityCenter(attacker);
Vector3 motionVector = thisVector.subtract(playerVector).normalize().multiply(0.75);
if(getHealth() > 0) {
motionX = -motionVector.x;
motionY = 0.5;
motionZ = -motionVector.z;
tpDelay = 4;
spawnPixies = aggro;
}
aggro = true;
}
}
@Override
public void onDeath(@Nonnull DamageSource source) {
super.onDeath(source);
EntityLivingBase entitylivingbase = getAttackingEntity();
if(entitylivingbase instanceof EntityPlayer) {
((EntityPlayer) entitylivingbase).addStat(ModAchievements.gaiaGuardianKill, 1);
if(!anyWithArmor)
((EntityPlayer) entitylivingbase).addStat(ModAchievements.gaiaGuardianNoArmor, 1);
}
playSound(SoundEvents.ENTITY_GENERIC_EXPLODE, 20F, (1F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.2F) * 0.7F);
world.spawnParticle(EnumParticleTypes.EXPLOSION_HUGE, posX, posY, posZ, 1D, 0D, 0D);
}
@Override
protected void applyEntityAttributes() {
super.applyEntityAttributes();
getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.4);
getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(MAX_HP);
getEntityAttribute(SharedMonsterAttributes.KNOCKBACK_RESISTANCE).setBaseValue(1.0);
}
@Override
protected boolean canDespawn() {
return false;
}
@Override
public ResourceLocation getLootTable() {
return new ResourceLocation(LibMisc.MOD_ID, isHardMode() ? "gaia_guardian_2" : "gaia_guardian");
}
@Override
protected void dropLoot(boolean wasRecentlyHit, int lootingModifier, @Nonnull DamageSource source)
{
// Save true killer, they get extra loot
if ("player".equals(source.getDamageType())
&& source.getEntity() instanceof EntityPlayer) {
trueKiller = (EntityPlayer) source.getEntity();
}
// Drop equipment and clear it so multiple calls to super don't do it again
super.dropEquipment(wasRecentlyHit, lootingModifier);
for (EntityEquipmentSlot e : EntityEquipmentSlot.values()) {
setItemStackToSlot(e, ItemStack.EMPTY);
}
// Generate loot table for every single attacking player
for (UUID u : playersWhoAttacked) {
EntityPlayer player = world.getPlayerEntityByUUID(u);
if (player == null)
continue;
EntityPlayer saveLastAttacker = attackingPlayer;
double savePosX = posX;
double savePosY = posY;
double savePosZ = posZ;
attackingPlayer = player; // Fake attacking player as the killer
posX = player.posX; // Spoof pos so drops spawn at the player
posY = player.posY;
posZ = player.posZ;
super.dropLoot(wasRecentlyHit, lootingModifier, DamageSource.causePlayerDamage(player));
posX = savePosX;
posY = savePosY;
posZ = savePosZ;
attackingPlayer = saveLastAttacker;
}
trueKiller = null;
}
@Override
public void setDead() {
Botania.proxy.removeBoss(this);
world.playEvent(1010, getSource(), 0);
isPlayingMusic = false;
super.setDead();
}
private List<EntityPlayer> getPlayersAround() {
BlockPos source = getSource();
float range = 15F;
return world.getEntitiesWithinAABB(EntityPlayer.class, new AxisAlignedBB(source.getX() + 0.5 - range, source.getY() + 0.5 - range, source.getZ() + 0.5 - range, source.getX() + 0.5 + range, source.getY() + 0.5 + range, source.getZ() + 0.5 + range));
}
private static int getGaiaGuardiansAround(World world, BlockPos source) {
float range = 15F;
List l = world.getEntitiesWithinAABB(EntityDoppleganger.class, new AxisAlignedBB(source.getX() + 0.5 - range, source.getY() + 0.5 - range, source.getZ() + 0.5 - range, source.getX() + 0.5 + range, source.getY() + 0.5 + range, source.getZ() + 0.5 + range));
return l.size();
}
@Override
public void onLivingUpdate() {
super.onLivingUpdate();
BlockPos source = getSource();
float range = ARENA_RANGE;
int invul = getInvulTime();
if (world.isRemote) {
for(int i = 0; i < 360; i += 8) {
float r = 0.6F;
float g = 0F;
float b = 0.2F;
float m = 0.15F;
float mv = 0.35F;
float rad = i * (float) Math.PI / 180F;
double x = source.getX() + 0.5 - Math.cos(rad) * range;
double y = source.getY() + 0.5;
double z = source.getZ() + 0.5 - Math.sin(rad) * range;
Botania.proxy.wispFX(x, y, z, r, g, b, 0.5F, (float) (Math.random() - 0.5F) * m, (float) (Math.random() - 0.5F) * mv, (float) (Math.random() - 0.5F) * m);
}
EntityPlayer player = Botania.proxy.getClientPlayer();
player.capabilities.isFlying = player.capabilities.isFlying && player.capabilities.isCreativeMode;
if(invul > 10) {
Vector3 pos = Vector3.fromEntityCenter(this).subtract(new Vector3(0, 0.2, 0));
for (BlockPos arr : PYLON_LOCATIONS) {
Vector3 pylonPos = new Vector3(source.getX() + arr.getX(), source.getY() + arr.getY(), source.getZ() + arr.getZ());
double worldTime = ticksExisted;
worldTime /= 5;
float rad = 0.75F + (float) Math.random() * 0.05F;
double xp = pylonPos.x + 0.5 + Math.cos(worldTime) * rad;
double zp = pylonPos.z + 0.5 + Math.sin(worldTime) * rad;
Vector3 partPos = new Vector3(xp, pylonPos.y, zp);
Vector3 mot = pos.subtract(partPos).multiply(0.04);
float r = 0.7F + (float) Math.random() * 0.3F;
float g = (float) Math.random() * 0.3F;
float b = 0.7F + (float) Math.random() * 0.3F;
Botania.proxy.wispFX(partPos.x, partPos.y, partPos.z, r, g, b, 0.25F + (float) Math.random() * 0.1F, -0.075F - (float) Math.random() * 0.015F);
Botania.proxy.wispFX(partPos.x, partPos.y, partPos.z, r, g, b, 0.4F, (float) mot.x, (float) mot.y, (float) mot.z);
}
}
return;
}
if(!isPlayingMusic && !isDead && !getPlayersAround().isEmpty()) {
world.playEvent(1010, source, Item.getIdFromItem(isHardMode() ? ModItems.recordGaia2 : ModItems.recordGaia1));
isPlayingMusic = true;
}
dataManager.set(BOSSINFO_ID, Optional.of(bossInfo.getUniqueId()));
bossInfo.setPercent(getHealth() / getMaxHealth());
if(!getPassengers().isEmpty())
dismountRidingEntity();
if(world.getDifficulty() == EnumDifficulty.PEACEFUL)
setDead();
int radius = 1;
int posXInt = MathHelper.floor(posX);
int posYInt = MathHelper.floor(posY);
int posZInt = MathHelper.floor(posZ);
for(int i = -radius; i < radius + 1; i++)
for(int j = -radius; j < radius + 1; j++)
for(int k = -radius; k < radius + 1; k++) {
int xp = posXInt + i;
int yp = posYInt + j;
int zp = posZInt + k;
BlockPos posp = new BlockPos(xp, yp, zp);
if(isCheatyBlock(world, posp)) {
world.destroyBlock(posp, true);
}
}
boolean hard = isHardMode();
List<EntityPlayer> players = getPlayersAround();
int playerCount = getPlayerCount();
if(players.isEmpty() && !world.playerEntities.isEmpty())
setDead();
else {
for(EntityPlayer player : players) {
for(EntityEquipmentSlot e : EntityEquipmentSlot.values()) {
if(e.getSlotType() == EntityEquipmentSlot.Type.ARMOR && !player.getItemStackFromSlot(e).isEmpty()) {
anyWithArmor = true;
break;
}
}
List<Potion> potionsToRemove = player.getActivePotionEffects().stream()
.filter(effect -> effect.getDuration() < 160 && effect.getIsAmbient() && !effect.getPotion().isBadEffect())
.map(PotionEffect::getPotion)
.distinct()
.collect(Collectors.toList());
potionsToRemove.forEach(potion -> {
player.removePotionEffect(potion);
((WorldServer) world).getPlayerChunkMap().getEntry(posXInt >> 4, posZInt >> 4).sendPacket(new SPacketRemoveEntityEffect(player.getEntityId(), potion));
});
player.capabilities.isFlying = player.capabilities.isFlying && player.capabilities.isCreativeMode;
if(vazkii.botania.common.core.helper.MathHelper.pointDistanceSpace(player.posX, player.posY, player.posZ, source.getX() + 0.5, source.getY() + 0.5, source.getZ() + 0.5) >= range) {
Vector3 sourceVector = new Vector3(source.getX() + 0.5, source.getY() + 0.5, source.getZ() + 0.5);
Vector3 playerVector = Vector3.fromEntityCenter(player);
Vector3 motion = sourceVector.subtract(playerVector).normalize();
player.motionX = motion.x;
player.motionY = 0.2;
player.motionZ = motion.z;
player.velocityChanged = true;
}
}
}
if(isDead)
return;
boolean spawnMissiles = hard && ticksExisted % 15 < 4;
if(invul > 0 && mobSpawnTicks == MOB_SPAWN_TICKS) {
if(invul < SPAWN_TICKS) {
if(invul > SPAWN_TICKS / 2 && world.rand.nextInt(SPAWN_TICKS - invul + 1) == 0)
for(int i = 0; i < 2; i++)
spawnExplosionParticle();
}
setHealth(getHealth() + (getMaxHealth() - 1F) / SPAWN_TICKS);
setInvulTime(invul - 1);
motionY = 0;
} else {
if(aggro) {
boolean dying = getHealth() / getMaxHealth() < 0.2;
if(dying && mobSpawnTicks > 0) {
motionX = 0;
motionY = 0;
motionZ = 0;
int reverseTicks = MOB_SPAWN_TICKS - mobSpawnTicks;
if(reverseTicks < MOB_SPAWN_START_TICKS) {
motionY = 0.2;
setInvulTime(invul + 1);
}
if(reverseTicks > MOB_SPAWN_START_TICKS * 2 && mobSpawnTicks > MOB_SPAWN_END_TICKS && mobSpawnTicks % MOB_SPAWN_WAVE_TIME == 0) {
for(int pl = 0; pl < playerCount; pl++)
for(int i = 0; i < 3 + world.rand.nextInt(2); i++) {
EntityLiving entity = null;
switch(world.rand.nextInt(2)) {
case 0 : {
entity = new EntityZombie(world);
if(world.rand.nextInt(hard ? 3 : 12) == 0)
entity = new EntityWitch(world);
break;
}
case 1 : {
entity = new EntitySkeleton(world);
if(world.rand.nextInt(8) == 0) {
entity = new EntityWitherSkeleton(world);
}
break;
}
case 3 : {
if(!players.isEmpty())
for(int j = 0; j < 1 + world.rand.nextInt(hard ? 8 : 5); j++) {
EntityPixie pixie = new EntityPixie(world);
pixie.setProps(players.get(rand.nextInt(players.size())), this, 1, 8);
pixie.setPosition(posX + width / 2, posY + 2, posZ + width / 2);
pixie.onInitialSpawn(world.getDifficultyForLocation(new BlockPos(pixie)), null);
world.spawnEntity(pixie);
}
}
}
if(entity != null) {
if(!entity.isImmuneToFire())
entity.addPotionEffect(new PotionEffect(MobEffects.FIRE_RESISTANCE, 600, 0));
range = 6F;
entity.setPosition(posX + 0.5 + Math.random() * range - range / 2, posY - 1, posZ + 0.5 + Math.random() * range - range / 2);
entity.onInitialSpawn(world.getDifficultyForLocation(new BlockPos(entity)), null);
if (entity instanceof EntityWitherSkeleton && hard) {
entity.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(ModItems.elementiumSword));
}
world.spawnEntity(entity);
}
}
if(hard && ticksExisted % 3 < 2) {
for(int i = 0; i < playerCount; i++)
spawnMissile();
spawnMissiles = false;
}
}
mobSpawnTicks--;
tpDelay = 10;
} else if(tpDelay > 0) {
if(invul > 0)
setInvulTime(invul - 1);
tpDelay--;
if(tpDelay == 0 && getHealth() > 0) {
int tries = 0;
while(!teleportRandomly() && tries < 50)
tries++;
if(tries >= 50)
teleportTo(source.getX() + 0.5, source.getY() + 1.6, source.getZ() + 0.5);
if(spawnLandmines) {
int count = dying && hard ? 7 : 6;
for(int i = 0; i < count; i++) {
int x = source.getX() - 10 + rand.nextInt(20);
int z = source.getZ() - 10 + rand.nextInt(20);
int y = world.getTopSolidOrLiquidBlock(new BlockPos(x, -1, z)).getY();
EntityMagicLandmine landmine = new EntityMagicLandmine(world);
landmine.setPosition(x + 0.5, y, z + 0.5);
landmine.summoner = this;
world.spawnEntity(landmine);
}
}
if(!players.isEmpty())
for(int pl = 0; pl < playerCount; pl++)
for(int i = 0; i < (spawnPixies ? world.rand.nextInt(hard ? 6 : 3) : 1); i++) {
EntityPixie pixie = new EntityPixie(world);
pixie.setProps(players.get(rand.nextInt(players.size())), this, 1, 8);
pixie.setPosition(posX + width / 2, posY + 2, posZ + width / 2);
pixie.onInitialSpawn(world.getDifficultyForLocation(new BlockPos(pixie)), null);
world.spawnEntity(pixie);
}
tpDelay = hard ? dying ? 35 : 45 : dying ? 40 : 60;
spawnLandmines = true;
spawnPixies = false;
}
}
if(spawnMissiles)
spawnMissile();
} else {
range = 3F;
players = world.getEntitiesWithinAABB(EntityPlayer.class, new AxisAlignedBB(posX - range, posY - range, posZ - range, posX + range, posY + range, posZ + range));
if(!players.isEmpty())
damageEntity(DamageSource.causePlayerDamage(players.get(0)), 0);
}
}
}
@Override
public boolean isNonBoss()
{
return false;
}
@Override
public void addTrackingPlayer(EntityPlayerMP player)
{
super.addTrackingPlayer(player);
bossInfo.addPlayer(player);
}
@Override
public void removeTrackingPlayer(EntityPlayerMP player)
{
super.removeTrackingPlayer(player);
bossInfo.removePlayer(player);
}
@Override
protected void collideWithNearbyEntities() {
if(getInvulTime() == 0)
super.collideWithNearbyEntities();
}
@Override
public boolean canBePushed() {
return super.canBePushed() && getInvulTime() == 0;
}
private void spawnMissile() {
EntityMagicMissile missile = new EntityMagicMissile(this, true);
missile.setPosition(posX + (Math.random() - 0.5 * 0.1), posY + 2.4 + (Math.random() - 0.5 * 0.1), posZ + (Math.random() - 0.5 * 0.1));
if(missile.getTarget()) {
playSound(BotaniaSoundEvents.missile, 0.6F, 0.8F + (float) Math.random() * 0.2F);
world.spawnEntity(missile);
}
}
private static boolean isCheatyBlock(World world, BlockPos pos) {
Block block = world.getBlockState(pos).getBlock();
String name = Block.REGISTRY.getNameForObject(block).toString();
return CHEATY_BLOCKS.contains(name);
}
// [VanillaCopy] EntityEnderman.teleportRandomly, edits noted.
private boolean teleportRandomly() {
double d0 = this.posX + (this.rand.nextDouble() - 0.5D) * 64.0D;
double d1 = this.posY + (double)(this.rand.nextInt(64) - 32);
double d2 = this.posZ + (this.rand.nextDouble() - 0.5D) * 64.0D;
return this.teleportTo(d0, d1, d2);
}
// [VanillaCopy] EntityEnderman.teleportTo, edits noted.
private boolean teleportTo(double x, double y, double z) {
/* Botania - no events
net.minecraftforge.event.entity.living.EnderTeleportEvent event = new net.minecraftforge.event.entity.living.EnderTeleportEvent(this, x, y, z, 0);
if (net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(event)) return false;
boolean flag = this.attemptTeleport(event.getTargetX(), event.getTargetY(), event.getTargetZ());
*/
boolean flag = this.attemptTeleport(x, y, z);
if (flag)
{
this.world.playSound((EntityPlayer)null, this.prevPosX, this.prevPosY, this.prevPosZ, SoundEvents.ENTITY_ENDERMEN_TELEPORT, this.getSoundCategory(), 1.0F, 1.0F);
this.playSound(SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0F);
}
return flag;
}
// [VanillaCopy] of super, edits noted
@Override
public boolean attemptTeleport(double x, double y, double z) {
double d0 = this.posX;
double d1 = this.posY;
double d2 = this.posZ;
this.posX = x;
this.posY = y;
this.posZ = z;
boolean flag = false;
BlockPos blockpos = new BlockPos(this);
World world = this.world;
Random random = this.getRNG();
if (world.isBlockLoaded(blockpos))
{
boolean flag1 = false;
while (!flag1 && blockpos.getY() > 0)
{
BlockPos blockpos1 = blockpos.down();
IBlockState iblockstate = world.getBlockState(blockpos1);
if (iblockstate.getMaterial().blocksMovement())
{
flag1 = true;
}
else
{
--this.posY;
blockpos = blockpos1;
}
}
if (flag1)
{
this.setPositionAndUpdate(this.posX, this.posY, this.posZ);
if (world.getCollisionBoxes(this, this.getEntityBoundingBox()).isEmpty() && !world.containsAnyLiquid(this.getEntityBoundingBox()))
{
flag = true;
}
// Botania - Prevent out of bounds teleporting
BlockPos source = getSource();
if(vazkii.botania.common.core.helper.MathHelper.pointDistanceSpace(posX, posY, posZ, source.getX(), source.getY(), source.getZ()) > 12)
flag = false;
}
}
if (!flag)
{
this.setPositionAndUpdate(d0, d1, d2);
return false;
}
else
{
int i = 128;
for (int j = 0; j < 128; ++j)
{
double d6 = (double)j / 127.0D;
float f = (random.nextFloat() - 0.5F) * 0.2F;
float f1 = (random.nextFloat() - 0.5F) * 0.2F;
float f2 = (random.nextFloat() - 0.5F) * 0.2F;
double d3 = d0 + (this.posX - d0) * d6 + (random.nextDouble() - 0.5D) * (double)this.width * 2.0D;
double d4 = d1 + (this.posY - d1) * d6 + random.nextDouble() * (double)this.height;
double d5 = d2 + (this.posZ - d2) * d6 + (random.nextDouble() - 0.5D) * (double)this.width * 2.0D;
world.spawnParticle(EnumParticleTypes.PORTAL, d3, d4, d5, (double)f, (double)f1, (double)f2, new int[0]);
}
// Botania - invalid/unneeded check
/*if (this instanceof EntityCreature)
{
((EntityCreature)this).getNavigator().clearPathEntity();
}*/
// Botania - damage any players in our way
Vec3d origPos = new Vec3d(d0, d1 + height / 2, d2);
Vec3d newPos = new Vec3d(posX, posY + height / 2, posZ);
if(origPos.squareDistanceTo(newPos) > 1) {
for(EntityPlayer player : getPlayersAround()) {
RayTraceResult rtr = player.getEntityBoundingBox().expandXyz(0.25).calculateIntercept(origPos, newPos);
if(rtr != null)
player.attackEntityFrom(DamageSource.causeMobDamage(this), 6);
}
}
return true;
}
}
@Override
@SideOnly(Side.CLIENT)
public ResourceLocation getBossBarTexture() {
return BossBarHandler.defaultBossBar;
}
@SideOnly(Side.CLIENT)
private static Rectangle barRect;
@SideOnly(Side.CLIENT)
private static Rectangle hpBarRect;
@Override
@SideOnly(Side.CLIENT)
public Rectangle getBossBarTextureRect() {
if(barRect == null)
barRect = new Rectangle(0, 0, 185, 15);
return barRect;
}
@Override
@SideOnly(Side.CLIENT)
public Rectangle getBossBarHPTextureRect() {
if(hpBarRect == null)
hpBarRect = new Rectangle(0, barRect.y + barRect.height, 181, 7);
return hpBarRect;
}
@Override
@SideOnly(Side.CLIENT)
public int bossBarRenderCallback(ScaledResolution res, int x, int y) {
GlStateManager.pushMatrix();
int px = x + 160;
int py = y + 12;
Minecraft mc = Minecraft.getMinecraft();
ItemStack stack = new ItemStack(Items.SKULL, 1, 3);
mc.renderEngine.bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE);
net.minecraft.client.renderer.RenderHelper.enableGUIStandardItemLighting();
GlStateManager.enableRescaleNormal();
mc.getRenderItem().renderItemIntoGUI(stack, px, py);
net.minecraft.client.renderer.RenderHelper.disableStandardItemLighting();
boolean unicode = mc.fontRendererObj.getUnicodeFlag();
mc.fontRendererObj.setUnicodeFlag(true);
mc.fontRendererObj.drawStringWithShadow("" + getPlayerCount(), px + 15, py + 4, 0xFFFFFF);
mc.fontRendererObj.setUnicodeFlag(unicode);
GlStateManager.popMatrix();
return 5;
}
@Override
public UUID getBossInfoUuid() {
return dataManager.get(BOSSINFO_ID).or(new UUID(0, 0));
}
@Override
@SideOnly(Side.CLIENT)
public int getBossBarShaderProgram(boolean background) {
return background ? 0 : ShaderHelper.dopplegangerBar;
}
@SideOnly(Side.CLIENT)
private ShaderCallback shaderCallback;
@Override
@SideOnly(Side.CLIENT)
public ShaderCallback getBossBarShaderCallback(boolean background, int shader) {
if(shaderCallback == null)
shaderCallback = shader1 -> {
int grainIntensityUniform = ARBShaderObjects.glGetUniformLocationARB(shader1, "grainIntensity");
int hpFractUniform = ARBShaderObjects.glGetUniformLocationARB(shader1, "hpFract");
float time = getInvulTime();
float grainIntensity = time > 20 ? 1F : Math.max(isHardMode() ? 0.5F : 0F, time / 20F);
ARBShaderObjects.glUniform1fARB(grainIntensityUniform, grainIntensity);
ARBShaderObjects.glUniform1fARB(hpFractUniform, getHealth() / getMaxHealth());
};
return background ? null : shaderCallback;
}
private static class BeaconComponent extends MultiblockComponent {
public BeaconComponent(BlockPos relPos) {
super(relPos, Blocks.IRON_BLOCK.getDefaultState());
}
@Override
public boolean matches(World world, BlockPos pos) {
return world.getBlockState(pos).getBlock().isBeaconBase(world, pos, pos.add(new BlockPos(-relPos.getX(), -relPos.getY(), -relPos.getZ())));
}
}
private static class BeaconBeamComponent extends MultiblockComponent {
public BeaconBeamComponent(BlockPos relPos) {
super(relPos, Blocks.BEACON.getDefaultState());
}
@Override
public boolean matches(World world, BlockPos pos) {
return world.getTileEntity(pos) instanceof TileEntityBeacon;
}
}
}