/* * 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 com.l2jmobius.gameserver.model.entity; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import com.l2jmobius.gameserver.ThreadPoolManager; import com.l2jmobius.gameserver.ai.CtrlIntention; import com.l2jmobius.gameserver.enums.DuelResult; import com.l2jmobius.gameserver.enums.DuelState; import com.l2jmobius.gameserver.enums.Team; import com.l2jmobius.gameserver.instancemanager.DuelManager; import com.l2jmobius.gameserver.instancemanager.InstanceManager; import com.l2jmobius.gameserver.model.Location; import com.l2jmobius.gameserver.model.actor.L2Summon; import com.l2jmobius.gameserver.model.actor.instance.L2DoorInstance; import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; import com.l2jmobius.gameserver.model.skills.Skill; import com.l2jmobius.gameserver.model.zone.ZoneId; import com.l2jmobius.gameserver.network.SystemMessageId; import com.l2jmobius.gameserver.network.serverpackets.ActionFailed; import com.l2jmobius.gameserver.network.serverpackets.ExDuelEnd; import com.l2jmobius.gameserver.network.serverpackets.ExDuelReady; import com.l2jmobius.gameserver.network.serverpackets.ExDuelStart; import com.l2jmobius.gameserver.network.serverpackets.ExDuelUpdateUserInfo; import com.l2jmobius.gameserver.network.serverpackets.L2GameServerPacket; import com.l2jmobius.gameserver.network.serverpackets.PlaySound; import com.l2jmobius.gameserver.network.serverpackets.SocialAction; import com.l2jmobius.gameserver.network.serverpackets.SystemMessage; /** * @author Zealar */ public class Duel { protected static final Logger _log = Logger.getLogger(Duel.class.getName()); private static final PlaySound B04_S01 = new PlaySound(1, "B04_S01", 0, 0, 0, 0, 0); private static final int PARTY_DUEL_DURATION = 300; private static final int PARTY_DUEL_PREPARE_TIME = 30; private static final int PARTY_DUEL_TELEPORT_BACK_TIME = 10 * 1000; private static final int PLAYER_DUEL_DURATION = 120; private static final int DUEL_PREPARE_TIME = 5; private final int _duelId; private final List _teamA; private final L2PcInstance _leaderA; private final List _teamB; private final L2PcInstance _leaderB; private final boolean _partyDuel; private final Calendar _duelEndTime; private int _surrenderRequest = 0; private int _countdown; private final Map _playerConditions = new ConcurrentHashMap<>(); private int _duelInstanceId; public Duel(L2PcInstance playerA, L2PcInstance playerB, boolean partyDuel, int duelId) { _duelId = duelId; if (partyDuel) { _leaderA = playerA; _leaderB = playerB; _teamA = new ArrayList<>(playerA.getParty().getMembers()); _teamB = new ArrayList<>(playerB.getParty().getMembers()); } else { _leaderA = playerA; _leaderB = playerB; _teamA = new ArrayList<>(); _teamB = new ArrayList<>(); _teamA.add(playerA); _teamB.add(playerB); } _partyDuel = partyDuel; _duelEndTime = Calendar.getInstance(); _duelEndTime.add(Calendar.SECOND, _partyDuel ? PARTY_DUEL_DURATION : PLAYER_DUEL_DURATION); savePlayerConditions(); if (_partyDuel) { _countdown = PARTY_DUEL_PREPARE_TIME; teleportPlayers(); } else { _countdown = DUEL_PREPARE_TIME; } // Schedule duel start ThreadPoolManager.getInstance().scheduleGeneral(new DuelPreparationTask(this), _countdown - 3); } 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 Set _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); _player.setIsInDuel(0); _player.setTeam(Team.NONE); _player.broadcastUserInfo(); 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 = ConcurrentHashMap.newKeySet(); } _debuffs.add(debuff); } public void teleportBack() { _player.teleToLocation(_x, _y, _z); } public L2PcInstance getPlayer() { return _player; } } public static class DuelPreparationTask implements Runnable { private final Duel _duel; public DuelPreparationTask(Duel duel) { _duel = duel; } @Override public void run() { try { if (_duel.countdown() > 0) // duel not started yet - continue countdown { ThreadPoolManager.getInstance().scheduleGeneral(this, 1000); } else { _duel.startDuel(); } } catch (Exception e) { _log.log(Level.SEVERE, "There has been a problem while runing a duel start task!", e); } } } public class DuelClockTask implements Runnable { private final Duel _duel; public DuelClockTask(Duel duel) { _duel = duel; } @Override public void run() { try { switch (_duel.checkEndDuelCondition()) { case CONTINUE: { ThreadPoolManager.getInstance().scheduleGeneral(this, 1000); break; } default: { endDuel(); break; } } } catch (Exception e) { _log.log(Level.SEVERE, "There has been a problem while runing a duel end task!", e); } } } /** * Starts the duel */ public void startDuel() { // Set duel state and team // Send duel packets broadcastToTeam1(ExDuelReady.PARTY_DUEL); broadcastToTeam2(ExDuelReady.PARTY_DUEL); broadcastToTeam1(ExDuelStart.PARTY_DUEL); broadcastToTeam2(ExDuelStart.PARTY_DUEL); for (L2PcInstance temp : _teamA) { temp.cancelActiveTrade(); temp.setIsInDuel(_duelId); temp.setTeam(Team.BLUE); temp.broadcastUserInfo(); broadcastToTeam2(new ExDuelUpdateUserInfo(temp)); } for (L2PcInstance temp : _teamB) { temp.cancelActiveTrade(); temp.setIsInDuel(_duelId); temp.setTeam(Team.RED); temp.broadcastUserInfo(); broadcastToTeam1(new ExDuelUpdateUserInfo(temp)); } if (_partyDuel) { // Close doors chickens cannot run from the destiny for (L2DoorInstance door : InstanceManager.getInstance().getInstance(getDueldInstanceId()).getDoors()) { if ((door != null) && door.getOpen()) { door.closeMe(); } } } // play sound broadcastToTeam1(B04_S01); broadcastToTeam2(B04_S01); // start dueling task ThreadPoolManager.getInstance().scheduleGeneral(new DuelClockTask(this), 1000); } /** * Stops all players from attacking. Used for duel timeout / interrupt. */ private void stopFighting() { for (L2PcInstance temp : _teamA) { temp.abortCast(); temp.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); temp.setTarget(null); temp.sendPacket(ActionFailed.STATIC_PACKET); if (temp.hasSummon()) { for (L2Summon summon : temp.getServitors().values()) { if (!summon.isDead()) { summon.abortCast(); summon.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); summon.setTarget(null); summon.sendPacket(ActionFailed.STATIC_PACKET); } } } } for (L2PcInstance temp : _teamB) { temp.abortCast(); temp.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); temp.setTarget(null); temp.sendPacket(ActionFailed.STATIC_PACKET); if (temp.hasSummon()) { for (L2Summon summon : temp.getServitors().values()) { if (!summon.isDead()) { summon.abortCast(); summon.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); summon.setTarget(null); summon.sendPacket(ActionFailed.STATIC_PACKET); } } } } } /** * Save the current player condition: hp, mp, cp, location */ public void savePlayerConditions() { for (L2PcInstance player : _teamA) { _playerConditions.put(player.getObjectId(), new PlayerCondition(player, _partyDuel)); } for (L2PcInstance player : _teamB) { _playerConditions.put(player.getObjectId(), new PlayerCondition(player, _partyDuel)); } } /** * Restore player conditions */ public void restorePlayerConditions() { // restore player conditions ThreadPoolManager.getInstance().scheduleGeneral(() -> { _playerConditions.values().forEach(c -> c.restoreCondition()); }, _partyDuel ? PARTY_DUEL_TELEPORT_BACK_TIME : 1000); ThreadPoolManager.getInstance().scheduleGeneral(() -> clear(), _partyDuel ? PARTY_DUEL_TELEPORT_BACK_TIME : 1000); } /** * Get the duel id * @return id */ public int getId() { return _duelId; } /** * Get duel instance id * @return id */ public int getDueldInstanceId() { return _duelInstanceId; } /** * Returns the remaining time * @return remaining time */ public int getRemainingTime() { return (int) (_duelEndTime.getTimeInMillis() - Calendar.getInstance().getTimeInMillis()); } /** * Get the team that requested the duel * @return duel requester */ public List getTeamA() { return _teamA; } /** * Get the team that was challenged * @return challenged team */ public List getTeamB() { return _teamB; } /** * Get the team that requested the duel * @return duel requester */ public L2PcInstance getTeamLeaderA() { return _leaderA; } /** * Get the team that was challenged * @return challenged team */ public L2PcInstance getTeamLeaderB() { return _leaderB; } /** * Get the duel looser * @return looser */ public List getLooser() { if ((_leaderA == null) || (_leaderB == null)) { return null; } if (_leaderA.getDuelState() == DuelState.WINNER) { return _teamB; } else if (_leaderB.getDuelState() == DuelState.WINNER) { return _teamA; } return null; } /** * Returns whether this is a party duel or not * @return is party duel */ public boolean isPartyDuel() { return _partyDuel; } /** * Teleports all players to a party duel instance. */ public void teleportPlayers() { if (!_partyDuel) { return; } _duelInstanceId = InstanceManager.getInstance().createDynamicInstance("PartyDuel.xml"); final Instance instance = InstanceManager.getInstance().getInstance(_duelInstanceId); int i = 0; for (L2PcInstance player : _teamA) { final Location loc = instance.getEnterLocs().get(i++); player.teleToLocation(loc.getX(), loc.getY(), loc.getZ(), 0, _duelInstanceId, 0); } i = 9; for (L2PcInstance player : _teamB) { final Location loc = instance.getEnterLocs().get(i++); player.teleToLocation(loc.getX(), loc.getY(), loc.getZ(), 0, _duelInstanceId, 0); } } /** * Broadcast a packet to the challenger team * @param packet */ public void broadcastToTeam1(L2GameServerPacket packet) { if ((_teamA == null) || _teamA.isEmpty()) { return; } for (L2PcInstance temp : _teamA) { temp.sendPacket(packet); } } /** * Broadcast a packet to the challenged team * @param packet */ public void broadcastToTeam2(L2GameServerPacket packet) { if ((_teamB == null) || _teamB.isEmpty()) { return; } for (L2PcInstance temp : _teamB) { temp.sendPacket(packet); } } /** * Playback the bow animation for all looser */ private void playKneelAnimation() { final List looser = getLooser(); if (looser == null) { return; } for (L2PcInstance temp : looser) { temp.broadcastPacket(new SocialAction(temp.getObjectId(), 7)); } } /** * Do the countdown and send message to players if necessary * @return current count */ int countdown() { if (--_countdown > 3) { return _countdown; } SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.THE_DUEL_WILL_BEGIN_IN_S1_SECOND_S); if (_countdown > 0) { 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 */ void endDuel() { // Send end duel packet final ExDuelEnd duelEnd = _partyDuel ? ExDuelEnd.PARTY_DUEL : ExDuelEnd.PLAYER_DUEL; broadcastToTeam1(duelEnd); broadcastToTeam2(duelEnd); playKneelAnimation(); sendEndMessages(); restorePlayerConditions(); } /** * Clear current duel from DuelManager */ private void clear() { InstanceManager.getInstance().destroyInstance(getDueldInstanceId()); DuelManager.getInstance().removeDuel(this); } /** * Send required messages for duel end */ private void sendEndMessages() { SystemMessage sm = null; switch (checkEndDuelCondition()) { case TEAM_1_WIN: case TEAM_2_SURRENDER: { if (_partyDuel) { sm = SystemMessage.getSystemMessage(SystemMessageId.C1_S_PARTY_HAS_WON_THE_DUEL); } else { sm = SystemMessage.getSystemMessage(SystemMessageId.C1_HAS_WON_THE_DUEL); } sm.addString(_leaderA.getName()); break; } case TEAM_1_SURRENDER: case TEAM_2_WIN: { if (_partyDuel) { sm = SystemMessage.getSystemMessage(SystemMessageId.C1_S_PARTY_HAS_WON_THE_DUEL); } else { sm = SystemMessage.getSystemMessage(SystemMessageId.C1_HAS_WON_THE_DUEL); } break; } case CANCELED: case TIMEOUT: { stopFighting(); sm = SystemMessage.getSystemMessage(SystemMessageId.THE_DUEL_HAS_ENDED_IN_A_TIE); break; } } broadcastToTeam1(sm); broadcastToTeam2(sm); } /** * Did a situation occur in which the duel has to be ended? * @return DuelResult duel status */ DuelResult checkEndDuelCondition() { // one of the players might leave during duel if ((_teamA == null) || (_teamB == null)) { return DuelResult.CANCELED; } // got a duel surrender request? if (_surrenderRequest != 0) { if (_surrenderRequest == 1) { return DuelResult.TEAM_1_SURRENDER; } return DuelResult.TEAM_2_SURRENDER; } // duel timed out else if (getRemainingTime() <= 0) { return DuelResult.TIMEOUT; } // Has a player been declared winner yet? else if (_leaderA.getDuelState() == DuelState.WINNER) { // If there is a Winner already there should be no more fighting going on stopFighting(); return DuelResult.TEAM_1_WIN; } else if (_leaderB.getDuelState() == DuelState.WINNER) { // If there is a Winner already there should be no more fighting going on stopFighting(); return DuelResult.TEAM_2_WIN; } // More end duel conditions for 1on1 duels else if (!_partyDuel) { // Duel was interrupted e.g.: player was attacked by mobs / other players if ((_leaderA.getDuelState() == DuelState.INTERRUPTED) || (_leaderB.getDuelState() == DuelState.INTERRUPTED)) { return DuelResult.CANCELED; } // Are the players too far apart? if (!_leaderA.isInsideRadius(_leaderB, 2000, false, false)) { return DuelResult.CANCELED; } // is one of the players in a Siege, Peace or PvP zone? if (_leaderA.isInsideZone(ZoneId.PEACE) || _leaderB.isInsideZone(ZoneId.PEACE) || _leaderA.isInsideZone(ZoneId.SIEGE) || _leaderB.isInsideZone(ZoneId.SIEGE) || _leaderA.isInsideZone(ZoneId.PVP) || _leaderB.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 received a surrender request if ((_surrenderRequest != 0) || _partyDuel) { return; } // stop the fight stopFighting(); if (player == _leaderA) { _surrenderRequest = 1; _leaderA.setDuelState(DuelState.DEAD); _leaderB.setDuelState(DuelState.WINNER); } else if (player == _leaderB) { _surrenderRequest = 2; _leaderB.setDuelState(DuelState.DEAD); _leaderA.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); player.setTeam(Team.NONE); if (_partyDuel) { boolean teamdefeated = true; boolean isInTeamA = true; if (_teamA.contains(player)) { for (L2PcInstance temp : _teamA) { if (temp.getDuelState() == DuelState.DUELLING) { teamdefeated = false; break; } } } else if (_teamB.contains(player)) { isInTeamA = false; for (L2PcInstance temp : _teamB) { if (temp.getDuelState() == DuelState.DUELLING) { teamdefeated = false; break; } } } if (teamdefeated) { final List winners = (isInTeamA ? _teamB : _teamA); for (L2PcInstance temp : winners) { temp.setDuelState(DuelState.WINNER); } } } else { if ((player != _leaderA) && (player != _leaderB)) { _log.warning("Error in onPlayerDefeat(): player is not part of this 1vs1 duel!"); } if (_leaderA == player) { _leaderB.setDuelState(DuelState.WINNER); } else { _leaderA.setDuelState(DuelState.WINNER); } } } public void onBuff(L2PcInstance player, Skill debuff) { final PlayerCondition cond = _playerConditions.get(player.getObjectId()); if (cond != null) { cond.registerDebuff(debuff); } } }