CCCPaste

New VIP Manager

diff --git a/game/data/xml/Custom/VipManager.xml b/game/data/xml/Custom/VipManager.xml
new file mode 100644
index 0000000..7d7e373
--- /dev/null
+++ b/game/data/xml/Custom/VipManager.xml
@@ -0,0 +1,88 @@
+
+
+
+  
+  
+    
+    
+    
+
+    
+    
+    
+    
+  
+
+  
+  
+    
+    
+
+    
+    
+    
+  
+
+  
+  
+    
+    
+
+    
+    
+  
+
+  
+  
+    
+    
+    
+
+    
+    
+    
+    
+  
+
+  
+  
+  
+  
+  
+
+
diff --git a/game/data/xml/items/custom/VipItens.xml b/game/data/xml/items/custom/VipItens.xml
new file mode 100644
index 0000000..071021b
--- /dev/null
+++ b/game/data/xml/items/custom/VipItens.xml
@@ -0,0 +1,24 @@
+
+
+	
+		
+		
+		
+	
+	
+	    
+	    
+	    
+	
+	
+	    
+	    
+	    
+	
+	
+	    
+	    
+	    
+	
+
+
diff --git a/java/TioPatinhas/Vip/VipDAO.java b/java/TioPatinhas/Vip/VipDAO.java
new file mode 100644
index 0000000..a7e99e4
--- /dev/null
+++ b/java/TioPatinhas/Vip/VipDAO.java
@@ -0,0 +1,72 @@
+package TioPatinhas.Vip;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+
+import net.sf.l2j.L2DatabaseFactory;
+
+/*
+ * @author -= TioPatinhas =-
+ */
+public final class VipDAO
+{
+	private static final String SELECT = "SELECT tier, expire_at FROM character_vip_player WHERE charId=?";
+	private static final String UPSERT = "REPLACE INTO character_vip_player (charId, tier, expire_at) VALUES (?,?,?)";
+	private static final String DELETE = "DELETE FROM character_vip_player WHERE charId=?";
+	
+	public static VipStatus load(int charId)
+	{
+		try (Connection con = L2DatabaseFactory.getInstance().getConnection();
+			PreparedStatement ps = con.prepareStatement(SELECT))
+		{
+			ps.setInt(1, charId);
+			try (ResultSet rs = ps.executeQuery())
+			{
+				if (rs.next())
+				{
+					final String tier = rs.getString("tier");
+					final LocalDateTime exp = rs.getTimestamp("expire_at").toLocalDateTime();
+					return new VipStatus(tier, exp);
+				}
+			}
+		}
+		catch (Exception e)
+		{
+			e.printStackTrace();
+		}
+		return null;
+	}
+	
+	public static void save(int charId, String tier, LocalDateTime expireAt)
+	{
+		try (Connection con = L2DatabaseFactory.getInstance().getConnection();
+			PreparedStatement ps = con.prepareStatement(UPSERT))
+		{
+			ps.setInt(1, charId);
+			ps.setString(2, tier);
+			ps.setTimestamp(3, Timestamp.valueOf(expireAt));
+			ps.executeUpdate();
+		}
+		catch (Exception e)
+		{
+			e.printStackTrace();
+		}
+	}
+	
+	public static void delete(int charId)
+	{
+		try (Connection con = L2DatabaseFactory.getInstance().getConnection();
+			PreparedStatement ps = con.prepareStatement(DELETE))
+		{
+			ps.setInt(1, charId);
+			ps.executeUpdate();
+		}
+		catch (Exception e)
+		{
+			e.printStackTrace();
+		}
+	}
+}
diff --git a/java/TioPatinhas/Vip/VipData.java b/java/TioPatinhas/Vip/VipData.java
new file mode 100644
index 0000000..cf6937c
--- /dev/null
+++ b/java/TioPatinhas/Vip/VipData.java
@@ -0,0 +1,326 @@
+package TioPatinhas.Vip;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+/*
+ * @author -= TioPatinhas =-
+ */
+public final class VipData
+{
+	private static final Logger _log = Logger.getLogger(VipData.class.getName());
+	private static final VipData INSTANCE = new VipData();
+	
+	public static VipData getInstance()
+	{
+		return INSTANCE;
+	}
+	
+	private boolean _enabled = false;
+	
+	private final Map> _tierChanceAdd = new HashMap<>();
+	private final Map> _tierCountMul = new HashMap<>();
+	private final Map _tierTitleColors = new HashMap<>();
+	private final Map _tierUseTitleColor = new HashMap<>();
+	private final Map _tierXpRate = new HashMap<>();
+	private final Map _tierSpRate = new HashMap<>();
+	private final Map _tierUseXpSp = new HashMap<>();
+	
+	private final Map _tierEnchantAddGlobal = new HashMap<>();
+	private final Map _tierEnchantMulGlobal = new HashMap<>();
+	
+	private final Map> _tierEnchantAddByScroll = new HashMap<>();
+	private final Map> _tierEnchantMulByScroll = new HashMap<>();
+	
+	public static final class TokenEntry
+	{
+		public final String tier;
+		public final int days;
+		
+		public TokenEntry(String t, int d)
+		{
+			tier = t;
+			days = d;
+		}
+	}
+	
+	private final Map _tokenByItem = new HashMap<>();
+	
+	private VipData()
+	{
+		reload();
+	}
+	
+	public void reload()
+	{
+		_enabled = false;
+		_tierChanceAdd.clear();
+		_tierCountMul.clear();
+		_tierTitleColors.clear();
+		_tierUseTitleColor.clear();
+		_tierXpRate.clear();
+		_tierSpRate.clear();
+		_tierUseXpSp.clear();
+		
+		_tierEnchantAddGlobal.clear();
+		_tierEnchantMulGlobal.clear();
+		_tierEnchantAddByScroll.clear();
+		_tierEnchantMulByScroll.clear();
+		
+		_tokenByItem.clear();
+		
+		try
+		{
+			final File f = new File("./data/xml/Custom/VipManager.xml");
+			final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+			dbf.setIgnoringComments(true);
+			dbf.setExpandEntityReferences(false);
+			final Document doc = dbf.newDocumentBuilder().parse(f);
+			doc.getDocumentElement().normalize();
+			
+			final NamedNodeMap rootAttrs = doc.getDocumentElement().getAttributes();
+			final Node enabledAttr = rootAttrs.getNamedItem("enabled");
+			_enabled = (enabledAttr != null && Boolean.parseBoolean(enabledAttr.getNodeValue()));
+			
+			for (Node n = doc.getDocumentElement().getFirstChild(); n != null; n = n.getNextSibling())
+			{
+				if (n.getNodeType() != Node.ELEMENT_NODE)
+					continue;
+				
+				if ("Tier".equalsIgnoreCase(n.getNodeName()))
+				{
+					final NamedNodeMap a = n.getAttributes();
+					final String name = a.getNamedItem("name").getNodeValue().toLowerCase();
+					
+					// titleColor / useTitleColor
+					Node tc = a.getNamedItem("titleColor");
+					if (tc != null)
+						_tierTitleColors.put(name, tc.getNodeValue());
+					
+					Node utc = a.getNamedItem("useTitleColor");
+					final boolean useTitle = (utc != null && Boolean.parseBoolean(utc.getNodeValue()));
+					_tierUseTitleColor.put(name, useTitle);
+					
+					// xpRate / spRate / useXpSp
+					double xpRate = 1.0, spRate = 1.0;
+					Node xr = a.getNamedItem("xpRate");
+					Node sr = a.getNamedItem("spRate");
+					if (xr != null)
+						xpRate = Double.parseDouble(xr.getNodeValue());
+					if (sr != null)
+						spRate = Double.parseDouble(sr.getNodeValue());
+					_tierXpRate.put(name, xpRate);
+					_tierSpRate.put(name, spRate);
+					
+					Node ux = a.getNamedItem("useXpSp");
+					boolean useXpSp = (ux != null && Boolean.parseBoolean(ux.getNodeValue()));
+					_tierUseXpSp.put(name, useXpSp);
+					
+					final Map chanceAdd = new HashMap<>();
+					final Map countMul = new HashMap<>();
+					
+					// ENCHANT maps locais do tier
+					final Map enchAddByScroll = new HashMap<>();
+					final Map enchMulByScroll = new HashMap<>();
+					Double enchAddGlobal = null;
+					Double enchMulGlobal = null;
+					
+					for (Node c = n.getFirstChild(); c != null; c = c.getNextSibling())
+					{
+						if (c.getNodeType() != Node.ELEMENT_NODE)
+							continue;
+						
+						final String nodeName = c.getNodeName();
+						
+						// Drops
+						if ("DropBonus".equalsIgnoreCase(nodeName))
+						{
+							final NamedNodeMap ca = c.getAttributes();
+							final int itemId = Integer.parseInt(ca.getNamedItem("itemId").getNodeValue());
+							
+							double addPercent = 0.0;
+							Node ap = ca.getNamedItem("addPercent");
+							if (ap != null)
+								addPercent = Double.parseDouble(ap.getNodeValue());
+							if (addPercent != 0.0)
+								chanceAdd.put(itemId, addPercent);
+							
+							double mul = 1.0;
+							Node cm = ca.getNamedItem("countMul");
+							if (cm != null)
+								mul = Double.parseDouble(cm.getNodeValue());
+							if (mul != 1.0)
+								countMul.put(itemId, mul);
+							
+							continue;
+						}
+						
+						// Enchant
+						if ("EnchantBonus".equalsIgnoreCase(nodeName))
+						{
+							final NamedNodeMap ca = c.getAttributes();
+							Node addN = ca.getNamedItem("addPercent");
+							Node mulN = ca.getNamedItem("mulRate");
+							Node sidN = ca.getNamedItem("scrollId");
+							
+							final Double add = (addN != null) ? Double.parseDouble(addN.getNodeValue()) : null;
+							final Double mul = (mulN != null) ? Double.parseDouble(mulN.getNodeValue()) : null;
+							
+							if (sidN != null)
+							{
+								final int scrollId = Integer.parseInt(sidN.getNodeValue());
+								if (add != null)
+									enchAddByScroll.put(scrollId, add);
+								if (mul != null)
+									enchMulByScroll.put(scrollId, mul);
+							}
+							else
+							{
+								if (add != null)
+									enchAddGlobal = add;
+								if (mul != null)
+									enchMulGlobal = mul;
+							}
+						}
+					}
+					
+					_tierChanceAdd.put(name, Collections.unmodifiableMap(chanceAdd));
+					_tierCountMul.put(name, Collections.unmodifiableMap(countMul));
+					
+					// salva enchant por-tier
+					_tierEnchantAddByScroll.put(name, Collections.unmodifiableMap(enchAddByScroll));
+					_tierEnchantMulByScroll.put(name, Collections.unmodifiableMap(enchMulByScroll));
+					if (enchAddGlobal != null)
+						_tierEnchantAddGlobal.put(name, enchAddGlobal);
+					if (enchMulGlobal != null)
+						_tierEnchantMulGlobal.put(name, enchMulGlobal);
+					
+					continue;
+				}
+				
+				if ("Token".equalsIgnoreCase(n.getNodeName()))
+				{
+					final NamedNodeMap a = n.getAttributes();
+					final int itemId = Integer.parseInt(a.getNamedItem("itemId").getNodeValue());
+					final String tier = a.getNamedItem("tier").getNodeValue().toLowerCase();
+					final int days = Integer.parseInt(a.getNamedItem("days").getNodeValue());
+					_tokenByItem.put(itemId, new TokenEntry(tier, days));
+				}
+			}
+		}
+		catch (Exception e)
+		{
+			e.printStackTrace();
+		}
+		
+		_log.info("VipData: carregado " + _tierChanceAdd.size() + " tiers, " + _tokenByItem.size() + " tokens.");
+	}
+	
+	public boolean isEnabled()
+	{
+		return _enabled;
+	}
+	
+	public double getAddPercent(String tierName, int itemId)
+	{
+		if (!_enabled || tierName == null)
+			return 0.0;
+		final Map m = _tierChanceAdd.get(tierName.toLowerCase());
+		return (m == null) ? 0.0 : m.getOrDefault(itemId, 0.0);
+	}
+	
+	public double getCountMul(String tierName, int itemId)
+	{
+		if (!_enabled || tierName == null)
+			return 1.0;
+		final Map m = _tierCountMul.get(tierName.toLowerCase());
+		return (m == null) ? 1.0 : m.getOrDefault(itemId, 1.0);
+	}
+	
+	public String getTitleColor(String tierName)
+	{
+		if (!_enabled || tierName == null)
+			return null;
+		return _tierTitleColors.get(tierName.toLowerCase());
+	}
+	
+	public boolean isUseTitleColor(String tierName)
+	{
+		if (!_enabled || tierName == null)
+			return false;
+		return _tierUseTitleColor.getOrDefault(tierName.toLowerCase(), false);
+	}
+	
+	public boolean isUseXpSp(String tierName)
+	{
+		if (!_enabled || tierName == null)
+			return false;
+		return _tierUseXpSp.getOrDefault(tierName.toLowerCase(), false);
+	}
+	
+	public double getXpRate(String tierName)
+	{
+		if (!_enabled || tierName == null)
+			return 1.0;
+		return _tierXpRate.getOrDefault(tierName.toLowerCase(), 1.0);
+	}
+	
+	public double getSpRate(String tierName)
+	{
+		if (!_enabled || tierName == null)
+			return 1.0;
+		return _tierSpRate.getOrDefault(tierName.toLowerCase(), 1.0);
+	}
+	
+	public double getEnchantAddPercent(String tierName, int scrollId)
+	{
+		if (!_enabled || tierName == null)
+			return 0.0;
+		final String t = tierName.toLowerCase();
+		final Map byScroll = _tierEnchantAddByScroll.get(t);
+		if (byScroll != null)
+		{
+			final Double v = byScroll.get(scrollId);
+			if (v != null)
+				return v;
+		}
+		return _tierEnchantAddGlobal.getOrDefault(t, 0.0);
+	}
+	
+	public double getEnchantMulRate(String tierName, int scrollId)
+	{
+		if (!_enabled || tierName == null)
+			return 1.0;
+		final String t = tierName.toLowerCase();
+		final Map byScroll = _tierEnchantMulByScroll.get(t);
+		if (byScroll != null)
+		{
+			final Double v = byScroll.get(scrollId);
+			if (v != null)
+				return v;
+		}
+		return _tierEnchantMulGlobal.getOrDefault(t, 1.0);
+	}
+	
+	public static int parseColor(String hex)
+	{
+		if (hex == null)
+			return 0xFFFFFF;
+		String clean = hex.replace("#", "");
+		return (int) Long.parseLong(clean, 16);
+	}
+	
+	public TokenEntry getToken(int itemId)
+	{
+		return _tokenByItem.get(itemId);
+	}
+}
diff --git a/java/TioPatinhas/Vip/VipManager.java b/java/TioPatinhas/Vip/VipManager.java
new file mode 100644
index 0000000..19ac606
--- /dev/null
+++ b/java/TioPatinhas/Vip/VipManager.java
@@ -0,0 +1,205 @@
+package TioPatinhas.Vip;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
+import net.sf.l2j.gameserver.network.clientpackets.Say2;
+import net.sf.l2j.gameserver.network.serverpackets.CreatureSay;
+
+/*
+ * @author -= TioPatinhas =-
+ */
+public final class VipManager
+{
+	private static final VipManager INSTANCE = new VipManager();
+	
+	public static VipManager getInstance()
+	{
+		return INSTANCE;
+	}
+	
+	private VipManager()
+	{
+	}
+	
+	public void onLogin(L2PcInstance player)
+	{
+		final VipStatus stage = VipDAO.load(player.getObjectId());
+		if (stage != null && stage.isActive())
+		{
+			player.setNewVipTier(stage.tier);
+			player.setNewVipExpire(stage.expire);
+			applyTitleColorFromTier(player, stage.tier);
+			
+			final String left = formatRemaining(LocalDateTime.now(), stage.expire);
+			player.sendPacket(new CreatureSay(0, Say2.CRITICAL_ANNOUNCE, "", "Seu VIP " + stage.tier + " expira em " + left + "."));
+			warnIfLessThanOneDay(player, stage.expire);
+		}
+		else
+		{
+			player.setNewVipTier(null);
+			player.setNewVipExpire(null);
+			if (stage != null)
+				VipDAO.delete(player.getObjectId());
+			resetTitleColorToDefault(player);
+		}
+	}
+	
+	public void setVip(L2PcInstance player, String tier, int days)
+	{
+		LocalDateTime base = LocalDateTime.now();
+		if (player.getNewVipExpire() != null && base.isBefore(player.getNewVipExpire()))
+			base = player.getNewVipExpire();
+		
+		final LocalDateTime exp = base.plusDays(Math.max(1, days));
+		final String normTier = tier.toLowerCase();
+		
+		VipDAO.save(player.getObjectId(), normTier, exp);
+		player.setNewVipTier(normTier);
+		player.setNewVipExpire(exp);
+		
+		applyTitleColorFromTier(player, normTier);
+		
+		final String left = formatRemaining(LocalDateTime.now(), exp);
+		player.sendMessage("VIP " + normTier + " renovado. Expira em " + left + ".");
+		player.sendPacket(new CreatureSay(0, Say2.CRITICAL_ANNOUNCE, "", "VIP " + normTier + " renovado. Expira em " + left + "."));
+		warnIfLessThanOneDay(player, exp);
+	}
+	
+	public void clearVip(L2PcInstance player)
+	{
+		VipDAO.delete(player.getObjectId());
+		player.setNewVipTier(null);
+		player.setNewVipExpire(null);
+		resetTitleColorToDefault(player);
+		player.sendMessage("Seu VIP foi removido.");
+		player.sendPacket(new CreatureSay(0, Say2.CRITICAL_ANNOUNCE, "", "Seu VIP foi removido."));
+	}
+	
+	public double getDropAddPercent(L2PcInstance player, int itemId)
+	{
+		if (player == null || player.getNewVipTier() == null)
+			return 0.0;
+		return VipData.getInstance().getAddPercent(player.getNewVipTier(), itemId);
+	}
+	
+	public double getCountMultiplier(L2PcInstance player, int itemId)
+	{
+		if (player == null || player.getNewVipTier() == null)
+			return 1.0;
+		return VipData.getInstance().getCountMul(player.getNewVipTier(), itemId);
+	}
+	
+	// XP / SP
+	public double getXpRateMultiplier(L2PcInstance player)
+	{
+		if (player == null)
+			return 1.0;
+		final String tier = player.getNewVipTier();
+		if (tier == null)
+			return 1.0;
+		if (!VipData.getInstance().isUseXpSp(tier))
+			return 1.0;
+		return VipData.getInstance().getXpRate(tier);
+	}
+	
+	public double getSpRateMultiplier(L2PcInstance player)
+	{
+		if (player == null)
+			return 1.0;
+		final String tier = player.getNewVipTier();
+		if (tier == null)
+			return 1.0;
+		if (!VipData.getInstance().isUseXpSp(tier))
+			return 1.0;
+		return VipData.getInstance().getSpRate(tier);
+	}
+	
+	public double getEnchantAddPercent(L2PcInstance player, int scrollId)
+	{
+		if (player == null)
+			return 0.0;
+		final String tier = player.getNewVipTier();
+		if (tier == null)
+			return 0.0;
+		return VipData.getInstance().getEnchantAddPercent(tier, scrollId);
+	}
+	
+	public double getEnchantMulRate(L2PcInstance player, int scrollId)
+	{
+		if (player == null)
+			return 1.0;
+		final String tier = player.getNewVipTier();
+		if (tier == null)
+			return 1.0;
+		return VipData.getInstance().getEnchantMulRate(tier, scrollId);
+	}
+	
+	private void applyTitleColorFromTier(L2PcInstance player, String tier)
+	{
+		if (player == null || tier == null)
+			return;
+		if (!VipData.getInstance().isUseTitleColor(tier))
+			return;
+		
+		final String hex = VipData.getInstance().getTitleColor(tier);
+		if (hex == null)
+			return;
+		
+		try
+		{
+			final int rgb = VipData.parseColor(hex); // "#rrggbb" -> 0xRRGGBB
+			player.getAppearance().setTitleColor(rgb);
+			player.broadcastUserInfo();
+		}
+		catch (Exception e)
+		{
+			e.printStackTrace();
+		}
+	}
+	
+	private void resetTitleColorToDefault(L2PcInstance player)
+	{
+		if (player == null)
+			return;
+		
+		final int DEFAULT_TITLE_COLOR = 0xFFFF77;
+		try
+		{
+			player.getAppearance().setTitleColor(DEFAULT_TITLE_COLOR);
+			player.broadcastUserInfo();
+		}
+		catch (Exception e)
+		{
+			e.printStackTrace();
+		}
+	}
+	
+	private static String formatRemaining(LocalDateTime now, LocalDateTime expire)
+	{
+		if (expire == null || !expire.isAfter(now))
+			return "0d 00h 00m";
+		
+		Duration day = Duration.between(now, expire);
+		long totalMin = day.toMinutes();
+		long days = totalMin / (24 * 60);
+		long hours = (totalMin % (24 * 60)) / 60;
+		long mins = totalMin % 60;
+		return String.format("%ddias %02dhoras %02dminutos", days, hours, mins);
+	}
+	
+	private static void warnIfLessThanOneDay(L2PcInstance player, LocalDateTime expire)
+	{
+		if (expire == null)
+			return;
+		
+		Duration days = Duration.between(LocalDateTime.now(), expire);
+		if (!days.isNegative() && days.compareTo(Duration.ofHours(24)) <= 0)
+		{
+			long hours = Math.max(0, days.toHours());
+			player.sendMessage("Aviso: seu VIP expira em menos de 24h (" + hours + "h).");
+			player.sendPacket(new CreatureSay(0, Say2.CRITICAL_ANNOUNCE, "Aviso: ", "seu VIP expira em menos de 24h (" + hours + "h)."));
+		}
+	}
+}
diff --git a/java/TioPatinhas/Vip/VipStatus.java b/java/TioPatinhas/Vip/VipStatus.java
new file mode 100644
index 0000000..53dfd7a
--- /dev/null
+++ b/java/TioPatinhas/Vip/VipStatus.java
@@ -0,0 +1,23 @@
+package TioPatinhas.Vip;
+
+import java.time.LocalDateTime;
+
+/*
+ * @author -= TioPatinhas =-
+ */
+public final class VipStatus
+{
+	public final String tier;
+	public final LocalDateTime expire;
+	
+	public VipStatus(String tier, LocalDateTime expire)
+	{
+		this.tier = tier;
+		this.expire = expire;
+	}
+	
+	public boolean isActive()
+	{
+		return tier != null && expire != null && LocalDateTime.now().isBefore(expire);
+	}
+}
diff --git a/java/net/sf/l2j/gameserver/GameServer.java b/java/net/sf/l2j/gameserver/GameServer.java
index 88af6d2..41024a0 100644
--- a/java/net/sf/l2j/gameserver/GameServer.java
+++ b/java/net/sf/l2j/gameserver/GameServer.java
@@ -211,6 +211,7 @@
 import TioPatinhas.Dungeon.DungeonManager;
 import TioPatinhas.Fusion.data.ItemFusionData;
 import TioPatinhas.TerritoryWar.TerritoryWarInit;
