/* * 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 silentium.gameserver.data.xml; import gnu.trove.map.hash.TIntObjectHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import silentium.gameserver.configs.MainConfig; import silentium.gameserver.data.xml.parsers.XMLDocumentFactory; import silentium.gameserver.geo.pathfinding.AbstractNodeLoc; import silentium.gameserver.idfactory.IdFactory; import silentium.gameserver.instancemanager.CastleManager; import silentium.gameserver.instancemanager.ClanHallManager; import silentium.gameserver.model.actor.instance.L2DoorInstance; import silentium.gameserver.model.entity.Castle; import silentium.gameserver.model.entity.ClanHall; import silentium.gameserver.templates.StatsSet; import silentium.gameserver.templates.chars.L2CharTemplate; import java.io.File; import java.util.ArrayList; public class DoorData { private static final Logger _log = LoggerFactory.getLogger(DoorData.class.getName()); private final TIntObjectHashMap<L2DoorInstance> _staticItems; private final TIntObjectHashMap<ArrayList<L2DoorInstance>> _regions; public static DoorData getInstance() { return SingletonHolder._instance; } protected DoorData() { _staticItems = new TIntObjectHashMap<>(); _regions = new TIntObjectHashMap<>(); parseData(); onStart(); } public void reload() { _staticItems.clear(); _regions.clear(); parseData(); } public void parseData() { try { File f = new File(MainConfig.DATAPACK_ROOT + "/data/xml/doors.xml"); Document doc = XMLDocumentFactory.getInstance().loadDocument(f); // Initialize variables L2DoorInstance door = null; String name; int id; NamedNodeMap attrs; Node att; int castleId = 0/* , sChId = 0 */; int x = 0, y = 0, z = 0; int rangeXMin = 0, rangeYMin = 0, rangeZMin = 0; int rangeXMax = 0, rangeYMax = 0, rangeZMax = 0; int hp = 0, pdef = 0, mdef = 0; boolean unlockable = false; int collisionRadius = 0; for (Node n = doc.getFirstChild(); n != null; n = n.getNextSibling()) { if ("list".equalsIgnoreCase(n.getNodeName())) { for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) { if (d.getNodeName().equalsIgnoreCase("door")) { attrs = d.getAttributes(); // Verify if the door got an id, else skip it att = attrs.getNamedItem("id"); if (att == null) { _log.error("DoorData: Missing id for door, skipping."); continue; } id = Integer.valueOf(att.getNodeValue()); // Verify if the door got a name, else skip it att = attrs.getNamedItem("name"); if (att == null) { _log.error("DoorData: Missing name for door id: " + id + ", skipping."); continue; } name = String.valueOf(att.getNodeValue()); for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling()) { attrs = c.getAttributes(); if ("castle".equalsIgnoreCase(c.getNodeName())) { castleId = Integer.valueOf(attrs.getNamedItem("id").getNodeValue()); } else if ("siegableclanhall".equalsIgnoreCase(c.getNodeName())) { // FIXME sChId = Integer.valueOf(attrs.getNamedItem("id").getNodeValue()); } else if ("position".equalsIgnoreCase(c.getNodeName())) { x = Integer.valueOf(attrs.getNamedItem("x").getNodeValue()); y = Integer.valueOf(attrs.getNamedItem("y").getNodeValue()); z = Integer.valueOf(attrs.getNamedItem("z").getNodeValue()); } else if ("minpos".equalsIgnoreCase(c.getNodeName())) { rangeXMin = Integer.valueOf(attrs.getNamedItem("x").getNodeValue()); rangeYMin = Integer.valueOf(attrs.getNamedItem("y").getNodeValue()); rangeZMin = Integer.valueOf(attrs.getNamedItem("z").getNodeValue()); } else if ("maxpos".equalsIgnoreCase(c.getNodeName())) { rangeXMax = Integer.valueOf(attrs.getNamedItem("x").getNodeValue()); rangeYMax = Integer.valueOf(attrs.getNamedItem("y").getNodeValue()); rangeZMax = Integer.valueOf(attrs.getNamedItem("z").getNodeValue()); } else if ("stats".equalsIgnoreCase(c.getNodeName())) { hp = Integer.valueOf(attrs.getNamedItem("hp").getNodeValue()); pdef = Integer.valueOf(attrs.getNamedItem("pdef").getNodeValue()); mdef = Integer.valueOf(attrs.getNamedItem("mdef").getNodeValue()); } else if ("unlockable".equalsIgnoreCase(c.getNodeName())) unlockable = Boolean.valueOf(attrs.getNamedItem("val").getNodeValue()); } if (rangeXMin > rangeXMax) _log.error("DoorData: Error on rangeX min/max, ID:" + id); if (rangeYMin > rangeYMax) _log.error("DoorData: Error on rangeY min/max, ID:" + id); if (rangeZMin > rangeZMax) _log.error("DoorData: Error on rangeZ min/max, ID:" + id); if ((rangeXMax - rangeXMin) > (rangeYMax - rangeYMin)) collisionRadius = rangeYMax - rangeYMin; else collisionRadius = rangeXMax - rangeXMin; // Template initialization StatsSet npcDat = new StatsSet(); npcDat.set("npcId", id); npcDat.set("level", 0); npcDat.set("jClass", "door"); npcDat.set("baseSTR", 0); npcDat.set("baseCON", 0); npcDat.set("baseDEX", 0); npcDat.set("baseINT", 0); npcDat.set("baseWIT", 0); npcDat.set("baseMEN", 0); npcDat.set("baseShldDef", 0); npcDat.set("baseShldRate", 0); npcDat.set("baseAccCombat", 38); npcDat.set("baseEvasRate", 38); npcDat.set("baseCritRate", 38); npcDat.set("collision_radius", collisionRadius); npcDat.set("collision_height", rangeZMax - rangeZMin); npcDat.set("sex", "male"); npcDat.set("type", ""); npcDat.set("baseAtkRange", 0); npcDat.set("baseMpMax", 0); npcDat.set("baseCpMax", 0); npcDat.set("rewardExp", 0); npcDat.set("rewardSp", 0); npcDat.set("basePAtk", 0); npcDat.set("baseMAtk", 0); npcDat.set("basePAtkSpd", 0); npcDat.set("aggroRange", 0); npcDat.set("baseMAtkSpd", 0); npcDat.set("rhand", 0); npcDat.set("lhand", 0); npcDat.set("armor", 0); npcDat.set("baseWalkSpd", 0); npcDat.set("baseRunSpd", 0); npcDat.set("name", name); npcDat.set("baseHpMax", hp); npcDat.set("baseHpReg", 3.e-3f); npcDat.set("baseMpReg", 3.e-3f); npcDat.set("basePDef", pdef); npcDat.set("baseMDef", mdef); L2CharTemplate template = new L2CharTemplate(npcDat); door = new L2DoorInstance(IdFactory.getInstance().getNextId(), template, id, name, unlockable); door.setRange(rangeXMin, rangeYMin, rangeZMin, rangeXMax, rangeYMax, rangeZMax); door.setCurrentHpMp(door.getMaxHp(), door.getMaxMp()); door.setXYZInvisible(x, y, z); door.setMapRegion(MapRegionData.getInstance().getMapRegion(x, y)); door.setOpen(false); // Attach door to a castle if a castleId is found if (castleId > 0) { Castle castle = CastleManager.getInstance().getCastleById(castleId); if (castle != null) { // Set the door as a wall if door name contains "wall". if (name.contains("wall")) door.setIsWall(true); castle.getDoors().add(door); // Add the door to castle doors list. _log.debug("DoorData: Door " + door.getDoorId() + " is now attached to " + castle.getName() + " castle."); } } // Test door, and attach it to a CH if a CH is found near ClanHall clanhall = ClanHallManager.getInstance().getNearbyClanHall(door.getX(), door.getY(), 500); if (clanhall != null) { clanhall.getDoors().add(door); // Add the door to CH doors list. door.setClanHall(clanhall); _log.debug("DoorData: Door " + door.getDoorId() + " is now attached to " + clanhall.getName() + " clanhall."); } putDoor(door); door.spawnMe(door.getX(), door.getY(), door.getZ()); } } } } _log.info("DoorData: Loaded " + _staticItems.size() + " doors templates for " + _regions.size() + " regions."); } catch (Exception e) { _log.warn("DoorData: Error while creating table: " + e); } } public L2DoorInstance getDoor(Integer id) { return _staticItems.get(id); } public void putDoor(L2DoorInstance door) { _staticItems.put(door.getDoorId(), door); if (_regions.contains(door.getMapRegion())) _regions.get(door.getMapRegion()).add(door); else { final ArrayList<L2DoorInstance> region = new ArrayList<>(); region.add(door); _regions.put(door.getMapRegion(), region); } } public L2DoorInstance[] getDoors() { return _staticItems.values(new L2DoorInstance[0]); } /** * Performs a check and sets up a scheduled task for those doors that require auto opening/closing. */ public void checkAutoOpen() { for (L2DoorInstance doorInst : getDoors()) // Garden of Eva (every 7 minutes) if (doorInst.getDoorName().startsWith("Eva")) doorInst.setAutoActionDelay(420000); // Tower of Insolence (every 5 minutes) else if (doorInst.getDoorName().startsWith("hubris")) doorInst.setAutoActionDelay(300000); } public boolean checkIfDoorsBetween(AbstractNodeLoc start, AbstractNodeLoc end) { return checkIfDoorsBetween(start.getX(), start.getY(), start.getZ(), end.getX(), end.getY(), end.getZ()); } public boolean checkIfDoorsBetween(int x, int y, int z, int tx, int ty, int tz) { ArrayList<L2DoorInstance> allDoors; allDoors = _regions.get(MapRegionData.getInstance().getMapRegion(x, y)); if (allDoors == null) return false; for (L2DoorInstance doorInst : allDoors) { if (doorInst.getXMax() == 0) continue; // line segment goes through box // first basic checks to stop most calculations short // phase 1, x if ((x <= doorInst.getXMax() && tx >= doorInst.getXMin()) || (tx <= doorInst.getXMax() && x >= doorInst.getXMin())) { // phase 2, y if ((y <= doorInst.getYMax() && ty >= doorInst.getYMin()) || (ty <= doorInst.getYMax() && y >= doorInst.getYMin())) { // phase 3, basically only z remains but now we calculate it with another formula (by rage) // in some cases the direct line check (only) in the beginning isn't sufficient, // when char z changes a lot along the path if (doorInst.getCurrentHp() > 0 && !doorInst.getOpen()) { int px1 = doorInst.getXMin(); int py1 = doorInst.getYMin(); int pz1 = doorInst.getZMin(); int px2 = doorInst.getXMax(); int py2 = doorInst.getYMax(); int pz2 = doorInst.getZMax(); int l = tx - x; int m = ty - y; int n = tz - z; int dk; if ((dk = (doorInst.getA() * l + doorInst.getB() * m + doorInst.getC() * n)) == 0) continue; // Parallel float p = (float) (doorInst.getA() * x + doorInst.getB() * y + doorInst.getC() * z + doorInst.getD()) / (float) dk; int fx = (int) (x - l * p); int fy = (int) (y - m * p); int fz = (int) (z - n * p); if ((Math.min(x, tx) <= fx && fx <= Math.max(x, tx)) && (Math.min(y, ty) <= fy && fy <= Math.max(y, ty)) && (Math.min(z, tz) <= fz && fz <= Math.max(z, tz))) { if (((fx >= px1 && fx <= px2) || (fx >= px2 && fx <= px1)) && ((fy >= py1 && fy <= py2) || (fy >= py2 && fy <= py1)) && ((fz >= pz1 && fz <= pz2) || (fz >= pz2 && fz <= pz1))) return true; // Door between } } } } } return false; } public void onStart() { try { // Open following doors at server start: coliseums, ToI - RB Area Doors getDoor(24190001).openMe(); getDoor(24190002).openMe(); getDoor(24190003).openMe(); getDoor(24190004).openMe(); getDoor(23180001).openMe(); getDoor(23180002).openMe(); getDoor(23180003).openMe(); getDoor(23180004).openMe(); getDoor(23180005).openMe(); getDoor(23180006).openMe(); // Schedules a task to automatically open/close doors checkAutoOpen(); } catch (NullPointerException e) { _log.warn("There are errors in your doors.xml file. Please update doors.xml", e); } } private static class SingletonHolder { protected static final DoorData _instance = new DoorData(); } }