Răsfoiți Sursa

Vitality system. REQUIRED DP UPDATE !
Really big thanks to all peoples who worked and helps with it !
First vitality system was created by cool_santino, many things taken from l2jfree, and tnx to yuri and Forsaiken.

_DS_ 15 ani în urmă
părinte
comite
de831c8ab7
19 a modificat fișierele cu 473 adăugiri și 51 ștergeri
  1. 15 0
      L2_GameServer/java/config/Character.properties
  2. 6 0
      L2_GameServer/java/config/NPC.properties
  3. 8 0
      L2_GameServer/java/config/l2jmods.properties
  4. 35 1
      L2_GameServer/java/config/rates.properties
  5. 31 0
      L2_GameServer/java/net/sf/l2j/Config.java
  6. 21 12
      L2_GameServer/java/net/sf/l2j/gameserver/model/L2Party.java
  7. 49 2
      L2_GameServer/java/net/sf/l2j/gameserver/model/actor/L2Attackable.java
  8. 12 0
      L2_GameServer/java/net/sf/l2j/gameserver/model/actor/instance/L2GrandBossInstance.java
  9. 6 0
      L2_GameServer/java/net/sf/l2j/gameserver/model/actor/instance/L2MinionInstance.java
  10. 80 30
      L2_GameServer/java/net/sf/l2j/gameserver/model/actor/instance/L2PcInstance.java
  11. 12 0
      L2_GameServer/java/net/sf/l2j/gameserver/model/actor/instance/L2RaidBossInstance.java
  12. 143 1
      L2_GameServer/java/net/sf/l2j/gameserver/model/actor/stat/PcStat.java
  13. 30 0
      L2_GameServer/java/net/sf/l2j/gameserver/network/SystemMessageId.java
  14. 4 1
      L2_GameServer/java/net/sf/l2j/gameserver/network/clientpackets/CharacterCreate.java
  15. 7 0
      L2_GameServer/java/net/sf/l2j/gameserver/network/clientpackets/EnterWorld.java
  16. 1 1
      L2_GameServer/java/net/sf/l2j/gameserver/network/serverpackets/UserInfo.java
  17. 4 1
      L2_GameServer/java/net/sf/l2j/gameserver/skills/Stats.java
  18. 5 1
      L2_GameServer/java/net/sf/l2j/gameserver/templates/chars/L2NpcTemplate.java
  19. 4 1
      L2_GameServer/java/net/sf/l2j/gameserver/templates/skills/L2SkillType.java

+ 15 - 0
L2_GameServer/java/config/Character.properties

@@ -170,6 +170,21 @@ AltGameSkillLearn = False
 # Default: False
 AltSubClassWithoutQuests = False
 
+# ---------------------------------------------------------------------------
+# Vitality configuration
+# ---------------------------------------------------------------------------
+# Enables vitality system
+# Default: True
+EnableVitality = True
+
+# Do you want players to recover their vitality when they reconnect?
+# This is calculated with the time they've been offline
+# Actual Time - Last Time Online / 1000 x rate recovery on reconnect
+# Notes:
+#	Works only if EnableVitality = True
+# Default: True
+RecoverVitalityOnReconnect = True
+
 
 # ---------------------------------------------------------------------------
 # Limits

+ 6 - 0
L2_GameServer/java/config/NPC.properties

@@ -46,6 +46,12 @@ UseDeepBlueDropRulesRaid = True
 # Default: False 
 ShowNpcLevel = False
 
+# Do you want mobs to drop Vitality resplenishing herbs?
+# Notes:
+#	Works only if EnableVitality = True
+# Default: True
+EnableVitalityHerbs = True
+
 
 # ---------------------------------------------------------------------------
 # Guards

+ 8 - 0
L2_GameServer/java/config/l2jmods.properties

@@ -54,6 +54,14 @@ ChampionRewardLowerLvlItemChance = 0
 # % Chance to obtain a specified reward item from a LOWER lvl Champion mob.
 ChampionRewardHigherLvlItemChance = 0
 
+# Do you want to enable the vitality calculation when killing champion mobs?
+# Be aware that it can lead to huge unbalance on your server, your rate for that mob would
+# then be "mobXP x serverRate x vitalityRate x championXpRate
+# Notes:
+#	Works only if EnableVitality = True
+# Default: False
+ChampionEnableVitality = False
+
 
 # ---------------------------------------------------------------------------
 # Wedding System (by evill33t)

+ 35 - 1
L2_GameServer/java/config/rates.properties

@@ -30,6 +30,34 @@ RateSiegeGuardsPrice = 1
 # for this setting to work properly.
 RateDropQuest = 1
 
+# ---------------------------------------------------------------------------
+# Vitality system rates. Works only if EnableVitality = True
+# ---------------------------------------------------------------------------
+# The following configures the xp multiplicator of each vitality level. Basically, you have
+# 5 levels, the first one being 0. Official rates are:
+# Level 1: 150%
+# Level 2: 200%
+# Level 3: 250%
+# Level 4: 300%
+# Take care setting these values according to your server rates, as the can lead to huge differences!
+# Example with a server rate 15x and a level 4 vitality = 3. => final server rate = 45 (15x3)!
+RateVitalityLevel1 = 1.5
+RateVitalityLevel2 = 2.
+RateVitalityLevel3 = 2.5
+RateVitalityLevel4 = 3.
+
+# These options are to be used if you want to increase the vitality gain/lost for each mob you kills
+# Default values are 1.
+RateVitalityGain = 1.
+RateVitalityLost = 1.
+
+# This defines how many times faster do the players regain their vitality when in peace zones
+RateRecoveryPeaceZone = 1.
+
+# This defines how many times faster do the players regain their vitality when offline
+# Note that you need to turn on "RecoverVitalityOnReconnect" to have this option effective
+RateRecoveryOnReconnect = 4.
+
 
 # ---------------------------------------------------------------------------
 # Player Drops (values are set in PERCENTS)