+import TioPatinhas.Vip.VipData;
 import TioPatinhas.WhatsApp.WhatsappBridge;
 import hwid.Hwid;
 import phantom.FakePlayerConfig;
@@ -576,6 +577,7 @@
 			_log.info("Battle Farm Event is disabled");
 		
 		ItemFusionData.getInstance();
+		VipData.getInstance();
 	 		
 		Util.printSection("Lotus Dev: Interface");
 		TeleportLocationDataLotus.getInstance();
diff --git a/java/net/sf/l2j/gameserver/handler/AdminCommandHandler.java b/java/net/sf/l2j/gameserver/handler/AdminCommandHandler.java
index 646ec73..4fc3ad5 100644
--- a/java/net/sf/l2j/gameserver/handler/AdminCommandHandler.java
+++ b/java/net/sf/l2j/gameserver/handler/AdminCommandHandler.java
@@ -66,6 +66,7 @@
 import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminMonsterRace;
 import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminMovieMaker;
 import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminMultiTvT;
+import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminNewVip;
 import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminOlympiad;
 import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminPCBPoint;
 import net.sf.l2j.gameserver.handler.admincommandhandlers.AdminPForge;
@@ -184,6 +185,7 @@
 		registerAdminCommandHandler(new AdminSearch());
 		registerAdminCommandHandler(new AdminScanPlayers());
 		registerAdminCommandHandler(new AdminWhatsapp());
