/* * This file is part of aion-unique <aion-unique.smfnew.com>. * * aion-emu is free software: 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. * * aion-emu 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 aion-emu. If not, see <http://www.gnu.org/licenses/>. */ package com.aionemu.gameserver.model.gameobjects.stats; import java.util.List; import java.util.TreeSet; import java.util.Map.Entry; import java.util.concurrent.locks.ReentrantReadWriteLock; import javolution.util.FastMap; import org.apache.log4j.Logger; import com.aionemu.commons.callbacks.Enhancable; import com.aionemu.gameserver.model.SkillElement; import com.aionemu.gameserver.model.gameobjects.Creature; import com.aionemu.gameserver.model.gameobjects.player.Player; import com.aionemu.gameserver.model.gameobjects.stats.id.ItemStatEffectId; import com.aionemu.gameserver.model.gameobjects.stats.id.StatEffectId; import com.aionemu.gameserver.model.gameobjects.stats.listeners.StatChangeListener; import com.aionemu.gameserver.model.gameobjects.stats.modifiers.StatModifier; import com.aionemu.gameserver.model.items.ItemSlot; /** * @author xavier * */ public class CreatureGameStats<T extends Creature> { protected static final Logger log = Logger .getLogger(CreatureGameStats.class); private static final int ATTACK_MAX_COUNTER = Integer.MAX_VALUE; protected FastMap<StatEnum, Stat> stats; protected FastMap<StatEffectId, TreeSet<StatModifier>> statsModifiers; private int attackCounter = 0; protected T owner = null; protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); /** * * @param owner */ protected CreatureGameStats(T owner) { this.owner = owner; this.stats = new FastMap<StatEnum,Stat> (); this.statsModifiers = new FastMap<StatEffectId, TreeSet<StatModifier>>(); } /** * @return the atcount */ public int getAttackCounter() { return attackCounter; } /** * @param atcount * the atcount to set */ protected void setAttackCounter(int attackCounter) { if(attackCounter <= 0) { this.attackCounter = 1; } else { this.attackCounter = attackCounter; } } public void increaseAttackCounter() { if(attackCounter == ATTACK_MAX_COUNTER) { this.attackCounter = 1; } else { this.attackCounter++; } } /** * * @param stat * @param value */ public void setStat(StatEnum stat, int value) { lock.writeLock().lock(); try { setStat(stat,value,false); } finally { lock.writeLock().unlock(); } } /** * @param stat * @return */ public int getBaseStat(StatEnum stat) { int value = 0; lock.readLock().lock(); try { if(stats.containsKey(stat)) { value = stats.get(stat).getBase(); } } finally { lock.readLock().unlock(); } return value; } /** * * @param stat * @return */ public int getStatBonus(StatEnum stat) { int value = 0; lock.readLock().lock(); try { if (stats.containsKey(stat)) { value = stats.get(stat).getBonus(); } } finally { lock.readLock().unlock(); } return value; } /** * * @param stat * @return */ public int getCurrentStat(StatEnum stat) { int value = 0; lock.readLock().lock(); try { if (stats.containsKey(stat)) { value = stats.get(stat).getCurrent(); } } finally { lock.readLock().unlock(); } return value; } /** * * @param id * @param modifiers */ public void addModifiers(StatEffectId id, TreeSet<StatModifier> modifiers) { if (modifiers==null) { return; } if (statsModifiers.containsKey(id)) { throw new IllegalArgumentException("Effect "+id+" already active"); } statsModifiers.put(id, modifiers); recomputeStats(); } /* * @return True if the StatEffectId is already added */ public boolean effectAlreadyAdded(StatEffectId id) { return statsModifiers.containsKey(id); } /** * Recomputation of all stats * Additional logic is in StatChangeListener callbacks */ @Enhancable(callback = StatChangeListener.class) protected void recomputeStats() { //need check this lock, may be remove lock.writeLock().lock(); try { resetStats(); FastMap<StatEnum, StatModifiers> orderedModifiers = new FastMap<StatEnum, StatModifiers>(); for(Entry<StatEffectId, TreeSet<StatModifier>> modifiers : statsModifiers.entrySet()) { StatEffectId eid = modifiers.getKey(); int slots; if(modifiers.getValue() == null) continue; for(StatModifier modifier : modifiers.getValue()) { slots = ItemSlot.NONE.getSlotIdMask(); if(eid instanceof ItemStatEffectId) { slots = ((ItemStatEffectId) eid).getSlot(); } if(modifier.getStat().isMainOrSubHandStat() && owner instanceof Player) { if(slots != ItemSlot.MAIN_HAND.getSlotIdMask() && slots != ItemSlot.SUB_HAND.getSlotIdMask()) { if(((Player) owner).getEquipment().getOffHandWeaponType() != null) slots = ItemSlot.MAIN_OR_SUB.getSlotIdMask(); else { slots = ItemSlot.MAIN_HAND.getSlotIdMask(); setStat(StatEnum.OFF_HAND_ACCURACY, 0, false); } } else if(slots == ItemSlot.MAIN_HAND.getSlotIdMask()) setStat(StatEnum.MAIN_HAND_POWER, 0); } List<ItemSlot> oSlots = ItemSlot.getSlotsFor(slots); for(ItemSlot slot : oSlots) { StatEnum statToModify = modifier.getStat().getMainOrSubHandStat(slot); if(!orderedModifiers.containsKey(statToModify)) { orderedModifiers.put(statToModify, new StatModifiers()); } orderedModifiers.get(statToModify).add(modifier); } } } for(Entry<StatEnum, StatModifiers> entry : orderedModifiers.entrySet()) { applyModifiers(entry.getKey(), entry.getValue()); } setStat(StatEnum.ATTACK_SPEED, Math.round(getBaseStat(StatEnum.MAIN_HAND_ATTACK_SPEED) + getBaseStat(StatEnum.OFF_HAND_ATTACK_SPEED) * 0.25f), false); setStat(StatEnum.ATTACK_SPEED, getStatBonus(StatEnum.MAIN_HAND_ATTACK_SPEED) + getStatBonus(StatEnum.OFF_HAND_ATTACK_SPEED), true); } finally { lock.writeLock().unlock(); } } /** * * @param id */ public void endEffect(StatEffectId id) { statsModifiers.remove(id); recomputeStats(); } /** * * @param element * @return */ public int getMagicalDefenseFor(SkillElement element) { switch(element) { case EARTH: return getCurrentStat(StatEnum.EARTH_RESISTANCE); case FIRE: return getCurrentStat(StatEnum.FIRE_RESISTANCE); case WATER: return getCurrentStat(StatEnum.WATER_RESISTANCE); case WIND: return getCurrentStat(StatEnum.WIND_RESISTANCE); default: return 0; } } /** * Reset all stats * No need to syncronized becaused guarded by recompute() write lock */ protected void resetStats() { for(Stat stat : stats.values()) { stat.reset(); } } /** * * @param stat * @param modifiers */ protected void applyModifiers(StatEnum stat, StatModifiers modifiers) { if(modifiers == null) return; if(!stats.containsKey(stat)) { initStat(stat, 0); } Stat oStat = stats.get(stat); for(StatModifierPriority priority : StatModifierPriority.values()) { for(StatModifier modifier : modifiers.getModifiers(priority)) { int newValue = modifier.apply(oStat.getBase(), oStat.getCurrent()); oStat.increase(newValue, modifier.isBonus()); } } } /** * * @param stat * @param value */ protected void initStat(StatEnum stat, int value) { if(!stats.containsKey(stat)) { stats.put(stat, new Stat(stat, value)); } else { stats.get(stat).reset(); stats.get(stat).set(value, false); } } /** * * @param stat * @param value * @param bonus */ protected void setStat(StatEnum stat, int value, boolean bonus) { if(!stats.containsKey(stat)) { stats.put(stat, new Stat(stat, 0)); } stats.get(stat).set(value, bonus); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append('{'); sb.append("owner:" + owner.getObjectId()); for(Stat stat : stats.values()) { sb.append(stat); } sb.append('}'); return sb.toString(); } }