@@ -102,4 +130,10 @@ RateSuperiorHerbs = 0.8
 
 # Herb of Warrior, Herb of Mystic, Herb of Recovery
 # Default: 0.2
-RateSpecialHerbs = 0.2
+RateSpecialHerbs = 0.2
+
+# Rate to configure the chance in percent vitality resplenishing herbs are dropping.
+# Note you'll need to enable EnableVitalityHerbs
+# Notes:
+#	Works only if EnableVitality = True
+RateVitalityHerbs = 2.

+ 31 - 0
L2_GameServer/java/net/sf/l2j/Config.java

@@ -545,6 +545,7 @@ public final class Config
 	public static int L2JMOD_CHAMPION_REWARD_HIGHER_LVL_ITEM_CHANCE;
 	public static int L2JMOD_CHAMPION_REWARD_ID;
 	public static int L2JMOD_CHAMPION_REWARD_QTY;
+	public static boolean	L2JMOD_CHAMPION_ENABLE_VITALITY;
 	public static boolean TVT_EVENT_ENABLED;
 	public static String[] TVT_EVENT_INTERVAL;
 	public static int TVT_EVENT_PARTICIPATION_TIME;
@@ -736,6 +737,23 @@ public final class Config
 	public static int MMO_IO_SELECTOR_THREAD_COUNT;
 
 
+	//--------------------------------------------------
+	// Vitality Settings
+	//--------------------------------------------------
+	public static boolean ENABLE_VITALITY;
+	public static boolean RECOVER_VITALITY_ON_RECONNECT;
+	public static boolean ENABLE_DROP_VITALITY_HERBS;
+	public static float RATE_VITALITY_LEVEL_1;
+	public static float RATE_VITALITY_LEVEL_2;
+	public static float RATE_VITALITY_LEVEL_3;
+	public static float RATE_VITALITY_LEVEL_4;
+	public static float RATE_DROP_VITALITY_HERBS;
+	public static float RATE_RECOVERY_VITALITY_PEACE_ZONE;
+	public static float RATE_VITALITY_LOST;
+	public static float RATE_VITALITY_GAIN;
+	public static float RATE_RECOVERY_ON_RECONNECT;
+
+
 	//--------------------------------------------------
 	// No classification assigned to the following yet
 	//--------------------------------------------------
@@ -1176,6 +1194,8 @@ public final class Config
 					DIVINE_SP_BOOK_NEEDED = Boolean.parseBoolean(Character.getProperty("DivineInspirationSpBookNeeded", "true"));
 					ALT_GAME_SKILL_LEARN = Boolean.parseBoolean(Character.getProperty("AltGameSkillLearn", "false"));
 					ALT_GAME_SUBCLASS_WITHOUT_QUESTS = Boolean.parseBoolean(Character.getProperty("AltSubClassWithoutQuests", "False"));
+					ENABLE_VITALITY = Boolean.parseBoolean(Character.getProperty("EnableVitality", "True"));
+					RECOVER_VITALITY_ON_RECONNECT = Boolean.parseBoolean(Character.getProperty("RecoverVitalityOnReconnect", "True"));
 					MAX_RUN_SPEED = Integer.parseInt(Character.getProperty("MaxRunSpeed", "250"));
 					MAX_PCRIT_RATE = Integer.parseInt(Character.getProperty("MaxPCritRate", "500"));
 					MAX_MCRIT_RATE = Integer.parseInt(Character.getProperty("MaxMCritRate", "200"));
@@ -1598,6 +1618,7 @@ public final class Config
 					DEEPBLUE_DROP_RULES = Boolean.parseBoolean(NPC.getProperty("UseDeepBlueDropRules", "True"));
 					DEEPBLUE_DROP_RULES_RAID = Boolean.parseBoolean(NPC.getProperty("UseDeepBlueDropRulesRaid", "True"));
 					SHOW_NPC_LVL = Boolean.parseBoolean(NPC.getProperty("ShowNpcLevel", "False"));
+					ENABLE_DROP_VITALITY_HERBS = Boolean.parseBoolean(NPC.getProperty("EnableVitalityHerbs", "True"));
 					GUARD_ATTACK_AGGRO_MOB = Boolean.parseBoolean(NPC.getProperty("GuardAttackAggroMob", "False"));
 					ALLOW_WYVERN_UPGRADER = Boolean.parseBoolean(NPC.getProperty("AllowWyvernUpgrader", "False"));
 					PET_RENT_NPC = NPC.getProperty("ListPetRentNpc", "30827");
@@ -1642,6 +1663,14 @@ public final class Config
 					RATE_DROP_SPOIL = Float.parseFloat(ratesSettings.getProperty("RateDropSpoil", "1."));
 					RATE_DROP_MANOR = Integer.parseInt(ratesSettings.getProperty("RateDropManor", "1"));
 					RATE_DROP_QUEST = Float.parseFloat(ratesSettings.getProperty("RateDropQuest", "1."));
