BotReportTable.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. /*
  2. * Copyright (C) 2004-2015 L2J Server
  3. *
  4. * This file is part of L2J Server.
  5. *
  6. * L2J Server is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * L2J Server is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.l2jserver.gameserver.datatables;
  20. import java.io.File;
  21. import java.io.FileNotFoundException;
  22. import java.sql.Connection;
  23. import java.sql.PreparedStatement;
  24. import java.sql.ResultSet;
  25. import java.sql.Statement;
  26. import java.util.Calendar;
  27. import java.util.HashMap;
  28. import java.util.Map;
  29. import java.util.concurrent.ConcurrentHashMap;
  30. import java.util.logging.Level;
  31. import java.util.logging.Logger;
  32. import javax.xml.parsers.SAXParser;
  33. import javax.xml.parsers.SAXParserFactory;
  34. import org.xml.sax.Attributes;
  35. import org.xml.sax.helpers.DefaultHandler;
  36. import com.l2jserver.Config;
  37. import com.l2jserver.commons.database.pool.impl.ConnectionFactory;
  38. import com.l2jserver.gameserver.ThreadPoolManager;
  39. import com.l2jserver.gameserver.model.L2Clan;
  40. import com.l2jserver.gameserver.model.L2Object;
  41. import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
  42. import com.l2jserver.gameserver.model.skills.Skill;
  43. import com.l2jserver.gameserver.model.zone.ZoneId;
  44. import com.l2jserver.gameserver.network.SystemMessageId;
  45. import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
  46. /**
  47. * @author BiggBoss
  48. */
  49. public final class BotReportTable
  50. {
  51. // Zoey76: TODO: Split XML parsing from SQL operations, use IXmlReader instead of SAXParser.
  52. private static final Logger LOGGER = Logger.getLogger(BotReportTable.class.getName());
  53. private static final int COLUMN_BOT_ID = 1;
  54. private static final int COLUMN_REPORTER_ID = 2;
  55. private static final int COLUMN_REPORT_TIME = 3;
  56. public static final int ATTACK_ACTION_BLOCK_ID = -1;
  57. public static final int TRADE_ACTION_BLOCK_ID = -2;
  58. public static final int PARTY_ACTION_BLOCK_ID = -3;
  59. public static final int ACTION_BLOCK_ID = -4;
  60. public static final int CHAT_BLOCK_ID = -5;
  61. private static final String SQL_LOAD_REPORTED_CHAR_DATA = "SELECT * FROM bot_reported_char_data";
  62. private static final String SQL_INSERT_REPORTED_CHAR_DATA = "INSERT INTO bot_reported_char_data VALUES (?,?,?)";
  63. private static final String SQL_CLEAR_REPORTED_CHAR_DATA = "DELETE FROM bot_reported_char_data";
  64. private Map<Integer, Long> _ipRegistry;
  65. private Map<Integer, ReporterCharData> _charRegistry;
  66. private Map<Integer, ReportedCharData> _reports;
  67. private Map<Integer, PunishHolder> _punishments;
  68. BotReportTable()
  69. {
  70. if (Config.BOTREPORT_ENABLE)
  71. {
  72. _ipRegistry = new HashMap<>();
  73. _charRegistry = new ConcurrentHashMap<>();
  74. _reports = new ConcurrentHashMap<>();
  75. _punishments = new ConcurrentHashMap<>();
  76. try
  77. {
  78. File punishments = new File("./config/botreport_punishments.xml");
  79. if (!punishments.exists())
  80. {
  81. throw new FileNotFoundException(punishments.getName());
  82. }
  83. SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
  84. parser.parse(punishments, new PunishmentsLoader());
  85. }
  86. catch (Exception e)
  87. {
  88. LOGGER.log(Level.WARNING, "BotReportTable: Could not load punishments from /config/botreport_punishments.xml", e);
  89. }
  90. loadReportedCharData();
  91. scheduleResetPointTask();
  92. }
  93. }
  94. /**
  95. * Loads all reports of each reported bot into this cache class.<br>
  96. * Warning: Heavy method, used only on server start up
  97. */
  98. private void loadReportedCharData()
  99. {
  100. try (Connection con = ConnectionFactory.getInstance().getConnection();
  101. Statement st = con.createStatement();
  102. ResultSet rset = st.executeQuery(SQL_LOAD_REPORTED_CHAR_DATA))
  103. {
  104. long lastResetTime = 0;
  105. try
  106. {
  107. String[] hour = Config.BOTREPORT_RESETPOINT_HOUR;
  108. Calendar c = Calendar.getInstance();
  109. c.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour[0]));
  110. c.set(Calendar.MINUTE, Integer.parseInt(hour[1]));
  111. if (System.currentTimeMillis() < c.getTimeInMillis())
  112. {
  113. c.set(Calendar.DAY_OF_YEAR, c.get(Calendar.DAY_OF_YEAR) - 1);
  114. }
  115. lastResetTime = c.getTimeInMillis();
  116. }
  117. catch (Exception e)
  118. {
  119. }
  120. while (rset.next())
  121. {
  122. int botId = rset.getInt(COLUMN_BOT_ID);
  123. int reporter = rset.getInt(COLUMN_REPORTER_ID);
  124. long date = rset.getLong(COLUMN_REPORT_TIME);
  125. if (_reports.containsKey(botId))
  126. {
  127. _reports.get(botId).addReporter(reporter, date);
  128. }
  129. else
  130. {
  131. ReportedCharData rcd = new ReportedCharData();
  132. rcd.addReporter(reporter, date);
  133. _reports.put(rset.getInt(COLUMN_BOT_ID), rcd);
  134. }
  135. if (date > lastResetTime)
  136. {
  137. ReporterCharData rcd = _charRegistry.get(reporter);
  138. if (rcd != null)
  139. {
  140. rcd.setPoints(rcd.getPointsLeft() - 1);
  141. }
  142. else
  143. {
  144. rcd = new ReporterCharData();
  145. rcd.setPoints(6);
  146. _charRegistry.put(reporter, rcd);
  147. }
  148. }
  149. }
  150. LOGGER.info("BotReportTable: Loaded " + _reports.size() + " bot reports");
  151. }
  152. catch (Exception e)
  153. {
  154. LOGGER.log(Level.WARNING, "BotReportTable: Could not load reported char data!", e);
  155. }
  156. }
  157. /**
  158. * Save all reports for each reported bot down to database.<br>
  159. * Warning: Heavy method, used only at server shutdown
  160. */
  161. public void saveReportedCharData()
  162. {
  163. try (Connection con = ConnectionFactory.getInstance().getConnection();
  164. Statement st = con.createStatement();
  165. PreparedStatement ps = con.prepareStatement(SQL_INSERT_REPORTED_CHAR_DATA))
  166. {
  167. st.execute(SQL_CLEAR_REPORTED_CHAR_DATA);
  168. for (Map.Entry<Integer, ReportedCharData> entrySet : _reports.entrySet())
  169. {
  170. Map<Integer, Long> reportTable = entrySet.getValue()._reporters;
  171. for (int reporterId : reportTable.keySet())
  172. {
  173. ps.setInt(1, entrySet.getKey());
  174. ps.setInt(2, reporterId);
  175. ps.setLong(3, reportTable.get(reporterId));
  176. ps.execute();
  177. }
  178. }
  179. }
  180. catch (Exception e)
  181. {
  182. LOGGER.log(Level.SEVERE, "BotReportTable: Could not update reported char data in database!", e);
  183. }
  184. }
  185. /**
  186. * Attempts to perform a bot report. R/W to ip and char id registry is synchronized. Triggers bot punish management<br>
  187. * @param reporter (L2PcInstance who issued the report)
  188. * @return True, if the report was registered, False otherwise
  189. */
  190. public boolean reportBot(L2PcInstance reporter)
  191. {
  192. L2Object target = reporter.getTarget();
  193. if (target == null)
  194. {
  195. return false;
  196. }
  197. L2PcInstance bot = target.getActingPlayer();
  198. if ((bot == null) || (target.getObjectId() == reporter.getObjectId()))
  199. {
  200. return false;
  201. }
  202. if (bot.isInsideZone(ZoneId.PEACE) || bot.isInsideZone(ZoneId.PVP))
  203. {
  204. reporter.sendPacket(SystemMessageId.YOU_CANNOT_REPORT_CHARACTER_IN_PEACE_OR_BATTLE_ZONE);
  205. return false;
  206. }
  207. if (bot.isInOlympiadMode())
  208. {
  209. reporter.sendPacket(SystemMessageId.TARGET_NOT_REPORT_CANNOT_REPORT_PEACE_PVP_ZONE_OR_OLYMPIAD_OR_CLAN_WAR_ENEMY);
  210. return false;
  211. }
  212. if ((bot.getClan() != null) && bot.getClan().isAtWarWith(reporter.getClan()))
  213. {
  214. reporter.sendPacket(SystemMessageId.YOU_CANNOT_REPORT_CLAN_WAR_ENEMY);
  215. return false;
  216. }
  217. if (bot.getExp() == bot.getStat().getStartingExp())
  218. {
  219. reporter.sendPacket(SystemMessageId.YOU_CANNOT_REPORT_CHAR_WHO_ACQUIRED_XP);
  220. return false;
  221. }
  222. ReportedCharData rcd = _reports.get(bot.getObjectId());
  223. ReporterCharData rcdRep = _charRegistry.get(reporter.getObjectId());
  224. final int reporterId = reporter.getObjectId();
  225. synchronized (this)
  226. {
  227. if (_reports.containsKey(reporterId))
  228. {
  229. reporter.sendPacket(SystemMessageId.YOU_HAVE_BEEN_REPORTED_AND_CANNOT_REPORT);
  230. return false;
  231. }
  232. final int ip = hashIp(reporter);
  233. if (!timeHasPassed(_ipRegistry, ip))
  234. {
  235. reporter.sendPacket(SystemMessageId.CANNOT_REPORT_TARGET_ALREDY_REPORTED_BY_CLAN_ALLY_MEMBER_OR_SAME_IP);
  236. return false;
  237. }
  238. if (rcd != null)
  239. {
  240. if (rcd.alredyReportedBy(reporterId))
  241. {
  242. reporter.sendPacket(SystemMessageId.YOU_CANNOT_REPORT_CHAR_AT_THIS_TIME_1);
  243. return false;
  244. }
  245. if (!Config.BOTREPORT_ALLOW_REPORTS_FROM_SAME_CLAN_MEMBERS && rcd.reportedBySameClan(reporter.getClan()))
  246. {
  247. reporter.sendPacket(SystemMessageId.CANNOT_REPORT_TARGET_ALREDY_REPORTED_BY_CLAN_ALLY_MEMBER_OR_SAME_IP);
  248. return false;
  249. }
  250. }
  251. if (rcdRep != null)
  252. {
  253. if (rcdRep.getPointsLeft() == 0)
  254. {
  255. reporter.sendPacket(SystemMessageId.YOU_HAVE_USED_ALL_POINTS_POINTS_ARE_RESET_AT_NOON);
  256. return false;
  257. }
  258. long reuse = (System.currentTimeMillis() - rcdRep.getLastReporTime());
  259. if (reuse < Config.BOTREPORT_REPORT_DELAY)
  260. {
  261. SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_CAN_REPORT_IN_S1_MINS_YOU_HAVE_S2_POINTS_LEFT);
  262. sm.addInt((int) (reuse / 60000));
  263. sm.addInt(rcdRep.getPointsLeft());
  264. reporter.sendPacket(sm);
  265. return false;
  266. }
  267. }
  268. final long curTime = System.currentTimeMillis();
  269. if (rcd == null)
  270. {
  271. rcd = new ReportedCharData();
  272. _reports.put(bot.getObjectId(), rcd);
  273. }
  274. rcd.addReporter(reporterId, curTime);
  275. if (rcdRep == null)
  276. {
  277. rcdRep = new ReporterCharData();
  278. }
  279. rcdRep.registerReport(curTime);
  280. _ipRegistry.put(ip, curTime);
  281. _charRegistry.put(reporterId, rcdRep);
  282. }
  283. SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_WAS_REPORTED_AS_BOT);
  284. sm.addCharName(bot);
  285. reporter.sendPacket(sm);
  286. sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_USED_REPORT_POINT_ON_C1_YOU_HAVE_C2_POINTS_LEFT);
  287. sm.addCharName(bot);
  288. sm.addInt(rcdRep.getPointsLeft());
  289. reporter.sendPacket(sm);
  290. handleReport(bot, rcd);
  291. return true;
  292. }
  293. /**
  294. * Find the punishs to apply to the given bot and triggers the punish method.
  295. * @param bot (L2PcInstance to be punished)
  296. * @param rcd (RepotedCharData linked to this bot)
  297. */
  298. private void handleReport(L2PcInstance bot, final ReportedCharData rcd)
  299. {
  300. // Report count punishment
  301. punishBot(bot, _punishments.get(rcd.getReportCount()));
  302. // Range punishments
  303. for (int key : _punishments.keySet())
  304. {
  305. if ((key < 0) && (Math.abs(key) <= rcd.getReportCount()))
  306. {
  307. punishBot(bot, _punishments.get(key));
  308. }
  309. }
  310. }
  311. /**
  312. * Applies the given punish to the bot if the action is secure
  313. * @param bot (L2PcInstance to punish)
  314. * @param ph (PunishHolder containing the debuff and a possible system message to send)
  315. */
  316. private void punishBot(L2PcInstance bot, PunishHolder ph)
  317. {
  318. if (ph != null)
  319. {
  320. ph._punish.applyEffects(bot, bot);
  321. if (ph._systemMessageId > -1)
  322. {
  323. SystemMessageId id = SystemMessageId.getSystemMessageId(ph._systemMessageId);
  324. if (id != null)
  325. {
  326. bot.sendPacket(id);
  327. }
  328. }
  329. }
  330. }
  331. /**
  332. * Adds a debuff punishment into the punishments record. If skill does not exist, will log it and return
  333. * @param neededReports (report count to trigger this debuff)
  334. * @param skillId
  335. * @param skillLevel
  336. * @param sysMsg (id of a system message to send when applying the punish)
  337. */
  338. void addPunishment(int neededReports, int skillId, int skillLevel, int sysMsg)
  339. {
  340. Skill sk = SkillData.getInstance().getSkill(skillId, skillLevel);
  341. if (sk != null)
  342. {
  343. _punishments.put(neededReports, new PunishHolder(sk, sysMsg));
  344. }
  345. else
  346. {
  347. LOGGER.warning("BotReportTable: Could not add punishment for " + neededReports + " report(s): Skill " + skillId + "-" + skillLevel + " does not exist!");
  348. }
  349. }
  350. void resetPointsAndSchedule()
  351. {
  352. synchronized (_charRegistry)
  353. {
  354. for (ReporterCharData rcd : _charRegistry.values())
  355. {
  356. rcd.setPoints(7);
  357. }
  358. }
  359. scheduleResetPointTask();
  360. }
  361. private void scheduleResetPointTask()
  362. {
  363. try
  364. {
  365. String[] hour = Config.BOTREPORT_RESETPOINT_HOUR;
  366. Calendar c = Calendar.getInstance();
  367. c.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour[0]));
  368. c.set(Calendar.MINUTE, Integer.parseInt(hour[1]));
  369. if (System.currentTimeMillis() > c.getTimeInMillis())
  370. {
  371. c.set(Calendar.DAY_OF_YEAR, c.get(Calendar.DAY_OF_YEAR) + 1);
  372. }
  373. ThreadPoolManager.getInstance().scheduleGeneral(new ResetPointTask(), c.getTimeInMillis() - System.currentTimeMillis());
  374. }
  375. catch (Exception e)
  376. {
  377. ThreadPoolManager.getInstance().scheduleGeneral(new ResetPointTask(), 24 * 3600 * 1000);
  378. LOGGER.log(Level.WARNING, "BotReportTable: Could not properly schedule bot report points reset task. Scheduled in 24 hours.", e);
  379. }
  380. }
  381. public static BotReportTable getInstance()
  382. {
  383. return SingletonHolder.INSTANCE;
  384. }
  385. /**
  386. * Returns a integer representative number from a connection
  387. * @param player (The L2PcInstance owner of the connection)
  388. * @return int (hashed ip)
  389. */
  390. private static int hashIp(L2PcInstance player)
  391. {
  392. String con = player.getClient().getConnection().getInetAddress().getHostAddress();
  393. String[] rawByte = con.split("\\.");
  394. int[] rawIp = new int[4];
  395. for (int i = 0; i < 4; i++)
  396. {
  397. rawIp[i] = Integer.parseInt(rawByte[i]);
  398. }
  399. return rawIp[0] | (rawIp[1] << 8) | (rawIp[2] << 16) | (rawIp[3] << 24);
  400. }
  401. /**
  402. * Checks and return if the abstrat barrier specified by an integer (map key) has accomplished the waiting time
  403. * @param map (a Map to study (Int = barrier, Long = fully qualified unix time)
  404. * @param objectId (an existent map key)
  405. * @return true if the time has passed.
  406. */
  407. private static boolean timeHasPassed(Map<Integer, Long> map, int objectId)
  408. {
  409. if (map.containsKey(objectId))
  410. {
  411. return (System.currentTimeMillis() - map.get(objectId)) > Config.BOTREPORT_REPORT_DELAY;
  412. }
  413. return true;
  414. }
  415. /**
  416. * Represents the info about a reporter
  417. */
  418. private final class ReporterCharData
  419. {
  420. private long _lastReport;
  421. private byte _reportPoints;
  422. ReporterCharData()
  423. {
  424. _reportPoints = 7;
  425. _lastReport = 0;
  426. }
  427. void registerReport(long time)
  428. {
  429. _reportPoints -= 1;
  430. _lastReport = time;
  431. }
  432. long getLastReporTime()
  433. {
  434. return _lastReport;
  435. }
  436. byte getPointsLeft()
  437. {
  438. return _reportPoints;
  439. }
  440. void setPoints(int points)
  441. {
  442. _reportPoints = (byte) points;
  443. }
  444. }
  445. /**
  446. * Represents the info about a reported character
  447. */
  448. private final class ReportedCharData
  449. {
  450. Map<Integer, Long> _reporters;
  451. ReportedCharData()
  452. {
  453. _reporters = new HashMap<>();
  454. }
  455. int getReportCount()
  456. {
  457. return _reporters.size();
  458. }
  459. boolean alredyReportedBy(int objectId)
  460. {
  461. return _reporters.containsKey(objectId);
  462. }
  463. void addReporter(int objectId, long reportTime)
  464. {
  465. _reporters.put(objectId, reportTime);
  466. }
  467. boolean reportedBySameClan(L2Clan clan)
  468. {
  469. if (clan == null)
  470. {
  471. return false;
  472. }
  473. for (int reporterId : _reporters.keySet())
  474. {
  475. if (clan.isMember(reporterId))
  476. {
  477. return true;
  478. }
  479. }
  480. return false;
  481. }
  482. }
  483. /**
  484. * SAX loader to parse /config/botreport_punishments.xml file
  485. */
  486. private final class PunishmentsLoader extends DefaultHandler
  487. {
  488. PunishmentsLoader()
  489. {
  490. }
  491. @Override
  492. public void startElement(String uri, String localName, String qName, Attributes attr)
  493. {
  494. if (qName.equals("punishment"))
  495. {
  496. int reportCount = -1, skillId = -1, skillLevel = 1, sysMessage = -1;
  497. try
  498. {
  499. reportCount = Integer.parseInt(attr.getValue("neededReportCount"));
  500. skillId = Integer.parseInt(attr.getValue("skillId"));
  501. String level = attr.getValue("skillLevel");
  502. String systemMessageId = attr.getValue("sysMessageId");
  503. if (level != null)
  504. {
  505. skillLevel = Integer.parseInt(level);
  506. }
  507. if (systemMessageId != null)
  508. {
  509. sysMessage = Integer.parseInt(systemMessageId);
  510. }
  511. }
  512. catch (Exception e)
  513. {
  514. e.printStackTrace();
  515. }
  516. addPunishment(reportCount, skillId, skillLevel, sysMessage);
  517. }
  518. }
  519. }
  520. class PunishHolder
  521. {
  522. final Skill _punish;
  523. final int _systemMessageId;
  524. PunishHolder(final Skill sk, final int sysMsg)
  525. {
  526. _punish = sk;
  527. _systemMessageId = sysMsg;
  528. }
  529. }
  530. class ResetPointTask implements Runnable
  531. {
  532. @Override
  533. public void run()
  534. {
  535. resetPointsAndSchedule();
  536. }
  537. }
  538. private static final class SingletonHolder
  539. {
  540. static final BotReportTable INSTANCE = new BotReportTable();
  541. }
  542. }