/* * This file is part of the L2J Mobius 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 . */ package handlers.admincommandhandlers; import java.awt.Color; import java.io.File; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.StringJoiner; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import com.l2jmobius.Config; import com.l2jmobius.commons.util.CommonUtil; import com.l2jmobius.commons.util.Rnd; import com.l2jmobius.gameserver.GeoData; import com.l2jmobius.gameserver.enums.PlayerAction; import com.l2jmobius.gameserver.handler.IAdminCommandHandler; import com.l2jmobius.gameserver.instancemanager.ZoneManager; import com.l2jmobius.gameserver.model.Location; import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; import com.l2jmobius.gameserver.model.events.EventType; import com.l2jmobius.gameserver.model.events.ListenerRegisterType; import com.l2jmobius.gameserver.model.events.annotations.Priority; import com.l2jmobius.gameserver.model.events.annotations.RegisterEvent; import com.l2jmobius.gameserver.model.events.annotations.RegisterType; import com.l2jmobius.gameserver.model.events.impl.character.player.OnPlayerDlgAnswer; import com.l2jmobius.gameserver.model.events.impl.character.player.OnPlayerMoveRequest; import com.l2jmobius.gameserver.model.events.returns.TerminateReturn; import com.l2jmobius.gameserver.model.html.PageBuilder; import com.l2jmobius.gameserver.model.html.PageResult; import com.l2jmobius.gameserver.model.zone.L2ZoneType; import com.l2jmobius.gameserver.model.zone.form.ZoneNPoly; import com.l2jmobius.gameserver.network.serverpackets.ConfirmDlg; import com.l2jmobius.gameserver.network.serverpackets.ExServerPrimitive; import com.l2jmobius.gameserver.network.serverpackets.ExShowTerritory; import com.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage; import com.l2jmobius.gameserver.util.Util; import ai.AbstractNpcAI; /** * @author UnAfraid */ public class AdminZones extends AbstractNpcAI implements IAdminCommandHandler { private static final Logger _log = Logger.getLogger(AdminPathNode.class.getName()); private final Map _zones = new ConcurrentHashMap<>(); private static final String[] COMMANDS = { "admin_zones", }; public AdminZones() { } @Override public boolean useAdminCommand(String command, L2PcInstance activeChar) { final StringTokenizer st = new StringTokenizer(command); final String cmd = st.nextToken(); switch (cmd) { case "admin_zones": { if (!st.hasMoreTokens()) { buildZonesEditorWindow(activeChar); return false; } final String subCmd = st.nextToken(); switch (subCmd) { case "load": { if (st.hasMoreTokens()) { String name = ""; while (st.hasMoreTokens()) { name += st.nextToken() + " "; } loadZone(activeChar, name.trim()); } break; } case "create": { buildHtmlWindow(activeChar, 0); break; } case "setname": { String name = ""; while (st.hasMoreTokens()) { name += st.nextToken() + " "; } if (!name.isEmpty()) { name = name.substring(0, name.length() - 1); } setName(activeChar, name); break; } case "start": { enablePicking(activeChar); break; } case "finish": { disablePicking(activeChar); break; } case "setMinZ": { if (st.hasMoreTokens()) { final int minZ = Integer.parseInt(st.nextToken()); setMinZ(activeChar, minZ); } break; } case "setMaxZ": { if (st.hasMoreTokens()) { final int maxZ = Integer.parseInt(st.nextToken()); setMaxZ(activeChar, maxZ); } break; } case "show": { showPoints(activeChar); final ConfirmDlg dlg = new ConfirmDlg("When enable show territory you must restart client to remove it, are you sure about that?"); dlg.addTime(15 * 1000); activeChar.sendPacket(dlg); activeChar.addAction(PlayerAction.ADMIN_SHOW_TERRITORY); break; } case "hide": { final ZoneNodeHolder holder = _zones.get(activeChar.getObjectId()); if (holder != null) { final ExServerPrimitive exsp = new ExServerPrimitive("DebugPoint_" + activeChar.getObjectId(), activeChar.getX(), activeChar.getY(), activeChar.getZ()); exsp.addPoint(Color.BLACK, 0, 0, 0); activeChar.sendPacket(exsp); } break; } case "change": { if (!st.hasMoreTokens()) { activeChar.sendMessage("Missing node index!"); break; } final String indexToken = st.nextToken(); if (!Util.isDigit(indexToken)) { activeChar.sendMessage("Node index should be int!"); break; } final int index = Integer.parseInt(indexToken); changePoint(activeChar, index); break; } case "delete": { if (!st.hasMoreTokens()) { activeChar.sendMessage("Missing node index!"); break; } final String indexToken = st.nextToken(); if (!Util.isDigit(indexToken)) { activeChar.sendMessage("Node index should be int!"); break; } final int index = Integer.parseInt(indexToken); deletePoint(activeChar, index); showPoints(activeChar); break; } case "clear": { _zones.remove(activeChar.getObjectId()); break; } case "dump": { dumpPoints(activeChar); break; } case "list": { final int page = CommonUtil.parseNextInt(st, 0); buildHtmlWindow(activeChar, page); return false; } } break; } } buildHtmlWindow(activeChar, 0); return false; } /** * @param activeChar * @param minZ */ private void setMinZ(L2PcInstance activeChar, int minZ) { _zones.computeIfAbsent(activeChar.getObjectId(), key -> new ZoneNodeHolder(activeChar)).setMinZ(minZ); } /** * @param activeChar * @param maxZ */ private void setMaxZ(L2PcInstance activeChar, int maxZ) { _zones.computeIfAbsent(activeChar.getObjectId(), key -> new ZoneNodeHolder(activeChar)).setMaxZ(maxZ); } private void buildZonesEditorWindow(L2PcInstance activeChar) { final StringBuilder sb = new StringBuilder(); final List zones = ZoneManager.getInstance().getZones(activeChar); for (L2ZoneType zone : zones) { if (zone.getZone() instanceof ZoneNPoly) { sb.append(""); sb.append("" + zone.getName() + ""); sb.append(""); } } final NpcHtmlMessage msg = new NpcHtmlMessage(0, 1); msg.setFile(activeChar.getHtmlPrefix(), "data/html/admin/zone_editor.htm"); msg.replace("%zones%", sb.toString()); activeChar.sendPacket(msg); } /** * @param activeChar * @param zoneName */ private void loadZone(L2PcInstance activeChar, String zoneName) { activeChar.sendMessage("Searching for zone: " + zoneName); final List zones = ZoneManager.getInstance().getZones(activeChar); L2ZoneType zoneType = null; for (L2ZoneType zone : zones) { if (zone.getName().equalsIgnoreCase(zoneName)) { zoneType = zone; activeChar.sendMessage("Zone found: " + zone.getId()); break; } } if ((zoneType != null) && (zoneType.getZone() instanceof ZoneNPoly)) { final ZoneNPoly zone = (ZoneNPoly) zoneType.getZone(); final ZoneNodeHolder holder = _zones.computeIfAbsent(activeChar.getObjectId(), val -> new ZoneNodeHolder(activeChar)); holder.getNodes().clear(); holder.setName(zoneType.getName()); holder.setMinZ(zone.getLowZ()); holder.setMaxZ(zone.getHighZ()); for (int i = 0; i < zone.getX().length; i++) { final int x = zone.getX()[i]; final int y = zone.getY()[i]; holder.addNode(new Location(x, y, GeoData.getInstance().getSpawnHeight(x, y, Rnd.get(zone.getLowZ(), zone.getHighZ())))); } showPoints(activeChar); } } /** * @param activeChar * @param name */ private void setName(L2PcInstance activeChar, String name) { if (name.contains("<") || name.contains(">") || name.contains("&") || name.contains("\\") || name.contains("\"") || name.contains("$")) { activeChar.sendMessage("You cannot use symbols like: < > & \" $ \\"); return; } _zones.computeIfAbsent(activeChar.getObjectId(), key -> new ZoneNodeHolder(activeChar)).setName(name); } /** * @param activeChar */ private void enablePicking(L2PcInstance activeChar) { if (!activeChar.hasAction(PlayerAction.ADMIN_POINT_PICKING)) { activeChar.addAction(PlayerAction.ADMIN_POINT_PICKING); activeChar.sendMessage("Point picking mode activated!"); } else { activeChar.sendMessage("Point picking mode is already activated!"); } } /** * @param activeChar */ private void disablePicking(L2PcInstance activeChar) { if (activeChar.removeAction(PlayerAction.ADMIN_POINT_PICKING)) { activeChar.sendMessage("Point picking mode deactivated!"); } else { activeChar.sendMessage("Point picking mode was not activated!"); } } /** * @param activeChar */ private void showPoints(L2PcInstance activeChar) { final ZoneNodeHolder holder = _zones.get(activeChar.getObjectId()); if (holder != null) { if (holder.getNodes().size() < 3) { activeChar.sendMessage("In order to visualize this zone you must have at least 3 points."); return; } final ExServerPrimitive exsp = new ExServerPrimitive("DebugPoint_" + activeChar.getObjectId(), activeChar.getX(), activeChar.getY(), activeChar.getZ()); final List list = holder.getNodes(); for (int i = 1; i < list.size(); i++) { final Location prevLoc = list.get(i - 1); final Location nextLoc = list.get(i); if (holder.getMinZ() != 0) { exsp.addLine("Min Point " + i + " > " + (i + 1), Color.CYAN, true, prevLoc.getX(), prevLoc.getY(), holder.getMinZ(), nextLoc.getX(), nextLoc.getY(), holder.getMinZ()); } exsp.addLine("Point " + i + " > " + (i + 1), Color.WHITE, true, prevLoc, nextLoc); if (holder.getMaxZ() != 0) { exsp.addLine("Max Point " + i + " > " + (i + 1), Color.RED, true, prevLoc.getX(), prevLoc.getY(), holder.getMaxZ(), nextLoc.getX(), nextLoc.getY(), holder.getMaxZ()); } } final Location prevLoc = list.get(list.size() - 1); final Location nextLoc = list.get(0); if (holder.getMinZ() != 0) { exsp.addLine("Min Point " + list.size() + " > 1", Color.CYAN, true, prevLoc.getX(), prevLoc.getY(), holder.getMinZ(), nextLoc.getX(), nextLoc.getY(), holder.getMinZ()); } exsp.addLine("Point " + list.size() + " > 1", Color.WHITE, true, prevLoc, nextLoc); if (holder.getMaxZ() != 0) { exsp.addLine("Max Point " + list.size() + " > 1", Color.RED, true, prevLoc.getX(), prevLoc.getY(), holder.getMaxZ(), nextLoc.getX(), nextLoc.getY(), holder.getMaxZ()); } activeChar.sendPacket(exsp); } } /** * @param activeChar * @param index */ private void changePoint(L2PcInstance activeChar, int index) { final ZoneNodeHolder holder = _zones.get(activeChar.getObjectId()); if (holder != null) { final Location loc = holder.getNodes().get(index); if (loc != null) { enablePicking(activeChar); holder.setChangingLoc(loc); } } } /** * @param activeChar * @param index */ private void deletePoint(L2PcInstance activeChar, int index) { final ZoneNodeHolder holder = _zones.get(activeChar.getObjectId()); if (holder != null) { final Location loc = holder.getNodes().get(index); if (loc != null) { holder.getNodes().remove(loc); activeChar.sendMessage("Node " + index + " has been removed!"); if (holder.getNodes().isEmpty()) { activeChar.sendMessage("Since node list is empty destroying session!"); _zones.remove(activeChar.getObjectId()); } } } } /** * @param activeChar */ private void dumpPoints(L2PcInstance activeChar) { final ZoneNodeHolder holder = _zones.get(activeChar.getObjectId()); if ((holder != null) && !holder.getNodes().isEmpty()) { if (holder.getName().isEmpty()) { activeChar.sendMessage("Set name first!"); return; } final Location firstNode = holder.getNodes().get(0); final StringJoiner sj = new StringJoiner(Config.EOL); sj.add(""); sj.add(""); sj.add("\t"); for (Location loc : holder.getNodes()) { sj.add("\t\t"); } sj.add("\t"); sj.add(""); sj.add(""); // new line at end of file try { File file = new File("log/points/" + activeChar.getAccountName() + "/" + holder.getName() + ".xml"); if (file.exists()) { int i = 0; while ((file = new File("log/points/" + activeChar.getAccountName() + "/" + holder.getName() + i + ".xml")).exists()) { i++; } } if (!file.getParentFile().isDirectory()) { file.getParentFile().mkdirs(); } Files.write(file.toPath(), sj.toString().getBytes(StandardCharsets.UTF_8)); activeChar.sendMessage("Successfully written on: " + file.getAbsolutePath().replace(new File(".").getCanonicalFile().getAbsolutePath(), "")); } catch (Exception e) { activeChar.sendMessage("Failed writing the dump: " + e.getMessage()); _log.log(Level.WARNING, "Failed writing point picking dump for " + activeChar.getName() + ":" + e.getMessage(), e); } } } @RegisterEvent(EventType.ON_PLAYER_MOVE_REQUEST) @RegisterType(ListenerRegisterType.GLOBAL_PLAYERS) @Priority(Integer.MAX_VALUE) public TerminateReturn onPlayerPointPicking(OnPlayerMoveRequest event) { final L2PcInstance activeChar = event.getActiveChar(); if (activeChar.hasAction(PlayerAction.ADMIN_POINT_PICKING)) { final Location newLocation = event.getLocation(); final ZoneNodeHolder holder = _zones.computeIfAbsent(activeChar.getObjectId(), key -> new ZoneNodeHolder(activeChar)); final Location changeLog = holder.getChangingLoc(); if (changeLog != null) { changeLog.setXYZ(newLocation); holder.setChangingLoc(null); activeChar.sendMessage("Location " + (holder.indexOf(changeLog) + 1) + " has been updated!"); disablePicking(activeChar); } else { holder.addNode(newLocation); activeChar.sendMessage("Location " + (holder.indexOf(changeLog) + 1) + " has been added!"); } // Auto visualization when nodes >= 3 if (holder.getNodes().size() >= 3) { showPoints(activeChar); } buildHtmlWindow(activeChar, 0); return new TerminateReturn(true, true, false); } return null; } @RegisterEvent(EventType.ON_PLAYER_DLG_ANSWER) @RegisterType(ListenerRegisterType.GLOBAL_PLAYERS) public void onPlayerDlgAnswer(OnPlayerDlgAnswer event) { final L2PcInstance activeChar = event.getActiveChar(); if (activeChar.removeAction(PlayerAction.ADMIN_SHOW_TERRITORY) && (event.getAnswer() == 1)) { final ZoneNodeHolder holder = _zones.get(activeChar.getObjectId()); if (holder != null) { final List list = holder.getNodes(); if (list.size() < 3) { activeChar.sendMessage("You must have at least 3 nodes to use this option!"); return; } final Location firstLoc = list.get(0); final int minZ = holder.getMinZ() != 0 ? holder.getMinZ() : firstLoc.getZ() - 100; final int maxZ = holder.getMaxZ() != 0 ? holder.getMaxZ() : firstLoc.getZ() + 100; final ExShowTerritory exst = new ExShowTerritory(minZ, maxZ); list.forEach(exst::addVertice); activeChar.sendPacket(exst); activeChar.sendMessage("In order to remove the debug you must restart your game client!"); } } } @Override public String[] getAdminCommandList() { return COMMANDS; } private void buildHtmlWindow(L2PcInstance activeChar, int page) { final NpcHtmlMessage msg = new NpcHtmlMessage(0, 1); msg.setFile(activeChar.getHtmlPrefix(), "data/html/admin/zone_editor_create.htm"); final ZoneNodeHolder holder = _zones.computeIfAbsent(activeChar.getObjectId(), key -> new ZoneNodeHolder(activeChar)); final AtomicInteger position = new AtomicInteger(page * 20); final PageResult result = PageBuilder.newBuilder(holder.getNodes(), 20, "bypass -h admin_zones list").currentPage(page).bodyHandler((pages, loc, sb) -> { sb.append(""); sb.append(""); sb.append("" + position.getAndIncrement() + ""); sb.append("" + loc.getX() + ""); sb.append("" + loc.getY() + ""); sb.append("" + loc.getZ() + ""); sb.append("[E]"); sb.append("[T]"); sb.append("[D]"); sb.append(""); sb.append(""); }).build(); msg.replace("%name%", holder.getName()); msg.replace("%minZ%", holder.getMinZ()); msg.replace("%maxZ%", holder.getMaxZ()); msg.replace("%pages%", result.getPagerTemplate()); msg.replace("%nodes%", result.getBodyTemplate()); activeChar.sendPacket(msg); } protected class ZoneNodeHolder { private String _name = ""; private Location _changingLoc = null; private int _minZ; private int _maxZ; private final List _nodes = new ArrayList<>(); public ZoneNodeHolder(L2PcInstance player) { _minZ = player.getZ() - 200; _maxZ = player.getZ() + 200; } public void setName(String name) { _name = name; } public String getName() { return _name; } public void setChangingLoc(Location loc) { _changingLoc = loc; } public Location getChangingLoc() { return _changingLoc; } public void addNode(Location loc) { _nodes.add(loc); } public List getNodes() { return _nodes; } public int indexOf(Location loc) { return _nodes.indexOf(loc); } public int getMinZ() { return _minZ; } public int getMaxZ() { return _maxZ; } public void setMinZ(int minZ) { _minZ = minZ; } public void setMaxZ(int maxZ) { _maxZ = maxZ; } } }