+					RATE_VITALITY_LEVEL_1 = Float.parseFloat(ratesSettings.getProperty("RateVitalityLevel1", "1.5"));
+					RATE_VITALITY_LEVEL_2 = Float.parseFloat(ratesSettings.getProperty("RateVitalityLevel2", "2."));
+					RATE_VITALITY_LEVEL_3 = Float.parseFloat(ratesSettings.getProperty("RateVitalityLevel3", "2.5"));
+					RATE_VITALITY_LEVEL_4 = Float.parseFloat(ratesSettings.getProperty("RateVitalityLevel4", "3."));
+					RATE_RECOVERY_VITALITY_PEACE_ZONE = Float.parseFloat(ratesSettings.getProperty("RateRecoveryPeaceZone", "1."));
+					RATE_VITALITY_LOST = Float.parseFloat(ratesSettings.getProperty("RateVitalityLost", "1."));
+					RATE_VITALITY_GAIN = Float.parseFloat(ratesSettings.getProperty("RateVitalityGain", "1."));
+					RATE_RECOVERY_ON_RECONNECT = Float.parseFloat(ratesSettings.getProperty("RateRecoveryOnReconnect", "4."));
 					RATE_KARMA_EXP_LOST = Float.parseFloat(ratesSettings.getProperty("RateKarmaExpLost", "1."));
 					RATE_SIEGE_GUARDS_PRICE = Float.parseFloat(ratesSettings.getProperty("RateSiegeGuardsPrice", "1."));
 					RATE_DROP_COMMON_HERBS = Float.parseFloat(ratesSettings.getProperty("RateCommonHerbs", "15."));
@@ -1649,6 +1678,7 @@ public final class Config
 					RATE_DROP_GREATER_HERBS = Float.parseFloat(ratesSettings.getProperty("RateGreaterHerbs", "4."));
 					RATE_DROP_SUPERIOR_HERBS = Float.parseFloat(ratesSettings.getProperty("RateSuperiorHerbs", "0.8"))*10;
 					RATE_DROP_SPECIAL_HERBS = Float.parseFloat(ratesSettings.getProperty("RateSpecialHerbs", "0.2"))*10;
+					RATE_DROP_VITALITY_HERBS = Float.parseFloat(ratesSettings.getProperty("RateVitalityHerbs", "2."));
 					PLAYER_DROP_LIMIT = Integer.parseInt(ratesSettings.getProperty("PlayerDropLimit", "3"));
 					PLAYER_RATE_DROP = Integer.parseInt(ratesSettings.getProperty("PlayerRateDrop", "5"));
 					PLAYER_RATE_DROP_ITEM = Integer.parseInt(ratesSettings.getProperty("PlayerRateDropItem", "70"));
@@ -1727,6 +1757,7 @@ public final class Config
 					L2JMOD_CHAMPION_REWARD_HIGHER_LVL_ITEM_CHANCE = Integer.parseInt(L2JModSettings.getProperty("ChampionRewardHigherLvlItemChance", "0"));
 					L2JMOD_CHAMPION_REWARD_ID = Integer.parseInt(L2JModSettings.getProperty("ChampionRewardItemID", "6393"));
 					L2JMOD_CHAMPION_REWARD_QTY = Integer.parseInt(L2JModSettings.getProperty("ChampionRewardItemQty", "1"));
+					L2JMOD_CHAMPION_ENABLE_VITALITY = Boolean.parseBoolean(L2JModSettings.getProperty("ChampionEnableVitality", "False"));
 
 					TVT_EVENT_ENABLED = Boolean.parseBoolean(L2JModSettings.getProperty("TvTEventEnabled", "false"));
 					TVT_EVENT_INTERVAL = L2JModSettings.getProperty("TvTEventInterval", "20:00").split(",");

+ 21 - 12
L2_GameServer/java/net/sf/l2j/gameserver/model/L2Party.java

@@ -24,7 +24,6 @@ import net.sf.l2j.gameserver.datatables.SkillTable;
 import net.sf.l2j.gameserver.instancemanager.DuelManager;
 import net.sf.l2j.gameserver.model.actor.L2Attackable;
 import net.sf.l2j.gameserver.model.actor.L2Character;
-import net.sf.l2j.gameserver.model.actor.L2Npc;
 import net.sf.l2j.gameserver.model.actor.L2Playable;
 import net.sf.l2j.gameserver.model.actor.L2Summon;
 import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
@@ -591,7 +590,7 @@ public class L2Party {
 	 * @param rewardedMembers The list of L2PcInstance to reward
 	 *
 	 */
-	public void distributeXpAndSp(long xpReward, int spReward, List<L2Playable> rewardedMembers, int topLvl, L2Npc target)
+	public void distributeXpAndSp(long xpReward, int spReward, List<L2Playable> rewardedMembers, int topLvl, int partyDmg, L2Attackable target)
 	{
 		L2SummonInstance summon = null;
 		List<L2Playable> validMembers = getValidMembers(rewardedMembers, topLvl);
@@ -607,7 +606,10 @@ public class L2Party {
 		for (L2Playable character : validMembers)
 			sqLevelSum += (character.getLevel() * character.getLevel());
 
-		// Go through the L2PcInstances and L2PetInstances (not L2SummonInstances) that must be rewarded
+        final float vitalityPoints = target.getVitalityPoints(partyDmg) * Config.RATE_PARTY_XP / validMembers.size();
+        final boolean useVitalityRate = target.useVitalityRate();
+
+        // Go through the L2PcInstances and L2PetInstances (not L2SummonInstances) that must be rewarded
 		synchronized(rewardedMembers)
 		{
 			for (L2Character member : rewardedMembers)
@@ -643,17 +645,24 @@ public class L2Party {
                     {
                         long addexp = Math.round(member.calcStat(Stats.EXPSP_RATE, xpReward * preCalculation, null, null));
                         int addsp = (int)member.calcStat(Stats.EXPSP_RATE, spReward * preCalculation, null, null);
-                        if (target != null && member instanceof L2PcInstance)
+                        if (member instanceof L2PcInstance)
                         {
-                            if (((L2PcInstance)member).getSkillLevel(467) > 0)
-                            {
-                                L2Skill skill = SkillTable.getInstance().getInfo(467,((L2PcInstance)member).getSkillLevel(467));
-                                
-                                if (skill.getExpNeeded() <= addexp)
-                                    ((L2PcInstance)member).absorbSoul(skill,target);
-                            }
+                        	if (target != null)
+                        	{
+                                if (((L2PcInstance)member).getSkillLevel(467) > 0)
+                                {
+                                    L2Skill skill = SkillTable.getInstance().getInfo(467,((L2PcInstance)member).getSkillLevel(467));
+                                    
+                                    if (skill.getExpNeeded() <= addexp)
+                                        ((L2PcInstance)member).absorbSoul(skill,target);
+                                }
+                        	}
+                            ((L2PcInstance)member).addExpAndSp(addexp, addsp, useVitalityRate);
+                            if (addexp > 0)
+                                ((L2PcInstance)member).updateVitalityPoints(vitalityPoints, true, false);
                         }
-                        member.addExpAndSp(addexp,addsp);
+                        else
+                            member.addExpAndSp(addexp, addsp);
                     }
 				}
 				else

+ 49 - 2
L2_GameServer/java/net/sf/l2j/gameserver/model/actor/L2Attackable.java

@@ -635,8 +635,12 @@ public class L2Attackable extends L2Npc
 										if (skill.getExpNeeded() <= addexp)
 											((L2PcInstance)attacker).absorbSoul(skill,this);
 									}
+									((L2PcInstance)attacker).addExpAndSp(addexp,addsp, useVitalityRate());
+									if (addexp > 0)
+										((L2PcInstance)attacker).updateVitalityPoints(getVitalityPoints(damage), true, false);
 								}
-								attacker.addExpAndSp(addexp,addsp);
+								else
+									attacker.addExpAndSp(addexp,addsp);
 							}
 						}
 					}
