/*
* 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.stat;
import java.util.concurrent.atomic.AtomicInteger;
import com.l2jserver.Config;
import com.l2jserver.gameserver.data.xml.impl.ExperienceData;
import com.l2jserver.gameserver.data.xml.impl.PetDataTable;
import com.l2jserver.gameserver.enums.PartySmallWindowUpdateType;
import com.l2jserver.gameserver.enums.UserInfoType;
import com.l2jserver.gameserver.model.L2PetLevelData;
import com.l2jserver.gameserver.model.PcCondOverride;
import com.l2jserver.gameserver.model.actor.L2Summon;
import com.l2jserver.gameserver.model.actor.instance.L2ClassMasterInstance;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.actor.instance.L2PetInstance;
import com.l2jserver.gameserver.model.actor.transform.TransformTemplate;
import com.l2jserver.gameserver.model.events.EventDispatcher;
import com.l2jserver.gameserver.model.events.impl.character.player.OnPlayerLevelChanged;
import com.l2jserver.gameserver.model.stats.Formulas;
import com.l2jserver.gameserver.model.stats.MoveType;
import com.l2jserver.gameserver.model.stats.Stats;
import com.l2jserver.gameserver.model.zone.ZoneId;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.serverpackets.AcquireSkillList;
import com.l2jserver.gameserver.network.serverpackets.ExAcquireAPSkillList;
import com.l2jserver.gameserver.network.serverpackets.ExVitalityPointInfo;
import com.l2jserver.gameserver.network.serverpackets.ExVoteSystemInfo;
import com.l2jserver.gameserver.network.serverpackets.PartySmallWindowUpdate;
import com.l2jserver.gameserver.network.serverpackets.PledgeShowMemberListUpdate;
import com.l2jserver.gameserver.network.serverpackets.SocialAction;
import com.l2jserver.gameserver.network.serverpackets.StatusUpdate;
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
import com.l2jserver.gameserver.network.serverpackets.UserInfo;
import com.l2jserver.gameserver.network.serverpackets.friend.L2FriendStatus;
import com.l2jserver.gameserver.util.Util;
public class PcStat extends PlayableStat
{
private int _oldMaxHp; // stats watch
private int _oldMaxMp; // stats watch
private int _oldMaxCp; // stats watch
private long _startingXp;
/** Player's maximum cubic count. */
private int _maxCubicCount = 1;
/** Player's maximum talisman count. */
private final AtomicInteger _talismanSlots = new AtomicInteger();
private boolean _cloakSlot = false;
public static final int MAX_VITALITY_POINTS = 140000;
public static final int MIN_VITALITY_POINTS = 1;
public static String VITALITY_VARIABLE = "vitality_points";
public PcStat(L2PcInstance activeChar)
{
super(activeChar);
}
@Override
public boolean addExp(long value)
{
L2PcInstance activeChar = getActiveChar();
// Allowed to gain exp?
if (!getActiveChar().getAccessLevel().canGainExp())
{
return false;
}
if (!super.addExp(value))
{
return false;
}
// Set new karma
if (!activeChar.isCursedWeaponEquipped() && (activeChar.getKarma() > 0) && (activeChar.isGM() || !activeChar.isInsideZone(ZoneId.PVP)))
{
int karmaLost = Formulas.calculateKarmaLost(activeChar, value);
if (karmaLost > 0)
{
activeChar.setKarma(activeChar.getKarma() - karmaLost);
final SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.YOUR_REPUTATION_HAS_BEEN_CHANGED_TO_S1);
msg.addInt(activeChar.getKarma());
activeChar.sendPacket(msg);
}
}
// EXP status update currently not used in retail
activeChar.sendPacket(new UserInfo(activeChar));
return true;
}
public boolean addExpAndSp(long addToExp, long addToSp, boolean useBonuses)
{
L2PcInstance activeChar = getActiveChar();
// Allowed to gain exp/sp?
if (!activeChar.getAccessLevel().canGainExp())
{
return false;
}
long baseExp = addToExp;
long baseSp = addToSp;
double bonusExp = 1.;
double bonusSp = 1.;
if (useBonuses)
{
bonusExp = getExpBonusMultiplier();
bonusSp = getSpBonusMultiplier();
}
addToExp *= bonusExp;
addToSp *= bonusSp;
if (activeChar.hasPremiumStatus())
{
addToExp *= Config.PREMIUM_RATE_XP;
addToSp *= Config.PREMIUM_RATE_SP;
}
float ratioTakenByPlayer = 0;
// if this player has a pet and it is in his range he takes from the owner's Exp, give the pet Exp now
final L2Summon sPet = activeChar.getPet();
if ((sPet != null) && Util.checkIfInShortRadius(Config.ALT_PARTY_RANGE, activeChar, sPet, false))
{
L2PetInstance pet = (L2PetInstance) sPet;
ratioTakenByPlayer = pet.getPetLevelData().getOwnerExpTaken() / 100f;
// only give exp/sp to the pet by taking from the owner if the pet has a non-zero, positive ratio
// allow possible customizations that would have the pet earning more than 100% of the owner's exp/sp
if (ratioTakenByPlayer > 1)
{
ratioTakenByPlayer = 1;
}
if (!pet.isDead())
{
pet.addExpAndSp((long) (addToExp * (1 - ratioTakenByPlayer)), (int) (addToSp * (1 - ratioTakenByPlayer)));
}
// now adjust the max ratio to avoid the owner earning negative exp/sp
addToExp = (long) (addToExp * ratioTakenByPlayer);
addToSp = (int) (addToSp * ratioTakenByPlayer);
}
if (!addExp(addToExp))
{
addToExp = 0;
}
if (!addSp(addToSp))
{
addToSp = 0;
}
if ((addToExp == 0) && (addToSp == 0))
{
return false;
}
SystemMessage sm = null;
if ((addToExp == 0) && (addToSp != 0))
{
sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_ACQUIRED_S1_SP);
sm.addLong(addToSp);
}
else if ((addToSp == 0) && (addToExp != 0))
{
sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_EARNED_S1_XP);
sm.addLong(addToExp);
}
else
{
if ((addToExp - baseExp) > 0)
{
sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_ACQUIRED_S1_XP_BONUS_S2_AND_S3_SP_BONUS_S4);
sm.addLong(addToExp);
sm.addLong(addToExp - baseExp);
sm.addLong(addToSp);
sm.addLong(addToSp - baseSp);
}
else
{
sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_EARNED_S1_XP_AND_S2_SP);
sm.addLong(addToExp);
sm.addLong(addToSp);
}
}
activeChar.sendPacket(sm);
return true;
}
@Override
public boolean removeExpAndSp(long addToExp, long addToSp)
{
return removeExpAndSp(addToExp, addToSp, true);
}
public boolean removeExpAndSp(long addToExp, long addToSp, boolean sendMessage)
{
int level = getLevel();
if (!super.removeExpAndSp(addToExp, addToSp))
{
return false;
}
if (sendMessage)
{
// Send a Server->Client System Message to the L2PcInstance
SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.YOUR_XP_HAS_DECREASED_BY_S1);
sm.addLong(addToExp);
getActiveChar().sendPacket(sm);
sm = SystemMessage.getSystemMessage(SystemMessageId.YOUR_SP_HAS_DECREASED_BY_S1);
sm.addLong(addToSp);
getActiveChar().sendPacket(sm);
if (getLevel() < level)
{
getActiveChar().broadcastStatusUpdate();
}
}
return true;
}
@Override
public final boolean addLevel(byte value)
{
if ((getLevel() + value) > (ExperienceData.getInstance().getMaxLevel() - 1))
{
return false;
}
// Notify to scripts
EventDispatcher.getInstance().notifyEventAsync(new OnPlayerLevelChanged(getActiveChar(), getLevel(), getLevel() + value), getActiveChar());
boolean levelIncreased = super.addLevel(value);
if (levelIncreased)
{
getActiveChar().setCurrentCp(getMaxCp());
getActiveChar().broadcastPacket(new SocialAction(getActiveChar().getObjectId(), SocialAction.LEVEL_UP));
getActiveChar().sendPacket(SystemMessageId.YOUR_LEVEL_HAS_INCREASED);
getActiveChar().notifyFriends(L2FriendStatus.MODE_LEVEL);
L2ClassMasterInstance.showQuestionMark(getActiveChar());
}
// Give AutoGet skills and all normal skills if Auto-Learn is activated.
getActiveChar().rewardSkills();
if (getActiveChar().getClan() != null)
{
getActiveChar().getClan().updateClanMember(getActiveChar());
getActiveChar().getClan().broadcastToOnlineMembers(new PledgeShowMemberListUpdate(getActiveChar()));
}
if (getActiveChar().isInParty())
{
getActiveChar().getParty().recalculatePartyLevel(); // Recalculate the party level
}
if (getActiveChar().isTransformed() || getActiveChar().isInStance())
{
getActiveChar().getTransformation().onLevelUp(getActiveChar());
}
// Synchronize level with pet if possible.
final L2Summon sPet = getActiveChar().getPet();
if (sPet != null)
{
final L2PetInstance pet = (L2PetInstance) sPet;
if (pet.getPetData().isSynchLevel() && (pet.getLevel() != getLevel()))
{
pet.getStat().setLevel(getLevel());
pet.getStat().getExpForLevel(getActiveChar().getLevel());
pet.setCurrentHp(pet.getMaxHp());
pet.setCurrentMp(pet.getMaxMp());
pet.broadcastPacket(new SocialAction(getActiveChar().getObjectId(), SocialAction.LEVEL_UP));
pet.updateAndBroadcastStatus(1);
}
}
StatusUpdate su = new StatusUpdate(getActiveChar());
su.addAttribute(StatusUpdate.LEVEL, getLevel());
su.addAttribute(StatusUpdate.MAX_CP, getMaxCp());
su.addAttribute(StatusUpdate.MAX_HP, getMaxHp());
su.addAttribute(StatusUpdate.MAX_MP, getMaxMp());
getActiveChar().sendPacket(su);
// Update the overloaded status of the L2PcInstance
getActiveChar().refreshOverloaded();
// Update the expertise status of the L2PcInstance
getActiveChar().refreshExpertisePenalty();
// Send a Server->Client packet UserInfo to the L2PcInstance
getActiveChar().sendPacket(new UserInfo(getActiveChar()));
// Send acquirable skill list
getActiveChar().sendPacket(new AcquireSkillList(getActiveChar()));
getActiveChar().sendPacket(new ExVoteSystemInfo(getActiveChar()));
if (getActiveChar().isInParty())
{
final PartySmallWindowUpdate partyWindow = new PartySmallWindowUpdate(getActiveChar(), false);
partyWindow.addUpdateType(PartySmallWindowUpdateType.LEVEL);
getActiveChar().getParty().broadcastToPartyMembers(getActiveChar(), partyWindow);
}
if ((getLevel() == ExperienceData.getInstance().getMaxLevel()) && getActiveChar().isNoble())
{
getActiveChar().sendPacket(new ExAcquireAPSkillList(getActiveChar()));
}
return levelIncreased;
}
@Override
public boolean addSp(long value)
{
if (!super.addSp(value))
{
return false;
}
UserInfo ui = new UserInfo(getActiveChar(), false);
ui.addComponentType(UserInfoType.CURRENT_HPMPCP_EXP_SP);
getActiveChar().sendPacket(ui);
return true;
}
@Override
public final long getExpForLevel(int level)
{
return ExperienceData.getInstance().getExpForLevel(level);
}
@Override
public final L2PcInstance getActiveChar()
{
return (L2PcInstance) super.getActiveChar();
}
@Override
public final long getExp()
{
if (getActiveChar().isSubClassActive())
{
return getActiveChar().getSubClasses().get(getActiveChar().getClassIndex()).getExp();
}
return super.getExp();
}
public final long getBaseExp()
{
return super.getExp();
}
@Override
public final void setExp(long value)
{
if (getActiveChar().isSubClassActive())
{
getActiveChar().getSubClasses().get(getActiveChar().getClassIndex()).setExp(value);
}
else
{
super.setExp(value);
}
}
public void setStartingExp(long value)
{
if (Config.BOTREPORT_ENABLE)
{
_startingXp = value;
}
}
public long getStartingExp()
{
return _startingXp;
}
/**
* Gets the maximum cubic count.
* @return the maximum cubic count
*/
public int getMaxCubicCount()
{
return _maxCubicCount;
}
/**
* Sets the maximum cubic count.
* @param cubicCount the maximum cubic count
*/
public void setMaxCubicCount(int cubicCount)
{
_maxCubicCount = cubicCount;
}
/**
* Gets the maximum talisman count.
* @return the maximum talisman count
*/
public int getTalismanSlots()
{
return _talismanSlots.get();
}
public void addTalismanSlots(int count)
{
_talismanSlots.addAndGet(count);
}
public boolean canEquipCloak()
{
return _cloakSlot;
}
public void setCloakSlotStatus(boolean cloakSlot)
{
_cloakSlot = cloakSlot;
}
@Override
public final byte getLevel()
{
if (getActiveChar().isSubClassActive())
{
return getActiveChar().getSubClasses().get(getActiveChar().getClassIndex()).getLevel();
}
return super.getLevel();
}
public final byte getBaseLevel()
{
return super.getLevel();
}
@Override
public final void setLevel(byte value)
{
if (value > (ExperienceData.getInstance().getMaxLevel() - 1))
{
value = (byte) (ExperienceData.getInstance().getMaxLevel() - 1);
}
if (getActiveChar().isSubClassActive())
{
getActiveChar().getSubClasses().get(getActiveChar().getClassIndex()).setLevel(value);
}
else
{
super.setLevel(value);
}
}
@Override
public final int getMaxCp()
{
// Get the Max CP (base+modifier) of the L2PcInstance
int val = (getActiveChar() == null) ? 1 : (int) calcStat(Stats.MAX_CP, getActiveChar().getTemplate().getBaseCpMax(getActiveChar().getLevel()));
if (val != _oldMaxCp)
{
_oldMaxCp = val;
// Launch a regen task if the new Max CP is higher than the old one
if (getActiveChar().getStatus().getCurrentCp() != val)
{
getActiveChar().getStatus().setCurrentCp(getActiveChar().getStatus().getCurrentCp()); // trigger start of regeneration
}
}
return val;
}
@Override
public final int getMaxHp()
{
// Get the Max HP (base+modifier) of the L2PcInstance
int val = (getActiveChar() == null) ? 1 : (int) calcStat(Stats.MAX_HP, getActiveChar().getTemplate().getBaseHpMax(getActiveChar().getLevel()));
if (val != _oldMaxHp)
{
_oldMaxHp = val;
// Launch a regen task if the new Max HP is higher than the old one
if (getActiveChar().getStatus().getCurrentHp() != val)
{
getActiveChar().getStatus().setCurrentHp(getActiveChar().getStatus().getCurrentHp()); // trigger start of regeneration
}
}
return val;
}
@Override
public final int getMaxMp()
{
// Get the Max MP (base+modifier) of the L2PcInstance
int val = (getActiveChar() == null) ? 1 : (int) calcStat(Stats.MAX_MP, getActiveChar().getTemplate().getBaseMpMax(getActiveChar().getLevel()));
if (val != _oldMaxMp)
{
_oldMaxMp = val;
// Launch a regen task if the new Max MP is higher than the old one
if (getActiveChar().getStatus().getCurrentMp() != val)
{
getActiveChar().getStatus().setCurrentMp(getActiveChar().getStatus().getCurrentMp()); // trigger start of regeneration
}
}
return val;
}
@Override
public final long getSp()
{
if (getActiveChar().isSubClassActive())
{
return getActiveChar().getSubClasses().get(getActiveChar().getClassIndex()).getSp();
}
return super.getSp();
}
public final long getBaseSp()
{
return super.getSp();
}
@Override
public final void setSp(long value)
{
if (getActiveChar().isSubClassActive())
{
getActiveChar().getSubClasses().get(getActiveChar().getClassIndex()).setSp(value);
}
else
{
super.setSp(value);
}
}
/**
* @param type movement type
* @return the base move speed of given movement type.
*/
@Override
public double getBaseMoveSpeed(MoveType type)
{
final L2PcInstance player = getActiveChar();
if (player.isTransformed())
{
final TransformTemplate template = player.getTransformation().getTemplate(player);
if (template != null)
{
return template.getBaseMoveSpeed(type);
}
}
else if (player.isMounted())
{
final L2PetLevelData data = PetDataTable.getInstance().getPetLevelData(player.getMountNpcId(), player.getMountLevel());
if (data != null)
{
return data.getSpeedOnRide(type);
}
}
return super.getBaseMoveSpeed(type);
}
@Override
public double getRunSpeed()
{
double val = super.getRunSpeed() + Config.RUN_SPD_BOOST;
// Apply max run speed cap.
if ((val > Config.MAX_RUN_SPEED) && !getActiveChar().canOverrideCond(PcCondOverride.MAX_STATS_VALUE))
{
return Config.MAX_RUN_SPEED;
}
// Check for mount penalties
if (getActiveChar().isMounted())
{
// if level diff with mount >= 10, it decreases move speed by 50%
if ((getActiveChar().getMountLevel() - getActiveChar().getLevel()) >= 10)
{
val /= 2;
}
// if mount is hungry, it decreases move speed by 50%
if (getActiveChar().isHungry())
{
val /= 2;
}
}
return val;
}
@Override
public double getWalkSpeed()
{
double val = super.getWalkSpeed() + Config.RUN_SPD_BOOST;
// Apply max run speed cap.
if ((val > Config.MAX_RUN_SPEED) && !getActiveChar().canOverrideCond(PcCondOverride.MAX_STATS_VALUE))
{
return Config.MAX_RUN_SPEED;
}
if (getActiveChar().isMounted())
{
// if level diff with mount >= 10, it decreases move speed by 50%
if ((getActiveChar().getMountLevel() - getActiveChar().getLevel()) >= 10)
{
val /= 2;
}
// if mount is hungry, it decreases move speed by 50%
if (getActiveChar().isHungry())
{
val /= 2;
}
}
return val;
}
@Override
public int getPAtkSpd()
{
int val = super.getPAtkSpd();
if ((val > Config.MAX_PATK_SPEED) && !getActiveChar().canOverrideCond(PcCondOverride.MAX_STATS_VALUE))
{
return Config.MAX_PATK_SPEED;
}
return val;
}
/*
* Set current vitality points to this value if quiet = true - does not send system messages
*/
public void setVitalityPoints(int points, boolean quiet)
{
points = Math.min(Math.max(points, MIN_VITALITY_POINTS), MAX_VITALITY_POINTS);
if (points == getActiveChar().getVitalityPoints())
{
return;
}
if (points < getActiveChar().getVitalityPoints())
{
getActiveChar().sendPacket(SystemMessageId.YOUR_VITALITY_HAS_DECREASED);
}
else
{
getActiveChar().sendPacket(SystemMessageId.YOUR_VITALITY_HAS_INCREASED);
}
getActiveChar().setVitalityPoints(points);
if (points == 0)
{
getActiveChar().sendPacket(SystemMessageId.YOUR_VITALITY_IS_FULLY_EXHAUSTED);
}
else if (points == MAX_VITALITY_POINTS)
{
getActiveChar().sendPacket(SystemMessageId.YOUR_VITALITY_IS_AT_MAXIMUM);
}
final L2PcInstance player = getActiveChar();
player.sendPacket(new ExVitalityPointInfo(getActiveChar().getVitalityPoints()));
if (player.isInParty())
{
final PartySmallWindowUpdate partyWindow = new PartySmallWindowUpdate(player, false);
partyWindow.addUpdateType(PartySmallWindowUpdateType.VITALITY_POINTS);
player.getParty().broadcastToPartyMembers(player, partyWindow);
}
}
public synchronized void updateVitalityPoints(int points, boolean useRates, boolean quiet)
{
if ((points == 0) || !Config.ENABLE_VITALITY)
{
return;
}
if (useRates)
{
if (getActiveChar().isLucky())
{
return;
}
if (points < 0) // vitality consumed
{
int stat = (int) calcStat(Stats.VITALITY_CONSUME_RATE, 1, getActiveChar(), null);
if (stat == 0)
{
return;
}
if (stat < 0)
{
points = -points;
}
}
if (points > 0)
{
// vitality increased
points *= Config.RATE_VITALITY_GAIN;
}
else
{
// vitality decreased
points *= Config.RATE_VITALITY_LOST;
}
}
if (points > 0)
{
points = Math.min(getActiveChar().getVitalityPoints() + points, MAX_VITALITY_POINTS);
}
else
{
points = Math.max(getActiveChar().getVitalityPoints() + points, MIN_VITALITY_POINTS);
}
if (Math.abs(points - getActiveChar().getVitalityPoints()) <= 1e-6)
{
return;
}
getActiveChar().setVitalityPoints(points);
}
public double getVitalityMultiplier()
{
return Config.ENABLE_VITALITY ? Config.RATE_VITALITY_EXP_MULTIPLIER : 1;
}
public double getExpBonusMultiplier()
{
double bonus = 1.0;
double vitality = 1.0;
double bonusExp = 1.0;
// Bonus from Vitality System
vitality = getVitalityMultiplier();
// Bonus exp from skills
bonusExp = 1 + (calcStat(Stats.BONUS_EXP, 0, null, null) / 100);
if (vitality > 1.0)
{
bonus += (vitality - 1);
}
if (bonusExp > 1)
{
bonus += (bonusExp - 1);
}
// Check for abnormal bonuses
bonus = Math.max(bonus, 1);
bonus = Math.min(bonus, Config.MAX_BONUS_EXP);
return bonus;
}
public double getSpBonusMultiplier()
{
double bonus = 1.0;
double vitality = 1.0;
double bonusSp = 1.0;
// Bonus from Vitality System
vitality = getVitalityMultiplier();
// Bonus sp from skills
bonusSp = 1 + (calcStat(Stats.BONUS_SP, 0, null, null) / 100);
if (vitality > 1.0)
{
bonus += (vitality - 1);
}
if (bonusSp > 1)
{
bonus += (bonusSp - 1);
}
// Check for abnormal bonuses
bonus = Math.max(bonus, 1);
bonus = Math.min(bonus, Config.MAX_BONUS_SP);
return bonus;
}
/**
* Gets the maximum brooch jewel count.
* @return the maximum brooch jewel count
*/
public int getBroochJewelSlots()
{
return (int) calcStat(Stats.BROOCH_JEWELS, 0);
}
}