/**
Copyright (C) <2015> <coolAlias>
This file is part of coolAlias' Zelda Sword Skills Minecraft Mod; as such,
you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package zeldaswordskills.entity;
import java.util.Collection;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.Map;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.boss.IBossDisplayData;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.DamageSource;
import net.minecraft.world.World;
import net.minecraftforge.common.IExtendedEntityProperties;
import net.minecraftforge.common.util.Constants;
import zeldaswordskills.api.entity.CustomExplosion;
import zeldaswordskills.api.entity.IEntityBombEater;
import zeldaswordskills.api.entity.IEntityBombIngestible;
import zeldaswordskills.entity.buff.Buff;
import zeldaswordskills.entity.buff.BuffBase;
import zeldaswordskills.network.PacketDispatcher;
import zeldaswordskills.network.client.SyncEntityInfoPacket;
import zeldaswordskills.ref.Config;
/**
*
* For classes that extend EntityLivingBase
*
*/
public class ZSSEntityInfo implements IExtendedEntityProperties
{
private static final String EXT_PROP_NAME = "ZSSEntityInfo";
/** The entity to which these properties belong */
private final EntityLivingBase entity;
/** Map of active buffs */
private Map<Buff, BuffBase> activeBuffs = new EnumMap<Buff, BuffBase>(Buff.class);
/** Time this entity will remain immune to further stun effects */
private int stunResistTime;
/** Time until the ingested bomb explodes */
private int fuseTime;
/** The ingested bomb instance will also be an Entity */
private IEntityBombIngestible ingestedBomb;
public ZSSEntityInfo(EntityLivingBase entity) {
this.entity = entity;
}
@Override
public void init(Entity entity, World world) {}
/** Whether a Buff is currently active */
public boolean isBuffActive(Buff buff) {
return activeBuffs.containsKey(buff);
}
/** Returns a currently active buff or null if that buff isn't active */
public BuffBase getActiveBuff(Buff buff) {
return activeBuffs.get(buff);
}
/** Returns the amplifier of the Buff, or 0 if not active */
public int getBuffAmplifier(Buff buff) {
return (isBuffActive(buff) ? getActiveBuff(buff).getAmplifier() : 0);
}
/** Returns true if a buff is both active and permanent */
public boolean isBuffPermanent(Buff buff) {
return isBuffActive(buff) && getActiveBuff(buff).isPermanent();
}
/** Returns active buffs map */
public Collection<BuffBase> getActiveBuffs() {
return activeBuffs.values();
}
/**
* Shortcut method for applying a new buff
* @param buff The type of buff
* @param duration Number of ticks; a duration equal to Integer.MAX_VALUE is 'permanent'
* @param amplifier How powerful the effect is: see individual {@link Buff buffs} for valid values
*/
public void applyBuff(Buff buff, int duration, int amplifier) {
applyBuff(new BuffBase(buff, duration, amplifier));
}
/**
* Applies a new Buff to the active buffs map
*/
public void applyBuff(BuffBase newBuff) {
synchronized (activeBuffs) {
if (isBuffActive(newBuff.getBuff())) {
getActiveBuff(newBuff.getBuff()).combine(newBuff);
getActiveBuff(newBuff.getBuff()).onChanged(this.entity);
} else {
activeBuffs.put(newBuff.getBuff(), newBuff);
newBuff.onAdded(this.entity);
}
}
}
/**
* Removes all buffs from this entity
*/
public void removeAllBuffs() {
removeAllBuffs(true, false);
}
/**
* Removes all temporary buffs from this entity
* @param removeAll If true, permanent buffs will also be removed
* @param sendUpdate True will send a packet for each buff removed
*/
public void removeAllBuffs(boolean removeAll, boolean sendUpdate) {
if (!entity.worldObj.isRemote) {
Iterator<Buff> iterator = activeBuffs.keySet().iterator();
while (iterator.hasNext()) {
Buff buff = iterator.next();
if (removeAll || !activeBuffs.get(buff).isPermanent()) {
activeBuffs.get(buff).onRemoved(entity, sendUpdate);
iterator.remove();
}
}
}
}
/**
* Removes a buff from the entity
*/
public void removeBuff(Buff buff) {
synchronized (activeBuffs) {
BuffBase buffBase = activeBuffs.remove(buff);
if (buffBase != null) {
buffBase.onRemoved(this.entity);
}
}
}
/**
* Updates all active buffs, removing any whose duration reaches zero
*/
protected void updateBuffs() {
Iterator<BuffBase> iterator = activeBuffs.values().iterator();
while (iterator.hasNext()) {
BuffBase buff = iterator.next();
if (buff.onUpdate(entity)) {
buff.onRemoved(entity);
iterator.remove();
}
}
}
/** Stuns this entity for the time given (not additive with previous stuns) */
public void stun(int time) {
stun(time, false);
}
/**
* Stuns this entity for the time given with optional override for players
* @param canStunPlayer whether the stun time should ignore config settings for players
*/
public void stun(int time, boolean alwaysStuns) {
int stunTime = (stunResistTime > 0 || (!alwaysStuns && isImmuneToStun()) ? 0 : time);
if (stunTime > 0 && !entity.worldObj.isRemote) {
stunTime *= 1.0F + (getBuffAmplifier(Buff.RESIST_STUN) * 0.01F);
stunTime *= 1.0F - (getBuffAmplifier(Buff.RESIST_STUN) * 0.01F);
if (stunTime > 0) {
stunResistTime = 40;
applyBuff(new BuffBase(Buff.STUN, stunTime, 0));
}
}
}
/**
* Returns true if this property's entity is immune to stun effects
*/
public boolean isImmuneToStun() {
// TODO make a public list that other mods can add entities to
return ((entity instanceof EntityPlayer && !Config.canPlayersBeStunned()) || entity instanceof IBossDisplayData);
}
/**
* Whether this entity is currently digesting a bomb
*/
public boolean hasIngestedBomb() {
return ingestedBomb != null;
}
/**
* Call when the entity ingests an ingestible bomb entity - only one bomb
* may be ingested at a time using this implementation.
* @param bomb Ingested bombs are immediately set to dead in the world
* @param boolean true if the bomb was ingested
*/
public boolean onBombIngested(IEntityBombIngestible bomb) {
if (ingestedBomb != null || !(bomb instanceof Entity)) {
return false;
}
fuseTime = bomb.getFuseTime(entity);
ingestedBomb = bomb;
if (!entity.worldObj.isRemote) {
((Entity) bomb).setDead();
}
return true;
}
/**
* Refreshes the fuse time from the current ingested bomb entity,
* e.g. if it was set to something else after #onBombIngested
*/
public void refreshFuseTime() {
if (ingestedBomb != null) {
fuseTime = ingestedBomb.getFuseTime(entity);
}
}
/**
* This method should be called every update tick; currently called from LivingUpdateEvent
*/
public void onUpdate() {
updateBuffs();
// Use a number higher than 100 otherwise it is nearly instantaneous even at low resists
if (entity.isBurning() && entity.worldObj.rand.nextInt(500) < getBuffAmplifier(Buff.RESIST_FIRE)) {
entity.extinguish();
}
if (stunResistTime > 0 && !isBuffActive(Buff.STUN)) {
--stunResistTime;
}
updateIngestedTime();
}
private void updateIngestedTime() {
if (fuseTime > 0) {
--fuseTime;
if (fuseTime == 0 && ingestedBomb != null) {
onBombIndigestion();
}
}
}
private void onBombIndigestion() {
boolean explode = true;
boolean isFatal = true;
if (entity instanceof IEntityBombEater) {
IEntityBombEater eater = (IEntityBombEater) entity;
if (!eater.onBombIndigestion(ingestedBomb)) {
return; // custom implementation handled it
}
explode = eater.doesIngestedBombExplode(ingestedBomb);
isFatal = eater.isIngestedBombFatal(ingestedBomb);
}
if (isFatal) {
entity.attackEntityFrom(DamageSource.setExplosionSource(null), entity.getMaxHealth() * 2);
}
if (explode) {
float r = ingestedBomb.getExplosionRadius(entity);
float dmg = ingestedBomb.getExplosionDamage(entity);
CustomExplosion.createExplosion(ingestedBomb, entity.worldObj, entity.posX, entity.posY, entity.posZ, r, dmg, false);
}
ingestedBomb = null;
}
/**
* Used to register these extended properties for the entity during EntityConstructing event
*/
public static final void register(EntityLivingBase entity) {
entity.registerExtendedProperties(EXT_PROP_NAME, new ZSSEntityInfo(entity));
}
/**
* Returns ExtendedPlayer properties for entity
*/
public static final ZSSEntityInfo get(EntityLivingBase entity) {
return (ZSSEntityInfo) entity.getExtendedProperties(EXT_PROP_NAME);
}
/**
* Call each time the player joins the world to sync data to the client
*/
public void onJoinWorld() {
if (entity instanceof EntityPlayerMP) {
PacketDispatcher.sendTo(new SyncEntityInfoPacket(this), (EntityPlayerMP) entity);
}
}
/**
* Copies given data to this one when a player is cloned
* If the client also needs the data, the packet must be sent from
* EntityJoinWorldEvent to ensure it is sent to the new client player
*/
public void copy(ZSSEntityInfo info) {
NBTTagCompound compound = new NBTTagCompound();
info.saveNBTData(compound);
this.loadNBTData(compound);
// remove temporary buffs after copying to use the new entity instance when removed
this.removeAllBuffs(false, false);
}
@Override
public void saveNBTData(NBTTagCompound compound) {
if (!activeBuffs.isEmpty()) {
NBTTagList list = new NBTTagList();
for (BuffBase buff : activeBuffs.values()) {
list.appendTag(buff.writeToNBT(new NBTTagCompound()));
}
compound.setTag("ActiveBuffs", list);
}
}
@Override
public void loadNBTData(NBTTagCompound compound) {
if (compound.hasKey("ActiveBuffs")) {
NBTTagList list = compound.getTagList("ActiveBuffs", Constants.NBT.TAG_COMPOUND);
for (int i = 0; i < list.tagCount(); ++i) {
NBTTagCompound tag = list.getCompoundTagAt(i);
BuffBase buff = BuffBase.readFromNBT(tag);
activeBuffs.put(buff.getBuff(), buff);
}
}
}
}