@@ -759,7 +763,7 @@ public class L2Attackable extends L2Npc
 						}
 						// Distribute Experience and SP rewards to L2PcInstance Party members in the known area of the last attacker
 						if (partyDmg > 0)
-							attackerParty.distributeXpAndSp(exp, sp, rewardedMembers, partyLvl, this);
+							attackerParty.distributeXpAndSp(exp, sp, rewardedMembers, partyLvl, partyDmg, this);
 					}
 				}
 			}
@@ -1631,6 +1635,19 @@ public class L2Attackable extends L2Npc
 				else
 					dropItem(player, item);
 			}
+			// Vitality Herb
+			if (Config.ENABLE_VITALITY && Config.ENABLE_DROP_VITALITY_HERBS)
+			{
+				random = Rnd.get(100);
+				if (random < Config.RATE_DROP_VITALITY_HERBS)
+				{
+					RewardItem item = new RewardItem(13028, 1);
+					if (Config.AUTO_LOOT && Config.AUTO_LOOT_HERBS)
+						player.addItem("Loot", item.getItemId(), item.getCount(), this, true);
+					else
+						dropItem(player, item);
+				}
+			}
 		}
 	}
 
@@ -2464,4 +2481,34 @@ public class L2Attackable extends L2Npc
 		if (hasAI() && getSpawn() != null)
 			getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(getSpawn().getLocx(), getSpawn().getLocy(), getSpawn().getLocz(), 0));
 	}
+
+	/*
+	 * Return vitality points decrease (if positive)
+	 * or increase (if negative) based on damage.
+	 * Maximum for damage = maxHp.
+	 */
+	public float getVitalityPoints(int damage)
+	{
+		// sanity check
+		if (damage <= 0)
+			return 0;
+
+		final float divider = getTemplate().baseVitalityDivider;
+		if (divider == 0)
+			return 0;
+
+		// negative value - vitality will be consumed
+		return - Math.min(damage, getMaxHp()) / divider;
+	}
+
+	/*
+	 * True if vitality rate for exp and sp should be applied
+	 */
+	public boolean useVitalityRate()
+	{
+		if (isChampion() && !Config.L2JMOD_CHAMPION_ENABLE_VITALITY)
+			return false;
+
+		return true;
+	}
 }

+ 12 - 0
L2_GameServer/java/net/sf/l2j/gameserver/model/actor/instance/L2GrandBossInstance.java

@@ -96,4 +96,16 @@ public final class L2GrandBossInstance extends L2MonsterInstance
 		}
 		return true;
 	}
+
+    @Override
+    public float getVitalityPoints(int damage)
+    {
+    	return - super.getVitalityPoints(damage) / 100;
+    }
+
+    @Override
+    public boolean useVitalityRate()
+    {
+    	return false;
+    }
 }

+ 6 - 0
L2_GameServer/java/net/sf/l2j/gameserver/model/actor/instance/L2MinionInstance.java

@@ -97,4 +97,10 @@ public class L2MinionInstance extends L2MonsterInstance
 		_master.notifyMinionDied(this);
 		return true;
 	}
+
+	@Override
+	public float getVitalityPoints(int damage)
+	{
+		return 0;
+	}
 }

+ 80 - 30
L2_GameServer/java/net/sf/l2j/gameserver/model/actor/instance/L2PcInstance.java

@@ -165,6 +165,7 @@ import net.sf.l2j.gameserver.network.serverpackets.ExPrivateStoreSetWholeMsg;
 import net.sf.l2j.gameserver.network.serverpackets.ExSetCompassZoneCode;
 import net.sf.l2j.gameserver.network.serverpackets.ExSpawnEmitter;
 import net.sf.l2j.gameserver.network.serverpackets.ExStorageMaxCount;
+import net.sf.l2j.gameserver.network.serverpackets.ExVitalityPointInfo;
 import net.sf.l2j.gameserver.network.serverpackets.GMHide;
 import net.sf.l2j.gameserver.network.serverpackets.GameGuardQuery;
 import net.sf.l2j.gameserver.network.serverpackets.GetOnVehicle;
