Jump to content

Trance

Global Moderator
  • Posts

    3,896
  • Credits

  • Joined

  • Last visited

  • Days Won

    61
  • Feedback

    0%

Everything posted by Trance

  1. Don't waste your time using basic files from L2Scripts and betting on them. There are many good servers out there that sell their files. It's better to use those.
  2. Yes, please.
  3. Poland is part of NATO.
  4. What if this was just for show to let that group into Belarus to attack Ukraine from the north? I hope not though.
  5. As per @Red-Hair-Shanks's reply I understand that they won't for the core.
  6. Does Eternity provide access to the core source code or only the scripts?
  7. If you provide more details about the tasks, client, and requirements, it will help you find the right person faster.
  8. Interesting. I have not.
  9. Based on 2 projects means you have ripped what you could from them.
  10. @L2Evolutionformat your topic properly.
  11. Add a scheduled task to answer it after X seconds, so your players can’t abuse it. Try this:
  12. @injonesIs the design only being sold to one customer? Are you selling only the design or is the code included as well? Does the purchase include the PSD file?
  13. of course you can emulate but AAC is the issue if the server is using it.
  14. https://maxcheaters.com/forum/72-marketplace-l2packs-files/
  15. Mobius is using aCis' buffer as well, so it's mostly the same.
  16. I like the idea.
  17. In my previous comment, I was talking about a situation where you don't give someone the source code. Hypothetically speaking. Most people prefer using a finished product and don't like to help with its development. I also admit that I can be lazy when it comes to doing things that I find boring. But when it comes to aCis, developers enjoy using it, even if it's not yet complete. They like the way it's written and organized. I'm worried that by the time aCis is ready to use, fewer people will be interested in it. Right now, everyone wants to use the Classic or Essence client. You should think about making a new version of aCis that uses the Interlude data, so that people who like that version can still use it. This will attract even more people towards aCis.
  18. Those are pretty good numbers. Now imagine the struggle of server developers using aCis if they had no source and you would be the only contributor.
  19. Russian speakers in this community have a tendency to be sarcastic and egotistic. Does having hundreds of open tickets make a project good for you?
  20. You've deviated slightly from the main point, but yes, you're correct. What you're mentioning has always been true; "just for fun" servers have been around since L2J started. It's just that people didn't notice them much before.
  21. No L2J project meets the standards to be sold without its source code. It's unfair to make clients solely dependent on you when you can’t provide what you have promised.
  22. The integration of Google Captcha (or any other captcha) will be implemented within the website. By the way, if you're interested in developing an algorithm to monitor players' actions, this could serve as a starting point: P.S. You may incorporate a variation of +/- 10 points for the coordinates. package gold.lineage2.beta; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.logging.Level; import java.util.logging.Logger; import gold.lineage2.Config; import gold.lineage2.commons.database.DatabaseFactory; /** * Validates and stores player coordinates in a database. * @author Trance */ public class CoordinateQueue { private static final Logger LOGGER = Logger.getLogger(CoordinateQueue.class.getName()); private final BlockingQueue<CoordinateRequest> queue = new LinkedBlockingQueue<>(); private final Thread validationThread; /** * Creates a new thread that continuously retrieves CoordinateRequest objects from a queue, * and then validates and stores the player coordinates contained in the request. * The validation process is performed by calling the validateAndStorePlayerCoordinates method. * If an InterruptedException is thrown during the validation, the thread logs a warning message and sets its interrupt status. */ public CoordinateQueue() { validationThread = new Thread(() -> { while (true) { try { CoordinateRequest request = queue.take(); validateAndStorePlayerCoordinates(request.getPlayerName(), request.getX(), request.getY(), request.getZ()); } catch (InterruptedException e) { LOGGER.log(Level.WARNING, "Validation thread was interrupted", e); break; } } }); // A daemon thread is a background thread that is used to support the main program but doesn't prevent it from ending. validationThread.setDaemon(true); // Starting a new thread. validationThread.start(); } /** * Adds a new CoordinateRequest object to the queue with the provided playerName, x, y, and z values. * @param playerName * @param x * @param y * @param z */ public void addRequest(String playerName, int x, int y, int z) { queue.add(new CoordinateRequest(playerName, x, y, z)); } /** * Interrupts the validation thread to shut it down. */ public void shutdown() { validationThread.interrupt(); } /** * Stores the player's name and the three coordinates (x, y, z) */ private static class CoordinateRequest { private final String playerName; private final int x; private final int y; private final int z; public CoordinateRequest(String playerName, int x, int y, int z) { this.playerName = playerName; this.x = x; this.y = y; this.z = z; } public String getPlayerName() { return playerName; } public int getX() { return x; } public int getY() { return y; } public int getZ() { return z; } } /** * Records and validates the coordinate data of a player. * If there is a previous visit, the number of visits is incremented by 1. * If there isn't a previous visit, a new record is inserted into the database. * @param playerName * @param x * @param y * @param z */ private void validateAndStorePlayerCoordinates(String playerName, int x, int y, int z) { final int intervalMinutes = Config.VALIDATION_INTERVAL; final long intervalMilliseconds = intervalMinutes * 60 * 1000; final long currentTime = System.currentTimeMillis(); try (Connection con = DatabaseFactory.getConnection(); PreparedStatement insert = con.prepareStatement("INSERT INTO character_coordinate_history (playerName, x, y, z, visitTime, visitCount) VALUES (?, ?, ?, ?, ?, 1)"); PreparedStatement select = con.prepareStatement("SELECT visitTime, visitCount FROM character_coordinate_history WHERE playerName = ? AND x = ? AND y = ? AND z = ? AND visitTime >= ? ORDER BY visitTime DESC"); PreparedStatement update = con.prepareStatement("UPDATE character_coordinate_history SET visitCount = visitCount + 1 WHERE playerName = ? AND x = ? AND y = ? AND z = ? AND visitTime = ?")) { select.setString(1, playerName); select.setInt(2, x); select.setInt(3, y); select.setInt(4, z); select.setLong(5, currentTime - intervalMilliseconds); ResultSet resultSet = select.executeQuery(); if (resultSet.next()) { long visitTime = resultSet.getLong("visitTime"); int visitCount = resultSet.getInt("visitCount"); if (visitCount >= 2) { LOGGER.log(Level.INFO, "Player " + playerName + " visited the same coordinates " + x + " " + y + " " + z + " within the last " + intervalMinutes + " minutes."); return; } update.setString(1, playerName); update.setInt(2, x); update.setInt(3, y); update.setInt(4, z); update.setLong(5, visitTime); update.executeUpdate(); } else { insert.setString(1, playerName); insert.setInt(2, x); insert.setInt(3, y); insert.setInt(4, z); insert.setLong(5, currentTime); insert.executeUpdate(); } } catch (SQLException e) { LOGGER.log(Level.WARNING, "Failed to validate and store player coordinates", e); } } } Index: java/gold/lineage2/gameserver/network/clientpackets/ValidatePosition.java =================================================================== --- java/gold/lineage2/gameserver/network/clientpackets/ValidatePosition.java (revision 9) +++ java/gold/lineage2/gameserver/network/clientpackets/ValidatePosition.java (working copy) @@ -16,8 +16,6 @@ */ package gold.lineage2.gameserver.network.clientpackets; +import gold.lineage2.beta.CoordinateQueue; import gold.lineage2.commons.network.PacketReader; import gold.lineage2.gameserver.data.xml.DoorData; import gold.lineage2.gameserver.model.World; @@ -116,13 +114,6 @@ player.setClientZ(_z); player.setClientHeading(_heading); // No real need to validate heading. + // Validates and stores player coordinates in a database. + final CoordinateQueue queue = new CoordinateQueue(); + ueue.addRequest(player.getName(), _x, _y, _z); + // Mobius: Check for possible door logout and move over exploit. Also checked at MoveBackwardToLocation. if (!DoorData.getInstance().checkIfDoorsBetween(realX, realY, realZ, _x, _y, _z, player.getInstanceWorld(), false)) { package gold.lineage2.beta; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.logging.Level; import java.util.logging.Logger; import gold.lineage2.commons.database.DatabaseFactory; /** * @author Trance */ public class ActionQueue { private static final Logger LOGGER = Logger.getLogger(ActionQueue.class.getName()); /** * Checks the timing difference between consecutive player actions represented as an array of timestamps. * @param playerName * @param actionName * @param actionTimestamps * @param threshold */ public void checkActionTiming(String playerName, String actionName, long[] actionTimestamps, long threshold) { for (int i = 1; i < actionTimestamps.length; i++) { long timeDiff = actionTimestamps[i] - actionTimestamps[i - 1]; if (timeDiff < threshold) { recordActionToDatabase(playerName, actionName, actionTimestamps[i], timeDiff); } } } /** * Records a player's action timestamp and time difference. * @param playerName * @param actionName * @param actionTimestamp * @param timeDiff */ private void recordActionToDatabase(String playerName, String actionName, long actionTimestamp, long timeDiff) { final String insert = "INSERT INTO character_actions_history (timestamp, time_diff, count, action_name, player_name) " + "SELECT ?, ?, IFNULL(MAX(count) + 1, 1), ?, ? FROM character_actions_history WHERE timestamp < ?"; final String update = "UPDATE character_actions_history SET time_diff = ?, count = ? WHERE id = ?"; try (Connection con = DatabaseFactory.getConnection(); PreparedStatement insertStmt = con.prepareStatement(insert); PreparedStatement updateStmt = con.prepareStatement(update)) { // Set the parameter values for the INSERT statement insertStmt.setLong(1, actionTimestamp); insertStmt.setLong(2, timeDiff); insertStmt.setString(3, actionName); insertStmt.setString(4, playerName); insertStmt.setLong(5, actionTimestamp); // Execute the INSERT statement and get the count value int count = insertStmt.executeUpdate(); // Set the parameter values for the UPDATE statement updateStmt.setLong(1, timeDiff); updateStmt.setInt(2, count); updateStmt.setInt(3, count > 1 ? count - 1 : 1); updateStmt.executeUpdate(); } catch (SQLException e) { LOGGER.log(Level.WARNING, "Failed to record action to database", e); } } } package gold.lineage2.beta; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.logging.Level; import java.util.logging.Logger; import gold.lineage2.commons.database.DatabaseFactory; /** * Checking the integrity of the database tables. * @author Trance */ public class DatabaseIntegrity { private static final Logger LOGGER = Logger.getLogger(DatabaseIntegrity.class.getName()); protected DatabaseIntegrity() { createCharacterCoordinateHistoryTable(); createCharacterActionsHistoryTable(); } /** * Creates a table called character_coordinate_history in a database. */ private static void createCharacterCoordinateHistoryTable() { final String tableName = "character_coordinate_history"; final String columns = "playerName VARCHAR(100), x INT, y INT, z INT, visitTime BIGINT, visitCount INT DEFAULT 0"; final String primaryKey = "playerName, x, y, z, visitTime"; final String sql = "CREATE TABLE IF NOT EXISTS " + tableName + " (" + columns + ", PRIMARY KEY (" + primaryKey + "))"; try (Connection con = DatabaseFactory.getConnection(); Statement stmt = con.createStatement()) { stmt.executeUpdate(sql); } catch (SQLException e) { LOGGER.log(Level.WARNING, "Failed to create table " + tableName, e); } } /** * Creates a table called character_actions_history in a database. */ private void createCharacterActionsHistoryTable() { final String tableName = "character_actions_history"; final String columnTimestamp = "timestamp"; final String columnTimeDiff = "time_diff"; final String columnCount = "count"; final String columnPlayerName = "player_name"; final String primaryKey = "id"; final String sql = "CREATE TABLE IF NOT EXISTS " + tableName + " (" + primaryKey + " INT AUTO_INCREMENT PRIMARY KEY, " + columnTimestamp + " BIGINT, " + columnTimeDiff + " BIGINT, " + columnCount + " INT NOT NULL DEFAULT 1, " + columnPlayerName + " VARCHAR(255)) ENGINE=InnoDB"; try (Connection con = DatabaseFactory.getConnection(); Statement stmt = con.createStatement()) { stmt.executeUpdate(sql); } catch (SQLException e) { LOGGER.log(Level.WARNING, "Failed to create table " + tableName, e); } } public static DatabaseIntegrity getInstance() { return SingletonHolder.INSTANCE; } private static class SingletonHolder { protected static final DatabaseIntegrity INSTANCE = new DatabaseIntegrity(); } }
  23. What if you introduce a Google Captcha verification system that would be better against Adrenaline. Game client opens server's website. On the server's website, the player would be prompted to complete a Google Captcha challenge. Once the player successfully completes the Captcha, the server would receive a confirmation and send an authorization back to the game server, allowing the player to continue playing without any disruption.
×
×
  • Create New...