Mod Para Report de BOts

1ª Parte do Codigo
Código:
Index: java/com/l2jserver/Config.java
===================================================================
--- java/com/l2jserver/Config.java    (revision 3970)
+++ java/com/l2jserver/Config.java    (working copy)
@@ -543,6 +543,7 @@
    public static boolean CUSTOM_DROPLIST_TABLE;
    public static boolean CUSTOM_MERCHANT_TABLES;
    public static boolean CUSTOM_NPCBUFFER_TABLES;
+    public static boolean ENABLE_BOTREPORT;


    //--------------------------------------------------
@@ -658,6 +659,9 @@
    public static String ANNOUNCE_PK_MSG;
    public static String ANNOUNCE_PVP_MSG;
    public static boolean L2JMOD_CHAT_ADMIN;
+    
+    public static boolean ENABLE_CHAR_CREATE_TITLE;
+    public static String CHAR_CREATE_TITLE;

    //--------------------------------------------------
    // NPC Settings
@@ -1727,6 +1731,7 @@
                    CUSTOM_DROPLIST_TABLE = Boolean.valueOf(General.getProperty("CustomDroplistTable", "false"));
                    CUSTOM_MERCHANT_TABLES = Boolean.valueOf(General.getProperty("CustomMerchantTables", "false"));
                    CUSTOM_NPCBUFFER_TABLES = Boolean.valueOf(General.getProperty("CustomNpcBufferTables", "false"));
+                    ENABLE_BOTREPORT = Boolean.valueOf(General.getProperty("EnableBotReport", "false"));
                }
                catch (Exception e)
                {
@@ -2173,6 +2178,9 @@
                    ANNOUNCE_PVP_MSG = L2JModSettings.getProperty("AnnouncePvpMsg", "$killer has defeated $target");
                    
                    L2JMOD_CHAT_ADMIN = Boolean.parseBoolean(L2JModSettings.getProperty("ChatAdmin", "false"));
+                    
+                    ENABLE_CHAR_CREATE_TITLE = Boolean.parseBoolean(L2JModSettings.getProperty("EnableCharCreateTitle", "False"));
+                    CHAR_CREATE_TITLE = L2JModSettings.getProperty("CharCreateTitle", "");
                }
                catch (Exception e)
                {
@@ -2818,6 +2826,7 @@
        else if (pName.equalsIgnoreCase("GlobalChat")) DEFAULT_GLOBAL_CHAT = pValue;
        else if (pName.equalsIgnoreCase("TradeChat")) DEFAULT_TRADE_CHAT = pValue;
        else if (pName.equalsIgnoreCase("GMAdminMenuStyle")) GM_ADMIN_MENU_STYLE = pValue;
+        else if (pName.equalsIgnoreCase("EnableBotReport")) ENABLE_BOTREPORT = Boolean.parseBoolean(pValue);
        else return false;
        return true;
    }
Index: java/com/l2jserver/gameserver/GameServer.java
===================================================================
--- java/com/l2jserver/gameserver/GameServer.java    (revision 3970)
+++ java/com/l2jserver/gameserver/GameServer.java    (working copy)
@@ -83,6 +83,7 @@
import com.l2jserver.gameserver.instancemanager.AirShipManager;
import com.l2jserver.gameserver.instancemanager.AuctionManager;
import com.l2jserver.gameserver.instancemanager.BoatManager;
+import com.l2jserver.gameserver.instancemanager.BotManager;
import com.l2jserver.gameserver.instancemanager.CastleManager;
import com.l2jserver.gameserver.instancemanager.CastleManorManager;
import com.l2jserver.gameserver.instancemanager.ClanHallManager;
@@ -260,7 +261,8 @@
        SiegeManager.getInstance().getSieges();
        FortManager.getInstance().loadInstances();
        FortSiegeManager.getInstance();
-        
+        if(Config.ENABLE_BOTREPORT)
+            BotManager.getInstance();
        TeleportLocationTable.getInstance();
        LevelUpData.getInstance();
        L2World.getInstance();