@@ -250,8 +251,8 @@ public final class L2PcInstance extends L2Playable
 
 	// Character Character SQL String Definitions:
     private static final String INSERT_CHARACTER = "INSERT INTO characters (account_name,charId,char_name,level,maxHp,curHp,maxCp,curCp,maxMp,curMp,face,hairStyle,hairColor,sex,exp,sp,karma,fame,pvpkills,pkkills,clanid,race,classid,deletetime,cancraft,title,accesslevel,online,isin7sdungeon,clan_privs,wantspeace,base_class,newbie,nobless,power_grade,last_recom_date) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
-	private static final String UPDATE_CHARACTER = "UPDATE characters SET level=?,maxHp=?,curHp=?,maxCp=?,curCp=?,maxMp=?,curMp=?,face=?,hairStyle=?,hairColor=?,sex=?,heading=?,x=?,y=?,z=?,exp=?,expBeforeDeath=?,sp=?,karma=?,fame=?,pvpkills=?,pkkills=?,rec_have=?,rec_left=?,clanid=?,race=?,classid=?,deletetime=?,title=?,accesslevel=?,online=?,isin7sdungeon=?,clan_privs=?,wantspeace=?,base_class=?,onlinetime=?,punish_level=?,punish_timer=?,newbie=?,nobless=?,power_grade=?,subpledge=?,last_recom_date=?,lvl_joined_academy=?,apprentice=?,sponsor=?,varka_ketra_ally=?,clan_join_expiry_time=?,clan_create_expiry_time=?,char_name=?,death_penalty_level=?,bookmarkslot=? WHERE charId=?";
-    private static final String RESTORE_CHARACTER = "SELECT account_name, charId, char_name, level, maxHp, curHp, maxCp, curCp, maxMp, curMp, face, hairStyle, hairColor, sex, heading, x, y, z, exp, expBeforeDeath, sp, karma, fame, pvpkills, pkkills, clanid, race, classid, deletetime, cancraft, title, rec_have, rec_left, accesslevel, online, char_slot, lastAccess, clan_privs, wantspeace, base_class, onlinetime, isin7sdungeon, punish_level, punish_timer, newbie, nobless, power_grade, subpledge, last_recom_date, lvl_joined_academy, apprentice, sponsor, varka_ketra_ally,clan_join_expiry_time,clan_create_expiry_time,death_penalty_level,bookmarkslot FROM characters WHERE charId=?";
+	private static final String UPDATE_CHARACTER = "UPDATE characters SET level=?,maxHp=?,curHp=?,maxCp=?,curCp=?,maxMp=?,curMp=?,face=?,hairStyle=?,hairColor=?,sex=?,heading=?,x=?,y=?,z=?,exp=?,expBeforeDeath=?,sp=?,karma=?,fame=?,pvpkills=?,pkkills=?,rec_have=?,rec_left=?,clanid=?,race=?,classid=?,deletetime=?,title=?,accesslevel=?,online=?,isin7sdungeon=?,clan_privs=?,wantspeace=?,base_class=?,onlinetime=?,punish_level=?,punish_timer=?,newbie=?,nobless=?,power_grade=?,subpledge=?,last_recom_date=?,lvl_joined_academy=?,apprentice=?,sponsor=?,varka_ketra_ally=?,clan_join_expiry_time=?,clan_create_expiry_time=?,char_name=?,death_penalty_level=?,bookmarkslot=?,vitality_points=? WHERE charId=?";
+    private static final String RESTORE_CHARACTER = "SELECT account_name, charId, char_name, level, maxHp, curHp, maxCp, curCp, maxMp, curMp, face, hairStyle, hairColor, sex, heading, x, y, z, exp, expBeforeDeath, sp, karma, fame, pvpkills, pkkills, clanid, race, classid, deletetime, cancraft, title, rec_have, rec_left, accesslevel, online, char_slot, lastAccess, clan_privs, wantspeace, base_class, onlinetime, isin7sdungeon, punish_level, punish_timer, newbie, nobless, power_grade, subpledge, last_recom_date, lvl_joined_academy, apprentice, sponsor, varka_ketra_ally,clan_join_expiry_time,clan_create_expiry_time,death_penalty_level,bookmarkslot,vitality_points FROM characters WHERE charId=?";
 
     // Character Teleport Bookmark:
     private static final String INSERT_TP_BOOKMARK = "INSERT INTO character_tpbookmark (charId,Id,x,y,z,icon,tag,name) values (?,?,?,?,?,?,?,?)";
@@ -391,6 +392,9 @@ public final class L2PcInstance extends L2Playable
 	private int _fame;
 	private ScheduledFuture<?> _fameTask;
 
+	/** Vitality recovery task */
+	private ScheduledFuture<?> _vitalityTask;
+
 	/** The Siege state of the L2PcInstance */
 	private byte _siegeState = 0;
 
@@ -460,9 +464,6 @@ public final class L2PcInstance extends L2Playable
 
 	public boolean _exploring = false;
 	
-	/** Vitality Level of this L2PcInstance */
-	private int _vitalityLevel = 5;
-	
 	private boolean _inCrystallize;
 	private boolean _inCraftMode;
     
@@ -1083,6 +1084,7 @@ public final class L2PcInstance extends L2Playable
 		if (!Config.WAREHOUSE_CACHE)
 			getWarehouse();
 		getFreight();
+		startVitalityTask();
 	}
 
 	private L2PcInstance(int objectId)
@@ -5909,6 +5911,7 @@ public final class L2PcInstance extends L2Playable
 		stopSoulTask();
 		stopChargeTask();
 		stopFameTask();
