BotReportTable.java 16 KB

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