+		registerAdminCommandHandler(new AdminNewVip());
 	}
 	
 	public void registerAdminCommandHandler(IAdminCommandHandler handler)
diff --git a/java/net/sf/l2j/gameserver/handler/ItemHandler.java b/java/net/sf/l2j/gameserver/handler/ItemHandler.java
index 7ecd306..edef874 100644
--- a/java/net/sf/l2j/gameserver/handler/ItemHandler.java
+++ b/java/net/sf/l2j/gameserver/handler/ItemHandler.java
@@ -50,6 +50,7 @@
 import net.sf.l2j.gameserver.handler.itemhandlers.SpecialXMas;
 import net.sf.l2j.gameserver.handler.itemhandlers.SpiritShot;
 import net.sf.l2j.gameserver.handler.itemhandlers.SummonItems;
+import net.sf.l2j.gameserver.handler.itemhandlers.VipToken;
 import net.sf.l2j.gameserver.handler.itemhandlers.clan.ClanLevel;
 import net.sf.l2j.gameserver.handler.itemhandlers.clan.ClanReputation;
 import net.sf.l2j.gameserver.handler.itemhandlers.clan.skills.SkillAegis;
@@ -144,6 +145,7 @@
 		registerItemHandler(new RouletteItem());
 		registerItemHandler(new AgationItems());
 		registerItemHandler(new CapsuleBox());
