Noticias:

No tienes permiso para ver los enlaces. Para poder verlos Registrate o Conectate.

Menú Principal

Comando SaveLocation

Iniciado por Swarlog, Nov 13, 2026, 10:04 PM

Tema anterior - Siguiente tema

Swarlog

Save Location: permite guardar y organizar múltiples ubicaciones del personaje directamente desde el juego. Cada localización se almacena en un archivo TXT con sus coordenadas X, Y, Z y orientación (Heading), identificadas mediante un ID correlativo.

Incluye dos funciones:

  • Guardar Location: almacena la ubicación actual del personaje en el archivo activo.
  • Siguiente Línea: crea un nuevo archivo TXT y reinicia el contador de IDs.
    Ideal para recopilar coordenadas de NPCs, zonas, eventos, spawns y otros puntos de interés del mundo de Lineage II.

Formato del txt generado:

id="1", x="150268", y="46253", z="-3438", h="25451";
id="2", x="150300", y="46260", z="-3430", h="25451";
id="3", x="150350", y="46300", z="-3420", h="25451";

/*
 * Copyright © 2026 L2JDevs
 * 
 * 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 <https://www.gnu.org/licenses/>.
 */
package handlers.voicedcommandhandlers.custom;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.l2jdevs.gameserver.handler.IVoicedCommandHandler;
import org.l2jdevs.gameserver.model.actor.instance.L2PcInstance;
import org.l2jdevs.gameserver.network.serverpackets.NpcHtmlMessage;

/**
 * Voice Command - Save Location (.savelocation)
 * @author swarlog
 */
public class SaveLocation implements IVoicedCommandHandler
{
	private static final Logger LOGGER = Logger.getLogger(SaveLocation.class.getName());
	
	// Directory where locations are stored.
	private static final Path SAVE_DIRECTORY = Paths.get("config", "save");
	
	// HTML file used by this command.
	private static final String HTML_FILE = "data/html/mods/handler/SaveLocation.htm";
	
	private static final String[] VOICED_COMMANDS =
	{
		"savelocation",
		"saveloc"
	};
	
	private static final Pattern LOCATION_ID_PATTERN = Pattern.compile("id=\"(\\d+)\"");
	
	@Override
	public boolean useVoicedCommand(String command, L2PcInstance activeChar, String target)
	{
		if (activeChar == null)
		{
			return false;
		}
		
		if ((target == null) || target.trim().isEmpty())
		{
			showHtml(activeChar);
			return true;
		}
		
		String action = target.trim().toLowerCase();
		
		switch (action)
		{
			case "save":
				saveLocation(activeChar);
				return true;
			
			case "next":
				nextFile(activeChar);
				return true;
			
			default:
				showHtml(activeChar);
				return true;
		}
	}
	
	private void showHtml(L2PcInstance activeChar)
	{
		final NpcHtmlMessage html = new NpcHtmlMessage(0);
		html.setFile(activeChar.getHtmlPrefix(), HTML_FILE);
		activeChar.sendPacket(html);
	}
	