+		stopVitalityTask();
 	}
 
 	/**
@@ -7142,7 +7145,9 @@ public final class L2PcInstance extends L2Playable
 
                 player.setDeathPenaltyBuffLevel(rset.getInt("death_penalty_level"));
 
-				// Add the L2PcInstance object in _allObjects
+                player.setVitalityPoints(rset.getInt("vitality_points"), true);
+
+                // Add the L2PcInstance object in _allObjects
 				//L2World.getInstance().storeObject(player);
 
 				// Set the x,y,z position of the L2PcInstance and make it invisible
@@ -7476,7 +7481,8 @@ public final class L2PcInstance extends L2Playable
 			statement.setString(50, getName());
 			statement.setLong(51, getDeathPenaltyBuffLevel());
 			statement.setInt(52, getBookMarkSlot());
-            statement.setInt(53, getObjectId());
+			statement.setInt(53, getVitalityPoints());
+            statement.setInt(54, getObjectId());
 
 			statement.execute();
 			statement.close();
@@ -10892,9 +10898,22 @@ public final class L2PcInstance extends L2Playable
 	}
 
 	@Override
-	public void addExpAndSp(long addToExp, int addToSp) { getStat().addExpAndSp(addToExp, addToSp); }
-    public void removeExpAndSp(long removeExp, int removeSp) { getStat().removeExpAndSp(removeExp, removeSp); }
-    @Override
+	public void addExpAndSp(long addToExp, int addToSp)
+	{
+		getStat().addExpAndSp(addToExp, addToSp, false);
+	}
+
+	public void addExpAndSp(long addToExp, int addToSp, boolean useVitality)
+	{
+		getStat().addExpAndSp(addToExp, addToSp, useVitality);
+	}
+
+	public void removeExpAndSp(long removeExp, int removeSp)
+	{
+		getStat().removeExpAndSp(removeExp, removeSp);
+	}
+
+	@Override
 	public void reduceCurrentHp(double i, L2Character attacker, L2Skill skill)
     {
     	getStatus().reduceHp(i, attacker);
@@ -12188,7 +12207,46 @@ public final class L2PcInstance extends L2Playable
         }
     }
 
-	/**
+    public void startVitalityTask()
+    {
+    	if (Config.ENABLE_VITALITY && _vitalityTask == null)
+    	{
+    		_vitalityTask = ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(new VitalityTask(this), 1000, 60000);
+    	}
+    }
+
+    public void stopVitalityTask()
+    {
+    	if (_vitalityTask != null)
+    	{
+    		_vitalityTask.cancel(false);
+    		_vitalityTask = null;
+    	}
+    }
+
+    private class VitalityTask implements Runnable
+    {
+    	private final L2PcInstance _player;
+
+    	protected VitalityTask(L2PcInstance player)
+    	{
+    		_player = player;
+    	}
+
+    	public void run()
+    	{
+    		if (!_player.isInsideZone(L2Character.ZONE_PEACE))
+    			return;
+
+    		if (_player.getVitalityPoints() >= PcStat.MAX_VITALITY_POINTS)
+    			return;
+
+    		_player.updateVitalityPoints(Config.RATE_RECOVERY_VITALITY_PEACE_ZONE, false, false);
+    		_player.sendPacket(new ExVitalityPointInfo(getVitalityPoints()));
+    	}
+    }
+
+    /**
 	 * @return
 	 */
 	public int getPowerGrade()
@@ -12693,28 +12751,20 @@ public final class L2PcInstance extends L2Playable
     {
 	    return _agathionId;
     }
-    
-    /**
-     * Returns the VL <BR><BR>
-     * @return
-     */
-    public int getVitalityLevel()
+
+    public int getVitalityPoints()
     {
-    	return _vitalityLevel;
+    	return getStat().getVitalityPoints();
     }