+		registerItemHandler(new VipToken());
 		
 		
 		if (Config.ALLOW_ITEM_RECOVER)
diff --git a/java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminNewVip.java b/java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminNewVip.java
new file mode 100644
index 0000000..dca9a8c
--- /dev/null
+++ b/java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminNewVip.java
@@ -0,0 +1,93 @@
+package net.sf.l2j.gameserver.handler.admincommandhandlers;
+
+import java.util.StringTokenizer;
+
+import net.sf.l2j.gameserver.handler.IAdminCommandHandler;
+import net.sf.l2j.gameserver.model.L2World;
+import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
+
+import TioPatinhas.Vip.VipData;
+import TioPatinhas.Vip.VipManager;
+
+/*
+ * @author -= TioPatinhas =-
+ */
+public class AdminNewVip implements IAdminCommandHandler
+{
+	
+	private static final String[] ADMIN_COMMANDS =
+	{
+		"admin_newvip",
+		"admin_clearvip",
+		"admin_reloadvip"
+	};
+	
+	private L2PcInstance resolvePlayer(L2PcInstance admin, String nameOrDot)
+	{
+		if (nameOrDot.equals(".") && admin.getTarget() instanceof L2PcInstance)
+			return (L2PcInstance) admin.getTarget();
+		return L2World.getInstance().getPlayer(nameOrDot);
+	}
+	
+	@Override
+	public boolean useAdminCommand(String command, L2PcInstance activeChar)
+	{
+		final StringTokenizer st = new StringTokenizer(command, " ");
+		final String cmd = st.nextToken();
+		
+		if ("admin_newvip".equalsIgnoreCase(cmd))
+		{
+			if (st.countTokens() < 3)
+			{
+				activeChar.sendMessage("Uso: //newvip   ");
+				return true;
+			}
+			final String playerName = st.nextToken();
+			final String tier = st.nextToken().toLowerCase();
+			final int days = Integer.parseInt(st.nextToken());
+			
+			final L2PcInstance target = resolvePlayer(activeChar, playerName);
+			if (target == null)
+			{
+				activeChar.sendMessage("Player offline.");
+				return true;
+			}
+			
+			VipManager.getInstance().setVip(target, tier, days);
+			activeChar.sendMessage("VIP setado: " + target.getName() + " -> " + tier + " (" + days + "Dia(s))");
+			target.sendMessage("Seu VIP foi ativado: " + tier + " por " + days + " dia(s).");
+			return true;
+		}
+		else if ("admin_clearvip".equalsIgnoreCase(cmd))
+		{
+			if (!st.hasMoreTokens())
+			{
+				activeChar.sendMessage("Uso: //clearvip ");
+				return true;
+			}
+			final L2PcInstance target = resolvePlayer(activeChar, st.nextToken());
+			if (target == null)
+			{
+				activeChar.sendMessage("Player offline.");
+				return true;
+			}
+			VipManager.getInstance().clearVip(target);
+			activeChar.sendMessage("VIP removido de " + target.getName());
+			target.sendMessage("Seu VIP foi removido.");
+			return true;
+		}
+		else if ("admin_reloadvip".equalsIgnoreCase(cmd))
+		{
+			VipData.getInstance().reload();
+			activeChar.sendMessage("O VipData e VipItems Foram recarregado.");
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public String[] getAdminCommandList()
+	{
+		return ADMIN_COMMANDS;
+	}
+}
diff --git a/java/net/sf/l2j/gameserver/handler/itemhandlers/VipToken.java b/java/net/sf/l2j/gameserver/handler/itemhandlers/VipToken.java
new file mode 100644
index 0000000..b962cad
--- /dev/null
+++ b/java/net/sf/l2j/gameserver/handler/itemhandlers/VipToken.java
@@ -0,0 +1,48 @@
+package net.sf.l2j.gameserver.handler.itemhandlers;
+
+import net.sf.l2j.gameserver.handler.IItemHandler;
+import net.sf.l2j.gameserver.model.actor.L2Playable;
+import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
+import net.sf.l2j.gameserver.model.item.instance.ItemInstance;
+import net.sf.l2j.gameserver.network.SystemMessageId;
+import net.sf.l2j.gameserver.network.clientpackets.Say2;
+import net.sf.l2j.gameserver.network.serverpackets.CreatureSay;
+import net.sf.l2j.gameserver.network.serverpackets.SystemMessage;
+
+import TioPatinhas.Vip.VipData;
+import TioPatinhas.Vip.VipManager;
+
+/*
+ * @author -= TioPatinhas =-
+ */
+public final class VipToken implements IItemHandler
+{
+	
+	@Override
+	public void useItem(L2Playable playable, ItemInstance item, boolean forceUse)
+	{
+		if (!(playable instanceof L2PcInstance))
+			return;
+		
+		final L2PcInstance player = (L2PcInstance) playable;
+		
+		final VipData.TokenEntry token = VipData.getInstance().getToken(item.getItemId());
+		if (token == null)
+		{
+			player.sendMessage("Token VIP inválido ou não configurado no VipManager.xml.");
+			return;
+		}
+		
+		if (!player.destroyItemByItemId("VipToken", item.getItemId(), 1, player, true))
+		{
+			player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.NOT_ENOUGH_ITEMS));
+			return;
+		}
+		
+		final int days = Math.max(1, token.days);
+		VipManager.getInstance().setVip(player, token.tier, days);
+		
+		player.sendMessage("VIP " + token.tier + " aplicado/renovado por " + days + " dia(s).");
+		player.sendPacket(new CreatureSay(0, Say2.CRITICAL_ANNOUNCE, "", "VIP " + token.tier + " aplicado/renovado por " + days + " dia(s)."));
+	}
+}
diff --git a/java/net/sf/l2j/gameserver/model/actor/L2Attackable.java b/java/net/sf/l2j/gameserver/model/actor/L2Attackable.java
index 02fc312..ff3a8a9 100644
--- a/java/net/sf/l2j/gameserver/model/actor/L2Attackable.java
+++ b/java/net/sf/l2j/gameserver/model/actor/L2Attackable.java
@@ -76,6 +76,8 @@
 import TioPatinhas.BattleFarm.event.AbstractBattleFarm;
 import TioPatinhas.BattleFarm.event.RewardMobRegistry;
 import TioPatinhas.BossEngine.CustomBossManager;
