/* * Copyright (C) 2004-2015 L2J Server * * This file is part of L2J Server. * * L2J Server 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. * * L2J Server 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 com.l2jserver.gameserver.model.actor.instance; import java.util.ArrayList; import java.util.Collection; import java.util.Set; import java.util.concurrent.Future; import java.util.logging.Level; import com.l2jserver.gameserver.ThreadPoolManager; import com.l2jserver.gameserver.ai.L2CharacterAI; import com.l2jserver.gameserver.ai.L2DoorAI; import com.l2jserver.gameserver.data.xml.impl.DoorData; import com.l2jserver.gameserver.enums.InstanceType; import com.l2jserver.gameserver.enums.Race; import com.l2jserver.gameserver.instancemanager.CastleManager; import com.l2jserver.gameserver.instancemanager.ClanHallManager; import com.l2jserver.gameserver.instancemanager.FortManager; import com.l2jserver.gameserver.instancemanager.InstanceManager; import com.l2jserver.gameserver.model.L2Clan; import com.l2jserver.gameserver.model.L2Object; import com.l2jserver.gameserver.model.Location; import com.l2jserver.gameserver.model.actor.L2Character; import com.l2jserver.gameserver.model.actor.L2Playable; import com.l2jserver.gameserver.model.actor.knownlist.DoorKnownList; import com.l2jserver.gameserver.model.actor.stat.DoorStat; import com.l2jserver.gameserver.model.actor.status.DoorStatus; import com.l2jserver.gameserver.model.actor.templates.L2DoorTemplate; import com.l2jserver.gameserver.model.entity.Castle; import com.l2jserver.gameserver.model.entity.ClanHall; import com.l2jserver.gameserver.model.entity.Fort; import com.l2jserver.gameserver.model.entity.Instance; import com.l2jserver.gameserver.model.entity.clanhall.SiegableHall; import com.l2jserver.gameserver.model.items.L2Weapon; import com.l2jserver.gameserver.model.items.instance.L2ItemInstance; import com.l2jserver.gameserver.model.skills.Skill; import com.l2jserver.gameserver.network.SystemMessageId; import com.l2jserver.gameserver.network.serverpackets.DoorStatusUpdate; import com.l2jserver.gameserver.network.serverpackets.OnEventTrigger; import com.l2jserver.gameserver.network.serverpackets.StaticObject; import com.l2jserver.gameserver.network.serverpackets.SystemMessage; import com.l2jserver.util.Rnd; public class L2DoorInstance extends L2Character { public static final byte OPEN_BY_CLICK = 1; public static final byte OPEN_BY_TIME = 2; public static final byte OPEN_BY_ITEM = 4; public static final byte OPEN_BY_SKILL = 8; public static final byte OPEN_BY_CYCLE = 16; /** The castle index in the array of L2Castle this L2NpcInstance belongs to */ private int _castleIndex = -2; /** The fort index in the array of L2Fort this L2NpcInstance belongs to */ private int _fortIndex = -2; private ClanHall _clanHall; private boolean _open = false; private boolean _isAttackableDoor = false; private boolean _isTargetable; private int _meshindex = 1; // used for autoclose on open private Future<?> _autoCloseTask; /** * Creates a door. * @param template the door template */ public L2DoorInstance(L2DoorTemplate template) { super(template); setInstanceType(InstanceType.L2DoorInstance); setIsInvul(false); setLethalable(false); _open = template.isOpenByDefault(); _isAttackableDoor = template.isAttackable(); _isTargetable = template.isTargetable(); if (getGroupName() != null) { DoorData.addDoorGroup(getGroupName(), getId()); } if (isOpenableByTime()) { startTimerOpen(); } int clanhallId = template.getClanHallId(); if (clanhallId > 0) { ClanHall hall = ClanHallManager.getAllClanHalls().get(clanhallId); if (hall != null) { setClanHall(hall); hall.getDoors().add(this); } } } @Override protected L2CharacterAI initAI() { return new L2DoorAI(this); } private void startTimerOpen() { int delay = _open ? getTemplate().getOpenTime() : getTemplate().getCloseTime(); if (getTemplate().getRandomTime() > 0) { delay += Rnd.get(getTemplate().getRandomTime()); } ThreadPoolManager.getInstance().scheduleGeneral(new TimerOpen(), delay * 1000); } @Override public final DoorKnownList getKnownList() { return (DoorKnownList) super.getKnownList(); } @Override public void initKnownList() { setKnownList(new DoorKnownList(this)); } @Override public L2DoorTemplate getTemplate() { return (L2DoorTemplate) super.getTemplate(); } @Override public final DoorStatus getStatus() { return (DoorStatus) super.getStatus(); } @Override public void initCharStatus() { setStatus(new DoorStatus(this)); } @Override public void initCharStat() { setStat(new DoorStat(this)); } @Override public DoorStat getStat() { return (DoorStat) super.getStat(); } /** * @return {@code true} if door is open-able by skill. */ public final boolean isOpenableBySkill() { return (getTemplate().getOpenType() & OPEN_BY_SKILL) == OPEN_BY_SKILL; } /** * @return {@code true} if door is open-able by item. */ public final boolean isOpenableByItem() { return (getTemplate().getOpenType() & OPEN_BY_ITEM) == OPEN_BY_ITEM; } /** * @return {@code true} if door is open-able by double-click. */ public final boolean isOpenableByClick() { return (getTemplate().getOpenType() & OPEN_BY_CLICK) == OPEN_BY_CLICK; } /** * @return {@code true} if door is open-able by time. */ public final boolean isOpenableByTime() { return (getTemplate().getOpenType() & OPEN_BY_TIME) == OPEN_BY_TIME; } /** * @return {@code true} if door is open-able by Field Cycle system. */ public final boolean isOpenableByCycle() { return (getTemplate().getOpenType() & OPEN_BY_CYCLE) == OPEN_BY_CYCLE; } @Override public final int getLevel() { return getTemplate().getLevel(); } /** * Gets the door ID. * @return the door ID */ @Override public int getId() { return getTemplate().getId(); } /** * @return Returns the open. */ public boolean getOpen() { return _open; } /** * @param open The open to set. */ public void setOpen(boolean open) { _open = open; if (getChildId() > 0) { L2DoorInstance sibling = getSiblingDoor(getChildId()); if (sibling != null) { sibling.notifyChildEvent(open); } else { _log.log(Level.WARNING, getClass().getSimpleName() + ": cannot find child id: " + getChildId()); } } } public boolean getIsAttackableDoor() { return _isAttackableDoor; } public boolean getIsShowHp() { return getTemplate().isShowHp(); } public void setIsAttackableDoor(boolean val) { _isAttackableDoor = val; } public int getDamage() { int dmg = 6 - (int) Math.ceil((getCurrentHp() / getMaxHp()) * 6); if (dmg > 6) { return 6; } if (dmg < 0) { return 0; } return dmg; } // TODO: Replace index with the castle id itself. public final Castle getCastle() { if (_castleIndex < 0) { _castleIndex = CastleManager.getInstance().getCastleIndex(this); } if (_castleIndex < 0) { return null; } return CastleManager.getInstance().getCastles().get(_castleIndex); } // TODO: Replace index with the fort id itself. public final Fort getFort() { if (_fortIndex < 0) { _fortIndex = FortManager.getInstance().getFortIndex(this); } if (_fortIndex < 0) { return null; } return FortManager.getInstance().getForts().get(_fortIndex); } public void setClanHall(ClanHall clanhall) { _clanHall = clanhall; } public ClanHall getClanHall() { return _clanHall; } public boolean isEnemy() { if ((getCastle() != null) && (getCastle().getResidenceId() > 0) && getCastle().getZone().isActive() && getIsShowHp()) { return true; } if ((getFort() != null) && (getFort().getResidenceId() > 0) && getFort().getZone().isActive() && getIsShowHp()) { return true; } if ((getClanHall() != null) && getClanHall().isSiegableHall() && ((SiegableHall) getClanHall()).getSiegeZone().isActive() && getIsShowHp()) { return true; } return false; } @Override public boolean isAutoAttackable(L2Character attacker) { // Doors can`t be attacked by NPCs if (!(attacker instanceof L2Playable)) { return false; } if (getIsAttackableDoor()) { return true; } if (!getIsShowHp()) { return false; } L2PcInstance actingPlayer = attacker.getActingPlayer(); if (getClanHall() != null) { SiegableHall hall = (SiegableHall) getClanHall(); if (!hall.isSiegableHall()) { return false; } return hall.isInSiege() && hall.getSiege().doorIsAutoAttackable() && hall.getSiege().checkIsAttacker(actingPlayer.getClan()); } // Attackable only during siege by everyone (not owner) boolean isCastle = ((getCastle() != null) && (getCastle().getResidenceId() > 0) && getCastle().getZone().isActive()); boolean isFort = ((getFort() != null) && (getFort().getResidenceId() > 0) && getFort().getZone().isActive()); if (isFort) { L2Clan clan = actingPlayer.getClan(); if ((clan != null) && (clan == getFort().getOwnerClan())) { return false; } } else if (isCastle) { L2Clan clan = actingPlayer.getClan(); if ((clan != null) && (clan.getId() == getCastle().getOwnerId())) { return false; } } return (isCastle || isFort); } @Override public void updateAbnormalVisualEffects() { } /** * Return null. */ @Override public L2ItemInstance getActiveWeaponInstance() { return null; } @Override public L2Weapon getActiveWeaponItem() { return null; } @Override public L2ItemInstance getSecondaryWeaponInstance() { return null; } @Override public L2Weapon getSecondaryWeaponItem() { return null; } @Override public void broadcastStatusUpdate() { Collection<L2PcInstance> knownPlayers = getKnownList().getKnownPlayers().values(); if ((knownPlayers == null) || knownPlayers.isEmpty()) { return; } StaticObject su = new StaticObject(this, false); StaticObject targetableSu = new StaticObject(this, true); DoorStatusUpdate dsu = new DoorStatusUpdate(this); OnEventTrigger oe = null; if (getEmitter() > 0) { oe = new OnEventTrigger(getEmitter(), getOpen()); } for (L2PcInstance player : knownPlayers) { if ((player == null) || !isVisibleFor(player)) { continue; } if (player.isGM() || (((getCastle() != null) && (getCastle().getResidenceId() > 0)) || ((getFort() != null) && (getFort().getResidenceId() > 0)))) { player.sendPacket(targetableSu); } else { player.sendPacket(su); } player.sendPacket(dsu); if (oe != null) { player.sendPacket(oe); } } } public final void openMe() { if (getGroupName() != null) { manageGroupOpen(true, getGroupName()); return; } setOpen(true); broadcastStatusUpdate(); startAutoCloseTask(); } public final void closeMe() { // remove close task Future<?> oldTask = _autoCloseTask; if (oldTask != null) { _autoCloseTask = null; oldTask.cancel(false); } if (getGroupName() != null) { manageGroupOpen(false, getGroupName()); return; } setOpen(false); broadcastStatusUpdate(); } private void manageGroupOpen(boolean open, String groupName) { Set<Integer> set = DoorData.getDoorsByGroup(groupName); L2DoorInstance first = null; for (Integer id : set) { L2DoorInstance door = getSiblingDoor(id); if (first == null) { first = door; } if (door.getOpen() != open) { door.setOpen(open); door.broadcastStatusUpdate(); } } if ((first != null) && open) { first.startAutoCloseTask(); // only one from group } } /** * Door notify child about open state change * @param open true if opened */ private void notifyChildEvent(boolean open) { byte openThis = open ? getTemplate().getMasterDoorOpen() : getTemplate().getMasterDoorClose(); if (openThis == 0) { return; } else if (openThis == 1) { openMe(); } else if (openThis == -1) { closeMe(); } } @Override public String toString() { return getClass().getSimpleName() + "[" + getTemplate().getId() + "](" + getObjectId() + ")"; } public String getDoorName() { return getTemplate().getName(); } public int getX(int i) { return getTemplate().getNodeX()[i]; } public int getY(int i) { return getTemplate().getNodeY()[i]; } public int getZMin() { return getTemplate().getNodeZ(); } public int getZMax() { return getTemplate().getNodeZ() + getTemplate().getHeight(); } public Collection<L2DefenderInstance> getKnownDefenders() { ArrayList<L2DefenderInstance> result = new ArrayList<>(); Collection<L2Object> objs = getKnownList().getKnownObjects().values(); for (L2Object obj : objs) { if (obj instanceof L2DefenderInstance) { result.add((L2DefenderInstance) obj); } } return result; } public void setMeshIndex(int mesh) { _meshindex = mesh; } public int getMeshIndex() { return _meshindex; } public int getEmitter() { return getTemplate().getEmmiter(); } public boolean isWall() { return getTemplate().isWall(); } public String getGroupName() { return getTemplate().getGroupName(); } public int getChildId() { return getTemplate().getChildDoorId(); } @Override public void reduceCurrentHp(double damage, L2Character attacker, boolean awake, boolean isDOT, Skill skill) { if (isWall() && (getInstanceId() == 0)) { if (!attacker.isServitor()) { return; } final L2ServitorInstance servitor = (L2ServitorInstance) attacker; if (servitor.getTemplate().getRace() != Race.SIEGE_WEAPON) { return; } } super.reduceCurrentHp(damage, attacker, awake, isDOT, skill); } @Override public void reduceCurrentHpByDOT(double i, L2Character attacker, Skill skill) { // doors can't be damaged by DOTs } @Override public boolean doDie(L2Character killer) { if (!super.doDie(killer)) { return false; } boolean isFort = ((getFort() != null) && (getFort().getResidenceId() > 0) && getFort().getSiege().isInProgress()); boolean isCastle = ((getCastle() != null) && (getCastle().getResidenceId() > 0) && getCastle().getSiege().isInProgress()); boolean isHall = ((getClanHall() != null) && getClanHall().isSiegableHall() && ((SiegableHall) getClanHall()).isInSiege()); if (isFort || isCastle || isHall) { broadcastPacket(SystemMessage.getSystemMessage(SystemMessageId.THE_CASTLE_GATE_HAS_BEEN_DESTROYED)); } return true; } @Override public void moveToLocation(int x, int y, int z, int offset) { } @Override public void stopMove(Location loc) { } @Override public void doAttack(L2Character target) { } @Override public void doCast(Skill skill) { } @Override public void sendInfo(L2PcInstance activeChar) { if (isVisibleFor(activeChar)) { if (getEmitter() > 0) { activeChar.sendPacket(new OnEventTrigger(getEmitter(), getOpen())); } activeChar.sendPacket(new StaticObject(this, activeChar.isGM())); } } public void setTargetable(boolean b) { _isTargetable = b; broadcastStatusUpdate(); } @Override public boolean isTargetable() { return _isTargetable; } public boolean checkCollision() { return getTemplate().isCheckCollision(); } /** * All doors are stored at DoorTable except instance doors * @param doorId * @return */ private L2DoorInstance getSiblingDoor(int doorId) { if (getInstanceId() == 0) { return DoorData.getInstance().getDoor(doorId); } Instance inst = InstanceManager.getInstance().getInstance(getInstanceId()); if (inst != null) { return inst.getDoor(doorId); } return null; // 2 late } private void startAutoCloseTask() { if ((getTemplate().getCloseTime() < 0) || isOpenableByTime()) { return; } Future<?> oldTask = _autoCloseTask; if (oldTask != null) { _autoCloseTask = null; oldTask.cancel(false); } _autoCloseTask = ThreadPoolManager.getInstance().scheduleGeneral(new AutoClose(), getTemplate().getCloseTime() * 1000); } class AutoClose implements Runnable { @Override public void run() { if (getOpen()) { closeMe(); } } } class TimerOpen implements Runnable { @Override public void run() { boolean open = getOpen(); if (open) { closeMe(); } else { openMe(); } int delay = open ? getTemplate().getCloseTime() : getTemplate().getOpenTime(); if (getTemplate().getRandomTime() > 0) { delay += Rnd.get(getTemplate().getRandomTime()); } ThreadPoolManager.getInstance().scheduleGeneral(this, delay * 1000); } } @Override public boolean isDoor() { return true; } }