-    
-    /**
-     * Sets VL of this L2PcInstance<BR><BR>
-     * @param level
-     */
-    public void setVitalityLevel(int level)
+
+    public void setVitalityPoints(int points, boolean quiet)
     {
-    	if (level > 5)
-    		level = 5;
-    	else if (level < 0)
-    		level = 0;
-    	
-    	_vitalityLevel = level;
+    	getStat().setVitalityPoints(points, quiet);
+    }
+
+    public synchronized void updateVitalityPoints(float points, boolean useRates, boolean quiet)
+    {
+    	getStat().updateVitalityPoints(points, useRates, quiet);
     }
 
     /*

+ 12 - 0
L2_GameServer/java/net/sf/l2j/gameserver/model/actor/instance/L2RaidBossInstance.java

@@ -142,4 +142,16 @@ public class L2RaidBossInstance extends L2MonsterInstance
         super.setCurrentHp(super.getMaxHp());
         super.setCurrentMp(super.getMaxMp());
     }
+
+    @Override
+    public float getVitalityPoints(int damage)
+    {
+    	return - super.getVitalityPoints(damage) / 100;
+    }
+
+    @Override
+    public boolean useVitalityRate()
+    {
+    	return false;
+    }
 }

+ 143 - 1
L2_GameServer/java/net/sf/l2j/gameserver/model/actor/stat/PcStat.java

@@ -23,6 +23,7 @@ import net.sf.l2j.gameserver.model.base.Experience;
 import net.sf.l2j.gameserver.model.quest.QuestState;
 import net.sf.l2j.gameserver.network.SystemMessageId;
 import net.sf.l2j.gameserver.network.serverpackets.ExBrExtraUserInfo;
+import net.sf.l2j.gameserver.network.serverpackets.ExVitalityPointInfo;
 import net.sf.l2j.gameserver.network.serverpackets.PledgeShowMemberListUpdate;
 import net.sf.l2j.gameserver.network.serverpackets.SocialAction;
 import net.sf.l2j.gameserver.network.serverpackets.StatusUpdate;
@@ -40,6 +41,12 @@ public class PcStat extends PlayableStat
     private int _oldMaxHp;      // stats watch
     private int _oldMaxMp;      // stats watch
     private int _oldMaxCp;		// stats watch
+    private float _vitalityPoints = 1;
+    private byte _vitalityLevel = 0;
+
+    public static final int VITALITY_LEVELS[] = {240, 1800, 14600, 18200, 20000};
+    public static final int MAX_VITALITY_POINTS = VITALITY_LEVELS[4];
+    public static final int MIN_VITALITY_POINTS = 1;
 
     // =========================================================
     // Constructor
@@ -135,7 +142,34 @@ public class PcStat extends PlayableStat
         return true;
     }
 
-    @Override
+	public boolean addExpAndSp(long addToExp, int addToSp, boolean useVitality)
+	{
+		if (useVitality)
+		{
+			switch (_vitalityLevel)
+			{
+				case 1:
+					addToExp *= Config.RATE_VITALITY_LEVEL_1;
+					addToSp *= Config.RATE_VITALITY_LEVEL_1;
+					break;
+				case 2:
+					addToExp *= Config.RATE_VITALITY_LEVEL_2;
+					addToSp *= Config.RATE_VITALITY_LEVEL_2;
+					break;
+				case 3:
+					addToExp *= Config.RATE_VITALITY_LEVEL_3;
+					addToSp *= Config.RATE_VITALITY_LEVEL_3;
+					break;
+				case 4:
+					addToExp *= Config.RATE_VITALITY_LEVEL_4;
+					addToSp *= Config.RATE_VITALITY_LEVEL_4;
+					break;
+			}
+		}
+		return addExpAndSp(addToExp, addToSp);
+	}
+
+	@Override
 	public boolean removeExpAndSp(long addToExp, int addToSp)
     {
         if (!super.removeExpAndSp(addToExp, addToSp)) return false;
@@ -403,4 +437,112 @@ public class PcStat extends PlayableStat
 
 		return (getRunSpeed() * 70) / 100;			
 	}
+
+    private void updateVitalityLevel(boolean quiet)
+    {
+    	final byte level;
+
+    	if (_vitalityPoints <= VITALITY_LEVELS[0])
+    		level = 0;
+    	else if (_vitalityPoints <= VITALITY_LEVELS[1])
+    		level = 1;
+    	else if (_vitalityPoints <= VITALITY_LEVELS[2])
+    		level = 2;
+    	else if (_vitalityPoints <= VITALITY_LEVELS[3])
+    		level = 3;
+    	else 
+    		level = 4;
+
+    	if (!quiet && level != _vitalityLevel)
+    	{
+    		if (level < _vitalityLevel)
+    			getActiveChar().sendPacket(new SystemMessage(SystemMessageId.VITALITY_HAS_DECREASED));
+    		else
+    			getActiveChar().sendPacket(new SystemMessage(SystemMessageId.VITALITY_HAS_INCREASED));
+    		if (level == 0)
+    			getActiveChar().sendPacket(new SystemMessage(SystemMessageId.VITALITY_IS_EXHAUSTED));
+    		else if (level == 4)
+    			getActiveChar().sendPacket(new SystemMessage(SystemMessageId.VITALITY_IS_AT_MAXIMUM));
+    	}
+
+    	_vitalityLevel = level;
+    }
+
+    /*
+     * Return current vitality points in integer format
+     */
+    public int getVitalityPoints()
+    {
+    	return (int)_vitalityPoints;
+    }
+
+    /*
+     * 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 == _vitalityPoints)
+    		return;
+
+    	_vitalityPoints = points;
+    	updateVitalityLevel(quiet);
+		getActiveChar().sendPacket(new ExVitalityPointInfo(getVitalityPoints()));
+    }
+
+    public void updateVitalityPoints(float points, boolean useRates, boolean quiet)
+    {
+    	if (points == 0)
+    		return;
+
+    	if (useRates)
+    	{
+    		byte level = getLevel();
+    		if (level < 10)
+    			return;
+
+    		if (points < 0) // vitality consumed
+    		{
+    			int stat = (int)calcStat(Stats.VITALITY_CONSUME_RATE, 1, getActiveChar(), null);
+
+    			if (stat == 0) // is vitality consumption stopped ?
+    				return;
+    			if (stat < 0) // is vitality gained ?
+    				points = -points;
+    		}
+
+    		if (level >= 79)
+        		points *= 2;
+        	else if (level >= 76)
+        		points += points / 2;
+
+        	if (points > 0)
+        	{
+        		// vitality increased
+        		points *= Config.RATE_VITALITY_GAIN;
+        	}
+        	else
+        	{
+        		// vitality decreased
+        		points *= Config.RATE_VITALITY_LOST;
+        	}
+    	}
+
+    	if (points > 0)
+    	{
+        	points = Math.min(_vitalityPoints + points, MAX_VITALITY_POINTS);
+    	}
+    	else
+    	{
+        	points = Math.max(_vitalityPoints + points, MIN_VITALITY_POINTS);
+    	}
+
+    	if (points == _vitalityPoints)
+    		return;
+
+    	_vitalityPoints = points;
+    	updateVitalityLevel(quiet);
+    }
 }

+ 30 - 0
L2_GameServer/java/net/sf/l2j/gameserver/network/SystemMessageId.java

@@ -13677,6 +13677,12 @@ public enum SystemMessageId
 	*/
 	LOC_STEEL_CITADEL_S1_S2_S3(2293),
 	