+import TioPatinhas.Vip.VipData;
+import TioPatinhas.Vip.VipManager;
 
 /**
  * This class manages all NPC that can be attacked, such as :
@@ -643,6 +645,16 @@
 						sp *= Config.VIP_XP_SP_RATE;
 					}
 					
+					if (attacker != null && attacker.isNewVip() && VipData.getInstance().isUseXpSp(attacker.getNewVipTier()))
+					{
+						final double xpMul = VipManager.getInstance().getXpRateMultiplier(attacker);
+						final double spMul = VipManager.getInstance().getSpRateMultiplier(attacker);
+						
+						exp = Math.round(exp * xpMul);
+						sp = (int) Math.round(sp * spMul);
+					}
+					
 					exp *= 1 - penalty;
 					
 					if (isOverhit() && _overhitAttacker != null && _overhitAttacker.getActingPlayer() != null && attacker == _overhitAttacker.getActingPlayer())
@@ -734,6 +746,17 @@
 						exp *= Config.VIP_XP_SP_RATE;
 						sp *= Config.VIP_XP_SP_RATE;
 					}
+					
+					if (attacker != null && attacker.isNewVip() && VipData.getInstance().isUseXpSp(attacker.getNewVipTier()))
+					{
+						final double xpMul = VipManager.getInstance().getXpRateMultiplier(attacker);
+						final double spMul = VipManager.getInstance().getSpRateMultiplier(attacker);
+						
+						exp = Math.round(exp * xpMul);
+						sp = (int) Math.round(sp * spMul);
+					}
+					
 				}
 				
 				if (isChampion())
@@ -751,6 +774,15 @@
 						exp *= Config.VIP_XP_SP_RATE;
 						sp *= Config.VIP_XP_SP_RATE;
 					}

+					if (attacker != null && attacker.isNewVip() && VipData.getInstance().isUseXpSp(attacker.getNewVipTier()))
+					{
+						final double xpMul = VipManager.getInstance().getXpRateMultiplier(attacker);
+						final double spMul = VipManager.getInstance().getSpRateMultiplier(attacker);
+						
+						exp = Math.round(exp * xpMul);
+						sp = (int) Math.round(sp * spMul);
+					}
 				}
 				
 				exp *= partyMul;
@@ -1069,6 +1101,18 @@
 		if (isChampion())
 			dropChance *= Config.CHAMPION_REWARDS;
 		
+		if (lastAttacker != null && VipData.getInstance().isEnabled() && lastAttacker.isNewVip())
+		{
+			final double addPercent = VipManager.getInstance().getDropAddPercent(lastAttacker, drop.getItemId());
+			
+			if (addPercent > 0.0)
+			{
+				final double addInChanceUnits = addPercent * (DropData.MAX_CHANCE / 100.0); // MAX_CHANCE = 100%
+				dropChance += addInChanceUnits;
+			}
+		}
+		
 		// Set our limits for chance of drop
 		if (dropChance < 1)
 			dropChance = 1;
@@ -1104,6 +1148,27 @@
 			if (drop.getItemId() >= 6360 && drop.getItemId() <= 6362)
 				itemCount *= Config.RATE_DROP_SEAL_STONES;
 			
+		if (itemCount > 0 && lastAttacker != null && VipData.getInstance().isEnabled() && lastAttacker.isNewVip())
+		{
+			final double mul = VipManager.getInstance().getCountMultiplier(lastAttacker, drop.getItemId());
+			
+			if (mul > 1.0)
+			{
+				final int base = itemCount;
+				final int whole = (int) Math.floor(mul);
+				final double frac = mul - whole;
+				
+				long newCount = (long) base * (long) whole;
+				if (frac > 0d && Rnd.nextDouble() < frac)
+					newCount += base;
+				
+				if (newCount < 1 && itemCount > 0)
+					newCount = 1;
+				itemCount = (int) Math.min(Integer.MAX_VALUE, newCount);
+			}
+		}
+		
 		if (lastAttacker.isVip())
 		{
 			if (drop.getItemId() == Config.VIP_DROP_RATE)
@@ -1187,6 +1252,14 @@
 			if (isChampion())
 				dropChance *= Config.CHAMPION_REWARDS;
 			
+			if (lastAttacker != null && VipData.getInstance().isEnabled() && lastAttacker.isNewVip())
+			{
+				final double addPercent = VipManager.getInstance().getDropAddPercent(lastAttacker, drop.getItemId());
+				if (addPercent > 0.0)
+					dropChance += (addPercent * (DropData.MAX_CHANCE / 100.0));
+			}
+			
 			if (dropChance < DropData.MAX_CHANCE)
 				dropChance = DropData.MAX_CHANCE;
 			
@@ -1232,6 +1305,26 @@
 				}
 			}
 			
+			if (itemCount > 0 && lastAttacker != null && VipData.getInstance().isEnabled() && lastAttacker.isNewVip())
+			{
+				final double mul = VipManager.getInstance().getCountMultiplier(lastAttacker, drop.getItemId());
+				if (mul > 1.0)
+				{
+					final int base = itemCount;
+					final int whole = (int) Math.floor(mul);
+					final double frac = mul - whole;
+					
+					long newCount = (long) base * (long) whole;
+					if (frac > 0d && Rnd.nextDouble() < frac)
+						newCount += base;
+					
+					if (newCount < 1 && itemCount > 0)
+						newCount = 1;
+					itemCount = (int) Math.min(Integer.MAX_VALUE, newCount);
+				}
+			}
+			
 			if (itemCount > 0)
 				return new ItemHolder(drop.getItemId(), itemCount, drop.getMinEnchant(), drop.getMaxEnchant(), drop.getEnchantSucess());
 		}
diff --git a/java/net/sf/l2j/gameserver/model/actor/instance/L2PcInstance.java b/java/net/sf/l2j/gameserver/model/actor/instance/L2PcInstance.java
index ae7ddc0..cf38913 100644
--- a/java/net/sf/l2j/gameserver/model/actor/instance/L2PcInstance.java
+++ b/java/net/sf/l2j/gameserver/model/actor/instance/L2PcInstance.java
@@ -17,6 +17,7 @@
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -14613,4 +14614,34 @@
 	{
 		_originalBatlleTitleColor = color;
 	}
+	
+	// NewVIP
+	private String _vipTier = null;
+	private LocalDateTime _vipExpire = null;
+	
+	public void setNewVipTier(String t)
+	{
+		_vipTier = t;
+	}
+	
+	public String getNewVipTier()
+	{
+		return _vipTier;
+	}
+	
+	public void setNewVipExpire(LocalDateTime e)
+	{
+		_vipExpire = e;
+	}
+	
+	public LocalDateTime getNewVipExpire()
+	{
+		return _vipExpire;
+	}
+	
+	public boolean isNewVip()
+	{
+		return _vipTier != null && _vipExpire != null && LocalDateTime.now().isBefore(_vipExpire);
+	}
+	
 }
\ No newline at end of file
diff --git a/java/net/sf/l2j/gameserver/network/clientpackets/EnterWorld.java b/java/net/sf/l2j/gameserver/network/clientpackets/EnterWorld.java
index 2ce2d4a..07635f1 100644
--- a/java/net/sf/l2j/gameserver/network/clientpackets/EnterWorld.java
+++ b/java/net/sf/l2j/gameserver/network/clientpackets/EnterWorld.java
@@ -106,6 +106,7 @@
 
 import TioPatinhas.BotPrevention.BotsPreventionManager;
 import TioPatinhas.DollSystem.DollsData;
+import TioPatinhas.Vip.VipManager;
 import hwid.Hwid;
 import phantom.FakePlayer;
 
@@ -487,6 +488,8 @@
 		
 		L2ClassMasterInstance.showQuestionMark(activeChar);
 		
+		VipManager.getInstance().onLogin(activeChar);
+		
 		// VIP Delete
 		if (activeChar.isVip())
 		{
diff --git a/java/net/sf/l2j/gameserver/network/clientpackets/RequestEnchantItem.java b/java/net/sf/l2j/gameserver/network/clientpackets/RequestEnchantItem.java
index a1c9d5c..2e836f8 100644
--- a/java/net/sf/l2j/gameserver/network/clientpackets/RequestEnchantItem.java
+++ b/java/net/sf/l2j/gameserver/network/clientpackets/RequestEnchantItem.java
@@ -29,6 +29,8 @@
 import net.sf.l2j.gameserver.util.Broadcast;
 import net.sf.l2j.util.Rnd;
 
+import TioPatinhas.Vip.VipManager;
+
 public final class RequestEnchantItem extends L2GameClientPacket
 {
 	private int _objectId;
@@ -104,6 +106,28 @@
 		
 		synchronized (item)
 		{
+			
+			double chance = enchant.getChance(item);
+			
+			// if (player != null && player.isNewVip())
+			if (player.isNewVip())
+			{
+				final int scrollId = scroll.getItemId();
+				final double mul = VipManager.getInstance().getEnchantMulRate(player, scrollId);
+				final double add = VipManager.getInstance().getEnchantAddPercent(player, scrollId);
+				
+				if (mul != 1.0)
+					chance *= mul;
+				if (add != 0.0)
+					chance += add;
+				
+				if (chance < 0.0)
+					chance = 0.0;
+				if (chance > 100.0)
+					chance = 100.0;
+			}
+			
 			if (Rnd.get(100) < enchant.getChance(item)) // success
 			{
 				// announce the success


//================================== SQL ===========

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for character_vip_player
-- ----------------------------
DROP TABLE IF EXISTS `character_vip_player`;
CREATE TABLE `character_vip_player`  (
  `charId` int(11) NOT NULL,
  `tier` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `expire_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`charId`) USING BTREE,
  INDEX `idx_character_vip_expire`(`expire_at` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of character_vip_player
-- ----------------------------

SET FOREIGN_KEY_CHECKS = 1;