Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- diff --git a/config/CustomMods/SchemeBuff.ini b/config/CustomMods/SchemeBuff.ini
- index e69de29..2ee26fb 100644
- --- a/config/CustomMods/SchemeBuff.ini
- +++ b/config/CustomMods/SchemeBuff.ini
- @@ -0,0 +1,18 @@
- +#=============================================================
- +# Buffer
- +#=============================================================
- +
- +# Maximum number of available schemes per player.
- +BufferMaxSchemesPerChar = 4
- +
- +# Static cost of buffs ; override skills price if different of -1.
- +BufferStaticCostPerBuff = -1
- +
- +# Price Buff Special
- +CoinIdPriceBuff = -1
- +
- +# Auto buffer for fighter
- +FighterBuffList = 4342,4357,4349,4346,1087,1062,4347,4360,4359,4358,4345,4350,4344,274,275,272,271,264,269,304,267,266,268,1389,1363,4699,1416,1323
- +
- +# Auto buffer for mage
- +MageBuffList = 4342,4355,4347,4351,4356,4349,4346,4350,4344,1303,276,277,273,365,269,270,304,267,268,1389,1413,4703,1416,1393,1392,1352,1353,1323
- \ No newline at end of file
- diff --git a/head-src/Dev/SpecialMods/BuffSkillHolder.java b/head-src/Dev/SpecialMods/BuffSkillHolder.java
- new file mode 100644
- index 0000000..85f4c82
- --- /dev/null
- +++ b/head-src/Dev/SpecialMods/BuffSkillHolder.java
- @@ -0,0 +1,28 @@
- +package Dev.SpecialMods;
- +
- +/**
- + * A container used for schemes buffer.
- + */
- +public final class BuffSkillHolder extends IntIntHolder
- +{
- + private final String _type;
- + private final String _description;
- +
- + public BuffSkillHolder(int id, int price, String type, String description)
- + {
- + super(id, price);
- +
- + _type = type;
- + _description = description;
- + }
- +
- + public final String getType()
- + {
- + return _type;
- + }
- +
- + public final String getDescription()
- + {
- + return _description;
- + }
- +}
- \ No newline at end of file
- diff --git a/head-src/Dev/SpecialMods/IntIntHolder.java b/head-src/Dev/SpecialMods/IntIntHolder.java
- new file mode 100644
- index 0000000..a01f69e
- --- /dev/null
- +++ b/head-src/Dev/SpecialMods/IntIntHolder.java
- @@ -0,0 +1,67 @@
- +/*
- + * This program 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.
- + *
- + * This program 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 Dev.SpecialMods;
- +
- +import com.l2jfrozen.gameserver.datatables.SkillTable;
- +import com.l2jfrozen.gameserver.model.L2Skill;
- +
- +/**
- + * A generic int/int container.
- + */
- +public class IntIntHolder
- +{
- + private int _id;
- + private int _value;
- +
- + public IntIntHolder(int id, int value)
- + {
- + _id = id;
- + _value = value;
- + }
- +
- + public int getId()
- + {
- + return _id;
- + }
- +
- + public int getValue()
- + {
- + return _value;
- + }
- +
- + public void setId(int id)
- + {
- + _id = id;
- + }
- +
- + public void setValue(int value)
- + {
- + _value = value;
- + }
- +
- + /**
- + * @return the L2Skill associated to the id/value.
- + */
- + public final L2Skill getSkill()
- + {
- + return SkillTable.getInstance().getInfo(_id, _value);
- + }
- +
- + @Override
- + public String toString()
- + {
- + return getClass().getSimpleName() + ": Id: " + _id + ", Value: " + _value;
- + }
- +}
- \ No newline at end of file
- diff --git a/head-src/Dev/SpecialMods/MathUtil.java b/head-src/Dev/SpecialMods/MathUtil.java
- new file mode 100644
- index 0000000..1a884b4
- --- /dev/null
- +++ b/head-src/Dev/SpecialMods/MathUtil.java
- @@ -0,0 +1,39 @@
- +/*
- + * This program 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.
- + *
- + * This program 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 Dev.SpecialMods;
- +
- +public class MathUtil
- +{
- + /**
- + * @param objectsSize : The overall elements size.
- + * @param pageSize : The number of elements per page.
- + * @return The number of pages, based on the number of elements and the number of elements we want per page.
- + */
- + public static int countPagesNumber(int objectsSize, int pageSize)
- + {
- + return objectsSize / pageSize + (objectsSize % pageSize == 0 ? 0 : 1);
- + }
- +
- + /**
- + * @param numToTest : The number to test.
- + * @param min : The minimum limit.
- + * @param max : The maximum limit.
- + * @return the number or one of the limit (mininum / maximum).
- + */
- + public static int limit(int numToTest, int min, int max)
- + {
- + return (numToTest > max) ? max : ((numToTest < min) ? min : numToTest);
- + }
- +}
- \ No newline at end of file
- diff --git a/head-src/Dev/SpecialMods/XMLDocumentFactory.java b/head-src/Dev/SpecialMods/XMLDocumentFactory.java
- new file mode 100644
- index 0000000..ebf695d
- --- /dev/null
- +++ b/head-src/Dev/SpecialMods/XMLDocumentFactory.java
- @@ -0,0 +1,72 @@
- +/*
- + * Copyright 2010 InC-Gaming, Patrick Biesenbach aka. Forsaiken. All rights reserved.
- + */
- +package Dev.SpecialMods;
- +
- +import java.io.File;
- +
- +import javax.xml.parsers.DocumentBuilder;
- +import javax.xml.parsers.DocumentBuilderFactory;
- +
- +import org.w3c.dom.Document;
- +
- +public final class XMLDocumentFactory
- +{
- + public static final XMLDocumentFactory getInstance()
- + {
- + return SingletonHolder._instance;
- + }
- +
- + private final DocumentBuilder _builder;
- +
- + protected XMLDocumentFactory() throws Exception
- + {
- + try
- + {
- + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- + factory.setValidating(false);
- + factory.setIgnoringComments(true);
- +
- + _builder = factory.newDocumentBuilder();
- + }
- + catch (Exception e)
- + {
- + throw new Exception("Failed initializing", e);
- + }
- + }
- +
- + public final Document loadDocument(final String filePath) throws Exception
- + {
- + return loadDocument(new File(filePath));
- + }
- +
- + public final Document loadDocument(final File file) throws Exception
- + {
- + if (!file.exists() || !file.isFile())
- + throw new Exception("File: " + file.getAbsolutePath() + " doesn't exist and/or is not a file.");
- +
- + return _builder.parse(file);
- + }
- +
- + public final Document newDocument()
- + {
- + return _builder.newDocument();
- + }
- +
- + private static class SingletonHolder
- + {
- + protected static final XMLDocumentFactory _instance;
- +
- + static
- + {
- + try
- + {
- + _instance = new XMLDocumentFactory();
- + }
- + catch (Exception e)
- + {
- + throw new ExceptionInInitializerError(e);
- + }
- + }
- + }
- +}
- \ No newline at end of file
- diff --git a/head-src/com/l2jfrozen/Config.java b/head-src/com/l2jfrozen/Config.java
- index e2d04ce..36631e6 100644
- --- a/head-src/com/l2jfrozen/Config.java
- +++ b/head-src/com/l2jfrozen/Config.java
- @@ -51,10 +51,61 @@
- import com.l2jfrozen.loginserver.LoginController;
- import com.l2jfrozen.util.StringUtil;
- +import Dev.SpecialMods.BuffSkillHolder;
- +
- public final class Config
- {
- private static final Logger LOGGER = Logger.getLogger(Config.class);
- + //============================================================
- + /** Buffer */
- + public static int BUFFER_MAX_SCHEMES;
- + public static int BUFFER_MAX_SKILLS;
- + public static int BUFFER_STATIC_BUFF_COST;
- + public static int COIN_ITEMID;
- + public static String BUFFER_BUFFS;
- + public static Map<Integer, BuffSkillHolder> BUFFER_BUFFLIST;
- + /** Buffer */
- + public static String FIGHTER_BUFF;
- + public static ArrayList<Integer> FIGHTER_BUFF_LIST = new ArrayList<>();
- + public static String MAGE_BUFF;
- + public static ArrayList<Integer> MAGE_BUFF_LIST = new ArrayList<>();
- +
- + //============================================================
- + public static void loadSchemeBuffConfig()
- + {
- + final String FILENAME = "./config/CustomMods/SchemeBuff.ini";
- + try
- + {
- + Properties buffer = new Properties();
- + InputStream is = new FileInputStream(new File(FILENAME));
- + buffer.load(is);
- + is.close();
- + BUFFER_MAX_SCHEMES = Integer.parseInt(buffer.getProperty("BufferMaxSchemesPerChar", "4"));
- + BUFFER_STATIC_BUFF_COST = Integer.parseInt(buffer.getProperty("BufferStaticCostPerBuff", "-1"));
- + COIN_ITEMID = Integer.parseInt(buffer.getProperty("CoinIdPriceBuff", "-1"));
- + FIGHTER_BUFF = buffer.getProperty("FighterBuffList", "0");
- + FIGHTER_BUFF_LIST = new ArrayList<>();
- + for (String id : FIGHTER_BUFF.trim().split(","))
- + {
- + FIGHTER_BUFF_LIST.add(Integer.parseInt(id.trim()));
- + }
- +
- + MAGE_BUFF = buffer.getProperty("MageBuffList", "0");
- + MAGE_BUFF_LIST = new ArrayList<>();
- + for (String id : MAGE_BUFF.trim().split(","))
- + {
- + MAGE_BUFF_LIST.add(Integer.parseInt(id.trim()));
- + }
- +
- + }
- + catch(Exception e)
- + {
- + e.printStackTrace();
- + throw new Error("Failed to Load " + FILENAME + " File.");
- + }
- + }
- +
- // ============================================================
- public static boolean BOTS_PREVENTION;
- public static int KILLS_COUNTER;
- @@ -4572,6 +4623,7 @@
- loadPHYSICSConfig();
- loadAccessConfig();
- loadModsProtection();
- + loadSchemeBuffConfig();
- loadPvpConfig();
- loadCraftConfig();
- diff --git a/head-src/com/l2jfrozen/gameserver/GameServer.java b/head-src/com/l2jfrozen/gameserver/GameServer.java
- index 3e4d951..9742b98 100644
- --- a/head-src/com/l2jfrozen/gameserver/GameServer.java
- +++ b/head-src/com/l2jfrozen/gameserver/GameServer.java
- @@ -61,6 +61,7 @@
- import com.l2jfrozen.gameserver.datatables.sql.AccessLevels;
- import com.l2jfrozen.gameserver.datatables.sql.AdminCommandAccessRights;
- import com.l2jfrozen.gameserver.datatables.sql.ArmorSetsTable;
- +import com.l2jfrozen.gameserver.datatables.sql.BufferTable;
- import com.l2jfrozen.gameserver.datatables.sql.CharNameTable;
- import com.l2jfrozen.gameserver.datatables.sql.CharTemplateTable;
- import com.l2jfrozen.gameserver.datatables.sql.ClanTable;
- @@ -153,6 +154,8 @@
- import com.l2jfrozen.util.Util;
- import com.l2jfrozen.util.database.L2DatabaseFactory;
- +import Dev.SpecialMods.XMLDocumentFactory;
- +
- public class GameServer
- {
- private static Logger LOGGER = Logger.getLogger("Loader");
- @@ -284,6 +287,7 @@
- FishTable.getInstance();
- Util.printSection("Npc");
- + BufferTable.getInstance();
- NpcWalkerRoutesTable.getInstance().load();
- if (!NpcTable.getInstance().isInitialized())
- {
- @@ -362,6 +366,7 @@
- DimensionalRiftManager.getInstance();
- Util.printSection("Misc");
- + XMLDocumentFactory.getInstance();
- RecipeTable.getInstance();
- RecipeController.getInstance();
- EventDroplist.getInstance();
- diff --git a/head-src/com/l2jfrozen/gameserver/Shutdown.java b/head-src/com/l2jfrozen/gameserver/Shutdown.java
- index 254380c..2c5e655 100644
- --- a/head-src/com/l2jfrozen/gameserver/Shutdown.java
- +++ b/head-src/com/l2jfrozen/gameserver/Shutdown.java
- @@ -26,6 +26,7 @@
- import com.l2jfrozen.gameserver.controllers.GameTimeController;
- import com.l2jfrozen.gameserver.controllers.TradeController;
- import com.l2jfrozen.gameserver.datatables.OfflineTradeTable;
- +import com.l2jfrozen.gameserver.datatables.sql.BufferTable;
- import com.l2jfrozen.gameserver.managers.AutoSaveManager;
- import com.l2jfrozen.gameserver.managers.CastleManorManager;
- import com.l2jfrozen.gameserver.managers.CursedWeaponsManager;
- @@ -699,6 +700,10 @@
- // Save all manor data
- CastleManorManager.getInstance().save();
- + // Schemes save.
- + BufferTable.getInstance().saveSchemes();
- + LOGGER.info("BufferTable data has been saved.");
- +
- // Save all global (non-player specific) Quest data that needs to persist after reboot
- if (!Config.ALT_DEV_NO_QUESTS)
- QuestManager.getInstance().save();
- diff --git a/head-src/com/l2jfrozen/gameserver/datatables/sql/BufferTable.java b/head-src/com/l2jfrozen/gameserver/datatables/sql/BufferTable.java
- new file mode 100644
- index 0000000..6964ef1
- --- /dev/null
- +++ b/head-src/com/l2jfrozen/gameserver/datatables/sql/BufferTable.java
- @@ -0,0 +1,247 @@
- +package com.l2jfrozen.gameserver.datatables.sql;
- +
- +import java.io.File;
- +import java.sql.Connection;
- +import java.sql.PreparedStatement;
- +import java.sql.ResultSet;
- +import java.util.ArrayList;
- +import java.util.Collections;
- +import java.util.HashMap;
- +import java.util.LinkedHashMap;
- +import java.util.List;
- +import java.util.Map;
- +import java.util.concurrent.ConcurrentHashMap;
- +import java.util.logging.Logger;
- +
- +import org.w3c.dom.Document;
- +import org.w3c.dom.NamedNodeMap;
- +import org.w3c.dom.Node;
- +
- +import com.l2jfrozen.Config;
- +import com.l2jfrozen.util.StringUtil;
- +import com.l2jfrozen.util.database.L2DatabaseFactory;
- +
- +import Dev.SpecialMods.BuffSkillHolder;
- +import Dev.SpecialMods.XMLDocumentFactory;
- +
- +/**
- + * This class loads available skills and stores players' buff schemes into _schemesTable.
- + */
- +public class BufferTable
- +{
- + private static final Logger _log = Logger.getLogger(BufferTable.class.getName());
- +
- + private static final String LOAD_SCHEMES = "SELECT * FROM buffer_schemes";
- + private static final String DELETE_SCHEMES = "TRUNCATE TABLE buffer_schemes";
- + private static final String INSERT_SCHEME = "INSERT INTO buffer_schemes (object_id, scheme_name, skills) VALUES (?,?,?)";
- +
- + private final Map<Integer, HashMap<String, ArrayList<Integer>>> _schemesTable = new ConcurrentHashMap<>();
- + private final Map<Integer, BuffSkillHolder> _availableBuffs = new LinkedHashMap<>();
- +
- + public BufferTable()
- + {
- + int count = 0;
- +
- + try (Connection con = L2DatabaseFactory.getInstance().getConnection())
- + {
- + PreparedStatement st = con.prepareStatement(LOAD_SCHEMES);
- + ResultSet rs = st.executeQuery();
- +
- + while (rs.next())
- + {
- + final int objectId = rs.getInt("object_id");
- +
- + final String schemeName = rs.getString("scheme_name");
- + final String[] skills = rs.getString("skills").split(",");
- +
- + ArrayList<Integer> schemeList = new ArrayList<>();
- +
- + for (String skill : skills)
- + {
- + // Don't feed the skills list if the list is empty.
- + if (skill.isEmpty())
- + break;
- +
- + schemeList.add(Integer.valueOf(skill));
- + }
- +
- + setScheme(objectId, schemeName, schemeList);
- + count++;
- + }
- +
- + rs.close();
- + st.close();
- + }
- + catch (Exception e)
- + {
- + _log.warning("BufferTable: Failed to load buff schemes : " + e);
- + }
- +
- + try
- + {
- + final File f = new File("./data/xml/buffer_skills.xml");
- + final Document doc = XMLDocumentFactory.getInstance().loadDocument(f);
- + final Node n = doc.getFirstChild();
- +
- + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
- + {
- + if (!d.getNodeName().equalsIgnoreCase("category"))
- + continue;
- +
- + final String category = d.getAttributes().getNamedItem("type").getNodeValue();
- +
- + for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling())
- + {
- + if (!c.getNodeName().equalsIgnoreCase("buff"))
- + continue;
- +
- + final NamedNodeMap attrs = c.getAttributes();
- + final int skillId = Integer.parseInt(attrs.getNamedItem("id").getNodeValue());
- +
- + _availableBuffs.put(skillId, new BuffSkillHolder(skillId, Integer.parseInt(attrs.getNamedItem("price").getNodeValue()), category, attrs.getNamedItem("desc").getNodeValue()));
- + }
- + }
- + }
- + catch (Exception e)
- + {
- + _log.warning("BufferTable: Failed to load buff info : " + e);
- + }
- + _log.info("BufferTable: Loaded " + count + " players schemes and " + _availableBuffs.size() + " available buffs.");
- + }
- +
- + public void saveSchemes()
- + {
- + try (Connection con = L2DatabaseFactory.getInstance().getConnection())
- + {
- + // Delete all entries from database.
- + PreparedStatement st = con.prepareStatement(DELETE_SCHEMES);
- + st.execute();
- + st.close();
- +
- + st = con.prepareStatement(INSERT_SCHEME);
- +
- + // Save _schemesTable content.
- + for (Map.Entry<Integer, HashMap<String, ArrayList<Integer>>> player : _schemesTable.entrySet())
- + {
- + for (Map.Entry<String, ArrayList<Integer>> scheme : player.getValue().entrySet())
- + {
- + // Build a String composed of skill ids seperated by a ",".
- + final StringBuilder sb = new StringBuilder();
- + for (int skillId : scheme.getValue())
- + StringUtil.append(sb, skillId, ",");
- +
- + // Delete the last "," : must be called only if there is something to delete !
- + if (sb.length() > 0)
- + sb.setLength(sb.length() - 1);
- +
- + st.setInt(1, player.getKey());
- + st.setString(2, scheme.getKey());
- + st.setString(3, sb.toString());
- + st.addBatch();
- + }
- + }
- + st.executeBatch();
- + st.close();
- + }
- + catch (Exception e)
- + {
- + _log.warning("BufferTable: Error while saving schemes : " + e);
- + }
- + }
- +
- + public void setScheme(int playerId, String schemeName, ArrayList<Integer> list)
- + {
- + if (!_schemesTable.containsKey(playerId))
- + _schemesTable.put(playerId, new HashMap<String, ArrayList<Integer>>());
- + else if (_schemesTable.get(playerId).size() >= Config.BUFFER_MAX_SCHEMES)
- + return;
- +
- + _schemesTable.get(playerId).put(schemeName, list);
- + }
- +
- + /**
- + * @param playerId : The player objectId to check.
- + * @return the list of schemes for a given player.
- + */
- + public Map<String, ArrayList<Integer>> getPlayerSchemes(int playerId)
- + {
- + return _schemesTable.get(playerId);
- + }
- +
- + /**
- + * @param playerId : The player objectId to check.
- + * @param schemeName : The scheme name to check.
- + * @return the List holding skills for the given scheme name and player, or null (if scheme or player isn't registered).
- + */
- + public List<Integer> getScheme(int playerId, String schemeName)
- + {
- + if (_schemesTable.get(playerId) == null || _schemesTable.get(playerId).get(schemeName) == null)
- + return Collections.emptyList();
- +
- + return _schemesTable.get(playerId).get(schemeName);
- + }
- +
- + /**
- + * @param playerId : The player objectId to check.
- + * @param schemeName : The scheme name to check.
- + * @param skillId : The skill id to check.
- + * @return true if the skill is already registered on the scheme, or false otherwise.
- + */
- + public boolean getSchemeContainsSkill(int playerId, String schemeName, int skillId)
- + {
- + final List<Integer> skills = getScheme(playerId, schemeName);
- + if (skills.isEmpty())
- + return false;
- +
- + for (int id : skills)
- + {
- + if (id == skillId)
- + return true;
- + }
- + return false;
- + }
- +
- + /**
- + * @param groupType : The type of skills to return.
- + * @return a list of skills ids based on the given groupType.
- + */
- + public List<Integer> getSkillsIdsByType(String groupType)
- + {
- + List<Integer> skills = new ArrayList<>();
- + for (BuffSkillHolder skill : _availableBuffs.values())
- + {
- + if (skill.getType().equalsIgnoreCase(groupType))
- + skills.add(skill.getId());
- + }
- + return skills;
- + }
- +
- + /**
- + * @return a list of all buff types available.
- + */
- + public List<String> getSkillTypes()
- + {
- + List<String> skillTypes = new ArrayList<>();
- + for (BuffSkillHolder skill : _availableBuffs.values())
- + {
- + if (!skillTypes.contains(skill.getType()))
- + skillTypes.add(skill.getType());
- + }
- + return skillTypes;
- + }
- +
- + public BuffSkillHolder getAvailableBuff(int skillId)
- + {
- + return _availableBuffs.get(skillId);
- + }
- +
- + public static BufferTable getInstance()
- + {
- + return SingletonHolder.INSTANCE;
- + }
- +
- + private static class SingletonHolder
- + {
- + protected static final BufferTable INSTANCE = new BufferTable();
- + }
- +}
- \ No newline at end of file
- diff --git a/head-src/com/l2jfrozen/gameserver/model/actor/instance/L2BufferInstance.java b/head-src/com/l2jfrozen/gameserver/model/actor/instance/L2BufferInstance.java
- new file mode 100644
- index 0000000..907d075
- --- /dev/null
- +++ b/head-src/com/l2jfrozen/gameserver/model/actor/instance/L2BufferInstance.java
- @@ -0,0 +1,560 @@
- +/*
- + * This program 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 2, or (at your option)
- + * any later version.
- + *
- + * This program 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, write to the Free Software
- + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
- + * 02111-1307, USA.
- + *
- + * http://www.gnu.org/copyleft/gpl.html
- + */
- +package com.l2jfrozen.gameserver.model.actor.instance;
- +
- +import java.util.ArrayList;
- +import java.util.List;
- +import java.util.Map;
- +import java.util.StringTokenizer;
- +
- +import com.l2jfrozen.Config;
- +import com.l2jfrozen.gameserver.ai.CtrlIntention;
- +import com.l2jfrozen.gameserver.datatables.SkillTable;
- +import com.l2jfrozen.gameserver.datatables.sql.BufferTable;
- +import com.l2jfrozen.gameserver.model.L2Character;
- +import com.l2jfrozen.gameserver.model.L2Skill;
- +import com.l2jfrozen.gameserver.model.L2Summon;
- +import com.l2jfrozen.gameserver.network.SystemMessageId;
- +import com.l2jfrozen.gameserver.network.serverpackets.ActionFailed;
- +import com.l2jfrozen.gameserver.network.serverpackets.MagicSkillUser;
- +import com.l2jfrozen.gameserver.network.serverpackets.MyTargetSelected;
- +import com.l2jfrozen.gameserver.network.serverpackets.NpcHtmlMessage;
- +import com.l2jfrozen.gameserver.network.serverpackets.SocialAction;
- +import com.l2jfrozen.gameserver.network.serverpackets.SystemMessage;
- +import com.l2jfrozen.gameserver.network.serverpackets.ValidateLocation;
- +import com.l2jfrozen.gameserver.templates.L2NpcTemplate;
- +import com.l2jfrozen.util.StringUtil;
- +import com.l2jfrozen.util.random.Rnd;
- +
- +import Dev.SpecialMods.MathUtil;
- +
- +public class L2BufferInstance extends L2NpcInstance
- +{
- + private static final int PAGE_LIMIT = 6;
- +
- + public L2BufferInstance(int objectId, L2NpcTemplate template)
- + {
- + super(objectId, template);
- + }
- +
- + @Override
- + public void onAction(L2PcInstance player)
- + {
- + if (this != player.getTarget())
- + {
- + player.setTarget(this);
- + player.sendPacket(new MyTargetSelected(getObjectId(), player.getLevel() - getLevel()));
- + player.sendPacket(new ValidateLocation(this));
- + }
- + else if (isInsideRadius(player, INTERACTION_DISTANCE, false, false))
- + {
- +
- + SocialAction sa = new SocialAction(getObjectId(), Rnd.get(8));
- + broadcastPacket(sa);
- + showSubBufferWindow(player);
- + player.sendPacket(ActionFailed.STATIC_PACKET);
- + }
- + else
- + {
- + player.getAI().setIntention(CtrlIntention.AI_INTENTION_INTERACT, this);
- + player.sendPacket(ActionFailed.STATIC_PACKET);
- + }
- + }
- +
- + @Override
- + public void onBypassFeedback(L2PcInstance player, String command)
- + {
- + StringTokenizer st = new StringTokenizer(command, " ");
- + String currentCommand = st.nextToken();
- +
- + if (currentCommand.startsWith("menu"))
- + {
- + NpcHtmlMessage html = new NpcHtmlMessage(1);
- + html.setFile(getHtmlPath(getNpcId(), 0));
- + html.replace("%objectId%", getObjectId());
- + player.sendPacket(html);
- + }
- + else if (currentCommand.equalsIgnoreCase("getbuff"))
- + {
- + int buffid = 0;
- + int bufflevel = 1;
- + String nextWindow = null;
- +
- + if (st.countTokens() == 3)
- + {
- + buffid = Integer.valueOf(st.nextToken());
- + bufflevel = Integer.valueOf(st.nextToken());
- + nextWindow = st.nextToken();
- + }
- + else if (st.countTokens() == 1)
- + buffid = Integer.valueOf(st.nextToken());
- +
- + if (buffid != 0)
- + {
- + player.broadcastPacket(new MagicSkillUser(this, player, buffid, bufflevel, 5, 0));
- +
- + player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.YOU_FEEL_S1_EFFECT).addSkillName(buffid, bufflevel));
- + SkillTable.getInstance().getInfo(buffid, bufflevel).getEffects(this, player);
- + showSubBufferWindow(player);
- + showChatWindow(player, nextWindow);
- + }
- + }
- + else if (currentCommand.equalsIgnoreCase("vipbuff"))
- + {
- + int buffid = 0;
- + int bufflevel = 1;
- + String nextWindow = null;
- +
- + if (st.countTokens() == 3)
- + {
- + buffid = Integer.valueOf(st.nextToken());
- + bufflevel = Integer.valueOf(st.nextToken());
- + nextWindow = st.nextToken();
- + }
- + else if (st.countTokens() == 1)
- + buffid = Integer.valueOf(st.nextToken());
- +
- + if (!player.isGM())
- + {
- + player.sendMessage("You must be vip to get this buff.");
- + showChatWindow(player, nextWindow);
- + return;
- + }
- +
- + if (buffid != 0)
- + {
- + player.broadcastPacket(new MagicSkillUser(this, player, buffid, bufflevel, 5, 0));
- +
- + player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.YOU_FEEL_S1_EFFECT).addSkillName(buffid, bufflevel));
- + SkillTable.getInstance().getInfo(buffid, bufflevel).getEffects(this, player);
- + showSubBufferWindow(player);
- + showChatWindow(player, nextWindow);
- + }
- + }
- + else if (currentCommand.equalsIgnoreCase("paybuff"))
- + {
- + int buffid = 0;
- + int bufflevel = 1;
- + int buffPrice = 0;
- + String nextWindow = null;
- +
- + if (st.countTokens() == 4)
- + {
- + buffid = Integer.valueOf(st.nextToken());
- + bufflevel = Integer.valueOf(st.nextToken());
- + buffPrice = Integer.valueOf(st.nextToken());
- + nextWindow = st.nextToken();
- + }
- + else if (st.countTokens() == 3)
- + {
- + buffid = Integer.valueOf(st.nextToken());
- + bufflevel = Integer.valueOf(st.nextToken());
- + nextWindow = st.nextToken();
- + }
- + else if (st.countTokens() == 1)
- + buffid = Integer.valueOf(st.nextToken());
- +
- + if (buffid != 0 && player.destroyItemByItemId("buff",Config.COIN_ITEMID ,buffPrice, player.getLastFolkNPC(), true))
- + {
- + player.broadcastPacket(new MagicSkillUser(this, player, buffid, bufflevel, 5, 0));
- + player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.YOU_FEEL_S1_EFFECT).addSkillName(buffid, bufflevel));
- + SkillTable.getInstance().getInfo(buffid, bufflevel).getEffects(this, player);
- + showChatWindow(player, nextWindow);
- + }
- + else
- + showSubBufferWindow(player);
- + }
- + else if (currentCommand.equalsIgnoreCase("fbuff"))
- + {
- + player.stopAllEffects();
- +
- + for (Integer skillid : Config.FIGHTER_BUFF_LIST)
- + {
- + L2Skill skill = SkillTable.getInstance().getInfo(skillid, SkillTable.getInstance().getMaxLevel(skillid, 0));
- + if (skill != null)
- + skill.getEffects(player, player);
- + }
- +
- + player.setCurrentHpMp(player.getMaxHp(), player.getMaxMp());
- + player.setCurrentCp(player.getMaxCp());
- + player.sendMessage("You get a Fighter-buff complect.");
- +
- + showSubBufferWindow(player);
- + }
- + else if (currentCommand.equalsIgnoreCase("mbuff"))
- + {
- + player.stopAllEffects();
- +
- +
- + for (Integer skillid : Config.MAGE_BUFF_LIST)
- + {
- + L2Skill skill = SkillTable.getInstance().getInfo(skillid, SkillTable.getInstance().getMaxLevel(skillid, 0));
- + if (skill != null)
- + skill.getEffects(player, player);
- + }
- +
- + player.setCurrentHpMp(player.getMaxHp(), player.getMaxMp());
- + player.setCurrentCp(player.getMaxCp());
- + player.sendMessage("You get a Mage-buff complect.");
- +
- + showSubBufferWindow(player);
- + }
- + else if (currentCommand.startsWith("cleanup"))
- + {
- + player.broadcastPacket(new MagicSkillUser(this, player, 1056, 12, 100, 0));
- + player.stopAllEffects();
- +
- + final L2Summon summon = player.getPet();
- + if (summon != null)
- + summon.stopAllEffects();
- + NpcHtmlMessage html = new NpcHtmlMessage(1);
- + html.setFile(getHtmlPath(getNpcId(), 0));
- + html.replace("%objectId%", getObjectId());
- + player.sendPacket(html);
- + }
- + else if (currentCommand.startsWith("heal"))
- + {
- + player.broadcastPacket(new MagicSkillUser(this, player, 1218, 33, 100, 0));
- +
- + player.setCurrentHpMp(player.getMaxHp(), player.getMaxMp());
- + player.setCurrentCp(player.getMaxCp());
- +
- + final L2Summon summon = player.getPet();
- + if (summon != null)
- + summon.setCurrentHpMp(summon.getMaxHp(), summon.getMaxMp());
- +
- + NpcHtmlMessage html = new NpcHtmlMessage(1);
- + html.setFile(getHtmlPath(getNpcId(), 0));
- + html.replace("%objectId%", getObjectId());
- + player.sendPacket(html);
- + }
- + else if (currentCommand.startsWith("support"))
- + {
- + try
- + {
- + showGiveBuffsWindow(player);
- + }
- + catch(Exception e)
- + {
- + e.printStackTrace();
- + }
- + }
- + else if (currentCommand.startsWith("givebuffs"))
- + {
- + try
- + {
- + final String schemeName = st.nextToken();
- + //final int cost = Integer.parseInt(st.nextToken());
- +
- + L2Character target = null;
- + if (st.hasMoreTokens())
- + {
- + final String targetType = st.nextToken();
- + if (targetType != null && targetType.equalsIgnoreCase("pet"))
- + target = player.getPet();
- + }
- + else
- + target = player;
- +
- + if (target == null)
- + player.sendMessage("You don't have a pet.");
- +
- + else
- + //else if (cost == 0 || player.reduceAdena("NPC Buffer", cost, this, true))
- + //{
- + for (int skillId : BufferTable.getInstance().getScheme(player.getObjectId(), schemeName))
- + SkillTable.getInstance().getInfo(skillId, SkillTable.getInstance().getMaxLevel(skillId, 0)).getEffects(this, target);
- + //}
- + }
- + catch (Exception e)
- + {
- + e.printStackTrace();
- + }
- + }
- + else if (currentCommand.startsWith("editschemes"))
- + {
- + showEditSchemeWindow(player, st.nextToken(), st.nextToken(), Integer.parseInt(st.nextToken()));
- + }
- + else if (currentCommand.startsWith("skill"))
- + {
- + final String groupType = st.nextToken();
- + final String schemeName = st.nextToken();
- +
- + final int skillId = Integer.parseInt(st.nextToken());
- + final int page = Integer.parseInt(st.nextToken());
- +
- + final List<Integer> skills = BufferTable.getInstance().getScheme(player.getObjectId(), schemeName);
- +
- + if (currentCommand.startsWith("skillselect") && !schemeName.equalsIgnoreCase("none"))
- + {
- + if (skills.size() < player.getMaxBuffCount())
- + skills.add(skillId);
- + else
- + player.sendMessage("This scheme has reached the maximum amount of buffs.");
- + }
- + else if (currentCommand.startsWith("skillunselect"))
- + skills.remove(Integer.valueOf(skillId));
- +
- + showEditSchemeWindow(player, groupType, schemeName, page);
- + }
- + else if (currentCommand.startsWith("subbuffer"))
- + {
- + showSubBufferWindow(player);
- + }
- + else if (currentCommand.startsWith("createscheme"))
- + {
- + try
- + {
- + final String schemeName = st.nextToken();
- + if (schemeName.length() > 14)
- + {
- + player.sendMessage("Scheme's name must contain up to 14 chars. Spaces are trimmed.");
- + return;
- + }
- +
- + final Map<String, ArrayList<Integer>> schemes = BufferTable.getInstance().getPlayerSchemes(player.getObjectId());
- + if (schemes != null)
- + {
- + if (schemes.size() == Config.BUFFER_MAX_SCHEMES)
- + {
- + player.sendMessage("Maximum schemes amount is already reached.");
- + return;
- + }
- +
- + if (schemes.containsKey(schemeName))
- + {
- + player.sendMessage("The scheme name already exists.");
- + return;
- + }
- + }
- +
- + BufferTable.getInstance().setScheme(player.getObjectId(), schemeName.trim(), new ArrayList<Integer>());
- + showGiveBuffsWindow(player);
- + }
- + catch (Exception e)
- + {
- + player.sendMessage("Scheme's name must contain up to 14 chars. Spaces are trimmed.");
- + }
- + }
- + else if (currentCommand.startsWith("deletescheme"))
- + {
- + try
- + {
- + final String schemeName = st.nextToken();
- + final Map<String, ArrayList<Integer>> schemes = BufferTable.getInstance().getPlayerSchemes(player.getObjectId());
- +
- + if (schemes != null && schemes.containsKey(schemeName))
- + schemes.remove(schemeName);
- + }
- + catch (Exception e)
- + {
- + player.sendMessage("This scheme name is invalid.");
- + }
- + showGiveBuffsWindow(player);
- + }
- + else
- + super.onBypassFeedback(player, command);
- + }
- +
- + @Override
- + public String getHtmlPath(int npcId, int val)
- + {
- + String filename = "";
- + if (val == 0)
- + filename = "" + npcId;
- + else
- + filename = npcId + "-" + val;
- +
- + return "data/html/mods/buffer/" + filename + ".htm";
- + }
- +
- + private void showSubBufferWindow(L2PcInstance player)
- + {
- + NpcHtmlMessage html = new NpcHtmlMessage(1);
- +
- + html.setFile(getHtmlPath(getNpcId(), 0));
- + html.replace("%objectId%", getObjectId());
- + player.sendPacket(html);
- + }
- +
- + /**
- + * Sends an html packet to player with Give Buffs menu info for player and pet, depending on targetType parameter {player, pet}
- + * @param player : The player to make checks on.
- + */
- + private void showGiveBuffsWindow(L2PcInstance player)
- + {
- + final StringBuilder sb = new StringBuilder(200);
- +
- + final Map<String, ArrayList<Integer>> schemes = BufferTable.getInstance().getPlayerSchemes(player.getObjectId());
- + if (schemes == null || schemes.isEmpty())
- + sb.append("<font color=\"LEVEL\">You haven't defined any scheme.</font>");
- + else
- + {
- + for (Map.Entry<String, ArrayList<Integer>> scheme : schemes.entrySet())
- + {
- + StringUtil.append(sb, "<font color=\"LEVEL\">", scheme.getKey(), " [", scheme.getValue().size(), " / ", player.getMaxBuffCount(), "]</font><br1>");
- + StringUtil.append(sb, "<a action=\"bypass -h npc_%objectId%_givebuffs ", scheme.getKey(), "\">Use on Me</a> | ");
- + StringUtil.append(sb, "<a action=\"bypass -h npc_%objectId%_givebuffs ", scheme.getKey(), " pet\">Use on Pet</a> | ");
- + StringUtil.append(sb, "<a action=\"bypass -h npc_%objectId%_editschemes Buffs ", scheme.getKey(), " 1\">Edit</a> | ");
- + StringUtil.append(sb, "<a action=\"bypass -h npc_%objectId%_deletescheme ", scheme.getKey(), "\">Delete</a><br>");
- + }
- + }
- +
- + final NpcHtmlMessage html = new NpcHtmlMessage(0);
- + html.setFile(getHtmlPath(getNpcId(), 1));
- + html.replace("%schemes%", sb.toString());
- + html.replace("%max_schemes%", Config.BUFFER_MAX_SCHEMES);
- + html.replace("%objectId%", getObjectId());
- + player.sendPacket(html);
- + }
- +
- + /**
- + * This sends an html packet to player with Edit Scheme Menu info. This allows player to edit each created scheme (add/delete skills)
- + * @param player : The player to make checks on.
- + * @param groupType : The group of skills to select.
- + * @param schemeName : The scheme to make check.
- + * @param page The page.
- + */
- + private void showEditSchemeWindow(L2PcInstance player, String groupType, String schemeName, int page)
- + {
- + final NpcHtmlMessage html = new NpcHtmlMessage(0);
- + final List<Integer> schemeSkills = BufferTable.getInstance().getScheme(player.getObjectId(), schemeName);
- +
- + html.setFile(getHtmlPath(getNpcId(), 2));
- + html.replace("%schemename%", schemeName);
- + html.replace("%count%", schemeSkills.size() + " / " + player.getMaxBuffCount());
- + html.replace("%typesframe%", getTypesFrame(groupType, schemeName));
- + html.replace("%skilllistframe%", getGroupSkillList(player, groupType, schemeName, page));
- + html.replace("%objectId%", getObjectId());
- + player.sendPacket(html);
- + }
- +
- + /**
- + * @param player : The player to make checks on.
- + * @param groupType : The group of skills to select.
- + * @param schemeName : The scheme to make check.
- + * @param page The page.
- + * @return a String representing skills available to selection for a given groupType.
- + */
- + private String getGroupSkillList(L2PcInstance player, String groupType, String schemeName, int page)
- + {
- + // Retrieve the entire skills list based on group type.
- + List<Integer> skills = BufferTable.getInstance().getSkillsIdsByType(groupType);
- + if (skills.isEmpty())
- + return "That group doesn't contain any skills.";
- +
- + // Calculate page number.
- + final int max = MathUtil.countPagesNumber(skills.size(), PAGE_LIMIT);
- + if (page > max)
- + page = max;
- +
- + // Cut skills list up to page number.
- + skills = skills.subList((page - 1) * PAGE_LIMIT, Math.min(page * PAGE_LIMIT, skills.size()));
- +
- + final List<Integer> schemeSkills = BufferTable.getInstance().getScheme(player.getObjectId(), schemeName);
- + final StringBuilder sb = new StringBuilder(skills.size() * 150);
- +
- + int row = 0;
- + for (int skillId : skills)
- + {
- + sb.append(((row % 2) == 0 ? "<table width=\"280\" bgcolor=\"000000\"><tr>" : "<table width=\"280\"><tr>"));
- +
- + if (skillId < 100)
- + {
- + if (schemeSkills.contains(skillId))
- + StringUtil.append(sb, "<td height=40 width=40><img src=\"icon.skill00", skillId, "\" width=32 height=32></td><td width=190>", SkillTable.getInstance().getInfo(skillId, 1).getName(), "<br1><font color=\"B09878\">", BufferTable.getInstance().getAvailableBuff(skillId).getDescription(), "</font></td><td><button action=\"bypass -h npc_%objectId%_skillunselect ", groupType, " ", schemeName, " ", skillId, " ", page, "\" width=32 height=32 back=\"L2UI_CH3.mapbutton_zoomout2\" fore=\"L2UI_CH3.mapbutton_zoomout1\"></td>");
- + else
- + StringUtil.append(sb, "<td height=40 width=40><img src=\"icon.skill00", skillId, "\" width=32 height=32></td><td width=190>", SkillTable.getInstance().getInfo(skillId, 1).getName(), "<br1><font color=\"B09878\">", BufferTable.getInstance().getAvailableBuff(skillId).getDescription(), "</font></td><td><button action=\"bypass -h npc_%objectId%_skillselect ", groupType, " ", schemeName, " ", skillId, " ", page, "\" width=32 height=32 back=\"L2UI_CH3.mapbutton_zoomin2\" fore=\"L2UI_CH3.mapbutton_zoomin1\"></td>");
- + }
- + else if (skillId < 1000)
- + {
- + if (schemeSkills.contains(skillId))
- + StringUtil.append(sb, "<td height=40 width=40><img src=\"icon.skill0", skillId, "\" width=32 height=32></td><td width=190>", SkillTable.getInstance().getInfo(skillId, 1).getName(), "<br1><font color=\"B09878\">", BufferTable.getInstance().getAvailableBuff(skillId).getDescription(), "</font></td><td><button action=\"bypass -h npc_%objectId%_skillunselect ", groupType, " ", schemeName, " ", skillId, " ", page, "\" width=32 height=32 back=\"L2UI_CH3.mapbutton_zoomout2\" fore=\"L2UI_CH3.mapbutton_zoomout1\"></td>");
- + else
- + StringUtil.append(sb, "<td height=40 width=40><img src=\"icon.skill0", skillId, "\" width=32 height=32></td><td width=190>", SkillTable.getInstance().getInfo(skillId, 1).getName(), "<br1><font color=\"B09878\">", BufferTable.getInstance().getAvailableBuff(skillId).getDescription(), "</font></td><td><button action=\"bypass -h npc_%objectId%_skillselect ", groupType, " ", schemeName, " ", skillId, " ", page, "\" width=32 height=32 back=\"L2UI_CH3.mapbutton_zoomin2\" fore=\"L2UI_CH3.mapbutton_zoomin1\"></td>");
- + }
- + else
- + {
- + if (schemeSkills.contains(skillId))
- + StringUtil.append(sb, "<td height=40 width=40><img src=\"icon.skill", skillId, "\" width=32 height=32></td><td width=190>", SkillTable.getInstance().getInfo(skillId, 1).getName(), "<br1><font color=\"B09878\">", BufferTable.getInstance().getAvailableBuff(skillId).getDescription(), "</font></td><td><button action=\"bypass -h npc_%objectId%_skillunselect ", groupType, " ", schemeName, " ", skillId, " ", page, "\" width=32 height=32 back=\"L2UI_CH3.mapbutton_zoomout2\" fore=\"L2UI_CH3.mapbutton_zoomout1\"></td>");
- + else
- + StringUtil.append(sb, "<td height=40 width=40><img src=\"icon.skill", skillId, "\" width=32 height=32></td><td width=190>", SkillTable.getInstance().getInfo(skillId, 1).getName(), "<br1><font color=\"B09878\">", BufferTable.getInstance().getAvailableBuff(skillId).getDescription(), "</font></td><td><button action=\"bypass -h npc_%objectId%_skillselect ", groupType, " ", schemeName, " ", skillId, " ", page, "\" width=32 height=32 back=\"L2UI_CH3.mapbutton_zoomin2\" fore=\"L2UI_CH3.mapbutton_zoomin1\"></td>");
- + }
- +
- + sb.append("</tr></table><img src=\"L2UI.SquareGray\" width=277 height=1>");
- + row++;
- + }
- +
- + // Build page footer.
- + sb.append("<br><img src=\"L2UI.SquareGray\" width=277 height=1><table width=\"100%\" bgcolor=000000><tr>");
- +
- + if (page > 1)
- + StringUtil.append(sb, "<td align=left width=70><a action=\"bypass -h npc_" + getObjectId() + "_editschemes ", groupType, " ", schemeName, " ", page - 1, "\">Previous</a></td>");
- + else
- + StringUtil.append(sb, "<td align=left width=70>Previous</td>");
- +
- + StringUtil.append(sb, "<td align=center width=100>Page ", page, "</td>");
- +
- + if (page < max)
- + StringUtil.append(sb, "<td align=right width=70><a action=\"bypass -h npc_" + getObjectId() + "_editschemes ", groupType, " ", schemeName, " ", page + 1, "\">Next</a></td>");
- + else
- + StringUtil.append(sb, "<td align=right width=70>Next</td>");
- +
- + sb.append("</tr></table><img src=\"L2UI.SquareGray\" width=277 height=1>");
- +
- + return sb.toString();
- + }
- +
- + /**
- + * @param groupType : The group of skills to select.
- + * @param schemeName : The scheme to make check.
- + * @return a string representing all groupTypes available. The group currently on selection isn't linkable.
- + */
- + private static String getTypesFrame(String groupType, String schemeName)
- + {
- + final StringBuilder sb = new StringBuilder(500);
- + sb.append("<table>");
- +
- + int count = 0;
- + for (String type : BufferTable.getInstance().getSkillTypes())
- + {
- + if (count == 0)
- + sb.append("<tr>");
- +
- + if (groupType.equalsIgnoreCase(type))
- + StringUtil.append(sb, "<td width=65>", type, "</td>");
- + else
- + StringUtil.append(sb, "<td width=65><a action=\"bypass -h npc_%objectId%_editschemes ", type, " ", schemeName, " 1\">", type, "</a></td>");
- +
- + count++;
- + if (count == 4)
- + {
- + sb.append("</tr>");
- + count = 0;
- + }
- + }
- +
- + if (!sb.toString().endsWith("</tr>"))
- + sb.append("</tr>");
- +
- + sb.append("</table>");
- +
- + return sb.toString();
- + }
- +}
- \ No newline at end of file
- diff --git a/head-src/com/l2jfrozen/util/StringUtil.java b/head-src/com/l2jfrozen/util/StringUtil.java
- index da2aa35..41288de 100644
- --- a/head-src/com/l2jfrozen/util/StringUtil.java
- +++ b/head-src/com/l2jfrozen/util/StringUtil.java
- @@ -262,4 +262,15 @@
- TextBuilder.recycle(sbString);
- return result;
- }
- +
- + /**
- + * Appends objects to an existing StringBuilder.
- + * @param sb : the StringBuilder to edit.
- + * @param content : parameters to append.
- + */
- + public static void append(StringBuilder sb, Object... content)
- + {
- + for (Object obj : content)
- + sb.append((obj == null) ? null : obj.toString());
- + }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement