/* * Copyright (C) 2004-2015 L2J Server * * This file is part of L2J Server. * * L2J Server 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. * * L2J Server 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 com.l2jserver.gameserver.model.actor.status; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; import com.l2jserver.Config; import com.l2jserver.gameserver.ThreadPoolManager; import com.l2jserver.gameserver.model.actor.L2Character; import com.l2jserver.gameserver.model.actor.instance.L2PcInstance; import com.l2jserver.gameserver.model.actor.stat.CharStat; import com.l2jserver.gameserver.model.stats.Formulas; import com.l2jserver.util.Rnd; public class CharStatus { protected static final Logger _log = Logger.getLogger(CharStatus.class.getName()); private final L2Character _activeChar; private double _currentHp = 0; // Current HP of the L2Character private double _currentMp = 0; // Current MP of the L2Character /** Array containing all clients that need to be notified about hp/mp updates of the L2Character */ private Set<L2Character> _StatusListener; private Future<?> _regTask; protected byte _flagsRegenActive = 0; protected static final byte REGEN_FLAG_CP = 4; private static final byte REGEN_FLAG_HP = 1; private static final byte REGEN_FLAG_MP = 2; public CharStatus(L2Character activeChar) { _activeChar = activeChar; } /** * Add the object to the list of L2Character that must be informed of HP/MP updates of this L2Character.<br> * <B><U>Concept</U>:</B><br> * Each L2Character owns a list called <B>_statusListener</B> that contains all L2PcInstance to inform of HP/MP updates.<br> * Players who must be informed are players that target this L2Character.<br> * When a RegenTask is in progress sever just need to go through this list to send Server->Client packet StatusUpdate.<br> * <B><U>Example of use</U>:</B> * <ul> * <li>Target a PC or NPC</li> * <ul> * @param object L2Character to add to the listener */ public final void addStatusListener(L2Character object) { if (object == getActiveChar()) { return; } getStatusListener().add(object); } /** * Remove the object from the list of L2Character that must be informed of HP/MP updates of this L2Character.<br> * <B><U>Concept</U>:</B><br> * Each L2Character owns a list called <B>_statusListener</B> that contains all L2PcInstance to inform of HP/MP updates.<br> * Players who must be informed are players that target this L2Character.<br> * When a RegenTask is in progress sever just need to go through this list to send Server->Client packet StatusUpdate.<br> * <B><U>Example of use </U>:</B> * <ul> * <li>Untarget a PC or NPC</li> * </ul> * @param object L2Character to add to the listener */ public final void removeStatusListener(L2Character object) { getStatusListener().remove(object); } /** * Return the list of L2Character that must be informed of HP/MP updates of this L2Character.<br> * <B><U>Concept</U>:</B><br> * Each L2Character owns a list called <B>_statusListener</B> that contains all L2PcInstance to inform of HP/MP updates.<br> * Players who must be informed are players that target this L2Character.<br> * When a RegenTask is in progress sever just need to go through this list to send Server->Client packet StatusUpdate. * @return The list of L2Character to inform or null if empty */ public final Set<L2Character> getStatusListener() { if (_StatusListener == null) { _StatusListener = new CopyOnWriteArraySet<>(); } return _StatusListener; } // place holder, only PcStatus has CP public void reduceCp(int value) { } /** * Reduce the current HP of the L2Character and launch the doDie Task if necessary. * @param value * @param attacker */ public void reduceHp(double value, L2Character attacker) { reduceHp(value, attacker, true, false, false); } public void reduceHp(double value, L2Character attacker, boolean isHpConsumption) { reduceHp(value, attacker, true, false, isHpConsumption); } public void reduceHp(double value, L2Character attacker, boolean awake, boolean isDOT, boolean isHPConsumption) { if (getActiveChar().isDead()) { return; } // invul handling if (getActiveChar().isInvul() && !(isDOT || isHPConsumption)) { return; } if (attacker != null) { final L2PcInstance attackerPlayer = attacker.getActingPlayer(); if ((attackerPlayer != null) && attackerPlayer.isGM() && !attackerPlayer.getAccessLevel().canGiveDamage()) { return; } } if (!isDOT && !isHPConsumption) { getActiveChar().stopEffectsOnDamage(awake); if (getActiveChar().isStunned() && (Rnd.get(10) == 0)) { getActiveChar().stopStunning(true); } } if (value > 0) { setCurrentHp(Math.max(getCurrentHp() - value, 0)); } if ((getActiveChar().getCurrentHp() < 0.5) && getActiveChar().isMortal()) // Die { getActiveChar().abortAttack(); getActiveChar().abortCast(); if (Config.DEBUG) { _log.fine("char is dead."); } getActiveChar().doDie(attacker); } } public void reduceMp(double value) { setCurrentMp(Math.max(getCurrentMp() - value, 0)); } /** * Start the HP/MP/CP Regeneration task.<br> * <B><U>Actions</U>:</B> * <ul> * <li>Calculate the regen task period</li> * <li>Launch the HP/MP/CP Regeneration task with Medium priority</li> * </ul> */ public final synchronized void startHpMpRegeneration() { if ((_regTask == null) && !getActiveChar().isDead()) { if (Config.DEBUG) { _log.fine("HP/MP regen started"); } // Get the Regeneration period int period = Formulas.getRegeneratePeriod(getActiveChar()); // Create the HP/MP/CP Regeneration task _regTask = ThreadPoolManager.getInstance().scheduleEffectAtFixedRate(new RegenTask(), period, period); } } /** * Stop the HP/MP/CP Regeneration task.<br> * <B><U>Actions</U>:</B> * <ul> * <li>Set the RegenActive flag to False</li> * <li>Stop the HP/MP/CP Regeneration task</li> * </ul> */ public final synchronized void stopHpMpRegeneration() { if (_regTask != null) { if (Config.DEBUG) { _log.fine("HP/MP regen stop"); } // Stop the HP/MP/CP Regeneration task _regTask.cancel(false); _regTask = null; // Set the RegenActive flag to false _flagsRegenActive = 0; } } // place holder, only PcStatus has CP public double getCurrentCp() { return 0; } // place holder, only PcStatus has CP public void setCurrentCp(double newCp) { } public final double getCurrentHp() { return _currentHp; } public final void setCurrentHp(double newHp) { setCurrentHp(newHp, true); } /** * Sets the current hp of this character. * @param newHp the new hp * @param broadcastPacket if true StatusUpdate packet will be broadcasted. * @return @{code true} if hp was changed, @{code false} otherwise. */ public boolean setCurrentHp(double newHp, boolean broadcastPacket) { // Get the Max HP of the L2Character int currentHp = (int) getCurrentHp(); final double maxHp = getActiveChar().getStat().getMaxHp(); synchronized (this) { if (getActiveChar().isDead()) { return false; } if (newHp >= maxHp) { // Set the RegenActive flag to false _currentHp = maxHp; _flagsRegenActive &= ~REGEN_FLAG_HP; // Stop the HP/MP/CP Regeneration task if (_flagsRegenActive == 0) { stopHpMpRegeneration(); } } else { // Set the RegenActive flag to true _currentHp = newHp; _flagsRegenActive |= REGEN_FLAG_HP; // Start the HP/MP/CP Regeneration task with Medium priority startHpMpRegeneration(); } } boolean hpWasChanged = currentHp != _currentHp; // Send the Server->Client packet StatusUpdate with current HP and MP to all other L2PcInstance to inform if (hpWasChanged && broadcastPacket) { getActiveChar().broadcastStatusUpdate(); } return hpWasChanged; } public final void setCurrentHpMp(double newHp, double newMp) { boolean hpOrMpWasChanged = setCurrentHp(newHp, false); hpOrMpWasChanged |= setCurrentMp(newMp, false); if (hpOrMpWasChanged) { getActiveChar().broadcastStatusUpdate(); } } public final double getCurrentMp() { return _currentMp; } public final void setCurrentMp(double newMp) { setCurrentMp(newMp, true); } /** * Sets the current mp of this character. * @param newMp the new mp * @param broadcastPacket if true StatusUpdate packet will be broadcasted. * @return @{code true} if mp was changed, @{code false} otherwise. */ public final boolean setCurrentMp(double newMp, boolean broadcastPacket) { // Get the Max MP of the L2Character int currentMp = (int) getCurrentMp(); final int maxMp = getActiveChar().getStat().getMaxMp(); synchronized (this) { if (getActiveChar().isDead()) { return false; } if (newMp >= maxMp) { // Set the RegenActive flag to false _currentMp = maxMp; _flagsRegenActive &= ~REGEN_FLAG_MP; // Stop the HP/MP/CP Regeneration task if (_flagsRegenActive == 0) { stopHpMpRegeneration(); } } else { // Set the RegenActive flag to true _currentMp = newMp; _flagsRegenActive |= REGEN_FLAG_MP; // Start the HP/MP/CP Regeneration task with Medium priority startHpMpRegeneration(); } } boolean mpWasChanged = currentMp != _currentMp; // Send the Server->Client packet StatusUpdate with current HP and MP to all other L2PcInstance to inform if (mpWasChanged && broadcastPacket) { getActiveChar().broadcastStatusUpdate(); } return mpWasChanged; } protected void doRegeneration() { final CharStat charstat = getActiveChar().getStat(); // Modify the current HP of the L2Character and broadcast Server->Client packet StatusUpdate if (getCurrentHp() < charstat.getMaxRecoverableHp()) { setCurrentHp(getCurrentHp() + Formulas.calcHpRegen(getActiveChar()), false); } // Modify the current MP of the L2Character and broadcast Server->Client packet StatusUpdate if (getCurrentMp() < charstat.getMaxRecoverableMp()) { setCurrentMp(getCurrentMp() + Formulas.calcMpRegen(getActiveChar()), false); } if (!getActiveChar().isInActiveRegion()) { // no broadcast necessary for characters that are in inactive regions. // stop regeneration for characters who are filled up and in an inactive region. if ((getCurrentHp() == charstat.getMaxRecoverableHp()) && (getCurrentMp() == charstat.getMaxMp())) { stopHpMpRegeneration(); } } else { getActiveChar().broadcastStatusUpdate(); // send the StatusUpdate packet } } /** Task of HP/MP regeneration */ class RegenTask implements Runnable { @Override public void run() { try { doRegeneration(); } catch (Exception e) { _log.log(Level.SEVERE, "", e); } } } public L2Character getActiveChar() { return _activeChar; } }