Index: java/com/l2jserver/gameserver/instancemanager/BotManager.java
===================================================================
--- java/com/l2jserver/gameserver/instancemanager/BotManager.java    (revision 0)
+++ java/com/l2jserver/gameserver/instancemanager/BotManager.java    (revision 0)
@@ -0,0 +1,532 @@
+/*
+ * 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 com.l2jserver.gameserver.instancemanager;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import javolution.util.FastList;
+import javolution.util.FastMap;
+
+import com.l2jserver.L2DatabaseFactory;
+import com.l2jserver.gameserver.ThreadPoolManager;
+import com.l2jserver.gameserver.model.L2Account;
+import com.l2jserver.gameserver.model.L2World;
+import com.l2jserver.gameserver.model.actor.L2Character;
+import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
+import com.l2jserver.gameserver.network.SystemMessageId;
+import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
+import com.l2jserver.gameserver.util.BotPunish;
+
+/**
+ * @author BiggBoss
+ * 
+ */
+public class BotManager
+{
+    private static final Logger _log = Logger.getLogger(BotManager.class.getName());
+    
+    private static FastMap<Integer, String[]> _unread;
+    // Number of reportes made over each player
+    private static FastMap<Integer, FastList<L2PcInstance>> _reportedCount = new FastMap<Integer, FastList<L2PcInstance>>();
+    // Reporters blocked by time
+    private static FastMap<Integer, Long> _lockedReporters = new FastMap<Integer, Long>();
+    // Blocked ips
+    private static Set<String> _lockedIps = new HashSet<String>();
+    // Blocked accounts
+    private static Set<String> _lockedAccounts = new HashSet<String>();
+    
+    private BotManager()
+    {
+        loadUnread();
+    }
+    
+    public static BotManager getInstance()
+    {
+        return SingletonHolder._instance;
+    }
+    
+    /**
+     * Check if the reported player is online
+     * @param reportedId
+     * @return true if L2World contains that player, else returns false
+     */
+    private static boolean reportedIsOnline(L2PcInstance player)
+    {
+        return L2World.getInstance().getPlayer(player.getObjectId()) != null;
+    }
+    
+    /**
+     * Will save the report in database
+     * @param reported (the L2PcInstance who was reported)
+     * @param reporter (the L2PcInstance who reported the bot) 
+     */
+    public synchronized void reportBot(L2PcInstance reported, L2PcInstance reporter)
+    {
+        if (!reportedIsOnline(reported))
+        {
+            reporter.sendMessage("The player you are reporting is offline.");
+            return;
+        }
+        
+        _lockedReporters.put(reporter.getObjectId(), System.currentTimeMillis());
+        _lockedIps.add(reporter.getClient().getConnection().getInetAddress().getHostAddress());
+        _lockedAccounts.add(reporter.getAccountName());
+        
+        long date = Calendar.getInstance().getTimeInMillis();
+        Connection con = null;
+        
+        try
+        {
+            if (!_reportedCount.containsKey(reported))
+            {
+                FastList<L2PcInstance> p = new FastList<L2PcInstance>();
+                p.add(reported);
+                _reportedCount.put(reporter.getObjectId(), p);
+            }
+            else
+            {
+                if(_reportedCount.get(reporter).contains(reported.getObjectId()))
+                {
+                    reporter.sendMessage("You cannot report a player more than 1 time");
+                    return;
+                }
+                _reportedCount.get(reporter).add(reported);
+            }
+            
+            con = L2DatabaseFactory.getInstance().getConnection();
+            PreparedStatement statement = con.prepareStatement("INSERT INTO `bot_report`(`reported_name`, `reported_objectId`, `reporter_name`, `reporter_objectId`, `date`) VALUES (?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS);
+            statement.setString(1, reported.getName());
+            statement.setInt(2, reported.getObjectId());
+            statement.setString(3, reporter.getName());
+            statement.setInt(4, reporter.getObjectId());
+            statement.setLong(5, date);
+            statement.executeUpdate();
+            
+            ResultSet rs = statement.getGeneratedKeys();
+            rs.next();
+            int maxId = rs.getInt(1);
+            
+            statement.close();
+            _unread.put(maxId, new String[]{reported.getName(), reporter.getName(), String.valueOf(date)});
+        }
+        catch (Exception e)
+        {
+            _log.severe("Could not save reported bot " + reported.getName() + " by " + reporter.getName() + " at " + date + ".");
+        }
+        finally
+        {
+            try
+            {
+                con.close();
+            }
+            catch (Exception e)
+            {
+            }
+        }
+        SystemMessage sm = new SystemMessage(SystemMessageId.C1_REPORTED_AS_BOT);
+        sm.addCharName(reported);
+        reporter.sendPacket(sm);
+    }
+    
+    /**
+     * Will load the data from all unreaded reports (used to load reports
+     * in a window for admins/GMs)
+     * @return a FastMap<Integer, String[]> (Integer - report id, String[] - reported name, report name, date)
+     */
+    private void loadUnread()
+    {
+        _unread = new FastMap<Integer, String[]>();
+        Connection con = null;
+        try
+        {
+            con = L2DatabaseFactory.getInstance().getConnection();
+            PreparedStatement statement = con.prepareStatement("SELECT `report_id`, `reported_name`, `reporter_name`, `date`, MAX(`report_id`) as `max_id` FROM `bot_report` WHERE `read` = ?");
+            statement.setString(1, "false");
+            
+            ResultSet rset = statement.executeQuery();
+            while (rset.next())
+            {
+                //Not loading objectIds to increase performance
+                //L2World.getInstance().getPlayer(name).getObjectId();
+                String[] data = new String[3];
+                data[0] = rset.getString("reported_name");
+                data[1] = rset.getString("reporter_name");
+                data[2] = rset.getString("date");
+                
+                _unread.put(rset.getInt("report_id"), data);
+            }
+            rset.close();
+            statement.close();
+        }
+        catch (Exception e)
+        {
+            _log.severe("Could not load data from bot_report:\n" + e.getMessage());
+        }
+        finally
+        {
+            try
+            {
+                con.close();
+            }
+            catch (Exception e)
+            {
+            }
+        }
+    }
+    
+    /**
+     * Return a FastMap holding all the reports data
+     * to be viewed by any GM
+     * @return _unread
+     */
+    public FastMap<Integer, String[]> getUnread()
+    {
+        return _unread;
+    }
+    
+    /**
+     * Marks a reported bot as readed (from admin menu)
+     * @param id (the report id)
+     */
+    public void markAsRead(int id)
+    {
+        Connection con = null;
+        try
+        {
+            con = L2DatabaseFactory.getInstance().getConnection();
+            PreparedStatement statement = con.prepareStatement("UPDATE `bot_report` SET `read` = ? WHERE `report_id` = ?");
+            statement.setString(1, "true");
+            statement.setInt(2, id);
+            statement.execute();
+            
+            statement.close();
+            _unread.remove(id);
+            _log.fine("Reported bot marked as read, id was: " + id);
+        }
+        catch (Exception e)
+        {
+            _log.severe("Could not mark as read the reported bot: " + id + ":\n" + e.getMessage());
+        }
+        finally
+        {
+            try
+            {
+                con.close();
+            }
+            catch (Exception e)
+            {
+            }
+        }
+    }
+    
+    /**
+     * Returns the number of times the player has been reported
+     * @param reported
+     * @return int
+     */
+    public int getPlayerReportsCount(L2PcInstance reported)
+    {
+        int count = 0;
+        Connection con = null;
+        try
+        {
+            con = L2DatabaseFactory.getInstance().getConnection();
+            PreparedStatement statement = con.prepareStatement("SELECT COUNT(*) FROM `bot_report` WHERE `reported_objectId` = ?");
+            statement.setInt(1, reported.getObjectId());
+            
+            ResultSet rset = statement.executeQuery();
+            count = rset.getInt(1);
+            rset.close();
+            statement.close();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        finally
+        {
+            try
+            {
+                con.close();
+            }
+            catch (Exception e)
+            {
+            }
+        }
+        return count;
+    }
+    
+    /**
+     * Will save the punish being suffered to player in database
+     * (at player logs out), to be restored next time players enter
+     * in server
+     * @param punished 
+     */
+    public void savePlayerPunish(L2PcInstance punished)
+    {
+        Connection con = null;
+        try
+        {
+            con = L2DatabaseFactory.getInstance().getConnection();
+            PreparedStatement statement = con.prepareStatement("UPDATE `bot_reported_punish` SET `time_left` = ? WHERE `charId` = ?");
+            statement.setLong(1, punished.getPlayerPunish().getPunishTimeLeft());
+            statement.setInt(2, punished.getObjectId());
+            statement.execute();
+            statement.close();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        finally
+        {
+            try
+            {
+                con.close();
+            }
+            catch (Exception e)
+            {
+            }
+        }
+    }
+    
+    /**
+     * Retail report restrictions (Validates the player - reporter relationship)
+     * @param reported (the reported bot)
+     * @return 
+     */
+    public boolean validateBot(L2PcInstance reported, L2PcInstance reporter)
+    {
+        if (reported == null || reporter == null)
+            return false;
+        
+        // Cannot report while reported is inside peace zone, war zone or olympiad
+        if (reported.isInsideZone(L2Character.ZONE_PEACE) || reported.isInsideZone(L2Character.ZONE_PVP) || reported.isInOlympiadMode())
+        {
+            reporter.sendPacket(new SystemMessage(SystemMessageId.CANNOT_REPORT_IN_WARZONE_PEACEZONE_CLANWAR_OLYMPIAD));
+            return false;
+        }
+        // Cannot report if reported and reporter are in war
+        if (reported.getClan() != null && reporter.getClan() != null)
+        {
+            if (reported.getClan().isAtWarWith(reporter.getClanId()))
+            {
+                reporter.sendPacket(new SystemMessage(SystemMessageId.CANNOT_REPORT_TARGET_IN_CLAN_WAR));
+                return false;
+            }
+        }
+        // Cannot report if the reported didnt earn exp since he logged in
+        if (!reported.getStat().hasEarnedExp())
+        {
+            reporter.sendPacket(new SystemMessage(SystemMessageId.CANNOT_REPORT_CHARACTER_WITHOUT_GAINEXP));
+            return false;
+        }
+        // Cannot report twice or more a player
+        if (_reportedCount.containsKey(reporter))
+        {
+            for (L2PcInstance p : _reportedCount.get(reporter))
+            {
+                if (reported == p)
+                {
+                    reporter.sendPacket(new SystemMessage(SystemMessageId.C1_REPORTED_AS_BOT));
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+    
+    /**
+     * Retail report restrictions (Validates the reporter state)
+     * @param reporter
+     * @return
+     */
+    public synchronized boolean validateReport(L2PcInstance reporter)
+    {
+        if (reporter == null)
+            return false;
+        
+        if(reporter._account == null)
+            reporter._account = new L2Account(reporter.getAccountName());
+        
+        // The player has a 30 mins lock before be able to report anyone again
+        if(reporter._account.getReportsPoints() == 0)
+        {
+            SystemMessage sm = new SystemMessage(SystemMessageId.YOU_CAN_REPORT_IN_S1_MINUTES_S2_REPORT_POINTS_REMAIN_IN_ACCOUNT);
+            sm.addNumber(0);
+            sm.addNumber(0);
+            reporter.sendPacket(sm);
+            return false;
+        }
+            
+        // 30 mins must pass before report again 
+        else if (_lockedReporters.containsKey(reporter.getObjectId()))
+        {
+            long delay = (System.currentTimeMillis() - _lockedReporters.get(reporter.getObjectId()));
+            if (delay <= 1800000)
+            {
+                int left = (int) (1800000 - delay) / 60000;
+                SystemMessage sm = new SystemMessage(SystemMessageId.YOU_CAN_REPORT_IN_S1_MINUTES_S2_REPORT_POINTS_REMAIN_IN_ACCOUNT);
+                sm.addNumber(left);
+                sm.addNumber(reporter._account.getReportsPoints());
+                reporter.sendPacket(sm);
+                return false;
+            }
+            else
+                ThreadPoolManager.getInstance().executeTask(new ReportClear(reporter));
+        }
+        // In those 30 mins, the ip which made the first report cannot report again
+        else if (_lockedIps.contains(reporter.getClient().getConnection().getInetAddress().getHostAddress()))
+        {
+            reporter.sendPacket(new SystemMessage(SystemMessageId.CANNOT_REPORT_ALREDY_REPORTED_FROM_YOUR_CLAN_OR_IP));
+            return false;
+        }
+        // In those 30 mins, the account which made report cannot report again
+        else if (_lockedAccounts.contains(reporter.getAccountName()))
+        {
+            reporter.sendPacket(new SystemMessage(SystemMessageId.CANNOT_REPORT_ALAREDY_REPORTED_FROM_SAME_ACCOUNT));
+            return false;
+        }
+        // If any clan/ally mate has reported any bot, you cannot report till he releases his lock
+        else if (reporter.getClan() != null)
+        {
+            for (int i : _lockedReporters.keySet())
+            {
+                // Same clan
+                L2PcInstance p = L2World.getInstance().getPlayer(i);
+                if (p == null)
+                    continue;
+                
+                if (p.getClanId() == reporter.getClanId())
+                {
+                    reporter.sendPacket(new SystemMessage(SystemMessageId.CANNOT_REPORT_ALREDY_REPORTED_FROM_YOUR_CLAN_OR_IP));
+                    return false;
+                }
+                // Same ally
+                else if (reporter.getClan().getAllyId() != 0)
+                {
+                    if (p.getClan().getAllyId() == reporter.getClan().getAllyId())
+                    {
+                        reporter.sendPacket(new SystemMessage(SystemMessageId.CANNOT_REPORT_ALREDY_REPORTED_FROM_YOUR_CLAN_OR_IP));
+                        return false;
+                    }
+                }
+            }
+        }
+        reporter._account.reducePoints();
+        return true;
+    }
+    
+    /**
+     * Will manage needed actions on enter
+     * @param activeChar
+     */
+    public void onEnter(L2PcInstance activeChar)
+    {
+        activeChar.getStat().setFirstExp(activeChar.getExp());
+        restorePlayerBotPunishment(activeChar);
+        activeChar._account = new L2Account(activeChar.getAccountName());
+    }
+    
+    /**
+     * Will retore the player punish on enter
+     * @param activeChar
+     */
+    private void restorePlayerBotPunishment(L2PcInstance activeChar)
+    {
+        String punish = "";
+        long delay = 0;
+        Connection con = null;
+        try
+        {
+            con = L2DatabaseFactory.getInstance().getConnection();
+            PreparedStatement statement = con.prepareStatement("SELECT `punish_type`, `time_left` FROM `bot_reported_punish` WHERE `charId` = ?");
+            statement.setInt(1, activeChar.getObjectId());
+            
+            ResultSet rset = statement.executeQuery();
+            while (rset.next())
+            {
+                punish = rset.getString("punish_type");
+                delay = rset.getLong("time_left");
+            }
+            rset.close();
+            statement.close();
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        finally
+        {
+            try
+            {
+                con.close();
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+        
+        if (!punish.isEmpty() && BotPunish.Punish.valueOf(punish) != null)
+        {
+            if (delay < 0)
+            {
+                BotPunish.Punish p = BotPunish.Punish.valueOf(punish);
+                long left = (-delay / 1000) / 60;
+                activeChar.setPunishDueBotting(p, (int) left);
+            }
+            else
+                activeChar.endPunishment();
+        }
+    }
+    
+    private static class SingletonHolder
+    {
+        private static BotManager _instance = new BotManager();
+    }
+    
+    /**
+     * Manages the reporter restriction data clean up
+     * to be able to report again
+     */
+    private class ReportClear implements Runnable
+    {
+        private L2PcInstance _reporter;
+        
+        private ReportClear(L2PcInstance reporter)
+        {
+            _reporter = reporter;
+        }
+        
+        /* (non-Javadoc)
+         * @see java.lang.Runnable#run()
+         */
+        @Override
+        public void run()
+        {
+            _lockedReporters.remove(_reporter.getObjectId());
+            _lockedIps.remove(_reporter.getClient().getConnection().getInetAddress().getHostAddress());
+            _lockedAccounts.remove(_reporter.getAccountName());
+        }
+    }
+}
Index: java/com/l2jserver/gameserver/model/L2Account.java
===================================================================
--- java/com/l2jserver/gameserver/model/L2Account.java    (revision 0)
+++ java/com/l2jserver/gameserver/model/L2Account.java    (revision 0)
@@ -0,0 +1,108 @@
+/*
+ * 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 com.l2jserver.gameserver.model;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.l2jserver.L2DatabaseFactory;
+
+/**
+ * @author BiggBoss
+ */
+public class L2Account
+{
+    private static Logger _log = Logger.getLogger(L2Account.class.getName());
+    
+    private int _reportBotPoints;
+
+    public L2Account(String accName)
+    {
+        loadBotPoints(accName);
+    }
+    
+    private void loadBotPoints(String accName)
+    {
+        Connection con = null;
+        try
+        {
+            con = L2DatabaseFactory.getInstance().getConnection();
+            PreparedStatement statement =  con.prepareStatement("SELECT bot_report_points FROM accounts WHERE login = ?");
+            statement.setString(1, accName);
+            
+            ResultSet rset = statement.executeQuery();
+            while(rset.next())
+            {
+                _reportBotPoints = rset.getInt("bot_report_points");
+            }
+            rset.close();
+            statement.close();
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+        }
+        finally
+        {
+            try
+            {
+                con.close();
+            }
+            catch(Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+    }
+    
+    public synchronized void updatePoints(String accName) throws SQLException
+    {
+        Connection con = null;
+        try
+        {
+            con = L2DatabaseFactory.getInstance().getConnection();
+            PreparedStatement statement = con.prepareStatement("UPDATE accounts SET bot_report_points = ? WHERE login = ?");
+            statement.setInt(1, _reportBotPoints);
+            statement.setString(2, accName);
+            statement.execute();
+            statement.close();
+        }
+        catch(SQLException e)
+        {
+            _log.log(Level.SEVERE, "Couldnt save bot reports points for "+accName);
+            e.printStackTrace();
+        }
+        finally
+        {
+            con.close();
+            if(!con.isClosed())
+                throw new SQLException();
+        }
+    }
+    
+    public synchronized int getReportsPoints()
+    {
+        return _reportBotPoints;
+    }
+    
+    public synchronized void reducePoints()
+    {
+        _reportBotPoints--;
+    }
+}
Index: java/com/l2jserver/gameserver/model/actor/instance/L2PcInstance.java
===================================================================
--- java/com/l2jserver/gameserver/model/actor/instance/L2PcInstance.java    (revision 3970)
+++ java/com/l2jserver/gameserver/model/actor/instance/L2PcInstance.java    (working copy)
@@ -76,6 +76,7 @@
import com.l2jserver.gameserver.handler.IItemHandler;
import com.l2jserver.gameserver.handler.ItemHandler;
import com.l2jserver.gameserver.instancemanager.AntiFeedManager;
+import com.l2jserver.gameserver.instancemanager.BotManager;
import com.l2jserver.gameserver.instancemanager.CastleManager;
import com.l2jserver.gameserver.instancemanager.CoupleManager;
import com.l2jserver.gameserver.instancemanager.CursedWeaponsManager;
@@ -91,6 +92,7 @@
import com.l2jserver.gameserver.model.Elementals;
import com.l2jserver.gameserver.model.FishData;
import com.l2jserver.gameserver.model.L2AccessLevel;
+import com.l2jserver.gameserver.model.L2Account;
import com.l2jserver.gameserver.model.L2CharPosition;
import com.l2jserver.gameserver.model.L2Clan;
import com.l2jserver.gameserver.model.L2ClanMember;
@@ -242,6 +244,7 @@
import com.l2jserver.gameserver.templates.item.L2WeaponType;
import com.l2jserver.gameserver.templates.skills.L2EffectType;
import com.l2jserver.gameserver.templates.skills.L2SkillType;
+import com.l2jserver.gameserver.util.BotPunish;
import com.l2jserver.gameserver.util.FloodProtectors;
import com.l2jserver.gameserver.util.Util;
import com.l2jserver.util.Point3D;
@@ -409,6 +412,10 @@

    /** The PK counter of the L2PcInstance (= Number of non PvP Flagged player killed) */
    private int _pkKills;
+    
+    /** The player's bot punishment */
+    private BotPunish _botPunish = null;
+    public L2Account _account = null;

    /** The PvP Flag state of the L2PcInstance (0=White, 1=Purple) */
    private byte _pvpFlag;
@@ -7465,7 +7472,8 @@
        {
            try { con.close(); } catch (Exception e) {}
        }
-
+        
+        player._account = new L2Account(player.getAccountName());
        return player;
    }

@@ -11716,6 +11724,35 @@
            _log.log(Level.SEVERE, "deleteMe()", e);
        }
        
+        // Bot punishment
+        if(Config.ENABLE_BOTREPORT)        
+        {
+            // Save punish
+            if(isBeingPunished())
+            {
+                try
+                {
+                    BotManager.getInstance().savePlayerPunish(this);
+                }
+                catch(Exception e)
+                {
+                    _log.log(Level.SEVERE, "deleteMe()", e);
+                }
+            }
+            // Save report points left
+            if(_account != null)
+            {
+                try
+                {
+                    _account.updatePoints(this._accountName);
+                }
+                catch(Exception e)
+                {
+                    e.printStackTrace();
+                }
+            }
+        }
+        
        try
        {
            if (_fusionSkill != null)
@@ -14717,4 +14754,76 @@
    {
        return _uiKeySettings;
    }
+    
+    /**
+     * Initializes his _botPunish object with the specified punish
+     * and for the specified time
+     * @param punishType
+     * @param minsOfPunish
+     */
+    public synchronized void setPunishDueBotting(BotPunish.Punish punishType, int minsOfPunish)
+    {
+        if(_botPunish == null)
+            _botPunish = new BotPunish(punishType, minsOfPunish);
+    }
+    
+    /**
+     * Returns the current object-representative player punish
+     * @return
+     */
+    public BotPunish getPlayerPunish()
+    {
+        return _botPunish;
+    }
+    
+    /**
+     * Returns the type of punish being applied
+     * @return
+     */
+    public BotPunish.Punish getBotPunishType()
+    {
+        return _botPunish.getBotPunishType();
+    }
+    
+    /**
+     * Will return true if the player has any bot punishment
+     * active
+     * @return
+     */
+    public boolean isBeingPunished()
+    {
+        return _botPunish != null;
+    }
+
+    /**
+     * Will end the punishment once a player attempt to
+     * perform any forbid action and his punishment has
+     * expired
+     */
+    public void endPunishment()
+    {
+        Connection con = null;
+        try
+        {
+            con = L2DatabaseFactory.getInstance().getConnection();
+            PreparedStatement statement = con.prepareStatement("DELETE FROM bot_reported_punish WHERE charId = ?");
+            statement.setInt(1, getObjectId());
+            statement.execute();
+            statement.close();
+        }
+        catch(SQLException sqle)
+        {
+            sqle.printStackTrace();
+        }
+        finally
+        {
+            try
+            {
+                con.close();
+            }
+            catch(SQLException sqle) { }
+        }
+        _botPunish = null;
+        this.sendMessage("Your punishment has expired. Do not bot again!");
+    }
}
Index: java/com/l2jserver/gameserver/model/actor/stat/PcStat.java
===================================================================
--- java/com/l2jserver/gameserver/model/actor/stat/PcStat.java    (revision 3970)
+++ java/com/l2jserver/gameserver/model/actor/stat/PcStat.java    (working copy)
@@ -48,6 +48,9 @@
    public static final int VITALITY_LEVELS[] = {240, 2000, 13000, 17000, 20000};
    public static final int MAX_VITALITY_POINTS = VITALITY_LEVELS[4];
    public static final int MIN_VITALITY_POINTS = 1;
+    
+    // Used to check if the player has gained exp since log in
+    public long _firstExp;

    // =========================================================
    // Constructor
@@ -563,4 +566,25 @@
    {
        return _vitalityLevel;
    }
+    
+    /**
+     * Sets exp holded by the character on log in
+     * @param current exp
+     */
+    public void setFirstExp(long value)
+    {
+        _firstExp = value;
+    }
+    
+    /**
+     * Will return true if the player has gained exp
+     * since logged in
+     * @return
+     */
+    public boolean hasEarnedExp()
+    {
+        if(getExp() - _firstExp != 0)
+            return true;
+        return false;
+    }
}
Index: java/com/l2jserver/gameserver/network/SystemMessageId.java
===================================================================
--- java/com/l2jserver/gameserver/network/SystemMessageId.java    (revision 3970)
+++ java/com/l2jserver/gameserver/network/SystemMessageId.java    (working copy)
@@ -13875,6 +13875,55 @@
    TIME_LIMITED_ITEM_DELETED(2366),
    
    /**
+     * ID: 2371
+     * Message: $c1 was reported as a BOT.
+     */
+    C1_REPORTED_AS_BOT(2371),
+    
+    /**
+     * ID: 2377
+     * Message: You cannot report a character who is in a peace zone or a battlefield.
+     */
+    CANNOT_REPORT_TARGET_IN_PEACE_ZONE(2377),
+    
+    /**
+     * ID: 2378
+     * Message: You cannot report when a clan war has been declared.
+     */
+    CANNOT_REPORT_TARGET_IN_CLAN_WAR(2378),
+    
+    /**
+     * ID: 2379
+     * Message: You cannot report a character who has not acquired 
+     *             any Exp. after connecting.
+     */
+    CANNOT_REPORT_CHARACTER_WITHOUT_GAINEXP(2379),
+    
+    /**
+     * ID: 2380
+     * Message: You cannot report this person again at this time.
+     */
+    CANNOT_REPORT_CHARACTER_AGAIN(2380),
+    
+    /**
+     * ID: 2381
+     * Message: You cannot report this person again at this time.
+     */
+    CANNOT_REPORT_CHARACTER_AGAIN_2(2381),
+
+    /**
+     * ID: 2382
+     * Message: You cannot report this person again at this time.
+     */
+    CANNOT_REPORT_CHARACTER_AGAIN_3(2382),
+
+    /**
+     * ID: 2383
+     * Message: You cannot report this person again at this time.
+     */
+    CANNOT_REPORT_CHARACTER_AGAIN_4(2383),
+
+    /**
    * ID: 2390<br>
    * Message: Your number of My Teleports slots has reached its maximum limit.
    */
@@ -13971,6 +14020,138 @@
    OLYMPIAD_3VS3_CONFIRM(2465),
    
    /**
+     * ID: 2470
+     * Message: This character cannot make a report. You cannot make a report while located 
+     *             inside a peace zone or a battlefield, while you are an opposing clan member 
+     *             during a clan war, or while participating in the Olympiad. 
+     */
+    CANNOT_REPORT_IN_WARZONE_PEACEZONE_CLANWAR_OLYMPIAD(2470),
+    
+    /**
+     * ID: 2471
+     * Message: This character cannot make a report. The target has already been reported 
+     *             by either your clan or alliance, or has already been reported from your 
+     *             current IP.
+     */
+    CANNOT_REPORT_ALREDY_REPORTED_FROM_YOUR_CLAN_OR_IP(2471),
+    
+    /**
+     * ID: 2472
+     * Message: This character cannot make a report because another character from this account 
+     *             has already done so.
+     */
+    CANNOT_REPORT_ALAREDY_REPORTED_FROM_SAME_ACCOUNT(2472),
+    
+    /**
+     * ID: 2473
+     * Message: You have been reported as an illegal program user, and your chatting will be blocked for 10 minutes.
+     */
+    REPORTED_10_MINS_WITHOUT_CHAT(2473),
+    
+    /**
+     * ID: 2474
+     * Message: You have been reported as an illegal program user, and your party participation will be blocked 
+     *             for 60 minutes.
+     */
+    REPORTED_60_MINS_WITHOUT_JOIN_PARTY(2474),
+    
+    /**
+     * ID: 2475
+     * Message: You have been reported as an illegal program user, and your party participation will be blocked 
+     *             for 120 minutes.
+     */
+    REPORTED_120_MINS_WITHOUT_JOIN_PARTY(2475),
+    
+    /**
+     * ID: 2476
+     * Message: You have been reported as an illegal program user, and your party participation will be blocked 
+     *             for 180 minutes.
+     */
+    REPORTED_180_MINS_WITHOUT_JOIN_PARTY(2476),
+    
+    /**
+     * ID: 2477
+     * Message: You have been reported as an illegal program user, and your actions will be restricted for 120 minutes.
+     */
+    REPORTED_120_MINS_WITHOUT_ACTIONS(2477),
+    
+    /**
+     * ID: 2478
+     * Message: You have been reported as an illegal program user, and your actions will be restricted for 180 minutes.
+     */
+    REPORTED_180_MINS_WITHOUT_ACTIONS(2478),
+
+    /**
+     * ID: 2479
+     * Message: You have been reported as an illegal program user, and your actions will be restricted for 180 minutes.
+     */
+    REPORTED_180_MINS_WITHOUT_ACTIONS_2(2479),
+    
+    /**
+     * ID: 2480
+     * Message: You have been reported as an illegal program user, and moving will be blocked for 120 minutes.
+     */
+    REPORTED_120_MINS_WITHOUT_MOVE(2480),
+    
+    /**
+     * ID: 2481
+     * Message: $c1% has been reported as an illegal program user and has been investigated.
+     */
+    USER_REPORTED_AND_BEING_INVESTIGATED(2481),
+    
+    /**
+     * ID: 2482
+     * Message: $c1% has been reported as an illegal program user and cannot join a party.
+     */
+    USER_REPORTED_AND_CANNOT_JOIN_PARTY(2482),
+    
+    /**
+     * ID: 2483
+     * Message: You have been reported as an illegal program user, and chatting is not allowed.
+     */
+    YOU_HAVE_BEEN_REPORTED_AND_CANNOT_CHAT(2483),
+    
+    /**
+     * ID: 2484
+     * Message: You have been reported as an illegal program user, and participating in a party is not allowed.
+     */
+    YOU_HAVE_BEEN_REPORTED_AND_CANNOT_JOIN_PARTY(2484),
+    
+    /**
+     * ID: 2485
+     * Message: You have been reported as an illegal program user, and your activities are only allowed within 
+     *             limitation.
+     */
+    YOU_HAVE_BEEN_REPORTED_AND_ACTIONS_ARE_LIMITED(2485),
+    
+    /**
+     * ID: 2486
+     * Message: You have been blocked due to verification that you are using a third party program. 
+     *             Subsequent violations may result termination of the account rather than a penalty within the game, 
+     *             so please keep this in mind.
+     */
+    CHAR_BLOCKED_DUE_THIRD_PARTY_SOFTWARE_USE_VERIFICATION(2486),
+    
+    /**
+     * ID: 2487
+     * Message: You have been reported as an illegal program user, and your connection has been ended. Please 
+     *             contact our CS team to confirm your identity. 
+     */
+    YOU_HAVE_BEEN_REPORTED_AND_CONNECTION_HAS_ENDED(2487),
+    
+    /**
+     * ID: 2748
+     * Message: You have been reported as an illegal program user and cannot report other users.
+     */
+    YOU_HAVE_BEEN_REPORTED_AND_CANNOT_REPORT(2748),
+    
+    /**
+     * ID: 2774
+     * Message: You can make another report in $s1-minute(s). You have $s2 points remaining on this account.
+     */
+    YOU_CAN_REPORT_IN_S1_MINUTES_S2_REPORT_POINTS_REMAIN_IN_ACCOUNT(2774),
+    
+    /**
    * ID: 2500<br>
    * Message: The collection has succeeded.
    */
Index: java/com/l2jserver/gameserver/network/clientpackets/EnterWorld.java
===================================================================
--- java/com/l2jserver/gameserver/network/clientpackets/EnterWorld.java    (revision 3970)
+++ java/com/l2jserver/gameserver/network/clientpackets/EnterWorld.java    (working copy)
@@ -30,6 +30,7 @@
import com.l2jserver.gameserver.datatables.GMSkillTable;
import com.l2jserver.gameserver.datatables.MapRegionTable;
import com.l2jserver.gameserver.datatables.SkillTable;
+import com.l2jserver.gameserver.instancemanager.BotManager;
import com.l2jserver.gameserver.instancemanager.CastleManager;
import com.l2jserver.gameserver.instancemanager.ClanHallManager;
import com.l2jserver.gameserver.instancemanager.CoupleManager;
@@ -184,6 +185,10 @@
            if (Config.GM_GIVE_SPECIAL_SKILLS)
                GMSkillTable.getInstance().addSkills(activeChar);
        }
+        
+        // Bot manager punishment
+        if(Config.ENABLE_BOTREPORT)
+            BotManager.getInstance().onEnter(activeChar);

        // Set dead status if applies
        if (activeChar.getCurrentHp() < 0.5)
Index: java/com/l2jserver/gameserver/network/clientpackets/RequestActionUse.java
===================================================================
--- java/com/l2jserver/gameserver/network/clientpackets/RequestActionUse.java    (revision 3970)
+++ java/com/l2jserver/gameserver/network/clientpackets/RequestActionUse.java    (working copy)
@@ -22,6 +22,7 @@
import com.l2jserver.gameserver.ai.L2SummonAI;
import com.l2jserver.gameserver.datatables.PetSkillsTable;
import com.l2jserver.gameserver.datatables.SkillTable;
+import com.l2jserver.gameserver.instancemanager.BotManager;
import com.l2jserver.gameserver.instancemanager.CastleManager;
import com.l2jserver.gameserver.model.L2CharPosition;
import com.l2jserver.gameserver.model.L2ManufactureList;
@@ -41,6 +42,7 @@
import com.l2jserver.gameserver.network.serverpackets.RecipeShopManageList;
import com.l2jserver.gameserver.network.serverpackets.SocialAction;
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
+import com.l2jserver.gameserver.util.BotPunish;


/**
@@ -73,6 +75,34 @@
        if (activeChar == null)
            return;
        
+        // Check if has any bot punishment
+        if(activeChar.isBeingPunished())
+        {
+            // Remove punishment if finished
+            if(activeChar.getPlayerPunish().canPerformAction() && activeChar.getBotPunishType() == BotPunish.Punish.ACTIONBAN)
+            {
+                activeChar.endPunishment();
+            }
+            // Else apply it
+            else
+            {
+                SystemMessageId msgId = null;
+                switch(activeChar.getPlayerPunish().getDuration())
+                {
+                    case 7200:
+                        msgId = SystemMessageId.REPORTED_120_MINS_WITHOUT_ACTIONS;
+                        break;
+                    case 10800:
+                        msgId = SystemMessageId.REPORTED_180_MINS_WITHOUT_ACTIONS;
+                        break;
+                        default:
+                }
+                activeChar.sendPacket(new SystemMessage(msgId));
+                return;
+            }
+                
+        }
+
        if (Config.DEBUG)
            _log.finest(activeChar.getName() + " request Action use: id " + _actionId + " 2:" + _ctrlPressed + " 3:" + _shiftPressed);
        
@@ -347,7 +377,29 @@
                activeChar.tryOpenPrivateSellStore(true);
                break;
            case 65: // Bot Report Button
-                activeChar.sendMessage("Action not handled yet.");
+                if(Config.ENABLE_BOTREPORT)
+                {
+                    if(activeChar.getTarget() instanceof L2PcInstance)
+                    {
+                        L2PcInstance reported = (L2PcInstance) activeChar.getTarget();
+                        if(!BotManager.getInstance().validateBot(reported, activeChar))
+                            return;
+                        if(!BotManager.getInstance().validateReport(activeChar))
+                            return;
+                        try
+                        {
+                            BotManager.getInstance().reportBot(reported, activeChar);
+                        }
+                        catch (Exception e)
+                        {
+                            e.printStackTrace();
+                        }
+                    }
+                    else
+                        activeChar.sendMessage("Your target is not a player!");
+                }
+                else
+                    activeChar.sendMessage("Action disabled.");
                break;
            case 67: // Steer
            case 68: // Cancel Control
Index: java/com/l2jserver/gameserver/network/clientpackets/RequestJoinParty.java
===================================================================
--- java/com/l2jserver/gameserver/network/clientpackets/RequestJoinParty.java    (revision 3970)
+++ java/com/l2jserver/gameserver/network/clientpackets/RequestJoinParty.java    (working copy)
@@ -24,6 +24,7 @@
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.serverpackets.AskJoinParty;
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
+import com.l2jserver.gameserver.util.BotPunish;


/**
@@ -67,6 +68,52 @@
             return;
         }
         
+        // Check for bot punishment on target
+        if(target.isBeingPunished())
+        {
+            // Check conditions
+            if(target.getPlayerPunish().canJoinParty() && target.getBotPunishType() == BotPunish.Punish.PARTYBAN)
+            {
+                target.endPunishment();
+            }
+            else
+            {
+                // Inform the player cannot join party
+                requestor.sendPacket(new SystemMessage(SystemMessageId.USER_REPORTED_AND_CANNOT_JOIN_PARTY));
+                return;
+            }
+                
+        }
+        
+        // Check for bot punishment on requestor
+        if(requestor.isBeingPunished())
+        {
+            // Check conditions
+            if(requestor.getPlayerPunish().canJoinParty() && requestor.getBotPunishType() == BotPunish.Punish.PARTYBAN)
+            {
+                requestor.endPunishment();
+            }
+            else
+            {
+                SystemMessageId msgId = null;
+                switch(requestor.getPlayerPunish().getDuration())
+                {
+                    case 3600:
+                        msgId = SystemMessageId.REPORTED_60_MINS_WITHOUT_JOIN_PARTY;
+                        break;
+                    case 7200:
+                        msgId = SystemMessageId.REPORTED_120_MINS_WITHOUT_JOIN_PARTY;
+                        break;
+                    case 10800:
+                        msgId = SystemMessageId.REPORTED_180_MINS_WITHOUT_JOIN_PARTY;
+                        break;
+                        default:
+                }    
+                requestor.sendPacket(new SystemMessage(msgId));
+                return;
+            }    
+        }
+        
         if (target.getAppearance().getInvisible())
         {
             requestor.sendPacket(new SystemMessage(SystemMessageId.TARGET_IS_INCORRECT));
Index: java/com/l2jserver/gameserver/network/clientpackets/Say2.java
===================================================================
--- java/com/l2jserver/gameserver/network/clientpackets/Say2.java    (revision 3970)
+++ java/com/l2jserver/gameserver/network/clientpackets/Say2.java    (working copy)
@@ -24,6 +24,8 @@
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.network.SystemMessageId;
import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
+import com.l2jserver.gameserver.util.BotPunish;
+import com.l2jserver.gameserver.util.Util;


/**
@@ -78,6 +80,10 @@
        "UNKNOWN",
        "BATTLEFIELD"
    };
+    
+    private static final String[] WALKER_COMMAND_LIST = { "USESKILL", "USEITEM", "BUYITEM", "SELLITEM", "SAVEITEM", "LOADITEM", "MSG", "SET", "DELAY", "LABEL", "JMP", "CALL",
+        "RETURN", "MOVETO", "NPCSEL", "NPCDLG", "DLGSEL", "CHARSTATUS", "POSOUTRANGE", "POSINRANGE", "GOHOME", "SAY", "EXIT", "PAUSE", "STRINDLG", "STRNOTINDLG", "CHANGEWAITTYPE",
+        "FORCEATTACK", "ISMEMBER", "REQUESTJOINPARTY", "REQUESTOUTPARTY", "QUITPARTY", "MEMBERSTATUS", "CHARBUFFS", "ITEMCOUNT", "FOLLOWTELEPORT" };

    private String _text;
    private int _type;
@@ -87,7 +93,7 @@
    protected void readImpl()
    {
        _text = readS();
-        _type = readD();
+            _type = readD();
        _target = (_type == TELL) ? readS() : null;
    }

@@ -101,6 +107,23 @@
        if (activeChar == null)
            return;
        
+        // Check for bot punishment
+        if(activeChar.isBeingPunished())
+        {
+            // Check if punishment expired
+            if(activeChar.getPlayerPunish().canTalk() && activeChar.getBotPunishType() == BotPunish.Punish.CHATBAN)
+            {
+                activeChar.endPunishment();
+            }
+            // Else, apply it
+            else
+            {
+                // Inform player
+                activeChar.sendPacket(new SystemMessage(SystemMessageId.REPORTED_10_MINS_WITHOUT_CHAT));
+                return;
+            }            
+        }
+        
        if (_type < 0 || _type >= CHAT_NAMES.length)
        {
            _log.warning("Say2: Invalid type: " +_type + " Player : " + activeChar.getName() + " text: " + String.valueOf(_text));
@@ -120,6 +143,12 @@
            activeChar.sendPacket(new SystemMessage(SystemMessageId.DONT_SPAM));
            return;
        }
+        
+        if (_type == TELL && checkBot(_text))
+        {
+            Util.handleIllegalPlayerAction(activeChar, "Client Emulator Detect: Player " + activeChar.getName() + " using l2walker.", Config.DEFAULT_PUNISH);
+            return;
+        }

        if (activeChar.isCursedWeaponEquipped() && (_type == TRADE || _type == SHOUT))
        {
@@ -177,6 +206,16 @@
            filteredText = filteredText.replaceAll("(?i)" + pattern, Config.CHAT_FILTER_CHARS);
        _text = filteredText;
    }
+    
+    private boolean checkBot(String text)
+    {
+        for (String botCommand : WALKER_COMMAND_LIST)
+        {
+            if (text.startsWith(botCommand))
+                return true;
+        }
+        return false;
+    }

    /* (non-Javadoc)
      * @see com.l2jserver.gameserver.clientpackets.ClientBasePacket#getType()
Index: java/com/l2jserver/gameserver/network/serverpackets/MoveToLocation.java
===================================================================
--- java/com/l2jserver/gameserver/network/serverpackets/MoveToLocation.java    (revision 3970)
+++ java/com/l2jserver/gameserver/network/serverpackets/MoveToLocation.java    (working copy)
@@ -14,7 +14,11 @@
  */
package com.l2jserver.gameserver.network.serverpackets;

+import com.l2jserver.Config;
import com.l2jserver.gameserver.model.actor.L2Character;
+import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
+import com.l2jserver.gameserver.network.SystemMessageId;
+import com.l2jserver.gameserver.util.BotPunish;

/**
  * 0000: 01  7a 73 10 4c  b2 0b 00 00  a3 fc 00 00  e8 f1 ff    .zs.L...........
@@ -29,9 +33,11 @@
{
    private static final String _S__01_CHARMOVETOLOCATION = "[S] 2f MoveToLocation";
    private int _charObjId, _x, _y, _z, _xDst, _yDst, _zDst;
+    private L2Character _cha;

    public MoveToLocation(L2Character cha)
    {
+        _cha = cha;
        _charObjId = cha.getObjectId();
        _x = cha.getX();
        _y = cha.getY();
@@ -44,6 +50,24 @@
    @Override
    protected final void writeImpl()
    {
+        // Bot punishment restriction
+        if(_cha instanceof L2PcInstance && Config.ENABLE_BOTREPORT)
+        {
+            L2PcInstance actor = (L2PcInstance) _cha;
+            if(actor.isBeingPunished())
+            {
+                if(actor.getPlayerPunish().canWalk() && actor.getPlayerPunish().getBotPunishType() == BotPunish.Punish.MOVEBAN)
+                {
+                    actor.endPunishment();
+                }
+                else
+                {
+                    actor.sendPacket(new SystemMessage(SystemMessageId.REPORTED_120_MINS_WITHOUT_MOVE));
+                    return;
+                }
+            }
+        }
+        
        writeC(0x2f);

        writeD(_charObjId);
Index: java/com/l2jserver/gameserver/taskmanager/tasks/TaskReportPointsRestore.java
===================================================================
--- java/com/l2jserver/gameserver/taskmanager/tasks/TaskReportPointsRestore.java    (revision 0)
+++ java/com/l2jserver/gameserver/taskmanager/tasks/TaskReportPointsRestore.java    (revision 0)
@@ -0,0 +1,78 @@
+/*
+ * 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 com.l2jserver.gameserver.taskmanager.tasks;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import com.l2jserver.L2DatabaseFactory;
+import com.l2jserver.gameserver.taskmanager.Task;
+import com.l2jserver.gameserver.taskmanager.TaskManager;
+import com.l2jserver.gameserver.taskmanager.TaskTypes;
+import com.l2jserver.gameserver.taskmanager.TaskManager.ExecutedTask;
+
+/**
+ * @author BiggBoss
+ */
+public class TaskReportPointsRestore extends Task
+{
+    private static final String NAME = "report_points_restore";
+    
+    /* (non-Javadoc)
+     * @see com.l2jserver.gameserver.taskmanager.Task#getName()
+     */
+    @Override
+    public String getName()
+    {
+        return NAME;
+    }
+
+    /* (non-Javadoc)
+     * @see com.l2jserver.gameserver.taskmanager.Task#onTimeElapsed(com.l2jserver.gameserver.taskmanager.TaskManager.ExecutedTask)
+     */
+    @Override
+    public void onTimeElapsed(ExecutedTask task)
+    {
+        Connection con = null;
+        try
+        {
+            con = L2DatabaseFactory.getInstance().getConnection();
+            PreparedStatement update = con.prepareStatement("UPDATE accounts SET bot_report_points = 7");
+            update.execute();
+            update.close();
+            System.out.println("Sucessfully restored Bot Report Points for all accounts!");
+        }
+        catch(SQLException sqle)
+        {
+            sqle.printStackTrace();
+        }
+        finally
+        {
+            try { con.close(); } catch(Exception e) { e.printStackTrace(); }
+        }
+    }
+    
+    /**
+     * 
+     * @see com.l2jserver.gameserver.taskmanager.Task#initializate()
+     */
+    @Override
+    public void initializate()
+    {
+        super.initializate();
+        TaskManager.addUniqueTask(NAME, TaskTypes.TYPE_GLOBAL_TASK, "1", "00:00:00", "");
+    }
+}
Index: java/com/l2jserver/gameserver/util/BotPunish.java
===================================================================
--- java/com/l2jserver/gameserver/util/BotPunish.java    (revision 0)
+++ java/com/l2jserver/gameserver/util/BotPunish.java    (revision 0)
@@ -0,0 +1,131 @@
+/*
+ * 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 com.l2jserver.gameserver.util;
+
+/**
+ * @author BiggBoss
+ */
+public class BotPunish
+{
+    // Kind of punish
+    private Punish _botPunishment;
+    // Time the punish will last
+    private long _punishTime;
+    // Punis time (in secs)
+    private int _punishDuration;
+    
+    // Type of punishments
+    public enum Punish
+    {
+        CHATBAN,
+        MOVEBAN,
+        PARTYBAN,
+        ACTIONBAN
+    }
+    
+    public BotPunish(Punish punish, int mins)
+    {
+        _botPunishment = punish;
+        _punishTime = System.currentTimeMillis() + ( mins * 60 * 1000);
+        _punishDuration = mins * 60;
+    }
+    
+    /**
+     * Returns the current punishment type
+     * @return Punish (BotPunish enum)
+     */
+    public Punish getBotPunishType()
+    {
+        return _botPunishment;
+    }
+    
+    /**
+     * Returns the time (in millis) when the player
+     * punish started
+     * @return long 
+     */
+    public long getPunishStarterTime()
+    {
+        return _punishTime;
+    }
+    
+    /**
+     * Returns the duration (in seconds) of the applied
+     * punish
+     * @return int 
+     */
+    public int getDuration()
+    {
+        return _punishDuration;
+    }
+    
+    /**
+     * Return the time left to end up this punish
+     * @return long
+     */
+    public long getPunishTimeLeft()
+    {
+        long left = System.currentTimeMillis() - _punishTime;
+        return left;
+    }
+    
+    /**
+     * @return true if the player punishment has
+     * expired 
+     */
+    public boolean canWalk()
+    {
+        if(_botPunishment == Punish.MOVEBAN
+                && System.currentTimeMillis() - _punishTime <= 0)
+            return false;
+        return true;
+    }
+    
+    /**
+     * @return true if the player punishment has
+     * expired 
+     */
+    public boolean canTalk()
+    {
+        if(_botPunishment == Punish.CHATBAN
+                && System.currentTimeMillis() - _punishTime <= 0)
+            return false;
+        return true;
+    }
+    
+    /**
+     * @return true if the player punishment has
+     * expired 
+     */
+    public boolean canJoinParty()
+    {
+        if(_botPunishment == Punish.PARTYBAN
+                && System.currentTimeMillis() - _punishTime <= 0)
+            return false;
+        return true;
+    }
+    
+    /**
+     * @return true if the player punishment has
+     * expired 
+     */
+    public boolean canPerformAction()
+    {
+        if(_botPunishment == Punish.ACTIONBAN
+                && System.currentTimeMillis() - _punishTime <= 0)
+            return false;
+        return true;
+    }
+}
Index: java/config/General.properties
===================================================================
--- java/config/General.properties    (revision 3970)
+++ java/config/General.properties    (working copy)
@@ -659,6 +659,13 @@
# Default: False
CustomNpcBufferTables = False

+# ---------------------------------------------------------------------------
+# Bot Report
+# ---------------------------------------------------------------------------
+# If enabled, the action button in the character action inventory
+# called Bot Report will be used to report bots (stored in database)
+# Default: True
+EnableBotReport = True

# ---------------------------------------------------------------------------
# Developer Settings
2ª Parte do Codigo
Código:
Index: data/scripts/handlers/MasterHandler.java
===================================================================
--- data/scripts/handlers/MasterHandler.java    (revision 7123)
+++ data/scripts/handlers/MasterHandler.java    (working copy)
@@ -50,6 +50,7 @@
        AdminCommandHandler.getInstance().registerAdminCommandHandler(new AdminCache());
        AdminCommandHandler.getInstance().registerAdminCommandHandler(new AdminCamera());
        AdminCommandHandler.getInstance().registerAdminCommandHandler(new AdminChangeAccessLevel());
+        AdminCommandHandler.getInstance().registerAdminCommandHandler(new AdminCheckBot());
        AdminCommandHandler.getInstance().registerAdminCommandHandler(new AdminCreateItem());
        AdminCommandHandler.getInstance().registerAdminCommandHandler(new AdminCursedWeapons());
        AdminCommandHandler.getInstance().registerAdminCommandHandler(new AdminDelete());
Index: data/scripts/handlers/admincommandhandlers/AdminCheckBot.java
===================================================================
--- data/scripts/handlers/admincommandhandlers/AdminCheckBot.java    (revision 0)
+++ data/scripts/handlers/admincommandhandlers/AdminCheckBot.java    (revision 0)
@@ -0,0 +1,228 @@
+package handlers.admincommandhandlers;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import javolution.text.TextBuilder;
+
+import com.l2jserver.Config;
+import com.l2jserver.L2DatabaseFactory;
+import com.l2jserver.gameserver.handler.IAdminCommandHandler;
+import com.l2jserver.gameserver.instancemanager.BotManager;
+import com.l2jserver.gameserver.model.L2World;
+import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
+import com.l2jserver.gameserver.network.SystemMessageId;
+import com.l2jserver.gameserver.network.serverpackets.NpcHtmlMessage;
+import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
+import com.l2jserver.gameserver.util.BotPunish;
+
+/**
+ * @author BiggBoss
+ */
+public class AdminCheckBot implements IAdminCommandHandler
+{
+    private static final String[] ADMIN_COMMANDS = {
+            "admin_checkBots",
+            "admin_readBot",
+            "admin_markBotReaded",
+            "admin_punish_bot"
+    };
+    
+    @Override
+    public String[] getAdminCommandList()
+    {
+        return ADMIN_COMMANDS;
+    }
+    
+    @Override
+    public boolean useAdminCommand(String command, L2PcInstance activeChar)
+    {
+        if (!Config.ENABLE_BOTREPORT)
+        {
+            activeChar.sendMessage("Bot reporting is not enabled!");
+            return false;
+        }
+        
+        String[] sub = command.split(" ");
+        if (command.startsWith("admin_checkBots"))
+        {
+            sendBotPage(activeChar);
+        }
+        else if (command.startsWith("admin_readBot"))
+        {
+            sendBotInfoPage(activeChar, Integer.valueOf(sub[1]));
+        }
+        else if (command.startsWith("admin_markBotReaded"))
+        {
+            try
+            {
+                BotManager.getInstance().markAsRead(Integer.valueOf(sub[1]));
+                sendBotPage(activeChar);
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+        else if (command.startsWith("admin_punish_bot"))
+        {
+            activeChar.sendMessage("Usage: //punish_bot <charName>");
+            
+            if (sub != null)
+            {
+                L2PcInstance target = L2World.getInstance().getPlayer(sub[1]);
+                if (target != null)
+                {
+                    synchronized (target)
+                    {
+                        int punishLevel = 0;
+                        try
+                        {
+                            punishLevel = BotManager.getInstance().getPlayerReportsCount(target);
+                        }
+                        catch (Exception e)
+                        {
+                            e.printStackTrace();
+                        }
+                        
+                        // By System Message guess:
+                        // Reported 1 time = 10 mins chat ban
+                        // Reported 2 times = 60 mins w/o join pt
+                        // Reported 3 times = 120 mins w/o join pt
+                        // Reported 4 times = 180 mins w/o join pt
+                        // Reported 5 times = 120 mins w/o move
+                        // Reported 6 times = 180 mins w/o move
+                        // Reported 7 times = 120 mins w/o any action
+                        
+                        // Must be handled by GM or automatically ?
+                        // Since never will be retail info, ill put manually
+                        switch (punishLevel)
+                        {
+                        case 1:
+                            target.setPunishDueBotting(BotPunish.Punish.CHATBAN, 10);
+                            target.sendPacket(new SystemMessage(SystemMessageId.REPORTED_10_MINS_WITHOUT_CHAT));
+                            break;
+                        case 2:
+                            target.setPunishDueBotting(BotPunish.Punish.PARTYBAN, 60);
+                            target.sendPacket(new SystemMessage(SystemMessageId.REPORTED_60_MINS_WITHOUT_JOIN_PARTY));
+                            break;
+                        case 3:
+                            target.setPunishDueBotting(BotPunish.Punish.PARTYBAN, 120);
+                            target.sendPacket(new SystemMessage(SystemMessageId.REPORTED_120_MINS_WITHOUT_JOIN_PARTY));
+                            break;
+                        case 4:
+                            target.setPunishDueBotting(BotPunish.Punish.PARTYBAN, 180);
+                            target.sendPacket(new SystemMessage(SystemMessageId.REPORTED_180_MINS_WITHOUT_JOIN_PARTY));
+                            break;
+                        case 5:
+                            target.setPunishDueBotting(BotPunish.Punish.MOVEBAN, 120);
+                            target.sendPacket(new SystemMessage(SystemMessageId.REPORTED_120_MINS_WITHOUT_MOVE));
+                            break;
+                        case 6:
+                            target.setPunishDueBotting(BotPunish.Punish.ACTIONBAN, 120);
+                            target.sendPacket(new SystemMessage(SystemMessageId.REPORTED_120_MINS_WITHOUT_ACTIONS));
+                            break;
+                        case 7:
+                            target.setPunishDueBotting(BotPunish.Punish.ACTIONBAN, 180);
+                            target.sendPacket(new SystemMessage(SystemMessageId.REPORTED_180_MINS_WITHOUT_ACTIONS));
+                            break;
+                        default:
+                            activeChar.sendMessage("Your target wasnt reported as a bot!");
+                        }
+                        // Inserts first time player punish in database, avoiding
+                        // problems to update punish state in future on log out
+                        if (punishLevel != 0)
+                        {
+                            introduceNewPunishedBotAndClear(target);
+                            activeChar.sendMessage(target.getName() + " has been punished");
+                        }
+                    }
+                }
+                else
+                    activeChar.sendMessage("Your target doesnt exist!");
+            }
+        }
+        return true;
+    }
+    
+    private static void sendBotPage(L2PcInstance activeChar)
+    {
+        TextBuilder tb = new TextBuilder();
+        tb.append("<html><title>Unread Bot List</title><body><center>");
+        tb.append("Here's a list of the current <font color=LEVEL>unread</font><br1>bots!<br>");
+        
+        for (int i : BotManager.getInstance().getUnread().keySet())
+        {
+            tb.append("<a action=\"bypass -h admin_readBot " + i + "\">Ticket #" + i + "</a><br1>");
+        }
+        tb.append("</center></body></html>");
+        
+        NpcHtmlMessage nhm = new NpcHtmlMessage(5);
+        nhm.setHtml(tb.toString());
+        activeChar.sendPacket(nhm);
+    }
+    
+    private static void sendBotInfoPage(L2PcInstance activeChar, int botId)
+    {
+        String[] report = BotManager.getInstance().getUnread().get(botId);
+        TextBuilder tb = new TextBuilder();
+        
+        tb.append("<html><title>Bot #" + botId + "</title><body><center><br>");
+        tb.append("- Bot report ticket Id: <font color=FF0000>" + botId + "</font><br>");
+        tb.append("- Player reported: <font color=FF0000>" + report[0] + "</font><br>");
+        tb.append("- Reported by: <font color=FF0000>" + report[1] + "</font><br>");
+        tb.append("- Date: <font color=FF0000>" + report[2] + "</font><br>");
+        tb.append("<a action=\"bypass -h admin_markBotReaded " + botId + "\">Mark Report as Read</a>");
+        tb.append("<a action=\"bypass -h admin_punish_bot " + report[0] + "\">Punish " + report[0] + "</a>");
+        tb.append("<a action=\"bypass -h admin_checkBots\">Go Back to bot list</a>");
+        tb.append("</center></body></html>");
+        
+        NpcHtmlMessage nhm = new NpcHtmlMessage(5);
+        nhm.setHtml(tb.toString());
+        activeChar.sendPacket(nhm);
+    }
+    
+    /**
+     * Will introduce the first time a new punished bot in database,
+     * to avoid problems on his punish time left update, as will remove
+     * his reports from database
+     * @param L2PcInstance
+     */
+    private static void introduceNewPunishedBotAndClear(L2PcInstance target)
+    {
+        Connection con = null;
+        try
+        {
+            
+            con = L2DatabaseFactory.getInstance().getConnection();
+            // Introduce new Punished Bot in database
+            PreparedStatement statement = con.prepareStatement("INSERT INTO bot_reported_punish VALUES ( ?, ?, ? )");
+            statement.setInt(1, target.getObjectId());
+            statement.setString(2, target.getPlayerPunish().getBotPunishType().name());
+            statement.setLong(3, target.getPlayerPunish().getPunishTimeLeft());
+            statement.execute();
+            statement.close();
+            
+            // Delete all his reports from database
+            PreparedStatement delStatement = con.prepareStatement("DELETE FROM bot_report WHERE reported_objectId = ?");
+            delStatement.setInt(1, target.getObjectId());
+            delStatement.execute();
+            delStatement.close();
+        }
+        catch (SQLException sqle)
+        {
+            sqle.printStackTrace();
+        }
+        finally
+        {
+            try
+            {
+                con.close();
+            }
+            catch (SQLException e)
+            {
+            }
+        }
+    }
+}
Index: sql/accounts.sql
===================================================================
--- sql/accounts.sql    (revision 7123)
+++ sql/accounts.sql    (working copy)
@@ -11,5 +11,6 @@
   `hop2` char(15) DEFAULT NULL,
   `hop3` char(15) DEFAULT NULL,
   `hop4` char(15) DEFAULT NULL,
+  `bot_report_points` int(10) NOT NULL DEFAULT 7,
   PRIMARY KEY (`login`)
);
\ No newline at end of file
Index: sql/admin_command_access_rights.sql
===================================================================
--- sql/admin_command_access_rights.sql    (revision 7123)
+++ sql/admin_command_access_rights.sql    (working copy)
@@ -559,4 +559,10 @@

-- voice commands
('banchat','7'),
-('unbanchat','7');
\ No newline at end of file
+('unbanchat','7'),
+
+-- BOT REPORT
+('admin_checkBots', '1'),
+('admin_readBot', '1'),
+('admin_markBotReaded', '1'),
+('admin_punish_bot', '1');
\ No newline at end of file
Index: sql/bot_report.sql
===================================================================
--- sql/bot_report.sql    (revision 0)
+++ sql/bot_report.sql    (revision 0)
@@ -0,0 +1,10 @@
+CREATE TABLE IF NOT EXISTS `bot_report` (
+  `report_id` int(10) NOT NULL auto_increment,
+  `reported_name` varchar(45) DEFAULT NULL,
+  `reported_objectId` int(10) DEFAULT NULL,
+  `reporter_name` varchar(45) DEFAULT NULL,
+  `reporter_objectId` int(10) DEFAULT NULL,
+  `date` DECIMAL(20,0) NOT NULL default 0,
+  `read` enum('true','false') DEFAULT 'false' NOT NULL,
+  PRIMARY KEY (`report_id`)
+);
\ No newline at end of file
Index: sql/bot_reported_punish.sql
===================================================================
--- sql/bot_reported_punish.sql    (revision 0)
+++ sql/bot_reported_punish.sql    (revision 0)
@@ -0,0 +1,6 @@
+CREATE TABLE IF NOT EXISTS `bot_reported_punish` (
+  `charId` int(11) NOT NULL DEFAULT '0',
+  `punish_type` varchar(45) DEFAULT NULL,
+  `time_left` bigint(20) DEFAULT NULL,
+  PRIMARY KEY (`charId`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
\ No newline at end of file
Index: tools/database_installer.bat
===================================================================
--- tools/database_installer.bat    (revision 7123)
+++ tools/database_installer.bat    (working copy)
@@ -706,6 +706,8 @@
auto_announcements.sql
auto_chat.sql
auto_chat_text.sql
+bot_report.sql
+bot_reported_punish.sql
castle_door.sql
castle_doorupgrade.sql
castle_functions.sql
Creditos: BiggBoss