	/**
	 * Saves the player's current location to the active TXT file. Each location is assigned a sequential ID.
	 */
	private synchronized void saveLocation(L2PcInstance activeChar)
	{
		try
		{
			Files.createDirectories(SAVE_DIRECTORY);
			
			String playerName = getSafePlayerName(activeChar);
			int fileIndex = getCurrentFileIndex(playerName);
			
			Path file = getLocationFile(playerName, fileIndex);
			
			// Create the first file if it does not exist yet.
			if (!Files.exists(file))
			{
				Files.createFile(file);
			}
			
			int nextId = getNextLocationId(file);
			
			String location = "id=\"" + nextId + "\", " + "x=\"" + activeChar.getX() + "\", " + "y=\"" + activeChar.getY() + "\", " + "z=\"" + activeChar.getZ() + "\", " + "h=\"" + activeChar.getHeading() + "\";";
			
			Files.write(file, (location + System.lineSeparator()).getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
			
			activeChar.sendMessage("Localizacion guardada correctamente. ID: " + nextId);
		}
		catch (Exception e)
		{
			LOGGER.log(Level.SEVERE, "Error guardando la localizacion de " + activeChar.getName(), e);
			activeChar.sendMessage("Error al guardar la localizacion: " + e.getMessage());
		}
	}
	
	/**
	 * Creates a new TXT file. The location ID counter always starts again from 1.
	 */
	private synchronized void nextFile(L2PcInstance activeChar)
	{
		try
		{
			Files.createDirectories(SAVE_DIRECTORY);
			
			String playerName = getSafePlayerName(activeChar);
			
			int currentIndex = getCurrentFileIndex(playerName);
			int newIndex = currentIndex + 1;
			
			Path newFile = getLocationFile(playerName, newIndex);
			
			Files.createFile(newFile);
			
			activeChar.sendMessage("Nuevo archivo de localizaciones creado: " + newFile.getFileName());
			activeChar.sendMessage("El contador de localizaciones comienza de nuevo en ID 1.");
		}
		catch (Exception e)
		{
			LOGGER.log(Level.SEVERE, "Error creando un nuevo archivo de localizaciones para " + activeChar.getName(), e);
			activeChar.sendMessage("Error al crear el nuevo archivo: " + e.getMessage());
		}
	}
	
	/**
	 * Gets the index of the player's latest TXT file. Returns 0 if no file exists.
	 */
	private int getCurrentFileIndex(String playerName) throws IOException
	{
		int maxIndex = 0;
		
		String prefix = "location_" + playerName + "_";
		Pattern filePattern = Pattern.compile("^" + Pattern.quote(prefix) + "(\\d+)\\.txt$");
		
		try (DirectoryStream<Path> files = Files.newDirectoryStream(SAVE_DIRECTORY))
		{
			for (Path file : files)
			{
				Matcher matcher = filePattern.matcher(file.getFileName().toString());
				
				if (matcher.matches() && Files.isRegularFile(file))
				{
					int index = Integer.parseInt(matcher.group(1));
					
					if (index > maxIndex)
					{
						maxIndex = index;
					}
				}
			}
		}
		
		return maxIndex;
	}
	
	/**
	 * Calculates the next location ID by reading the contents of the current file.
	 */
	private int getNextLocationId(Path file) throws IOException
	{
		if (!Files.exists(file))
		{
			return 1;
		}
		
		List<String> lines = Files.readAllLines(file, StandardCharsets.UTF_8);
		
		int maxId = 0;
		
		for (String line : lines)
		{
			Matcher matcher = LOCATION_ID_PATTERN.matcher(line);
			
			if (matcher.find())
			{
				int id = Integer.parseInt(matcher.group(1));
				
				if (id > maxId)
				{
					maxId = id;
				}
			}
		}
		
		return maxId + 1;
	}
	
	/**
	 * Builds the file path for the specified player.
	 */
	private Path getLocationFile(String playerName, int fileIndex)
	{
		return SAVE_DIRECTORY.resolve("location_" + playerName + "_" + fileIndex + ".txt");
	}
	
	/**
	 * Sanitizes the player's name so it can safely be used in a file name.
	 */
	private String getSafePlayerName(L2PcInstance activeChar)
	{
		return activeChar.getName().replaceAll("[^a-zA-Z0-9_-]", "_");
	}
	
	@Override
	public String[] getVoicedCommandList()
	{
		return VOICED_COMMANDS;
	}
}

<html>
<body>
    <center>
        <br>
        <font name="hs12" color="LEVEL">LOCATION SAVER</font>
        <br>
        <img src="L2UI.SquareWhite" width=280 height=1>
        <br>

        <table width=280>
            <tr>
                <td align=center>
                    <font color="FFFFFF">Linea actual:</font>
                    <font color="LEVEL">%line%</font>
                </td>
            </tr>
        </table>

        <br>

        <table width=220>
            <tr>
                <td align=center>
                    <button value="GUARDAR LOCATION" action="bypass -h voice .savelocation save" width=180 height=35 back="L2UI_ct1.button_df" fore="L2UI_ct1.button_df">
                </td>
            </tr>
            <tr>
                <td align=center>
                    <button value="SIGUIENTE LINEA" action="bypass -h voice .savelocation next" width=180 height=35 back="L2UI_ct1.button_df" fore="L2UI_ct1.button_df">
                </td>
            </tr>
        </table>

        <br>
        <img src="L2UI.SquareWhite" width=280 height=1>
        <br>
        <font color="AAAAAA">Location Saver - L2JDevs</font>
    </center>
</body>
</html>