/* * Copyright (C) 2004-2014 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 . */ package com.l2jserver.gameserver.model.entity; import java.util.Calendar; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javolution.util.FastList; import com.l2jserver.gameserver.ThreadPoolManager; import com.l2jserver.gameserver.ai.CtrlIntention; import com.l2jserver.gameserver.enums.DuelResult; import com.l2jserver.gameserver.enums.Team; import com.l2jserver.gameserver.instancemanager.DuelManager; import com.l2jserver.gameserver.model.Location; import com.l2jserver.gameserver.model.actor.instance.L2PcInstance; import com.l2jserver.gameserver.model.skills.Skill; import com.l2jserver.gameserver.model.zone.ZoneId; import com.l2jserver.gameserver.network.SystemMessageId; import com.l2jserver.gameserver.network.serverpackets.ActionFailed; import com.l2jserver.gameserver.network.serverpackets.ExDuelEnd; import com.l2jserver.gameserver.network.serverpackets.ExDuelReady; import com.l2jserver.gameserver.network.serverpackets.ExDuelStart; import com.l2jserver.gameserver.network.serverpackets.ExDuelUpdateUserInfo; import com.l2jserver.gameserver.network.serverpackets.L2GameServerPacket; import com.l2jserver.gameserver.network.serverpackets.PlaySound; import com.l2jserver.gameserver.network.serverpackets.SocialAction; import com.l2jserver.gameserver.network.serverpackets.SystemMessage; public class Duel { protected static final Logger _log = Logger.getLogger(Duel.class.getName()); public static final int DUELSTATE_NODUEL = 0; public static final int DUELSTATE_DUELLING = 1; public static final int DUELSTATE_DEAD = 2; public static final int DUELSTATE_WINNER = 3; public static final int DUELSTATE_INTERRUPTED = 4; private final int _duelId; private L2PcInstance _playerA; private L2PcInstance _playerB; private final boolean _partyDuel; private final Calendar _duelEndTime; private int _surrenderRequest = 0; private int _countdown = 4; private boolean _finished = false; private List _playerConditions; public Duel(L2PcInstance playerA, L2PcInstance playerB, int partyDuel, int duelId) { _duelId = duelId; _playerA = playerA; _playerB = playerB; _partyDuel = partyDuel == 1 ? true : false; _duelEndTime = Calendar.getInstance(); if (_partyDuel) { _duelEndTime.add(Calendar.SECOND, 300); } else { _duelEndTime.add(Calendar.SECOND, 120); } _playerConditions = new FastList<>(); setFinished(false); if (_partyDuel) { // increase countdown so that start task can teleport players _countdown++; // inform players that they will be portet shortly SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.IN_A_MOMENT_YOU_WILL_BE_TRANSPORTED_TO_THE_SITE_WHERE_THE_DUEL_WILL_TAKE_PLACE); broadcastToTeam1(sm); broadcastToTeam2(sm); } // Schedule duel start ThreadPoolManager.getInstance().scheduleGeneral(new ScheduleStartDuelTask(this), 3000); } public static class PlayerCondition { private L2PcInstance _player; private double _hp; private double _mp; private double _cp; private boolean _paDuel; private int _x, _y, _z; private FastList _debuffs; public PlayerCondition(L2PcInstance player, boolean partyDuel) { if (player == null) { return; } _player = player; _hp = _player.getCurrentHp(); _mp = _player.getCurrentMp(); _cp = _player.getCurrentCp(); _paDuel = partyDuel; if (_paDuel) { _x = _player.getX(); _y = _player.getY(); _z = _player.getZ(); } } public void restoreCondition() { if (_player == null) { return; } _player.setCurrentHp(_hp); _player.setCurrentMp(_mp); _player.setCurrentCp(_cp); if (_paDuel) { teleportBack(); } if (_debuffs != null) // Debuff removal { for (Skill skill : _debuffs) { if (skill != null) { _player.stopSkillEffects(true, skill.getId()); } } } } public void registerDebuff(Skill debuff) { if (_debuffs == null) { _debuffs = new FastList<>(); } _debuffs.add(debuff); } public void teleportBack() { if (_paDuel) { _player.teleToLocation(new Location(_x, _y, _z)); } } public L2PcInstance getPlayer() { return _player; } } public class ScheduleDuelTask implements Runnable { private final Duel _duel; public ScheduleDuelTask(Duel duel) { _duel = duel; } @Override public void run() { try { DuelResult status = _duel.checkEndDuelCondition(); if (status == DuelResult.Canceled) { // do not schedule duel end if it was interrupted setFinished(true); _duel.endDuel(status); } else if (status != DuelResult.Continue) { setFinished(true); playKneelAnimation(); ThreadPoolManager.getInstance().scheduleGeneral(new ScheduleEndDuelTask(_duel, status), 5000); } else { ThreadPoolManager.getInstance().scheduleGeneral(this, 1000); } } catch (Exception e) { _log.log(Level.SEVERE, "", e); } } } public static class ScheduleStartDuelTask implements Runnable { private final Duel _duel; public ScheduleStartDuelTask(Duel duel) { _duel = duel; } @Override public void run() { try { // start/continue countdown int count = _duel.countdown(); if (count == 4) { // players need to be teleportet first // TODO: stadia manager needs a function to return an unused stadium for duels // currently only teleports to the same stadium _duel.teleportPlayers(-83760, -238825, -3331); // give players 20 seconds to complete teleport and get ready (its ought to be 30 on offical..) ThreadPoolManager.getInstance().scheduleGeneral(this, 20000); } else if (count > 0) // duel not started yet - continue countdown { ThreadPoolManager.getInstance().scheduleGeneral(this, 1000); } else { _duel.startDuel(); } } catch (Exception e) { _log.log(Level.SEVERE, "", e); } } } public static class ScheduleEndDuelTask implements Runnable { private final Duel _duel; private final DuelResult _result; public ScheduleEndDuelTask(Duel duel, DuelResult result) { _duel = duel; _result = result; } @Override public void run() { try { _duel.endDuel(_result); } catch (Exception e) { _log.log(Level.SEVERE, "", e); } } } /** * Stops all players from attacking. Used for duel timeout / interrupt. */ private void stopFighting() { ActionFailed af = ActionFailed.STATIC_PACKET; if (_partyDuel) { for (L2PcInstance temp : _playerA.getParty().getMembers()) { temp.abortCast(); temp.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); temp.setTarget(null); temp.sendPacket(af); } for (L2PcInstance temp : _playerB.getParty().getMembers()) { temp.abortCast(); temp.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); temp.setTarget(null); temp.sendPacket(af); } } else { _playerA.abortCast(); _playerB.abortCast(); _playerA.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); _playerA.setTarget(null); _playerB.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); _playerB.setTarget(null); _playerA.sendPacket(af); _playerB.sendPacket(af); } } /** * Check if a player engaged in pvp combat (only for 1on1 duels) * @param sendMessage * @return returns true if a duelist is engaged in Pvp combat */ public boolean isDuelistInPvp(boolean sendMessage) { if (_partyDuel) { // Party duels take place in arenas - should be no other players there return false; } else if ((_playerA.getPvpFlag() != 0) || (_playerB.getPvpFlag() != 0)) { if (sendMessage) { String engagedInPvP = "The duel was canceled because a duelist engaged in PvP combat."; _playerA.sendMessage(engagedInPvP); _playerB.sendMessage(engagedInPvP); } return true; } return false; } /** * Starts the duel */ public void startDuel() { // Save player Conditions savePlayerConditions(); if ((_playerA == null) || (_playerB == null) || _playerA.isInDuel() || _playerB.isInDuel()) { // clean up _playerConditions.clear(); _playerConditions = null; DuelManager.getInstance().removeDuel(this); return; } if (_partyDuel) { // set isInDuel() state // cancel all active trades, just in case? xD for (L2PcInstance temp : _playerA.getParty().getMembers()) { temp.cancelActiveTrade(); temp.setIsInDuel(_duelId); temp.setTeam(Team.BLUE); temp.broadcastUserInfo(); broadcastToTeam2(new ExDuelUpdateUserInfo(temp)); } for (L2PcInstance temp : _playerB.getParty().getMembers()) { temp.cancelActiveTrade(); temp.setIsInDuel(_duelId); temp.setTeam(Team.RED); temp.broadcastUserInfo(); broadcastToTeam1(new ExDuelUpdateUserInfo(temp)); } // Send duel Start packets ExDuelReady ready = new ExDuelReady(1); ExDuelStart start = new ExDuelStart(1); broadcastToTeam1(ready); broadcastToTeam2(ready); broadcastToTeam1(start); broadcastToTeam2(start); } else { // set isInDuel() state _playerA.setIsInDuel(_duelId); _playerA.setTeam(Team.BLUE); _playerB.setIsInDuel(_duelId); _playerB.setTeam(Team.RED); // Send duel Start packets ExDuelReady ready = new ExDuelReady(0); ExDuelStart start = new ExDuelStart(0); broadcastToTeam1(ready); broadcastToTeam2(ready); broadcastToTeam1(start); broadcastToTeam2(start); broadcastToTeam1(new ExDuelUpdateUserInfo(_playerB)); broadcastToTeam2(new ExDuelUpdateUserInfo(_playerA)); _playerA.broadcastUserInfo(); _playerB.broadcastUserInfo(); } // play sound PlaySound ps = new PlaySound(1, "B04_S01", 0, 0, 0, 0, 0); broadcastToTeam1(ps); broadcastToTeam2(ps); // start duelling task ThreadPoolManager.getInstance().scheduleGeneral(new ScheduleDuelTask(this), 1000); } /** * Save the current player condition: hp, mp, cp, location */ public void savePlayerConditions() { if (_partyDuel) { for (L2PcInstance temp : _playerA.getParty().getMembers()) { _playerConditions.add(new PlayerCondition(temp, _partyDuel)); } for (L2PcInstance temp : _playerB.getParty().getMembers()) { _playerConditions.add(new PlayerCondition(temp, _partyDuel)); } } else { _playerConditions.add(new PlayerCondition(_playerA, _partyDuel)); _playerConditions.add(new PlayerCondition(_playerB, _partyDuel)); } } /** * Restore player conditions * @param abnormalDuelEnd true if the duel was the duel canceled */ public void restorePlayerConditions(boolean abnormalDuelEnd) { // update isInDuel() state for all players if (_partyDuel) { for (L2PcInstance temp : _playerA.getParty().getMembers()) { temp.setIsInDuel(0); temp.setTeam(Team.NONE); temp.broadcastUserInfo(); } for (L2PcInstance temp : _playerB.getParty().getMembers()) { temp.setIsInDuel(0); temp.setTeam(Team.NONE); temp.broadcastUserInfo(); } } else { _playerA.setIsInDuel(0); _playerA.setTeam(Team.NONE); _playerA.broadcastUserInfo(); _playerB.setIsInDuel(0); _playerB.setTeam(Team.NONE); _playerB.broadcastUserInfo(); } // if it is an abnormal DuelEnd do not restore hp, mp, cp if (abnormalDuelEnd) { return; } // restore player conditions for (PlayerCondition cond : _playerConditions) { cond.restoreCondition(); } } /** * Get the duel id * @return id */ public int getId() { return _duelId; } /** * Returns the remaining time * @return remaining time */ public int getRemainingTime() { return (int) (_duelEndTime.getTimeInMillis() - Calendar.getInstance().getTimeInMillis()); } /** * Get the player that requested the duel * @return duel requester */ public L2PcInstance getPlayerA() { return _playerA; } /** * Get the player that was challenged * @return challenged player */ public L2PcInstance getPlayerB() { return _playerB; } /** * Returns whether this is a party duel or not * @return is party duel */ public boolean isPartyDuel() { return _partyDuel; } public void setFinished(boolean mode) { _finished = mode; } public boolean getFinished() { return _finished; } /** * teleport all players to the given coordinates * @param x * @param y * @param z */ public void teleportPlayers(int x, int y, int z) { // TODO: adjust the values if needed... or implement something better (especially using more then 1 arena) if (!_partyDuel) { return; } int offset = 0; for (L2PcInstance temp : _playerA.getParty().getMembers()) { temp.teleToLocation(new Location((x + offset) - 180, y - 150, z)); offset += 40; } offset = 0; for (L2PcInstance temp : _playerB.getParty().getMembers()) { temp.teleToLocation(new Location((x + offset) - 180, y + 150, z)); offset += 40; } } /** * Broadcast a packet to the challenger team * @param packet */ public void broadcastToTeam1(L2GameServerPacket packet) { if (_playerA == null) { return; } if (_partyDuel && (_playerA.getParty() != null)) { for (L2PcInstance temp : _playerA.getParty().getMembers()) { temp.sendPacket(packet); } } else { _playerA.sendPacket(packet); } } /** * Broadcast a packet to the challenged team * @param packet */ public void broadcastToTeam2(L2GameServerPacket packet) { if (_playerB == null) { return; } if (_partyDuel && (_playerB.getParty() != null)) { for (L2PcInstance temp : _playerB.getParty().getMembers()) { temp.sendPacket(packet); } } else { _playerB.sendPacket(packet); } } /** * Get the duel winner * @return winner */ public L2PcInstance getWinner() { if (!getFinished() || (_playerA == null) || (_playerB == null)) { return null; } if (_playerA.getDuelState() == DUELSTATE_WINNER) { return _playerA; } if (_playerB.getDuelState() == DUELSTATE_WINNER) { return _playerB; } return null; } /** * Get the duel looser * @return looser */ public L2PcInstance getLooser() { if (!getFinished() || (_playerA == null) || (_playerB == null)) { return null; } if (_playerA.getDuelState() == DUELSTATE_WINNER) { return _playerB; } else if (_playerB.getDuelState() == DUELSTATE_WINNER) { return _playerA; } return null; } /** * Playback the bow animation for all loosers */ public void playKneelAnimation() { L2PcInstance looser = getLooser(); if (looser == null) { return; } if (_partyDuel && (looser.getParty() != null)) { for (L2PcInstance temp : looser.getParty().getMembers()) { temp.broadcastPacket(new SocialAction(temp.getObjectId(), 7)); } } else { looser.broadcastPacket(new SocialAction(looser.getObjectId(), 7)); } } /** * Do the countdown and send message to players if necessary * @return current count */ public int countdown() { _countdown--; if (_countdown > 3) { return _countdown; } // Broadcast countdown to duelists SystemMessage sm = null; if (_countdown > 0) { sm = SystemMessage.getSystemMessage(SystemMessageId.THE_DUEL_WILL_BEGIN_IN_S1_SECONDS); sm.addInt(_countdown); } else { sm = SystemMessage.getSystemMessage(SystemMessageId.LET_THE_DUEL_BEGIN); } broadcastToTeam1(sm); broadcastToTeam2(sm); return _countdown; } /** * The duel has reached a state in which it can no longer continue * @param result the duel result. */ public void endDuel(DuelResult result) { if ((_playerA == null) || (_playerB == null)) { // clean up _playerConditions.clear(); _playerConditions = null; DuelManager.getInstance().removeDuel(this); return; } // inform players of the result SystemMessage sm = null; switch (result) { case Team1Win: case Team2Surrender: restorePlayerConditions(false); // send SystemMessage if (_partyDuel) { sm = SystemMessage.getSystemMessage(SystemMessageId.C1_PARTY_HAS_WON_THE_DUEL); } else { sm = SystemMessage.getSystemMessage(SystemMessageId.C1_HAS_WON_THE_DUEL); } sm.addString(_playerA.getName()); broadcastToTeam1(sm); broadcastToTeam2(sm); break; case Team1Surrender: case Team2Win: restorePlayerConditions(false); // send SystemMessage if (_partyDuel) { sm = SystemMessage.getSystemMessage(SystemMessageId.C1_PARTY_HAS_WON_THE_DUEL); } else { sm = SystemMessage.getSystemMessage(SystemMessageId.C1_HAS_WON_THE_DUEL); } sm.addString(_playerB.getName()); broadcastToTeam1(sm); broadcastToTeam2(sm); break; case Canceled: stopFighting(); // dont restore hp, mp, cp restorePlayerConditions(true); // TODO: is there no other message for a canceled duel? // send SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_DUEL_HAS_ENDED_IN_A_TIE); broadcastToTeam1(sm); broadcastToTeam2(sm); break; case Timeout: stopFighting(); // hp,mp,cp seem to be restored in a timeout too... restorePlayerConditions(false); // send SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_DUEL_HAS_ENDED_IN_A_TIE); broadcastToTeam1(sm); broadcastToTeam2(sm); break; } // Send end duel packet ExDuelEnd duelEnd = null; if (_partyDuel) { duelEnd = new ExDuelEnd(1); } else { duelEnd = new ExDuelEnd(0); } broadcastToTeam1(duelEnd); broadcastToTeam2(duelEnd); // clean up _playerConditions.clear(); _playerConditions = null; DuelManager.getInstance().removeDuel(this); } /** * Did a situation occur in which the duel has to be ended? * @return DuelResult duel status */ public DuelResult checkEndDuelCondition() { // one of the players might leave during duel if ((_playerA == null) || (_playerB == null)) { return DuelResult.Canceled; } // got a duel surrender request? if (_surrenderRequest != 0) { if (_surrenderRequest == 1) { return DuelResult.Team1Surrender; } return DuelResult.Team2Surrender; } // duel timed out else if (getRemainingTime() <= 0) { return DuelResult.Timeout; } // Has a player been declared winner yet? else if (_playerA.getDuelState() == DUELSTATE_WINNER) { // If there is a Winner already there should be no more fighting going on stopFighting(); return DuelResult.Team1Win; } else if (_playerB.getDuelState() == DUELSTATE_WINNER) { // If there is a Winner already there should be no more fighting going on stopFighting(); return DuelResult.Team2Win; } // More end duel conditions for 1on1 duels else if (!_partyDuel) { // Duel was interrupted e.g.: player was attacked by mobs / other players if ((_playerA.getDuelState() == DUELSTATE_INTERRUPTED) || (_playerB.getDuelState() == DUELSTATE_INTERRUPTED)) { return DuelResult.Canceled; } // Are the players too far apart? if (!_playerA.isInsideRadius(_playerB, 1600, false, false)) { return DuelResult.Canceled; } // Did one of the players engage in PvP combat? if (isDuelistInPvp(true)) { return DuelResult.Canceled; } // is one of the players in a Siege, Peace or PvP zone? if (_playerA.isInsideZone(ZoneId.PEACE) || _playerB.isInsideZone(ZoneId.PEACE) || _playerA.isInsideZone(ZoneId.SIEGE) || _playerB.isInsideZone(ZoneId.SIEGE) || _playerA.isInsideZone(ZoneId.PVP) || _playerB.isInsideZone(ZoneId.PVP)) { return DuelResult.Canceled; } } return DuelResult.Continue; } /** * Register a surrender request * @param player the player that surrenders. */ public void doSurrender(L2PcInstance player) { // already recived a surrender request if (_surrenderRequest != 0) { return; } // stop the fight stopFighting(); // TODO: Can every party member cancel a party duel? or only the party leaders? if (_partyDuel) { if (_playerA.getParty().getMembers().contains(player)) { _surrenderRequest = 1; for (L2PcInstance temp : _playerA.getParty().getMembers()) { temp.setDuelState(DUELSTATE_DEAD); } for (L2PcInstance temp : _playerB.getParty().getMembers()) { temp.setDuelState(DUELSTATE_WINNER); } } else if (_playerB.getParty().getMembers().contains(player)) { _surrenderRequest = 2; for (L2PcInstance temp : _playerB.getParty().getMembers()) { temp.setDuelState(DUELSTATE_DEAD); } for (L2PcInstance temp : _playerA.getParty().getMembers()) { temp.setDuelState(DUELSTATE_WINNER); } } } else { if (player == _playerA) { _surrenderRequest = 1; _playerA.setDuelState(DUELSTATE_DEAD); _playerB.setDuelState(DUELSTATE_WINNER); } else if (player == _playerB) { _surrenderRequest = 2; _playerB.setDuelState(DUELSTATE_DEAD); _playerA.setDuelState(DUELSTATE_WINNER); } } } /** * This function is called whenever a player was defeated in a duel * @param player the player defeated. */ public void onPlayerDefeat(L2PcInstance player) { // Set player as defeated player.setDuelState(DUELSTATE_DEAD); if (_partyDuel) { boolean teamdefeated = true; for (L2PcInstance temp : player.getParty().getMembers()) { if (temp.getDuelState() == DUELSTATE_DUELLING) { teamdefeated = false; break; } } if (teamdefeated) { L2PcInstance winner = _playerA; if (_playerA.getParty().getMembers().contains(player)) { winner = _playerB; } for (L2PcInstance temp : winner.getParty().getMembers()) { temp.setDuelState(DUELSTATE_WINNER); } } } else { if ((player != _playerA) && (player != _playerB)) { _log.warning("Error in onPlayerDefeat(): player is not part of this 1vs1 duel"); } if (_playerA == player) { _playerB.setDuelState(DUELSTATE_WINNER); } else { _playerA.setDuelState(DUELSTATE_WINNER); } } } /** * This function is called whenever a player leaves a party * @param player the player quitting. */ public void onRemoveFromParty(L2PcInstance player) { // if it isnt a party duel ignore this if (!_partyDuel) { return; } // this player is leaving his party during party duel // if hes either playerA or playerB cancel the duel and port the players back if ((player == _playerA) || (player == _playerB)) { for (PlayerCondition cond : _playerConditions) { cond.teleportBack(); cond.getPlayer().setIsInDuel(0); } _playerA = null; _playerB = null; } else // teleport the player back & delete his PlayerCondition record { for (PlayerCondition cond : _playerConditions) { if (cond.getPlayer() == player) { cond.teleportBack(); _playerConditions.remove(cond); break; } } player.setIsInDuel(0); } } public void onBuff(L2PcInstance player, Skill debuff) { for (PlayerCondition cond : _playerConditions) { if (cond.getPlayer() == player) { cond.registerDebuff(debuff); return; } } } }