/*
* 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.model.actor.instance;
import java.util.Collection;
import java.util.concurrent.ScheduledFuture;
import javolution.util.FastList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import silentium.gameserver.ThreadPoolManager;
import silentium.gameserver.ai.CharacterAI;
import silentium.gameserver.ai.CtrlIntention;
import silentium.gameserver.ai.DoorAI;
import silentium.gameserver.data.html.StaticHtmPath;
import silentium.gameserver.instancemanager.CastleManager;
import silentium.gameserver.model.L2CharPosition;
import silentium.gameserver.model.L2Clan;
import silentium.gameserver.model.L2ItemInstance;
import silentium.gameserver.model.L2Object;
import silentium.gameserver.model.L2Skill;
import silentium.gameserver.model.actor.L2Character;
import silentium.gameserver.model.actor.L2Npc;
import silentium.gameserver.model.actor.L2Playable;
import silentium.gameserver.model.actor.knownlist.DoorKnownList;
import silentium.gameserver.model.actor.stat.DoorStat;
import silentium.gameserver.model.actor.status.DoorStatus;
import silentium.gameserver.model.entity.Castle;
import silentium.gameserver.model.entity.ClanHall;
import silentium.gameserver.network.L2GameClient;
import silentium.gameserver.network.SystemMessageId;
import silentium.gameserver.network.serverpackets.ActionFailed;
import silentium.gameserver.network.serverpackets.ConfirmDlg;
import silentium.gameserver.network.serverpackets.DoorInfo;
import silentium.gameserver.network.serverpackets.DoorStatusUpdate;
import silentium.gameserver.network.serverpackets.MyTargetSelected;
import silentium.gameserver.network.serverpackets.NpcHtmlMessage;
import silentium.gameserver.network.serverpackets.SystemMessage;
import silentium.gameserver.network.serverpackets.ValidateLocation;
import silentium.gameserver.templates.chars.L2CharTemplate;
import silentium.gameserver.templates.item.L2Weapon;
public class L2DoorInstance extends L2Character
{
protected static final Logger log = LoggerFactory.getLogger(L2DoorInstance.class.getName());
/** The castle index in the array of L2Castle this L2Npc belongs to */
private int _castleIndex = -2;
private int _mapRegion = -1;
// when door is closed, the dimensions are
private int _rangeXMin = 0;
private int _rangeYMin = 0;
private int _rangeZMin = 0;
private int _rangeXMax = 0;
private int _rangeYMax = 0;
private int _rangeZMax = 0;
// these variables assist in see-through calculation only
private int _A = 0;
private int _B = 0;
private int _C = 0;
private int _D = 0;
protected final int _doorId;
protected final String _name;
private boolean _open;
private final boolean _unlockable;
private boolean _isWall = false; // False by default
private ClanHall _clanHall;
protected int _autoActionDelay = -1;
private ScheduledFuture<?> _autoActionTask;
/** This class may be created only by L2Character and only for AI */
public class AIAccessor extends L2Character.AIAccessor
{
protected AIAccessor()
{
}
@Override
public L2DoorInstance getActor()
{
return L2DoorInstance.this;
}
@Override
public void moveTo(int x, int y, int z, int offset)
{
}
@Override
public void moveTo(int x, int y, int z)
{
}
@Override
public void stopMove(L2CharPosition pos)
{
}
@Override
public void doAttack(L2Character target)
{
}
@Override
public void doCast(L2Skill skill)
{
}
}
@Override
public CharacterAI getAI()
{
CharacterAI ai = _ai; // copy handle
if (ai == null)
{
synchronized (this)
{
if (_ai == null)
_ai = new DoorAI(new AIAccessor());
return _ai;
}
}
return ai;
}
class CloseTask implements Runnable
{
@Override
public void run()
{
try
{
onClose();
}
catch (Throwable e)
{
log.error("", e);
}
}
}
/**
* Manages the auto open and closing of a door.
*/
class AutoOpenClose implements Runnable
{
@Override
public void run()
{
try
{
String doorAction;
if (!getOpen())
{
doorAction = "opened";
openMe();
}
else
{
doorAction = "closed";
closeMe();
}
log.debug("Auto " + doorAction + " door ID " + _doorId + " (" + _name + ") for " + (_autoActionDelay / 60000) + " minute(s).");
}
catch (Exception e)
{
log.warn("Could not auto open/close door ID " + _doorId + " (" + _name + ")");
}
}
}
public L2DoorInstance(int objectId, L2CharTemplate template, int doorId, String name, boolean unlockable)
{
super(objectId, template);
_doorId = doorId;
_name = name;
_unlockable = unlockable;
}
@Override
public void initKnownList()
{
setKnownList(new DoorKnownList(this));
}
@Override
public final DoorKnownList getKnownList()
{
return (DoorKnownList) super.getKnownList();
}
@Override
public void initCharStat()
{
setStat(new DoorStat(this));
}
@Override
public final DoorStat getStat()
{
return (DoorStat) super.getStat();
}
@Override
public void initCharStatus()
{
setStatus(new DoorStatus(this));
}
@Override
public final DoorStatus getStatus()
{
return (DoorStatus) super.getStatus();
}
public final boolean isUnlockable()
{
return _unlockable;
}
@Override
public final int getLevel()
{
return 1;
}
/**
* @return Returns the doorId.
*/
public int getDoorId()
{
return _doorId;
}
/**
* @return Returns the open.
*/
public boolean getOpen()
{
return _open;
}
/**
* @param open
* The open to set.
*/
public void setOpen(boolean open)
{
_open = open;
}
/**
* Sets the delay for automatic opening/closing of this door instance.<BR>
* <B>Note:</B> A value of -1 cancels the auto open/close task.
*
* @param actionDelay
* Delay in milliseconds.
*/
public void setAutoActionDelay(int actionDelay)
{
if (_autoActionDelay == actionDelay)
return;
if (actionDelay > -1)
{
AutoOpenClose ao = new AutoOpenClose();
ThreadPoolManager.getInstance().scheduleGeneralAtFixedRate(ao, actionDelay, actionDelay);
}
else
{
if (_autoActionTask != null)
_autoActionTask.cancel(false);
}
_autoActionDelay = actionDelay;
}
public int getDamage()
{
int dmg = 6 - (int) Math.ceil(getCurrentHp() / getMaxHp() * 6);
if (dmg > 6)
return 6;
if (dmg < 0)
return 0;
return dmg;
}
public final Castle getCastle()
{
if (_castleIndex < 0)
_castleIndex = CastleManager.getInstance().getCastleIndex(this);
if (_castleIndex < 0)
return null;
return CastleManager.getInstance().getCastles().get(_castleIndex);
}
public void setClanHall(ClanHall clanhall)
{
_clanHall = clanhall;
}
public ClanHall getClanHall()
{
return _clanHall;
}
public boolean isEnemy()
{
if (getCastle() != null && getCastle().getCastleId() > 0 && getCastle().getSiege().getIsInProgress())
return true;
return false;
}
@Override
public boolean isAutoAttackable(L2Character attacker)
{
if (isUnlockable())
return true;
// Doors can`t be attacked by NPCs
if (!(attacker instanceof L2Playable))
return false;
// Attackable during siege by attacker only
boolean isCastle = (getCastle() != null && getCastle().getCastleId() > 0 && getCastle().getSiege().getIsInProgress());
L2PcInstance actingPlayer = attacker.getActingPlayer();
if (isCastle)
{
L2Clan clan = actingPlayer.getClan();
if (clan != null && clan.getClanId() == getCastle().getOwnerId())
return false;
}
return isCastle;
}
public boolean isAttackable(L2Character attacker)
{
return isAutoAttackable(attacker);
}
@Override
public void updateAbnormalEffect()
{
}
public int getDistanceToWatchObject(L2Object object)
{
if (!(object instanceof L2PcInstance))
return 0;
return 6000;
}
/**
* @param object
* The object to check on. If L2PcInstance : 9000, otherwise 0.
* @return The distance after which the object must be remove from _knownObject according to the type of the object.
*/
public int getDistanceToForgetObject(L2Object object)
{
if (!(object instanceof L2PcInstance))
return 0;
return 9000;
}
/**
* Return null.<BR>
* <BR>
*/
@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 onAction(L2PcInstance player)
{
if (!player.canTarget())
return;
// Check if the L2PcInstance already target the L2Npc
if (this != player.getTarget())
{
// Set the target of the L2PcInstance player
player.setTarget(this);
// Send a Server->Client packet MyTargetSelected to the L2PcInstance player
player.sendPacket(new MyTargetSelected(getObjectId(), 0));
player.sendPacket(new DoorStatusUpdate(this));
// Send a Server->Client packet ValidateLocation to correct the L2Npc position and heading on the client
player.sendPacket(new ValidateLocation(this));
}
else
{
if (isAutoAttackable(player))
{
if (Math.abs(player.getZ() - getZ()) < 400) // this max heigth difference might need some tweaking
player.getAI().setIntention(CtrlIntention.AI_INTENTION_ATTACK, this);
}
else if (!isInsideRadius(player, L2Npc.INTERACTION_DISTANCE, false, false))
player.getAI().setIntention(CtrlIntention.AI_INTENTION_INTERACT, this);
else if (player.getClan() != null && getClanHall() != null && player.getClanId() == getClanHall().getOwnerId())
{
player.gatesRequest(this);
if (!getOpen())
player.sendPacket(new ConfirmDlg(1140));
else
player.sendPacket(new ConfirmDlg(1141));
player.sendPacket(ActionFailed.STATIC_PACKET);
}
else
// Send a Server->Client ActionFailed to the L2PcInstance in order to avoid that the client wait another packet
player.sendPacket(ActionFailed.STATIC_PACKET);
}
}
@Override
public void onActionShift(L2GameClient client)
{
L2PcInstance player = client.getActiveChar();
if (player == null)
return;
if (player.getAccessLevel().isGm())
{
player.setTarget(this);
player.sendPacket(new MyTargetSelected(getObjectId(), player.getLevel()));
if (isAutoAttackable(player))
player.sendPacket(new DoorStatusUpdate(this));
NpcHtmlMessage html = new NpcHtmlMessage(0);
html.setFile(StaticHtmPath.AdminHtmPath + "infos/doorinfo.htm", player);
html.replace("%class%", getClass().getSimpleName());
html.replace("%hp%", String.valueOf((int) getCurrentHp()));
html.replace("%hpmax%", String.valueOf(getMaxHp()));
html.replace("%objid%", String.valueOf(getObjectId()));
html.replace("%doorid%", String.valueOf(getDoorId()));
html.replace("%minx%", String.valueOf(getXMin()));
html.replace("%miny%", String.valueOf(getYMin()));
html.replace("%minz%", String.valueOf(getZMin()));
html.replace("%maxx%", String.valueOf(getXMax()));
html.replace("%maxy%", String.valueOf(getYMax()));
html.replace("%maxz%", String.valueOf(getZMax()));
html.replace("%unlock%", isUnlockable() ? "<font color=00FF00>YES<font>" : "<font color=FF0000>NO</font>");
html.replace("%isWall%", isWall() ? "<font color=00FF00>YES<font>" : "<font color=FF0000>NO</font>");
player.sendPacket(html);
}
player.sendPacket(ActionFailed.STATIC_PACKET);
}
@Override
public void broadcastStatusUpdate()
{
Collection<L2PcInstance> knownPlayers = getKnownList().getKnownPlayers().values();
if (knownPlayers == null || knownPlayers.isEmpty())
return;
DoorStatusUpdate su = new DoorStatusUpdate(this);
for (L2PcInstance player : knownPlayers)
player.sendPacket(su);
}
public void onOpen()
{
ThreadPoolManager.getInstance().scheduleGeneral(new CloseTask(), 60000);
}
public void onClose()
{
closeMe();
}
public final void closeMe()
{
setOpen(false);
broadcastStatusUpdate();
}
public final void openMe()
{
setOpen(true);
broadcastStatusUpdate();
}
@Override
public String toString()
{
return "door " + _doorId;
}
public String getDoorName()
{
return _name;
}
public int getXMin()
{
return _rangeXMin;
}
public int getYMin()
{
return _rangeYMin;
}
public int getZMin()
{
return _rangeZMin;
}
public int getXMax()
{
return _rangeXMax;
}
public int getYMax()
{
return _rangeYMax;
}
public int getZMax()
{
return _rangeZMax;
}
public void setRange(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax)
{
_rangeXMin = xMin;
_rangeYMin = yMin;
_rangeZMin = zMin;
_rangeXMax = xMax;
_rangeYMax = yMax;
_rangeZMax = zMax;
_A = _rangeYMax * (_rangeZMax - _rangeZMin) + _rangeYMin * (_rangeZMin - _rangeZMax);
_B = _rangeZMin * (_rangeXMax - _rangeXMin) + _rangeZMax * (_rangeXMin - _rangeXMax);
_C = _rangeXMin * (_rangeYMax - _rangeYMin) + _rangeXMin * (_rangeYMin - _rangeYMax);
_D = -1 * (_rangeXMin * (_rangeYMax * _rangeZMax - _rangeYMin * _rangeZMax) + _rangeXMax * (_rangeYMin * _rangeZMin - _rangeYMin * _rangeZMax) + _rangeXMin * (_rangeYMin * _rangeZMax - _rangeYMax * _rangeZMin));
}
public int getMapRegion()
{
return _mapRegion;
}
public void setMapRegion(int region)
{
_mapRegion = region;
}
public Collection<L2SiegeGuardInstance> getKnownSiegeGuards()
{
FastList<L2SiegeGuardInstance> result = new FastList<>();
for (L2Object obj : getKnownList().getKnownObjects().values())
{
if (obj instanceof L2SiegeGuardInstance)
result.add((L2SiegeGuardInstance) obj);
}
return result;
}
public int getA()
{
return _A;
}
public int getB()
{
return _B;
}
public int getC()
{
return _C;
}
public int getD()
{
return _D;
}
public void setIsWall(boolean isWall)
{
_isWall = isWall;
}
public boolean isWall()
{
return _isWall;
}
@Override
public void reduceCurrentHp(double damage, L2Character attacker, boolean awake, boolean isDOT, L2Skill skill)
{
if (isWall() && !(attacker instanceof L2SiegeSummonInstance))
return;
if (!(getCastle() != null && getCastle().getCastleId() > 0 && getCastle().getSiege().getIsInProgress()))
return;
super.reduceCurrentHp(damage, attacker, awake, isDOT, skill);
}
@Override
public void reduceCurrentHpByDOT(double i, L2Character attacker, L2Skill skill)
{
// doors can't be damaged by DOTs
}
@Override
public boolean doDie(L2Character killer)
{
if (!super.doDie(killer))
return false;
boolean isCastle = (getCastle() != null && getCastle().getCastleId() > 0 && getCastle().getSiege().getIsInProgress());
if (isCastle)
broadcastPacket(SystemMessage.getSystemMessage(SystemMessageId.CASTLE_GATE_BROKEN_DOWN));
return true;
}
@Override
public void sendInfo(L2PcInstance activeChar)
{
activeChar.sendPacket(new DoorInfo(this));
activeChar.sendPacket(new DoorStatusUpdate(this));
}
}