BotReportTable.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  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.Map;
  9. import java.util.concurrent.ConcurrentHashMap;
  10. import java.util.logging.Level;
  11. import java.util.logging.Logger;
  12. import javax.xml.parsers.SAXParser;
  13. import javax.xml.parsers.SAXParserFactory;
  14. import org.xml.sax.Attributes;
  15. import org.xml.sax.helpers.DefaultHandler;
  16. import com.l2jserver.Config;
  17. import com.l2jserver.L2DatabaseFactory;
  18. import com.l2jserver.gameserver.ThreadPoolManager;
  19. import com.l2jserver.gameserver.model.L2Clan;
  20. import com.l2jserver.gameserver.model.L2Object;
  21. import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
  22. import com.l2jserver.gameserver.model.skills.L2Skill;
  23. import com.l2jserver.gameserver.model.zone.ZoneId;
  24. import com.l2jserver.gameserver.network.SystemMessageId;
  25. import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
  26. import gnu.trove.map.hash.TIntLongHashMap;
  27. /**
  28. * @author BiggBoss
  29. */
  30. public final class BotReportTable
  31. {
  32. 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 TIntLongHashMap _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 TIntLongHashMap();
  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. TIntLongHashMap reportTable = entrySet.getValue()._reporters;
  153. for (int reporterId : reportTable.keys())
  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.addNumber((int) (reuse / 60000));
  243. sm.addNumber(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.addNumber(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. L2Skill sk = SkillTable.getInstance().getInfo(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 TIntLongHashMap 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(TIntLongHashMap map, int objectId)
  388. {
  389. long time;
  390. if ((time = map.get(objectId)) != map.getNoEntryValue())
  391. {
  392. return (System.currentTimeMillis() - time) > Config.BOTREPORT_REPORT_DELAY;
  393. }
  394. return true;
  395. }
  396. /**
  397. * Represents the info about a reporter
  398. */
  399. private final class ReporterCharData
  400. {
  401. private long _lastReport;
  402. private byte _reportPoints;
  403. ReporterCharData()
  404. {
  405. _reportPoints = 7;
  406. _lastReport = 0;
  407. }
  408. void registerReport(long time)
  409. {
  410. _reportPoints -= 1;
  411. _lastReport = time;
  412. }
  413. long getLastReporTime()
  414. {
  415. return _lastReport;
  416. }
  417. byte getPointsLeft()
  418. {
  419. return _reportPoints;
  420. }
  421. void setPoints(int points)
  422. {
  423. _reportPoints = (byte) points;
  424. }
  425. }
  426. /**
  427. * Represents the info about a reported character
  428. */
  429. private final class ReportedCharData
  430. {
  431. TIntLongHashMap _reporters;
  432. ReportedCharData()
  433. {
  434. _reporters = new TIntLongHashMap();
  435. }
  436. int getReportCount()
  437. {
  438. return _reporters.size();
  439. }
  440. boolean alredyReportedBy(int objectId)
  441. {
  442. return _reporters.contains(objectId);
  443. }
  444. void addReporter(int objectId, long reportTime)
  445. {
  446. _reporters.put(objectId, reportTime);
  447. }
  448. boolean reportedBySameClan(L2Clan clan)
  449. {
  450. if (clan == null)
  451. {
  452. return false;
  453. }
  454. for (int reporterId : _reporters.keys())
  455. {
  456. if (clan.isMember(reporterId))
  457. {
  458. return true;
  459. }
  460. }
  461. return false;
  462. }
  463. }
  464. /**
  465. * SAX loader to parse /config/botreport_punishments.xml file
  466. */
  467. private final class PunishmentsLoader extends DefaultHandler
  468. {
  469. PunishmentsLoader()
  470. {
  471. }
  472. @Override
  473. public void startElement(String uri, String localName, String qName, Attributes attr)
  474. {
  475. if (qName.equals("punishment"))
  476. {
  477. int reportCount = -1, skillId = -1, skillLevel = 1, sysMessage = -1;
  478. try
  479. {
  480. reportCount = Integer.parseInt(attr.getValue("neededReportCount"));
  481. skillId = Integer.parseInt(attr.getValue("skillId"));
  482. String level = attr.getValue("skillLevel");
  483. String systemMessageId = attr.getValue("sysMessageId");
  484. if (level != null)
  485. {
  486. skillLevel = Integer.parseInt(level);
  487. }
  488. if (systemMessageId != null)
  489. {
  490. sysMessage = Integer.parseInt(systemMessageId);
  491. }
  492. }
  493. catch (Exception e)
  494. {
  495. e.printStackTrace();
  496. }
  497. addPunishment(reportCount, skillId, skillLevel, sysMessage);
  498. }
  499. }
  500. }
  501. class PunishHolder
  502. {
  503. final L2Skill _punish;
  504. final int _systemMessageId;
  505. PunishHolder(final L2Skill sk, final int sysMsg)
  506. {
  507. _punish = sk;
  508. _systemMessageId = sysMsg;
  509. }
  510. }
  511. class ResetPointTask implements Runnable
  512. {
  513. @Override
  514. public void run()
  515. {
  516. resetPointsAndSchedule();
  517. }
  518. }
  519. private static final class SingletonHolder
  520. {
  521. static final BotReportTable INSTANCE = new BotReportTable();
  522. }
  523. }