U3Games

Games | Desarrollo & Soporte => L2 | Implementaciones => L2 | Sección de Servidores => Lineage => L2 | Comandos => Mensaje iniciado por: Swarlog en Nov 13, 2026, 10:04 PM

Título: Comando SaveLocation
Publicado por: Swarlog en Nov 13, 2026, 10:04 PM
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:


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>