Jump to content

Trance

Global Moderator
  • Posts

    3,896
  • Credits

  • Joined

  • Last visited

  • Days Won

    61
  • Feedback

    0%

Everything posted by Trance

  1. Hey everyone, I want to talk about an important issue concerning L2J projects. It's time for us to stop supporting projects that are sold compiled without source code, and also to discourage others from buying or participating in them. Let me explain why. Currently, there are no L2J projects out there that are stable or of high enough quality to justify being sold without providing the source code. If you want to sell a project like that, you should first make sure it meets the highest standards. The sad truth is that most active L2J projects today don't have enough developers or a truly active development team to keep up with the demands of their users. You either need to provide a near-perfect project with everything implemented, working as expected, and free of bugs, or you need a larger, active development team to achieve that. Using an L2J project without access to the source code is a dead-end. Since there are no projects that meet the criteria I mentioned above, you're better off steering clear of them. In conclusion, do not buy an L2J project without a source.
  2. @Mobiusis one of the most active and well-respected developers in the L2J community, known for making significant contributions to both private and public projects. His biggest strength is in his ability to support a wide range of Chronicles. It's no wonder that Mobius and his work are considered to be at the forefront of the best L2J projects available. And let's be honest, with so many Chronicles supported, it's hard to argue with this. If you have experience with L2J, L2JServer is a good option with a solid foundation to build on. Or to put it differently, if you have enough time to spare and are willing to learn. If you want to quickly set up a custom PvP server with many customs and don't want to spend a lot of time on development or worry about critical bugs; in theory, @`NeverMore's L2JSunrise may be the best choice. It's been around for a long time, so you probably won't run into any major problems. However, I don't have any technical details, as I only worked with it once for a client many years ago.
  3. It is closing the connection automatically because the Connection object is being created within the try block.
  4. It's unusual to have connection issues on localhost. When this happens, check your OS' RAM and CPU usage. If they're too high, it might cause timeouts. Also, look at your MySQL settings, especially max_allowed_packet and wait_timeout. If they're too low, increase them. Have you changed any settings before? Poorly written queries can also cause connection issues. You can perform a Java Flight Recording: https://maxcheaters.com/topic/247610-jdk-mission-control-performance-analysis-tool/
  5. Get ready for an exhilarating, action-packed journey in L2Gold Essence, a gold-style Lineage 2 server completely revamped on the Essence Client. We've crafted this low-rate server with official files and a wealth of engaging features, ensuring an unforgettable experience for all players: - Long-lasting buffs from the NPC Buffer to keep you empowered and ready for action - Auto-Hunting mode, ensuring a more relaxed, enjoyable gaming experience for everyone - Character rebirth up to 3 times, allowing you to retain your S-grade gear and experience faster, more satisfying progression - And much more Embrace the nostalgia with our completely reimagined Interlude map, featuring Interlude-like classes and skills, sprinkled with gold-style enhancements to improve underused classes. Expect additional high-level buffs for buffers and new skills for summoning classes, empowering you to dominate in PvP encounters. Our built-in UI Gatekeeper will teleport you to familiar zones from our first season, while introducing a breath of fresh air with fewer NPCs in town. L2Gold Essence artfully balances fond memories with exciting new elements, setting the stage for countless epic moments to come. But that's not all! Immerse yourself in a myriad of entertaining activities as you level up with higher gear, ensuring there's never a dull moment in the world of L2Gold Essence. From challenging dungeons to exhilarating PvP battles, you'll find endless opportunities to showcase your skills and make a name for yourself. As soon as we have certainty, we will provide additional details regarding the beta phase and/or launch date. Join our vibrant community on Discord and share your most memorable moments with fellow adventurers. Together, we'll forge new friendships, conquer epic challenges, and create lasting memories in the captivating world of L2Gold Essence. https://discord.gg/zA5KWH8cMc
  6. That is not normal. However, you can increase connection timeout to 60 seconds for testing purposes. jdbc:mariadb://127.0.0.1:3306/SERVER_DB?connectTimeout=60000?user=hidden&password=hidden All you can do is wait for the logging results.
  7. Temporarily stop them: sudo systemctl stop firewalld sudo systemctl stop iptables Check if localhost can reach MySQL on port 3306: mysql -u root -h localhost -p<your password> -e "SELECT 'Connected Successfully'"
  8. Ensure that the following information in your server properties is accurate: the database URL, login credentials, and password. If you are using Linux, it's a good idea to verify if any firewall rules might be blocking the loopback interface (lo) for localhost, as this could be the cause of the issue.
  9. You mentioned that you've fixed the issues, but how it would be ready for a live server when only a few fixes have been shared. It would be helpful and appreciated if you could share more information about the fixes or the process. It's always great when everyone can collaborate and learn from each other. If you choose not to share this information, it is unreasonable to expect others to. I, along with @Tryskell(I believe), am completely fed up with the arrogance of these so-called "developers." They perform half-baked fixes on critical issues and have the audacity to call it a major accomplishment. You all deserve your fate!
  10. Most L2 developers are not willing to work remotely through Team Viewer or AnyDesk due to the low income and their moody nature. Therefore, it is highly unlikely to find an L2 developer willing to work under these conditions.
  11. You don't need to copy and paste everything. Instead, you can use the same logic to implement it in your Thread Pool Manager. However, I am providing you with the files: https://files.lineage2.gold/threads.zip
  12. Hi, This is a custom implementation of a thread pool management system, designed to efficiently manage the execution of tasks in a multi-threaded environment. Automatically adjusts the size of the thread pools based on the system's CPU load and the number of available cores. This dynamic adjustment ensures that the thread pools are always optimized for the current system load, resulting in efficient resource utilization and improved performance. If the calculated sizes are equal to the current core pool sizes, this means that there's no need to update the pool sizes, as they are already optimized for the current system load. I haven't conducted extensive testing yet; however, the code can be thoroughly tested, and if any issues are encountered, I can make the necessary modifications. Comments were added throughout the code. For testing purposes you can do the following: scheduleAtFixedRate(() -> { try { purge(); updateThreadPoolSizes(); } catch (Exception e) { LOGGER.severe("Error during scheduled ThreadPool update: " + e.getMessage()); } }, 10, 10, TimeUnit.SECONDS); private static void updateThreadPoolSizes() { double systemLoad = getSystemCpuLoad(); int availableCores = Runtime.getRuntime().availableProcessors(); int newScheduledThreadPoolSize = calculateThreadPoolSize(availableCores, systemLoad, true); int newInstantThreadPoolSize = calculateThreadPoolSize(availableCores, systemLoad, false); if (newScheduledThreadPoolSize != scheduledExecutor.getCorePoolSize()) { scheduledExecutor.setCorePoolSize(newScheduledThreadPoolSize); LOGGER.info("Updated scheduled thread pool size to " + newScheduledThreadPoolSize + ", CPU: " + getSystemCpuLoadPercentage()); } else { LOGGER.info("Scheduled thread pool size remains to " + newScheduledThreadPoolSize + ", CPU: " + getSystemCpuLoadPercentage()); } if (newInstantThreadPoolSize != instantExecutor.getCorePoolSize()) { instantExecutor.setCorePoolSize(newInstantThreadPoolSize); LOGGER.info("Updated instant thread pool size to " + newInstantThreadPoolSize + ", CPU: " + getSystemCpuLoadPercentage()); } else { LOGGER.info("Instant thread pool size remains to " + newInstantThreadPoolSize + ", CPU: " + getSystemCpuLoadPercentage()); } } P.S.: This is the lowest it can get: return Math.max(isScheduledPool ? 16 : 8, threadPoolSize); The initial code: * This file is part of the L2Gold Classic project. * * 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 gold.lineage2.commons.threads; import java.lang.management.ManagementFactory; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import com.sun.management.OperatingSystemMXBean; /** * A custom implementation of a thread pool management system, designed to efficiently manage the execution of tasks in a multi-threaded environment. * Automatically adjusts the size of the thread pools based on the system's CPU load and the number of available cores. This dynamic adjustment ensures * that the thread pools are always optimized for the current system load, resulting in efficient resource utilization and improved performance. * @author Trance */ public class ThreadPool { // Logger to log information and errors. private static final Logger LOGGER = Logger.getLogger(ThreadPool.class.getName()); // Maximum delay to be used to validate the delay. private static final long MAX_DELAY = TimeUnit.NANOSECONDS.toMillis(Long.MAX_VALUE - System.nanoTime()) / 2; // ScheduledThreadPoolExecutor for scheduled tasks. private static ScheduledThreadPoolExecutor scheduledExecutor; // ThreadPoolExecutor for instant tasks. private static ThreadPoolExecutor instantExecutor; /** * Initialize the ThreadPool with the appropriate sizes. */ public static void init() { LOGGER.info("ThreadPool: Initialized"); int availableCores = Runtime.getRuntime().availableProcessors(); int scheduledThreadPoolSize = availableCores * 4; int instantThreadPoolSize = availableCores * 2; scheduledExecutor = new ScheduledThreadPoolExecutor(scheduledThreadPoolSize, new PriorityThreadFactory("ScheduledThreadPool", Thread.NORM_PRIORITY), new ThreadPoolExecutor.CallerRunsPolicy()); scheduledExecutor.setRejectedExecutionHandler(new RejectedExecutionHandlerImpl()); scheduledExecutor.prestartAllCoreThreads(); LOGGER.info("Scheduled thread pool size: " + scheduledThreadPoolSize); instantExecutor = new ThreadPoolExecutor(instantThreadPoolSize, Integer.MAX_VALUE, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory("ThreadPoolExecutor", Thread.NORM_PRIORITY), new ThreadPoolExecutor.CallerRunsPolicy()); instantExecutor.setRejectedExecutionHandler(new RejectedExecutionHandlerImpl()); instantExecutor.prestartAllCoreThreads(); LOGGER.info("Instant thread pool size: " + instantThreadPoolSize); scheduleAtFixedRate(() -> { try { purge(); updateThreadPoolSizes(); } catch (Exception e) { LOGGER.severe("Error during scheduled ThreadPool update: " + e.getMessage()); } }, 5, 5, TimeUnit.MINUTES); } /** * Purge both thread pools. */ public static void purge() { scheduledExecutor.purge(); instantExecutor.purge(); } /** * Validate the delay ensuring it's within acceptable bounds. * @param delay The delay value to be validated. * @param timeUnit The time unit of the delay. * @return A validated delay value. */ private static long validate(long delay, TimeUnit timeUnit) { long delayInMilliseconds = timeUnit.toMillis(delay); long validatedDelay = Math.max(0, Math.min(MAX_DELAY, delayInMilliseconds)); if (delayInMilliseconds > validatedDelay) { return -1; } return timeUnit.convert(delayInMilliseconds, TimeUnit.MILLISECONDS); } /** * Schedule a Runnable with a specific delay. * @param r The Runnable to be scheduled. * @param delay The delay before the Runnable is executed, in milliseconds. * @return A ScheduledFuture representing the pending result of the task. */ public static ScheduledFuture<?> schedule(Runnable r, long delay) { return schedule(r, delay, TimeUnit.MILLISECONDS); } /** * Schedule a Runnable with a specific delay and TimeUnit. * @param r The Runnable to be scheduled. * @param delay The delay before the Runnable is executed. * @param timeUnit The time unit of the delay. * @return A ScheduledFuture representing the result of the scheduling. */ private static ScheduledFuture<?> schedule(Runnable r, long delay, TimeUnit timeUnit) { delay = validate(delay, timeUnit); if (delay == -1) { return null; } return scheduledExecutor.schedule(new RunnableWrapper(r), delay, timeUnit); } /** * Schedule a Runnable at a fixed rate with an initial delay. * @param r The Runnable to be scheduled. * @param initial The initial delay before the Runnable is executed for the first time. * @param delay The delay between the execution of the Runnable in subsequent runs. * @return A ScheduledFuture representing the result of the scheduling. */ public static ScheduledFuture<?> scheduleAtFixedRate(Runnable r, long initial, long delay) { return scheduleAtFixedRate(r, initial, delay, TimeUnit.MILLISECONDS); } /** * Schedule a Runnable at a fixed rate with an initial delay and TimeUnit. * @param r The Runnable to be scheduled. * @param initial The initial delay before the Runnable is executed for the first time. * @param delay The delay between the execution of the Runnable in subsequent runs. * @param timeUnit The time unit of the delay. * @return A ScheduledFuture representing the result of the scheduling. */ private static ScheduledFuture<?> scheduleAtFixedRate(Runnable r, long initial, long delay, TimeUnit timeUnit) { initial = validate(initial, timeUnit); if (initial == -1) { return null; } delay = validate(delay, timeUnit); if (delay == -1) { return scheduledExecutor.schedule(new RunnableWrapper(r), initial, timeUnit); } return scheduledExecutor.scheduleAtFixedRate(new RunnableWrapper(r), initial, delay, timeUnit); } /** * Execute a Runnable instantly. * @param r The Runnable to be executed. */ public static void execute(Runnable r) { instantExecutor.execute(r); } /** * Shut down both thread pools. * @throws InterruptedException If the shutdown process is interrupted. */ public static void shutdown() throws InterruptedException { try { scheduledExecutor.shutdown(); if (!scheduledExecutor.awaitTermination(10, TimeUnit.SECONDS)) { scheduledExecutor.shutdownNow(); } } finally { instantExecutor.shutdown(); if (!instantExecutor.awaitTermination(1, TimeUnit.MINUTES)) { instantExecutor.shutdownNow(); } } } /** * Update the thread pool sizes based on system load. */ private static void updateThreadPoolSizes() { double systemLoad = getSystemCpuLoad(); int availableCores = Runtime.getRuntime().availableProcessors(); int newScheduledThreadPoolSize = calculateThreadPoolSize(availableCores, systemLoad, true); int newInstantThreadPoolSize = calculateThreadPoolSize(availableCores, systemLoad, false); if (newScheduledThreadPoolSize != scheduledExecutor.getCorePoolSize()) { scheduledExecutor.setCorePoolSize(newScheduledThreadPoolSize); LOGGER.info("Updated scheduled thread pool size to " + newScheduledThreadPoolSize + ", CPU: " + getSystemCpuLoadPercentage()); } if (newInstantThreadPoolSize != instantExecutor.getCorePoolSize()) { instantExecutor.setCorePoolSize(newInstantThreadPoolSize); LOGGER.info("Updated instant thread pool size to " + newInstantThreadPoolSize + ", CPU: " + getSystemCpuLoadPercentage()); } } /** * Calculate the thread pool size based on available cores, system load, and whether it's a scheduled pool. * @param availableCores The number of available processor cores. * @param systemLoad The current system load. * @param isScheduledPool A boolean indicating if the pool is a scheduled thread pool or not. * @return The calculated thread pool size. */ private static int calculateThreadPoolSize(int availableCores, double systemLoad, boolean isScheduledPool) { double factor; if (systemLoad <= 0.4) { factor = isScheduledPool ? 4 : 2; } else if (systemLoad <= 0.6) { factor = isScheduledPool ? 3 : 1.5; } else if (systemLoad <= 0.8) { factor = isScheduledPool ? 2 : 1; } else { factor = 0.5; } int threadPoolSize = (int) Math.round(availableCores * factor); return Math.max(isScheduledPool ? 16 : 8, threadPoolSize); } /** * Get the system CPU load. * @return A double value representing the system CPU load. */ @SuppressWarnings("deprecation") private static double getSystemCpuLoad() { OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class); return osBean.getSystemCpuLoad(); } /** * Get the system CPU load as a percentage. * @return A string representing the system CPU load percentage. */ private static String getSystemCpuLoadPercentage() { double cpuLoad = getSystemCpuLoad(); return String.format("%.2f%%", cpuLoad * 100); } }
  13. Hi, It searches for a suitable location in a circular area around the original spawn location by incrementally increasing the distance from the original point and checking different angles. It then checks if there's a wall or obstruction within safeDistance units from the potential new location. Index: /main/L2GoldClassic/java/gold/lineage2/gameserver/model/Spawn.java =================================================================== --- /main/L2GoldClassic/java/gold/lineage2/gameserver/model/Spawn.java (revision 25) +++ /main/L2GoldClassic/java/gold/lineage2/gameserver/model/Spawn.java (revision 26) @@ -29,4 +29,5 @@ import gold.lineage2.gameserver.data.xml.NpcData; import gold.lineage2.gameserver.geoengine.GeoEngine; +import gold.lineage2.gameserver.geoengine.geodata.GeoStructure; import gold.lineage2.gameserver.instancemanager.DropManager; import gold.lineage2.gameserver.instancemanager.WalkingManager; @@ -407,13 +408,24 @@ } - // Correct Z of monsters. Do not correct Z of flying NPCs. - if (npc.isMonster() && !npc.isFlying()) - { - final int geoZ = GeoEngine.getInstance().getHeight(newlocx, newlocy, newlocz); - // Do not correct Z distances greater than 300. - if (Util.calculateDistance(newlocx, newlocy, newlocz, newlocx, newlocy, geoZ, true, false) < 300) + if (npc.isMonster()) + { + // Correct Z of monsters. Do not correct Z of flying NPCs. + if (!npc.isFlying()) { - newlocz = geoZ; + final int geoZ = GeoEngine.getInstance().getHeight(newlocx, newlocy, newlocz); + // Do not correct Z distances greater than 300. + if (Util.calculateDistance(newlocx, newlocy, newlocz, newlocx, newlocy, geoZ, true, false) < 300) + { + newlocz = geoZ; + } } + + // Find a safe spawn location + Location safeSpawnLocation = findSafeSpawnLocation(newlocx, newlocy, newlocz, 200); + + // Update the spawn location + newlocx = safeSpawnLocation.getX(); + newlocy = safeSpawnLocation.getY(); + newlocz = safeSpawnLocation.getZ(); } @@ -475,4 +487,53 @@ return npc; + } + + /** + * Finds a safe spawn location at least 'safeDistance' units away from the nearest wall. + * + * @param x the original x-coordinate of the spawn location + * @param y the original y-coordinate of the spawn location + * @param z the original z-coordinate of the spawn location + * @param safeDistance the minimum distance from the nearest wall for the new spawn location + * @return a new Location object containing the safe spawn location + */ + private Location findSafeSpawnLocation(int x, int y, int z, int safeDistance) + { + Location newLocation = new Location(x, y, z); + GeoEngine geoEngine = GeoEngine.getInstance(); + + // Search for a safe spawn location in a circular area around the original spawn location + for (int radius = safeDistance; radius < 5000; radius += safeDistance) + { + for (int angle = 0; angle < 360; angle += 45) + { + int newX = x + (int) (radius * Math.cos(Math.toRadians(angle))); + int newY = y + (int) (radius * Math.sin(Math.toRadians(angle))); + int newZ = geoEngine.getHeight(newX, newY, z); + + boolean canSeeWall = false; + for (int checkDistance = 0; checkDistance <= safeDistance; checkDistance += GeoStructure.CELL_IGNORE_HEIGHT) + { + int checkX = newX + (int) (checkDistance * Math.cos(Math.toRadians(angle))); + int checkY = newY + (int) (checkDistance * Math.sin(Math.toRadians(angle))); + int checkZ = geoEngine.getHeight(checkX, checkY, newZ); + + if (geoEngine.hasGeoPos(checkX, checkY) && !geoEngine.canMoveToTarget(newX, newY, newZ, checkX, checkY, checkZ, null)) + { + canSeeWall = true; + break; + } + } + + if (!canSeeWall) + { + newLocation.setXYZ(newX, newY, newZ); + return newLocation; + } + } + } + + LOGGER.warning("Could not find a safe spawn location for NPC " + _template.getId() + ". Using original location."); + return newLocation; }
      • 2
      • Like
  14. Hi, Admin command, scans a specified range around the admin. It gathers about players within the specified range, including their clan names, party leaders, and class names. Counting all players around can be a helpful task for admins. You could limit the scanning to players within the same region or zone as the admin - this would reduce the number of players to be processed and improve performance. Index: /main/L2GoldClassic/dist/game/config/AdminCommands.xml =================================================================== --- /main/L2GoldClassic/dist/game/config/AdminCommands.xml (revision 26) +++ /main/L2GoldClassic/dist/game/config/AdminCommands.xml (revision 27) @@ -626,4 +626,5 @@ <admin command="admin_scan" accessLevel="100" /> <admin command="admin_deleteNpcByObjectId" accessLevel="100" confirmDlg="true" /> + <admin command="admin_scan_players" accessLevel="50" /> <!-- ADMIN SERVERINFO --> Index: /main/L2GoldClassic/dist/game/data/scripts/handlers/MasterHandler.java =================================================================== --- /main/L2GoldClassic/dist/game/data/scripts/handlers/MasterHandler.java (revision 26) +++ /main/L2GoldClassic/dist/game/data/scripts/handlers/MasterHandler.java (revision 27) @@ -124,4 +124,5 @@ import handlers.admincommandhandlers.AdminRide; import handlers.admincommandhandlers.AdminScan; +import handlers.admincommandhandlers.AdminScanPlayers; import handlers.admincommandhandlers.AdminServerInfo; import handlers.admincommandhandlers.AdminShop; @@ -463,4 +464,5 @@ AdminRide.class, AdminScan.class, + AdminScanPlayers.class, AdminServerInfo.class, AdminShop.class, Index: /main/L2GoldClassic/dist/game/data/scripts/handlers/admincommandhandlers/AdminScanPlayers.java =================================================================== --- /main/L2GoldClassic/dist/game/data/scripts/handlers/admincommandhandlers/AdminScanPlayers.java (revision 27) +++ /main/L2GoldClassic/dist/game/data/scripts/handlers/admincommandhandlers/AdminScanPlayers.java (revision 27) @@ -0,0 +1,134 @@ +/* + * This file is part of the L2Gold Classic project. + * + * 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 handlers.admincommandhandlers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import gold.lineage2.gameserver.data.sql.ClanTable; +import gold.lineage2.gameserver.handler.IAdminCommandHandler; +import gold.lineage2.gameserver.model.Location; +import gold.lineage2.gameserver.model.World; +import gold.lineage2.gameserver.model.actor.instance.PlayerInstance; +import gold.lineage2.gameserver.model.clan.Clan; +import gold.lineage2.gameserver.util.Util; + +/** + * @author Trance + */ +public class AdminScanPlayers implements IAdminCommandHandler +{ + private static final String[] ADMIN_COMMANDS = + { + "admin_scan_players" + }; + + @Override + public boolean useAdminCommand(String command, PlayerInstance player) + { + if (command.startsWith("admin_scan_players")) + { + String[] args = command.split(" "); + if (args.length > 1) + { + String rangeStr = args[1]; + scanPlayers(player, rangeStr); + } + else + { + player.sendMessage("Usage: //admin_scan_players <range>"); + } + } + return true; + } + + private void scanPlayers(PlayerInstance player, String rangeStr) + { + // Parse the range from the command arguments + int range = Util.parseNextInt(rangeStr, 0); + + // Get the player's location + Location playerLocation = player.getLocation(); + + // Initialize data structures for storing the scan results + Map<String, Set<String>> clanMembers = new HashMap<>(); + Map<String, Set<String>> partyMembers = new HashMap<>(); + Map<String, List<String>> classMembers = new HashMap<>(); + + // Iterate through all players in the game world + for (PlayerInstance otherPlayer : World.getInstance().getPlayers()) + { + // Check if the other player is within the specified range of the player + if (playerLocation.distanceTo(otherPlayer.getLocation()) <= range) + { + // Gather data for clans, parties, and classes + String clanName = otherPlayer.getClan() != null ? otherPlayer.getClan().getName() : "No Clan"; + String partyLeaderName = otherPlayer.getParty() != null ? otherPlayer.getParty().getLeader().getName() : null; + String className = otherPlayer.getTemplate().getClassId().name(); + + // Update the player lists for clans, parties, and classes + clanMembers.computeIfAbsent(clanName, k -> new HashSet<>()).add(otherPlayer.getName()); + if (partyLeaderName != null) + { + partyMembers.computeIfAbsent(partyLeaderName, k -> new HashSet<>()).add(otherPlayer.getName()); + } + classMembers.computeIfAbsent(className, k -> new ArrayList<>()).add(otherPlayer.getName()); + } + } + + // Display the scan results to the player + player.sendMessage("Scan Results:"); + player.sendMessage("Range: " + range); + player.sendMessage("Clans (" + clanMembers.size() + "):"); + for (Map.Entry<String, Set<String>> entry : clanMembers.entrySet()) + { + String clanLeaderName = "Unknown"; + Clan clan = ClanTable.getInstance().getClanByName(entry.getKey()); + if (clan != null) + { + PlayerInstance clanLeader = World.getInstance().getPlayer(clan.getLeaderId()); + if (clanLeader != null) + { + clanLeaderName = clanLeader.getName(); + } + } + player.sendMessage(" - " + entry.getKey() + " (Leader: " + clanLeaderName + "): " + entry.getValue().size() + " members"); + player.sendMessage(" Members: " + String.join(", ", entry.getValue())); + } + player.sendMessage("Parties (" + partyMembers.size() + "):"); + for (Map.Entry<String, Set<String>> entry : partyMembers.entrySet()) + { + player.sendMessage(" - Leader: " + entry.getKey() + ", " + entry.getValue().size() + " members"); + player.sendMessage(" Members: " + String.join(", ", entry.getValue())); + } + player.sendMessage("Classes (" + classMembers.size() + "):"); + for (Map.Entry<String, List<String>> entry : classMembers.entrySet()) + { + player.sendMessage(" - " + entry.getKey() + ": " + entry.getValue().size() + " players " + entry.getValue().stream().collect(Collectors.joining(", ", "(", ")"))); + } + } + + @Override + public String[] getAdminCommandList() + { + return ADMIN_COMMANDS; + } +} Index: /main/L2GoldClassic/java/gold/lineage2/gameserver/model/Location.java =================================================================== --- /main/L2GoldClassic/java/gold/lineage2/gameserver/model/Location.java (revision 26) +++ /main/L2GoldClassic/java/gold/lineage2/gameserver/model/Location.java (revision 27) @@ -56,4 +56,17 @@ _z = set.getInt("z"); _heading = set.getInt("heading", 0); + } + + /** + * Calculates the Euclidean distance between two 3D points in the world + * @param other + * @return + */ + public double distanceTo(Location other) + { + int dx = _x - other._x; + int dy = _y - other._y; + int dz = _z - other._z; + return Math.sqrt((dx * dx) + (dy * dy) + (dz * dz)); } Index: /main/L2GoldClassic/java/gold/lineage2/gameserver/util/Util.java =================================================================== --- /main/L2GoldClassic/java/gold/lineage2/gameserver/util/Util.java (revision 26) +++ /main/L2GoldClassic/java/gold/lineage2/gameserver/util/Util.java (revision 27) @@ -266,4 +266,22 @@ /** + * Tries to parse an integer from a string. If it fails, it returns the default value provided. + * @param value + * @param defaultValue + * @return + */ + public static int parseNextInt(String value, int defaultValue) + { + try + { + return Integer.parseInt(value); + } + catch (NumberFormatException e) + { + return defaultValue; + } + } + + /** * @param text - the text to check * @return {@code true} if {@code text} is float, {@code false} otherwise
      • 4
      • Upvote
      • Like
  15. The client side only changes the information and skill category. What you need to change on the server side is the target, skill type, operation type and effect to be Damage Over Time instead of Buff.
  16. I didn't read the whole code: final Player tmp = _player.getClient().getPlayer(); final Party party = tmp.getParty();
  17. Kindly delete this code permanently. I'm not kidding.
  18. It's necessary for you to indicate the pack and the type of client protection you're using. By default, I don't believe the client is capable of supporting that.
  19. Moved to client section.
  20. You don't need: final Player tmp = getClient().getPlayer(); Change: final Party party = tmp.getParty(); To: final Party party = _player.getParty();
  21. stackType=""
  22. Why would you even want to do that?
×
×
  • Create New...