/**
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.skills;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.StatCollector;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import zeldaswordskills.ZSSAchievements;
import zeldaswordskills.network.PacketDispatcher;
import zeldaswordskills.network.client.UpdateComboPacket;
/**
*
* Each instance of this class is a self-synchronizing and mostly self-contained module containing
* all the data necessary to keep track of a player's current attack combo.
*
* When a combo ends, the player will gain the specified type of experience in an damage equal
* to the combo size minus one, plus an additional damage proportional to the total damage dealt.
*
* Specifications:
* A new instance should be used for each new attack combo.
* Each instance should be updated every tick from within its containing class' update method
* Determining when to add damage or end the combo prematurely must be handled extraneously.
* Only self-synchronizing when UpdateComboPacket class is kept up-to-date
*
*/
public class Combo
{
/** Used only to get correct Skill class from player during update */
private final byte skillId;
/** Max combo size attainable by this instance of Combo */
private final int maxComboSize;
/** Upon landing a blow, the combo timer is set to this damage */
private final int timeLimit;
/** Current combo timer; combo ends when timer reaches zero. */
private int comboTimer = 0;
/** Set to true when endCombo method is called */
private boolean isFinished = false;
/** List stores each hit's damage; combo size is inherent in the list */
private final List<Float> damageList = new ArrayList<Float>();
/** Running total of damage inflicted during a combo */
private float comboDamage = 0.0F;
/** Internal flag for correcting split damage such as IArmorBreak */
private boolean addToLast = false;
/** Internal value for last damage */
private float lastDamage = 0.0F;
/** Last entity hit; used to check if consecutive hit counter should increase or reset */
private Entity lastEntityHit = null;
/** EntityID of last entity hit, used client side to get the entity */
private int entityId;
/** Total number of consecutive hits on the same target entity */
private int consecutiveHits = 0;
/**
* Constructs a new Combo with specified max combo size and time limit and sends an update
* packet to the player with the new Combo instance.
* @param maxComboSize size at which the combo self-terminates
* @param timeLimit damage of time allowed between strikes before combo self-terminates
* @param xpType the Attribute that receives xp gained from combo
*/
public Combo(EntityPlayer player, SkillBase skill, int maxComboSize, int timeLimit) {
this.skillId = skill.getId();
this.maxComboSize = maxComboSize;
this.timeLimit = timeLimit;
if (player instanceof EntityPlayerMP) {
PacketDispatcher.sendTo(new UpdateComboPacket(this), (EntityPlayerMP) player);
}
}
/**
* Constructs a new Combo with specified max combo size and time limit
* @param maxComboSize size at which the combo self-terminates
* @param timeLimit damage of time allowed between strikes before combo self-terminates
* @param xpType the Attribute that receives xp gained from combo
*/
private Combo(byte skillId, int maxComboSize, int timeLimit) {
this.skillId = skillId;
this.maxComboSize = maxComboSize;
this.timeLimit = timeLimit;
}
/** Returns the skill id associated with this Combo */
public byte getSkill() { return skillId; }
/** Returns current number of hits */
public int getNumHits() { return damageList.size(); }
/** Returns maximum number of hits allowed before the combo self-terminates */
public int getMaxNumHits() { return maxComboSize; }
/** Returns current damage total for this combo */
public float getDamage() { return comboDamage; }
/** Returns a copy of the current damage list */
public List<Float> getDamageList() { return Collections.unmodifiableList(damageList); }
/** Returns the last entity directly hit during the combo */
public Entity getLastEntityHit() { return lastEntityHit; }
/** Returns the number of consecutive hits on the same target entity */
public int getConsecutiveHits() { return consecutiveHits; }
/** Returns true if this combo is finished, i.e. no longer active */
public boolean isFinished() { return isFinished; }
/** Returns translated current description of combo; e.g. "Great" */
public String getLabel() {
return StatCollector.translateToLocal("combo.label." + getNumHits());
}
/**
* Updates combo timer and triggers combo ending if timer reaches zero
*/
public void onUpdate(EntityPlayer player) {
if (comboTimer > 0) {
--comboTimer;
if (comboTimer == 0) {
endCombo(player);
}
}
}
/**
* Increases the combo size by one and adds the damage to the running total, as well as
* ending the combo if the max size is reached. This is only called server side.
* @param target used to track consecutive hits on a single target
*/
public void add(EntityPlayer player, Entity target, float damage) {
if (getNumHits() < maxComboSize && (comboTimer > 0 || getNumHits() == 0)) {
if (target != null && target == lastEntityHit) {
++consecutiveHits;
} else {
lastEntityHit = target;
consecutiveHits = (target != null ? 1 : 0);
}
if (addToLast) {
damageList.add(damage + lastDamage);
addToLast = false;
lastDamage = 0.0F;
} else {
damageList.add(damage);
}
switch(damageList.size()) {
case 3: player.triggerAchievement(ZSSAchievements.comboBasic); break;
case 8: player.triggerAchievement(ZSSAchievements.comboPerfect); break;
case 12: player.triggerAchievement(ZSSAchievements.comboLegend); break;
}
comboDamage += damage;
if (player instanceof EntityPlayerMP) {
PacketDispatcher.sendTo(new UpdateComboPacket(this), (EntityPlayerMP) player);
}
if (getNumHits() == maxComboSize) {
endCombo(player);
} else {
comboTimer = timeLimit;
}
} else {
endCombo(player);
}
}
/**
* Adds damage damage to combo's total, without incrementing the combo size.
* @param flag whether the damage should be added to the previous strike's total, for IArmorBreak
*/
public void addDamageOnly(EntityPlayer player, float damage, boolean flag) {
if (!isFinished()) {
comboDamage += damage;
addToLast = flag;
if (addToLast) {
lastDamage = damage;
}
if (getNumHits() == 0) {
comboTimer = timeLimit;
}
if (player instanceof EntityPlayerMP) {
PacketDispatcher.sendTo(new UpdateComboPacket(this), (EntityPlayerMP) player);
}
}
}
/**
* Ends a combo and grants Xp
*/
public void endCombo(EntityPlayer player) {
if (!isFinished) {
isFinished = true;
lastEntityHit = null;
consecutiveHits = 0;
if (player instanceof EntityPlayerMP) {
PacketDispatcher.sendTo(new UpdateComboPacket(this), (EntityPlayerMP) player);
}
}
}
/**
* Attempts to set the last entity hit after loading from NBT; use from update packet
*/
@SideOnly(Side.CLIENT)
public void getEntityFromWorld(World world) {
lastEntityHit = world.getEntityByID(entityId);
}
/**
* Writes this combo to NBT and returns the tag compound
*/
public final NBTTagCompound writeToNBT() {
NBTTagCompound compound = new NBTTagCompound();
compound.setByte("SkillID", skillId);
compound.setInteger("MaxSize", maxComboSize);
compound.setInteger("TimeLimit", timeLimit);
compound.setInteger("CurrentSize", getNumHits());
for (int i = 0; i < getNumHits(); ++i) {
compound.setFloat("Dmg" + i, damageList.get(i));
}
compound.setFloat("TotalDamage", comboDamage);
compound.setInteger("EntityId", (lastEntityHit != null ? lastEntityHit.getEntityId() : 0));
compound.setInteger("ConsecutiveHits", consecutiveHits);
compound.setBoolean("Finished", isFinished);
return compound;
}
/**
* Creates a new combo from the nbt tag data
*/
public static final Combo readFromNBT(NBTTagCompound compound) {
Combo combo = new Combo(compound.getByte("SkillID"), compound.getInteger("MaxSize"), compound.getInteger("TimeLimit"));
int size = compound.getInteger("CurrentSize");
for (int i = 0; i < size; ++i) {
combo.damageList.add(compound.getFloat("Dmg" + i));
}
combo.comboDamage = compound.getFloat("TotalDamage");
combo.entityId = compound.getInteger("EntityId");
combo.consecutiveHits = compound.getInteger("ConsecutiveHits");
combo.isFinished = compound.getBoolean("Finished");
return combo;
}
}