+	/**
+	 * ID: 2296<br>
+	 * Message: You have gained Vitality points.
+	 */
+	GAINED_VITALITY_POINTS(2296),
+	
 	/**
 	* ID: 2301<br>
 	* Message: Current location: Steel Citadel Resistance
@@ -13707,6 +13713,30 @@ public enum SystemMessageId
 	 * Would you like to resurrect now?
 	 */
 	RESURRECT_USING_CHARM_OF_COURAGE(2306),
+	
+	/**
+	 * ID: 2314<br>
+	 * Message: Your Vitality is at maximum.
+	 */
+	VITALITY_IS_AT_MAXIMUM(2314),
+	
+	/**
+	 * ID: 2315<br>
+	 * Message: You have gained Vitality points.
+	 */
+	VITALITY_HAS_INCREASED(2315),
+	
+	/**
+	 * ID: 2316<br>
+	 * Message: You have lost Vitality points.
+	 */
+	VITALITY_HAS_DECREASED(2316),
+	
+	/**
+	 * ID: 2317<br>
+	 * Message: Your Vitality is fully exhausted.
+	 */
+	VITALITY_IS_EXHAUSTED(2317),
 
 	/**
 	* ID: 2319<br>

+ 4 - 1
L2_GameServer/java/net/sf/l2j/gameserver/network/clientpackets/CharacterCreate.java

@@ -32,6 +32,7 @@ import net.sf.l2j.gameserver.model.L2ShortCut;
 import net.sf.l2j.gameserver.model.L2SkillLearn;
 import net.sf.l2j.gameserver.model.L2World;
 import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
+import net.sf.l2j.gameserver.model.actor.stat.PcStat;
 import net.sf.l2j.gameserver.model.quest.Quest;
 import net.sf.l2j.gameserver.model.quest.QuestState;
 import net.sf.l2j.gameserver.network.L2GameClient;
@@ -193,7 +194,9 @@ public final class CharacterCreate extends L2GameClientPacket
 		
 		newChar.setXYZInvisible(template.spawnX, template.spawnY, template.spawnZ);
 		newChar.setTitle("");
-		
+
+		newChar.setVitalityPoints(PcStat.MAX_VITALITY_POINTS, true);
+
 		L2ShortCut shortcut;
 		// add attack shortcut
 		shortcut = new L2ShortCut(0, 0, 3, 2, 0, 1);

+ 7 - 0
L2_GameServer/java/net/sf/l2j/gameserver/network/clientpackets/EnterWorld.java

@@ -269,6 +269,13 @@ public class EnterWorld extends L2GameClientPacket
 			activeChar.removeSkill(SkillTable.getInstance().getInfo(5075,1));
 		}
 
+		if (Config.ENABLE_VITALITY && Config.RECOVER_VITALITY_ON_RECONNECT)
+		{
+			float points = Config.RATE_RECOVERY_ON_RECONNECT * (System.currentTimeMillis() - activeChar.getLastAccess()) / 60000;
+			if (points > 0)
+				activeChar.updateVitalityPoints(points, false, true);
+		}
+
 		activeChar.sendPacket(new UserInfo(activeChar));
 
 		sendPacket(new ExBrExtraUserInfo(activeChar));

+ 1 - 1
L2_GameServer/java/net/sf/l2j/gameserver/network/serverpackets/UserInfo.java

@@ -345,7 +345,7 @@ public final class UserInfo extends L2GameServerPacket
         // T2 Starts
         writeD(_activeChar.getFame());  // Fame
         writeD(0x01); // Unknown
-        writeD(0x00);  // Vitality Points
+        writeD(_activeChar.getVitalityPoints());  // Vitality Points
         writeD(_activeChar.getSpecialEffect());
         writeD(0x00); // CT2.3
         writeD(0x00); // CT2.3

+ 4 - 1
L2_GameServer/java/net/sf/l2j/gameserver/skills/Stats.java

@@ -222,7 +222,10 @@ public enum Stats
 	SHIELD_DEFENCE_ANGLE("shieldDefAngle"),
 	
 	//Skill mastery
-	SKILL_MASTERY			("skillMastery");
+	SKILL_MASTERY			("skillMastery"),
+
+	// vitality
+	VITALITY_CONSUME_RATE("vitalityConsumeRate");
 	
 	public static final int NUM_STATS = values().length;
 	

+ 5 - 1
L2_GameServer/java/net/sf/l2j/gameserver/templates/chars/L2NpcTemplate.java

@@ -77,7 +77,8 @@ public final class L2NpcTemplate extends L2CharTemplate
 	public final AIType AI;
 	public final boolean dropherb;
 	public boolean isQuestMonster; // doesn't include all mobs that are involved in 
-	// quests, just plain quest monsters for preventing champion spawn 
+	// quests, just plain quest monsters for preventing champion spawn
+	public final float baseVitalityDivider;
 	
 	public static enum AbsorbCrystalType
 	{
@@ -203,6 +204,9 @@ public final class L2NpcTemplate extends L2CharTemplate
 		baseEarthRes += 20;
 		baseHolyRes += 20;
 		baseDarkRes += 20;
+
+		// can be loaded from db
+		baseVitalityDivider = level > 0 && rewardExp > 0 ? baseHpMax * 9 * level * level /(100 * rewardExp) : 0;
 	}
 	
 	public void addTeachInfo(ClassId classId)

+ 4 - 1
L2_GameServer/java/net/sf/l2j/gameserver/templates/skills/L2SkillType.java

@@ -84,7 +84,10 @@ public enum L2SkillType
 	
 	// sp
 	GIVE_SP,
-	
+
+	// vitality
+	GIVE_VITALITY,
+
 	// Aggro
 	AGGDAMAGE,
 	AGGREDUCE,