diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/instances/Coliseum.xml b/L2J_Mobius_CT_0_Interlude/dist/game/data/instances/Coliseum.xml new file mode 100644 index 0000000000..ec78182380 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/instances/Coliseum.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/TvT.java b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/TvT.java new file mode 100644 index 0000000000..88eca019ad --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/TvT.java @@ -0,0 +1,951 @@ +/* + * 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 custom.events.TeamVsTeam; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.enums.PartyDistributionType; +import org.l2jmobius.gameserver.enums.SkillFinishType; +import org.l2jmobius.gameserver.enums.Team; +import org.l2jmobius.gameserver.instancemanager.AntiFeedManager; +import org.l2jmobius.gameserver.instancemanager.InstanceManager; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.model.CommandChannel; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.Party; +import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.Player; +import org.l2jmobius.gameserver.model.actor.Summon; +import org.l2jmobius.gameserver.model.events.EventType; +import org.l2jmobius.gameserver.model.events.annotations.RegisterEvent; +import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureDeath; +import org.l2jmobius.gameserver.model.events.impl.creature.player.OnPlayerLogout; +import org.l2jmobius.gameserver.model.events.listeners.AbstractEventListener; +import org.l2jmobius.gameserver.model.events.listeners.ConsumerEventListener; +import org.l2jmobius.gameserver.model.holders.ItemHolder; +import org.l2jmobius.gameserver.model.holders.SkillHolder; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.instancezone.InstanceWorld; +import org.l2jmobius.gameserver.model.olympiad.Olympiad; +import org.l2jmobius.gameserver.model.quest.Event; +import org.l2jmobius.gameserver.model.quest.QuestTimer; +import org.l2jmobius.gameserver.model.skill.CommonSkill; +import org.l2jmobius.gameserver.model.skill.Skill; +import org.l2jmobius.gameserver.model.zone.ZoneId; +import org.l2jmobius.gameserver.model.zone.ZoneType; +import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillUse; +import org.l2jmobius.gameserver.util.Broadcast; + +/** + * Team vs Team event. + * @author Mobius + */ +public class TvT extends Event +{ + // NPC + private static final int MANAGER = 70010; + // Skills + private static final SkillHolder[] FIGHTER_BUFFS = + { + new SkillHolder(4322, 1), // Wind Walk + new SkillHolder(4323, 1), // Shield + new SkillHolder(4396, 1), // Magic Barrier + new SkillHolder(4324, 1), // Bless the Body + new SkillHolder(4325, 1), // Vampiric Rage + new SkillHolder(4326, 1), // Regeneration + new SkillHolder(4327, 1), // Haste + }; + private static final SkillHolder[] MAGE_BUFFS = + { + new SkillHolder(4322, 1), // Wind Walk + new SkillHolder(4323, 1), // Shield + new SkillHolder(4396, 1), // Magic Barrier + new SkillHolder(4328, 1), // Bless the Soul + new SkillHolder(4329, 1), // Acumen + new SkillHolder(4330, 1), // Concentration + new SkillHolder(4331, 1), // Empower + }; + private static final SkillHolder GHOST_WALKING = new SkillHolder(100000, 1); // Custom Ghost Walking + // Others + private static final int INSTANCE_ID = 3049; + private static final int BLUE_DOOR_ID = 24190002; + private static final int RED_DOOR_ID = 24190003; + private static final Location MANAGER_SPAWN_LOC = new Location(83425, 148585, -3406, 32938); + private static final Location BLUE_BUFFER_SPAWN_LOC = new Location(147450, 46913, -3400, 49000); + private static final Location RED_BUFFER_SPAWN_LOC = new Location(151545, 46528, -3400, 16000); + private static final Location BLUE_SPAWN_LOC = new Location(147447, 46722, -3416); + private static final Location RED_SPAWN_LOC = new Location(151536, 46722, -3416); + private static final ZoneType BLUE_PEACE_ZONE = ZoneManager.getInstance().getZoneByName("colosseum_peace1"); + private static final ZoneType RED_PEACE_ZONE = ZoneManager.getInstance().getZoneByName("colosseum_peace2"); + // Settings + private static final int REGISTRATION_TIME = 10; // Minutes + private static final int WAIT_TIME = 1; // Minutes + private static final int FIGHT_TIME = 20; // Minutes + private static final int INACTIVITY_TIME = 2; // Minutes + private static final int MINIMUM_PARTICIPANT_LEVEL = 76; + private static final int MAXIMUM_PARTICIPANT_LEVEL = 200; + private static final int MINIMUM_PARTICIPANT_COUNT = 4; + private static final int MAXIMUM_PARTICIPANT_COUNT = 24; // Scoreboard has 25 slots + private static final int PARTY_MEMBER_COUNT = 8; + private static final ItemHolder REWARD = new ItemHolder(57, 100000); // Adena + // Misc + private static final Map PLAYER_SCORES = new ConcurrentHashMap<>(); + private static final Set PLAYER_LIST = ConcurrentHashMap.newKeySet(); + private static final Set BLUE_TEAM = ConcurrentHashMap.newKeySet(); + private static final Set RED_TEAM = ConcurrentHashMap.newKeySet(); + private static volatile int BLUE_SCORE; + private static volatile int RED_SCORE; + private static InstanceWorld PVP_WORLD = null; + private static Npc MANAGER_NPC_INSTANCE = null; + private static boolean EVENT_ACTIVE = false; + + private TvT() + { + addTalkId(MANAGER); + addStartNpc(MANAGER); + addFirstTalkId(MANAGER); + addExitZoneId(BLUE_PEACE_ZONE.getId(), RED_PEACE_ZONE.getId()); + addEnterZoneId(BLUE_PEACE_ZONE.getId(), RED_PEACE_ZONE.getId()); + + // Daily task to start event at 20:00. + // final Calendar calendar = Calendar.getInstance(); + // if ((calendar.get(Calendar.HOUR_OF_DAY) >= 20) && (calendar.get(Calendar.MINUTE) >= 0)) + // { + // calendar.add(Calendar.DAY_OF_YEAR, 1); + // } + // calendar.set(Calendar.HOUR_OF_DAY, 20); + // calendar.set(Calendar.MINUTE, 0); + // calendar.set(Calendar.SECOND, 0); + // ThreadPool.scheduleAtFixedRate(() -> eventStart(null), calendar.getTimeInMillis() - System.currentTimeMillis(), 86400000); // 86400000 = 1 day + } + + @Override + public String onAdvEvent(String event, Npc npc, Player player) + { + if (!EVENT_ACTIVE) + { + return null; + } + + String htmltext = null; + switch (event) + { + case "Participate": + { + if (canRegister(player)) + { + if ((Config.DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP == 0) || AntiFeedManager.getInstance().tryAddPlayer(AntiFeedManager.L2EVENT_ID, player, Config.DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP)) + { + PLAYER_LIST.add(player); + PLAYER_SCORES.put(player, 0); + player.setRegisteredOnEvent(true); + addLogoutListener(player); + htmltext = "registration-success.html"; + } + else + { + htmltext = "registration-ip.html"; + } + } + else + { + htmltext = "registration-failed.html"; + } + break; + } + case "CancelParticipation": + { + if (player.isOnEvent()) + { + return null; + } + // Remove the player from the IP count + if (Config.DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP > 0) + { + AntiFeedManager.getInstance().removePlayer(AntiFeedManager.L2EVENT_ID, player); + } + PLAYER_LIST.remove(player); + PLAYER_SCORES.remove(player); + removeListeners(player); + player.setRegisteredOnEvent(false); + htmltext = "registration-canceled.html"; + break; + } + case "BuffHeal": + { + if (player.isOnEvent() || player.isGM()) + { + if (player.isInCombat()) + { + htmltext = "manager-combat.html"; + } + else + { + if (player.isMageClass()) + { + for (SkillHolder skill : MAGE_BUFFS) + { + npc.setTarget(player); + npc.doCast(skill.getSkill()); + // No animation. + // skill.getSkill().applyEffects(npc, player); + } + } + else + { + for (SkillHolder skill : FIGHTER_BUFFS) + { + npc.setTarget(player); + npc.doCast(skill.getSkill()); + // No animation. + // skill.getSkill().applyEffects(npc, player); + } + } + player.setCurrentHp(player.getMaxHp()); + player.setCurrentMp(player.getMaxMp()); + player.setCurrentCp(player.getMaxCp()); + } + } + break; + } + case "TeleportToArena": + { + // Remove offline players. + for (Player participant : PLAYER_LIST) + { + if ((participant == null) || (participant.isOnlineInt() != 1)) + { + PLAYER_LIST.remove(participant); + PLAYER_SCORES.remove(participant); + } + } + // Check if there are enough players to start the event. + if (PLAYER_LIST.size() < MINIMUM_PARTICIPANT_COUNT) + { + Broadcast.toAllOnlinePlayers("TvT Event: Event was canceled, not enough participants."); + for (Player participant : PLAYER_LIST) + { + removeListeners(participant); + participant.setRegisteredOnEvent(false); + } + EVENT_ACTIVE = false; + return null; + } + // Create the instance. + final InstanceWorld world = new InstanceWorld(); + world.setInstance(InstanceManager.getInstance().createDynamicInstance(INSTANCE_ID)); + InstanceManager.getInstance().addWorld(world); + PVP_WORLD = world; + // Close doors. + PVP_WORLD.closeDoor(24190001); // Blue outer door. + PVP_WORLD.closeDoor(BLUE_DOOR_ID); + PVP_WORLD.closeDoor(RED_DOOR_ID); + PVP_WORLD.closeDoor(24190004); // Red outer door. + // Randomize player list and separate teams. + final List playerList = new ArrayList<>(PLAYER_LIST.size()); + playerList.addAll(PLAYER_LIST); + Collections.shuffle(playerList); + PLAYER_LIST.clear(); + PLAYER_LIST.addAll(playerList); + boolean team = getRandomBoolean(); // If teams are not even, randomize where extra player goes. + for (Player participant : PLAYER_LIST) + { + participant.setOnEvent(true); + participant.setRegisteredOnEvent(false); + if (team) + { + BLUE_TEAM.add(participant); + PVP_WORLD.addAllowed(participant); + participant.leaveParty(); + participant.setTeam(Team.BLUE); + participant.teleToLocation(BLUE_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + team = false; + } + else + { + RED_TEAM.add(participant); + PVP_WORLD.addAllowed(participant); + participant.leaveParty(); + participant.setTeam(Team.RED); + participant.teleToLocation(RED_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + team = true; + } + addDeathListener(participant); + } + // Make Blue CC. + if (BLUE_TEAM.size() > 1) + { + CommandChannel blueCC = null; + Party lastBlueParty = null; + int blueParticipantCounter = 0; + for (Player participant : BLUE_TEAM) + { + blueParticipantCounter++; + if (blueParticipantCounter == 1) + { + lastBlueParty = new Party(participant, PartyDistributionType.FINDERS_KEEPERS); + participant.joinParty(lastBlueParty); + if (BLUE_TEAM.size() > PARTY_MEMBER_COUNT) + { + if (blueCC == null) + { + blueCC = new CommandChannel(participant); + } + else + { + blueCC.addParty(lastBlueParty); + } + } + } + else + { + participant.joinParty(lastBlueParty); + } + if (blueParticipantCounter == PARTY_MEMBER_COUNT) + { + blueParticipantCounter = 0; + } + } + } + // Make Red CC. + if (RED_TEAM.size() > 1) + { + CommandChannel redCC = null; + Party lastRedParty = null; + int redParticipantCounter = 0; + for (Player participant : RED_TEAM) + { + redParticipantCounter++; + if (redParticipantCounter == 1) + { + lastRedParty = new Party(participant, PartyDistributionType.FINDERS_KEEPERS); + participant.joinParty(lastRedParty); + if (RED_TEAM.size() > PARTY_MEMBER_COUNT) + { + if (redCC == null) + { + redCC = new CommandChannel(participant); + } + else + { + redCC.addParty(lastRedParty); + } + } + } + else + { + participant.joinParty(lastRedParty); + } + if (redParticipantCounter == PARTY_MEMBER_COUNT) + { + redParticipantCounter = 0; + } + } + } + // Spawn managers. + addSpawn(MANAGER, BLUE_BUFFER_SPAWN_LOC, false, (WAIT_TIME + FIGHT_TIME) * 60000, false, PVP_WORLD.getInstanceId()); + addSpawn(MANAGER, RED_BUFFER_SPAWN_LOC, false, (WAIT_TIME + FIGHT_TIME) * 60000, false, PVP_WORLD.getInstanceId()); + // Initialize scores. + BLUE_SCORE = 0; + RED_SCORE = 0; + // Initialize scoreboard. + // Packet does not exist in Interlude. + // PVP_WORLD.broadcastPacket(new ExPVPMatchCCRecord(ExPVPMatchCCRecord.INITIALIZE, Util.sortByValue(PLAYER_SCORES, true))); + // Schedule start. + startQuestTimer("5", (WAIT_TIME * 60000) - 5000, null, null); + startQuestTimer("4", (WAIT_TIME * 60000) - 4000, null, null); + startQuestTimer("3", (WAIT_TIME * 60000) - 3000, null, null); + startQuestTimer("2", (WAIT_TIME * 60000) - 2000, null, null); + startQuestTimer("1", (WAIT_TIME * 60000) - 1000, null, null); + startQuestTimer("StartFight", WAIT_TIME * 60000, null, null); + break; + } + case "StartFight": + { + // Open doors. + PVP_WORLD.openDoor(BLUE_DOOR_ID); + PVP_WORLD.openDoor(RED_DOOR_ID); + // Send message. + broadcastScreenMessageWithEffect("The fight has began!", 5); + // Schedule finish. + startQuestTimer("10", (FIGHT_TIME * 60000) - 10000, null, null); + startQuestTimer("9", (FIGHT_TIME * 60000) - 9000, null, null); + startQuestTimer("8", (FIGHT_TIME * 60000) - 8000, null, null); + startQuestTimer("7", (FIGHT_TIME * 60000) - 7000, null, null); + startQuestTimer("6", (FIGHT_TIME * 60000) - 6000, null, null); + startQuestTimer("5", (FIGHT_TIME * 60000) - 5000, null, null); + startQuestTimer("4", (FIGHT_TIME * 60000) - 4000, null, null); + startQuestTimer("3", (FIGHT_TIME * 60000) - 3000, null, null); + startQuestTimer("2", (FIGHT_TIME * 60000) - 2000, null, null); + startQuestTimer("1", (FIGHT_TIME * 60000) - 1000, null, null); + startQuestTimer("EndFight", FIGHT_TIME * 60000, null, null); + break; + } + case "EndFight": + { + // Close doors. + PVP_WORLD.closeDoor(BLUE_DOOR_ID); + PVP_WORLD.closeDoor(RED_DOOR_ID); + // Disable players. + for (Player participant : PLAYER_LIST) + { + participant.setInvul(true); + participant.setImmobilized(true); + participant.disableAllSkills(); + final Summon summon = participant.getSummon(); + if (summon != null) + { + summon.setInvul(true); + summon.setImmobilized(true); + summon.disableAllSkills(); + } + } + // Make sure noone is dead. + for (Player participant : PLAYER_LIST) + { + if (participant.isDead()) + { + participant.doRevive(); + } + } + // Team Blue wins. + if (BLUE_SCORE > RED_SCORE) + { + final Skill skill = CommonSkill.FIREWORK.getSkill(); + broadcastScreenMessageWithEffect("Team Blue won the event!", 7); + for (Player participant : BLUE_TEAM) + { + if ((participant != null) && (participant.getInstanceId() == PVP_WORLD.getInstanceId())) + { + participant.broadcastPacket(new MagicSkillUse(participant, participant, skill.getId(), skill.getLevel(), skill.getHitTime(), skill.getReuseDelay())); + participant.broadcastSocialAction(3); + giveItems(participant, REWARD); + } + } + } + // Team Red wins. + else if (RED_SCORE > BLUE_SCORE) + { + final Skill skill = CommonSkill.FIREWORK.getSkill(); + broadcastScreenMessageWithEffect("Team Red won the event!", 7); + for (Player participant : RED_TEAM) + { + if ((participant != null) && (participant.getInstanceId() == PVP_WORLD.getInstanceId())) + { + participant.broadcastPacket(new MagicSkillUse(participant, participant, skill.getId(), skill.getLevel(), skill.getHitTime(), skill.getReuseDelay())); + participant.broadcastSocialAction(3); + giveItems(participant, REWARD); + } + } + } + // Tie. + else + { + broadcastScreenMessageWithEffect("The event ended with a tie!", 7); + for (Player participant : PLAYER_LIST) + { + participant.broadcastSocialAction(13); + } + } + startQuestTimer("ScoreBoard", 3500, null, null); + startQuestTimer("TeleportOut", 7000, null, null); + break; + } + case "ScoreBoard": + { + // Packet does not exist in Interlude. + // PVP_WORLD.broadcastPacket(new ExPVPMatchCCRecord(ExPVPMatchCCRecord.FINISH, Util.sortByValue(PLAYER_SCORES, true))); + break; + } + case "TeleportOut": + { + // Remove event listeners. + for (Player participant : PLAYER_LIST) + { + removeListeners(participant); + participant.setTeam(Team.NONE); + participant.setOnEvent(false); + participant.leaveParty(); + PVP_WORLD.ejectPlayer(participant); + } + // Destroy world. + if (PVP_WORLD != null) + { + final Instance instance = InstanceManager.getInstance().getInstance(PVP_WORLD.getInstanceId()); + if (instance != null) + { + instance.setDuration(60000); + instance.setEmptyDestroyTime(0); + } + PVP_WORLD = null; + } + // Enable players. + for (Player participant : PLAYER_LIST) + { + participant.setInvul(false); + participant.setImmobilized(false); + participant.enableAllSkills(); + final Summon summon = participant.getSummon(); + if (summon != null) + { + summon.setInvul(true); + summon.setImmobilized(true); + summon.disableAllSkills(); + } + } + EVENT_ACTIVE = false; + break; + } + case "ResurrectPlayer": + { + if (player.isDead() && player.isOnEvent()) + { + if (BLUE_TEAM.contains(player)) + { + player.setIsPendingRevive(true); + player.teleToLocation(BLUE_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + // Make player invulnerable for 30 seconds. + GHOST_WALKING.getSkill().applyEffects(player, player); + // Reset existing activity timers. + resetActivityTimers(player); // In case player died in peace zone. + } + else if (RED_TEAM.contains(player)) + { + player.setIsPendingRevive(true); + player.teleToLocation(RED_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + // Make player invulnerable for 30 seconds. + GHOST_WALKING.getSkill().applyEffects(player, player); + // Reset existing activity timers. + resetActivityTimers(player); // In case player died in peace zone. + } + } + break; + } + case "10": + case "9": + case "8": + case "7": + case "6": + case "5": + case "4": + case "3": + case "2": + case "1": + { + broadcastScreenMessage(event, 4); + break; + } + } + // Activity timer. + if (event.startsWith("KickPlayer") && (player != null) && (player.getInstanceId() == PVP_WORLD.getInstanceId())) + { + if (event.contains("Warning")) + { + sendScreenMessage(player, "You have been marked as inactive!", 10); + } + else + { + player.setTeam(Team.NONE); + PVP_WORLD.ejectPlayer(player); + PLAYER_LIST.remove(player); + PLAYER_SCORES.remove(player); + BLUE_TEAM.remove(player); + RED_TEAM.remove(player); + player.setOnEvent(false); + removeListeners(player); + player.sendMessage("You have been kicked for been inactive."); + if (PVP_WORLD != null) + { + // Manage forfeit. + if ((BLUE_TEAM.isEmpty() && !RED_TEAM.isEmpty()) || // + (RED_TEAM.isEmpty() && !BLUE_TEAM.isEmpty())) + { + manageForfeit(); + } + else + { + broadcastScreenMessageWithEffect("Player " + player.getName() + " was kicked for been inactive!", 7); + } + } + } + } + return htmltext; + } + + @Override + public String onFirstTalk(Npc npc, Player player) + { + // Event not active. + if (!EVENT_ACTIVE) + { + return null; + } + + // Player has already registered. + if (PLAYER_LIST.contains(player)) + { + // Npc is in instance. + if (npc.getInstanceId() > 0) + { + return "manager-buffheal.html"; + } + return "manager-cancel.html"; + } + // Player is not registered. + return "manager-register.html"; + } + + @Override + public String onEnterZone(Creature creature, ZoneType zone) + { + if (creature.isPlayable() && creature.getActingPlayer().isOnEvent()) + { + // Kick enemy players. + if ((zone == BLUE_PEACE_ZONE) && (creature.getTeam() == Team.RED)) + { + creature.teleToLocation(RED_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + sendScreenMessage(creature.getActingPlayer(), "Entering the enemy headquarters is prohibited!", 10); + } + if ((zone == RED_PEACE_ZONE) && (creature.getTeam() == Team.BLUE)) + { + creature.teleToLocation(BLUE_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + sendScreenMessage(creature.getActingPlayer(), "Entering the enemy headquarters is prohibited!", 10); + } + // Start inactivity check. + if (creature.isPlayer() && // + (((zone == BLUE_PEACE_ZONE) && (creature.getTeam() == Team.BLUE)) || // + ((zone == RED_PEACE_ZONE) && (creature.getTeam() == Team.RED)))) + { + resetActivityTimers(creature.getActingPlayer()); + } + } + return null; + } + + @Override + public String onExitZone(Creature creature, ZoneType zone) + { + if (creature.isPlayer() && creature.getActingPlayer().isOnEvent()) + { + final Player player = creature.getActingPlayer(); + cancelQuestTimer("KickPlayer" + creature.getObjectId(), null, player); + cancelQuestTimer("KickPlayerWarning" + creature.getObjectId(), null, player); + // Removed invulnerability shield. + if (player.isAffectedBySkill(GHOST_WALKING.getSkillId())) + { + player.getEffectList().stopSkillEffects(SkillFinishType.REMOVED, GHOST_WALKING.getSkill()); + } + } + return super.onExitZone(creature, zone); + } + + private boolean canRegister(Player player) + { + if (PLAYER_LIST.contains(player)) + { + player.sendMessage("You are already registered on this event."); + return false; + } + if (player.getLevel() < MINIMUM_PARTICIPANT_LEVEL) + { + player.sendMessage("Your level is too low to participate."); + return false; + } + if (player.getLevel() > MAXIMUM_PARTICIPANT_LEVEL) + { + player.sendMessage("Your level is too high to participate."); + return false; + } + if (player.isRegisteredOnEvent()) + { + player.sendMessage("You are already registered on an event."); + return false; + } + if (PLAYER_LIST.size() >= MAXIMUM_PARTICIPANT_COUNT) + { + player.sendMessage("There are too many players registered on the event."); + return false; + } + if (player.isFlying()) + { + player.sendMessage("You cannot register on the event while flying."); + return false; + } + if (!player.isInventoryUnder80(false)) + { + player.sendMessage("There are too many items in your inventory."); + player.sendMessage("Try removing some items."); + return false; + } + if ((player.getWeightPenalty() != 0)) + { + player.sendMessage("Your invetory weight has exceeded the normal limit."); + player.sendMessage("Try removing some items."); + return false; + } + if (player.isCursedWeaponEquipped() || (player.getKarma() > 0)) + { + player.sendMessage("People with bad reputation can't register."); + return false; + } + if (player.isInDuel()) + { + player.sendMessage("You cannot register while on a duel."); + return false; + } + if (player.isInOlympiadMode() || Olympiad.getInstance().isRegistered(player)) + { + player.sendMessage("You cannot participate while registered on the Olympiad."); + return false; + } + if (player.getInstanceId() > 0) + { + player.sendMessage("You cannot register while in an instance."); + return false; + } + if (player.isInSiege() || player.isInsideZone(ZoneId.SIEGE)) + { + player.sendMessage("You cannot register while on a siege."); + return false; + } + if (player.isFishing()) + { + player.sendMessage("You cannot register while fishing."); + return false; + } + return true; + } + + private void sendScreenMessage(Player player, String message, int duration) + { + player.sendPacket(new ExShowScreenMessage(message, ExShowScreenMessage.TOP_CENTER, duration * 1000, 0, true, false)); + } + + private void broadcastScreenMessage(String message, int duration) + { + PVP_WORLD.broadcastPacket(new ExShowScreenMessage(message, ExShowScreenMessage.TOP_CENTER, duration * 1000, 0, true, false)); + } + + private void broadcastScreenMessageWithEffect(String message, int duration) + { + PVP_WORLD.broadcastPacket(new ExShowScreenMessage(message, ExShowScreenMessage.TOP_CENTER, duration * 1000, 0, true, true)); + } + + private void broadcastScoreMessage() + { + PVP_WORLD.broadcastPacket(new ExShowScreenMessage("Blue: " + BLUE_SCORE + " - Red: " + RED_SCORE, ExShowScreenMessage.BOTTOM_RIGHT, 15000, 0, true, false)); + } + + private void addLogoutListener(Player player) + { + player.addListener(new ConsumerEventListener(player, EventType.ON_PLAYER_LOGOUT, (OnPlayerLogout event) -> onPlayerLogout(event), this)); + } + + private void addDeathListener(Player player) + { + player.addListener(new ConsumerEventListener(player, EventType.ON_CREATURE_DEATH, (OnCreatureDeath event) -> onPlayerDeath(event), this)); + } + + private void removeListeners(Player player) + { + for (AbstractEventListener listener : player.getListeners(EventType.ON_PLAYER_LOGOUT)) + { + if (listener.getOwner() == this) + { + listener.unregisterMe(); + } + } + for (AbstractEventListener listener : player.getListeners(EventType.ON_CREATURE_DEATH)) + { + if (listener.getOwner() == this) + { + listener.unregisterMe(); + } + } + } + + private void resetActivityTimers(Player player) + { + cancelQuestTimer("KickPlayer" + player.getObjectId(), null, player); + cancelQuestTimer("KickPlayerWarning" + player.getObjectId(), null, player); + startQuestTimer("KickPlayer" + player.getObjectId(), PVP_WORLD.getDoor(BLUE_DOOR_ID).isOpen() ? INACTIVITY_TIME * 60000 : (INACTIVITY_TIME * 60000) + (WAIT_TIME * 60000), null, player); + startQuestTimer("KickPlayerWarning" + player.getObjectId(), PVP_WORLD.getDoor(BLUE_DOOR_ID).isOpen() ? (INACTIVITY_TIME / 2) * 60000 : ((INACTIVITY_TIME / 2) * 60000) + (WAIT_TIME * 60000), null, player); + } + + private void manageForfeit() + { + cancelQuestTimer("10", null, null); + cancelQuestTimer("9", null, null); + cancelQuestTimer("8", null, null); + cancelQuestTimer("7", null, null); + cancelQuestTimer("6", null, null); + cancelQuestTimer("5", null, null); + cancelQuestTimer("4", null, null); + cancelQuestTimer("3", null, null); + cancelQuestTimer("2", null, null); + cancelQuestTimer("1", null, null); + cancelQuestTimer("EndFight", null, null); + startQuestTimer("EndFight", 10000, null, null); + broadcastScreenMessageWithEffect("Enemy team forfeit!", 7); + } + + @RegisterEvent(EventType.ON_PLAYER_LOGOUT) + private void onPlayerLogout(OnPlayerLogout event) + { + final Player player = event.getPlayer(); + // Remove player from lists. + PLAYER_LIST.remove(player); + PLAYER_SCORES.remove(player); + BLUE_TEAM.remove(player); + RED_TEAM.remove(player); + // Manage forfeit. + if ((BLUE_TEAM.isEmpty() && !RED_TEAM.isEmpty()) || // + (RED_TEAM.isEmpty() && !BLUE_TEAM.isEmpty())) + { + manageForfeit(); + } + } + + @RegisterEvent(EventType.ON_CREATURE_DEATH) + public void onPlayerDeath(OnCreatureDeath event) + { + if (event.getTarget().isPlayer()) + { + final Player killedPlayer = event.getTarget().getActingPlayer(); + final Player killer = event.getAttacker().getActingPlayer(); + // Confirm Blue team kill. + if ((killer.getTeam() == Team.BLUE) && (killedPlayer.getTeam() == Team.RED)) + { + PLAYER_SCORES.put(killer, PLAYER_SCORES.get(killer) + 1); + BLUE_SCORE++; + broadcastScoreMessage(); + // Packet does not exist in Interlude. + // PVP_WORLD.broadcastPacket(new ExPVPMatchCCRecord(ExPVPMatchCCRecord.UPDATE, Util.sortByValue(PLAYER_SCORES, true))); + } + // Confirm Red team kill. + if ((killer.getTeam() == Team.RED) && (killedPlayer.getTeam() == Team.BLUE)) + { + PLAYER_SCORES.put(killer, PLAYER_SCORES.get(killer) + 1); + RED_SCORE++; + broadcastScoreMessage(); + // Packet does not exist in Interlude. + // PVP_WORLD.broadcastPacket(new ExPVPMatchCCRecord(ExPVPMatchCCRecord.UPDATE, Util.sortByValue(PLAYER_SCORES, true))); + } + // Auto release after 10 seconds. + startQuestTimer("ResurrectPlayer", 10000, null, killedPlayer); + } + } + + @Override + public boolean eventStart(Player eventMaker) + { + if (EVENT_ACTIVE) + { + return false; + } + EVENT_ACTIVE = true; + + // Cancel timers. (In case event started immediately after another event was canceled.) + for (List timers : getQuestTimers().values()) + { + for (QuestTimer timer : timers) + { + timer.cancel(); + } + } + // Register the event at AntiFeedManager and clean it for just in case if the event is already registered + if (Config.DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP > 0) + { + AntiFeedManager.getInstance().registerEvent(AntiFeedManager.L2EVENT_ID); + AntiFeedManager.getInstance().clear(AntiFeedManager.L2EVENT_ID); + } + // Clear player lists. + PLAYER_LIST.clear(); + PLAYER_SCORES.clear(); + BLUE_TEAM.clear(); + RED_TEAM.clear(); + // Spawn event manager. + MANAGER_NPC_INSTANCE = addSpawn(MANAGER, MANAGER_SPAWN_LOC, false, REGISTRATION_TIME * 60000); + startQuestTimer("TeleportToArena", REGISTRATION_TIME * 60000, null, null); + // Send message to players. + Broadcast.toAllOnlinePlayers("TvT Event: Registration opened for " + REGISTRATION_TIME + " minutes."); + Broadcast.toAllOnlinePlayers("TvT Event: You can register at Giran TvT Event Manager."); + return true; + } + + @Override + public boolean eventStop() + { + if (!EVENT_ACTIVE) + { + return false; + } + EVENT_ACTIVE = false; + + // Despawn event manager. + MANAGER_NPC_INSTANCE.deleteMe(); + // Cancel timers. + for (List timers : getQuestTimers().values()) + { + for (QuestTimer timer : timers) + { + timer.cancel(); + } + } + // Remove participants. + for (Player participant : PLAYER_LIST) + { + removeListeners(participant); + participant.setTeam(Team.NONE); + participant.setRegisteredOnEvent(false); + participant.setOnEvent(false); + PVP_WORLD.ejectPlayer(participant); + } + if (PVP_WORLD != null) + { + final Instance instance = InstanceManager.getInstance().getInstance(PVP_WORLD.getInstanceId()); + if (instance != null) + { + instance.setDuration(60000); + instance.setEmptyDestroyTime(0); + } + PVP_WORLD = null; + } + // Send message to players. + Broadcast.toAllOnlinePlayers("TvT Event: Event was canceled."); + return true; + } + + @Override + public boolean eventBypass(Player player, String bypass) + { + return false; + } + + public static void main(String[] args) + { + new TvT(); + } +} diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/manager-buffheal.html b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/manager-buffheal.html new file mode 100644 index 0000000000..f1d0949777 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/manager-buffheal.html @@ -0,0 +1,42 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Need assistance?
+
+
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/manager-cancel.html b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/manager-cancel.html new file mode 100644 index 0000000000..06920cb9ad --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/manager-cancel.html @@ -0,0 +1,42 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Cancel your participation?
+
+
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/manager-combat.html b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/manager-combat.html new file mode 100644 index 0000000000..ce123dca6b --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/manager-combat.html @@ -0,0 +1,39 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
You are in combat, calm down...
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/manager-register.html b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/manager-register.html new file mode 100644 index 0000000000..4583642356 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/manager-register.html @@ -0,0 +1,42 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Do you want to join the event?
+
+
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/registration-canceled.html b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/registration-canceled.html new file mode 100644 index 0000000000..a371f68493 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/registration-canceled.html @@ -0,0 +1,39 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Registration canceled.
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/registration-failed.html b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/registration-failed.html new file mode 100644 index 0000000000..8b4b0663a6 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/registration-failed.html @@ -0,0 +1,39 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Registration failed.
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/registration-ip.html b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/registration-ip.html new file mode 100644 index 0000000000..074732b093 --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/registration-ip.html @@ -0,0 +1,39 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Too many registrations.
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/registration-success.html b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/registration-success.html new file mode 100644 index 0000000000..b38b178e7d --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/scripts/custom/events/TeamVsTeam/registration-success.html @@ -0,0 +1,39 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Registration successful!
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/stats/npcs/custom/tvt_event.xml b/L2J_Mobius_CT_0_Interlude/dist/game/data/stats/npcs/custom/tvt_event.xml new file mode 100644 index 0000000000..351b366c9f --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/stats/npcs/custom/tvt_event.xml @@ -0,0 +1,21 @@ + + + + HUMAN + MALE + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/stats/skills/custom/tvt_event.xml b/L2J_Mobius_CT_0_Interlude/dist/game/data/stats/skills/custom/tvt_event.xml new file mode 100644 index 0000000000..4ac95c1fbb --- /dev/null +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/stats/skills/custom/tvt_event.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/data/xsd/skills.xsd b/L2J_Mobius_CT_0_Interlude/dist/game/data/xsd/skills.xsd index 6e317e88ff..d3c3a0c7cd 100644 --- a/L2J_Mobius_CT_0_Interlude/dist/game/data/xsd/skills.xsd +++ b/L2J_Mobius_CT_0_Interlude/dist/game/data/xsd/skills.xsd @@ -433,7 +433,7 @@ - + diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/instances/Coliseum.xml b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/instances/Coliseum.xml new file mode 100644 index 0000000000..ec78182380 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/instances/Coliseum.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/TvT.java b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/TvT.java new file mode 100644 index 0000000000..801b53d58e --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/TvT.java @@ -0,0 +1,954 @@ +/* + * 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 custom.events.TeamVsTeam; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.enums.PartyDistributionType; +import org.l2jmobius.gameserver.enums.SkillFinishType; +import org.l2jmobius.gameserver.enums.Team; +import org.l2jmobius.gameserver.instancemanager.AntiFeedManager; +import org.l2jmobius.gameserver.instancemanager.InstanceManager; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.model.CommandChannel; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.Party; +import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.Player; +import org.l2jmobius.gameserver.model.actor.Summon; +import org.l2jmobius.gameserver.model.events.EventType; +import org.l2jmobius.gameserver.model.events.annotations.RegisterEvent; +import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureDeath; +import org.l2jmobius.gameserver.model.events.impl.creature.player.OnPlayerLogout; +import org.l2jmobius.gameserver.model.events.listeners.AbstractEventListener; +import org.l2jmobius.gameserver.model.events.listeners.ConsumerEventListener; +import org.l2jmobius.gameserver.model.holders.ItemHolder; +import org.l2jmobius.gameserver.model.holders.SkillHolder; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.instancezone.InstanceWorld; +import org.l2jmobius.gameserver.model.olympiad.Olympiad; +import org.l2jmobius.gameserver.model.quest.Event; +import org.l2jmobius.gameserver.model.quest.QuestTimer; +import org.l2jmobius.gameserver.model.skill.CommonSkill; +import org.l2jmobius.gameserver.model.skill.Skill; +import org.l2jmobius.gameserver.model.zone.ZoneId; +import org.l2jmobius.gameserver.model.zone.ZoneType; +import org.l2jmobius.gameserver.network.serverpackets.ExPVPMatchCCRecord; +import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillUse; +import org.l2jmobius.gameserver.util.Broadcast; +import org.l2jmobius.gameserver.util.Util; + +/** + * Team vs Team event. + * @author Mobius + */ +public class TvT extends Event +{ + // NPC + private static final int MANAGER = 70010; + // Skills + private static final SkillHolder[] FIGHTER_BUFFS = + { + new SkillHolder(4322, 1), // Wind Walk + new SkillHolder(4323, 1), // Shield + new SkillHolder(5637, 1), // Magic Barrier + new SkillHolder(4324, 1), // Bless the Body + new SkillHolder(4325, 1), // Vampiric Rage + new SkillHolder(4326, 1), // Regeneration + new SkillHolder(5632, 1), // Haste + }; + private static final SkillHolder[] MAGE_BUFFS = + { + new SkillHolder(4322, 1), // Wind Walk + new SkillHolder(4323, 1), // Shield + new SkillHolder(5637, 1), // Magic Barrier + new SkillHolder(4328, 1), // Bless the Soul + new SkillHolder(4329, 1), // Acumen + new SkillHolder(4330, 1), // Concentration + new SkillHolder(4331, 1), // Empower + }; + private static final SkillHolder GHOST_WALKING = new SkillHolder(100000, 1); // Custom Ghost Walking + // Others + private static final int INSTANCE_ID = 3049; + private static final int BLUE_DOOR_ID = 24190002; + private static final int RED_DOOR_ID = 24190003; + private static final Location MANAGER_SPAWN_LOC = new Location(83425, 148585, -3406, 32938); + private static final Location BLUE_BUFFER_SPAWN_LOC = new Location(147450, 46913, -3400, 49000); + private static final Location RED_BUFFER_SPAWN_LOC = new Location(151545, 46528, -3400, 16000); + private static final Location BLUE_SPAWN_LOC = new Location(147447, 46722, -3416); + private static final Location RED_SPAWN_LOC = new Location(151536, 46722, -3416); + private static final ZoneType BLUE_PEACE_ZONE = ZoneManager.getInstance().getZoneByName("colosseum_peace1"); + private static final ZoneType RED_PEACE_ZONE = ZoneManager.getInstance().getZoneByName("colosseum_peace2"); + // Settings + private static final int REGISTRATION_TIME = 10; // Minutes + private static final int WAIT_TIME = 1; // Minutes + private static final int FIGHT_TIME = 20; // Minutes + private static final int INACTIVITY_TIME = 2; // Minutes + private static final int MINIMUM_PARTICIPANT_LEVEL = 76; + private static final int MAXIMUM_PARTICIPANT_LEVEL = 200; + private static final int MINIMUM_PARTICIPANT_COUNT = 4; + private static final int MAXIMUM_PARTICIPANT_COUNT = 24; // Scoreboard has 25 slots + private static final int PARTY_MEMBER_COUNT = 8; + private static final ItemHolder REWARD = new ItemHolder(57, 100000); // Adena + // Misc + private static final Map PLAYER_SCORES = new ConcurrentHashMap<>(); + private static final Set PLAYER_LIST = ConcurrentHashMap.newKeySet(); + private static final Set BLUE_TEAM = ConcurrentHashMap.newKeySet(); + private static final Set RED_TEAM = ConcurrentHashMap.newKeySet(); + private static volatile int BLUE_SCORE; + private static volatile int RED_SCORE; + private static InstanceWorld PVP_WORLD = null; + private static Npc MANAGER_NPC_INSTANCE = null; + private static boolean EVENT_ACTIVE = false; + + private TvT() + { + addTalkId(MANAGER); + addStartNpc(MANAGER); + addFirstTalkId(MANAGER); + addExitZoneId(BLUE_PEACE_ZONE.getId(), RED_PEACE_ZONE.getId()); + addEnterZoneId(BLUE_PEACE_ZONE.getId(), RED_PEACE_ZONE.getId()); + + // Daily task to start event at 20:00. + // final Calendar calendar = Calendar.getInstance(); + // if ((calendar.get(Calendar.HOUR_OF_DAY) >= 20) && (calendar.get(Calendar.MINUTE) >= 0)) + // { + // calendar.add(Calendar.DAY_OF_YEAR, 1); + // } + // calendar.set(Calendar.HOUR_OF_DAY, 20); + // calendar.set(Calendar.MINUTE, 0); + // calendar.set(Calendar.SECOND, 0); + // ThreadPool.scheduleAtFixedRate(() -> eventStart(null), calendar.getTimeInMillis() - System.currentTimeMillis(), 86400000); // 86400000 = 1 day + } + + @Override + public String onAdvEvent(String event, Npc npc, Player player) + { + if (!EVENT_ACTIVE) + { + return null; + } + + String htmltext = null; + switch (event) + { + case "Participate": + { + if (canRegister(player)) + { + if ((Config.DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP == 0) || AntiFeedManager.getInstance().tryAddPlayer(AntiFeedManager.L2EVENT_ID, player, Config.DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP)) + { + PLAYER_LIST.add(player); + PLAYER_SCORES.put(player, 0); + player.setRegisteredOnEvent(true); + addLogoutListener(player); + htmltext = "registration-success.html"; + } + else + { + htmltext = "registration-ip.html"; + } + } + else + { + htmltext = "registration-failed.html"; + } + break; + } + case "CancelParticipation": + { + if (player.isOnEvent()) + { + return null; + } + // Remove the player from the IP count + if (Config.DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP > 0) + { + AntiFeedManager.getInstance().removePlayer(AntiFeedManager.L2EVENT_ID, player); + } + PLAYER_LIST.remove(player); + PLAYER_SCORES.remove(player); + removeListeners(player); + player.setRegisteredOnEvent(false); + htmltext = "registration-canceled.html"; + break; + } + case "BuffHeal": + { + if (player.isOnEvent() || player.isGM()) + { + if (player.isInCombat()) + { + htmltext = "manager-combat.html"; + } + else + { + if (player.isMageClass()) + { + for (SkillHolder skill : MAGE_BUFFS) + { + npc.setTarget(player); + npc.doCast(skill.getSkill()); + // No animation. + // skill.getSkill().applyEffects(npc, player); + } + } + else + { + for (SkillHolder skill : FIGHTER_BUFFS) + { + npc.setTarget(player); + npc.doCast(skill.getSkill()); + // No animation. + // skill.getSkill().applyEffects(npc, player); + } + } + player.setCurrentHp(player.getMaxHp()); + player.setCurrentMp(player.getMaxMp()); + player.setCurrentCp(player.getMaxCp()); + } + } + break; + } + case "TeleportToArena": + { + // Remove offline players. + for (Player participant : PLAYER_LIST) + { + if ((participant == null) || (participant.isOnlineInt() != 1)) + { + PLAYER_LIST.remove(participant); + PLAYER_SCORES.remove(participant); + } + } + // Check if there are enough players to start the event. + if (PLAYER_LIST.size() < MINIMUM_PARTICIPANT_COUNT) + { + Broadcast.toAllOnlinePlayers("TvT Event: Event was canceled, not enough participants."); + for (Player participant : PLAYER_LIST) + { + removeListeners(participant); + participant.setRegisteredOnEvent(false); + } + EVENT_ACTIVE = false; + return null; + } + // Create the instance. + final InstanceWorld world = new InstanceWorld(); + world.setInstance(InstanceManager.getInstance().createDynamicInstance(INSTANCE_ID)); + InstanceManager.getInstance().addWorld(world); + PVP_WORLD = world; + // Close doors. + PVP_WORLD.closeDoor(24190001); // Blue outer door. + PVP_WORLD.closeDoor(BLUE_DOOR_ID); + PVP_WORLD.closeDoor(RED_DOOR_ID); + PVP_WORLD.closeDoor(24190004); // Red outer door. + // Randomize player list and separate teams. + final List playerList = new ArrayList<>(PLAYER_LIST.size()); + playerList.addAll(PLAYER_LIST); + Collections.shuffle(playerList); + PLAYER_LIST.clear(); + PLAYER_LIST.addAll(playerList); + boolean team = getRandomBoolean(); // If teams are not even, randomize where extra player goes. + for (Player participant : PLAYER_LIST) + { + participant.setOnEvent(true); + participant.setRegisteredOnEvent(false); + if (team) + { + BLUE_TEAM.add(participant); + PVP_WORLD.addAllowed(participant); + participant.leaveParty(); + participant.setTeam(Team.BLUE); + participant.teleToLocation(BLUE_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + team = false; + } + else + { + RED_TEAM.add(participant); + PVP_WORLD.addAllowed(participant); + participant.leaveParty(); + participant.setTeam(Team.RED); + participant.teleToLocation(RED_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + team = true; + } + addDeathListener(participant); + } + // Make Blue CC. + if (BLUE_TEAM.size() > 1) + { + CommandChannel blueCC = null; + Party lastBlueParty = null; + int blueParticipantCounter = 0; + for (Player participant : BLUE_TEAM) + { + blueParticipantCounter++; + if (blueParticipantCounter == 1) + { + lastBlueParty = new Party(participant, PartyDistributionType.FINDERS_KEEPERS); + participant.joinParty(lastBlueParty); + if (BLUE_TEAM.size() > PARTY_MEMBER_COUNT) + { + if (blueCC == null) + { + blueCC = new CommandChannel(participant); + } + else + { + blueCC.addParty(lastBlueParty); + } + } + } + else + { + participant.joinParty(lastBlueParty); + } + if (blueParticipantCounter == PARTY_MEMBER_COUNT) + { + blueParticipantCounter = 0; + } + } + } + // Make Red CC. + if (RED_TEAM.size() > 1) + { + CommandChannel redCC = null; + Party lastRedParty = null; + int redParticipantCounter = 0; + for (Player participant : RED_TEAM) + { + redParticipantCounter++; + if (redParticipantCounter == 1) + { + lastRedParty = new Party(participant, PartyDistributionType.FINDERS_KEEPERS); + participant.joinParty(lastRedParty); + if (RED_TEAM.size() > PARTY_MEMBER_COUNT) + { + if (redCC == null) + { + redCC = new CommandChannel(participant); + } + else + { + redCC.addParty(lastRedParty); + } + } + } + else + { + participant.joinParty(lastRedParty); + } + if (redParticipantCounter == PARTY_MEMBER_COUNT) + { + redParticipantCounter = 0; + } + } + } + // Spawn managers. + addSpawn(MANAGER, BLUE_BUFFER_SPAWN_LOC, false, (WAIT_TIME + FIGHT_TIME) * 60000, false, PVP_WORLD.getInstanceId()); + addSpawn(MANAGER, RED_BUFFER_SPAWN_LOC, false, (WAIT_TIME + FIGHT_TIME) * 60000, false, PVP_WORLD.getInstanceId()); + // Initialize scores. + BLUE_SCORE = 0; + RED_SCORE = 0; + // Initialize scoreboard. + PVP_WORLD.broadcastPacket(new ExPVPMatchCCRecord(ExPVPMatchCCRecord.INITIALIZE, Util.sortByValue(PLAYER_SCORES, true))); + // Schedule start. + startQuestTimer("5", (WAIT_TIME * 60000) - 5000, null, null); + startQuestTimer("4", (WAIT_TIME * 60000) - 4000, null, null); + startQuestTimer("3", (WAIT_TIME * 60000) - 3000, null, null); + startQuestTimer("2", (WAIT_TIME * 60000) - 2000, null, null); + startQuestTimer("1", (WAIT_TIME * 60000) - 1000, null, null); + startQuestTimer("StartFight", WAIT_TIME * 60000, null, null); + break; + } + case "StartFight": + { + // Open doors. + PVP_WORLD.openDoor(BLUE_DOOR_ID); + PVP_WORLD.openDoor(RED_DOOR_ID); + // Send message. + broadcastScreenMessageWithEffect("The fight has began!", 5); + // Schedule finish. + startQuestTimer("10", (FIGHT_TIME * 60000) - 10000, null, null); + startQuestTimer("9", (FIGHT_TIME * 60000) - 9000, null, null); + startQuestTimer("8", (FIGHT_TIME * 60000) - 8000, null, null); + startQuestTimer("7", (FIGHT_TIME * 60000) - 7000, null, null); + startQuestTimer("6", (FIGHT_TIME * 60000) - 6000, null, null); + startQuestTimer("5", (FIGHT_TIME * 60000) - 5000, null, null); + startQuestTimer("4", (FIGHT_TIME * 60000) - 4000, null, null); + startQuestTimer("3", (FIGHT_TIME * 60000) - 3000, null, null); + startQuestTimer("2", (FIGHT_TIME * 60000) - 2000, null, null); + startQuestTimer("1", (FIGHT_TIME * 60000) - 1000, null, null); + startQuestTimer("EndFight", FIGHT_TIME * 60000, null, null); + break; + } + case "EndFight": + { + // Close doors. + PVP_WORLD.closeDoor(BLUE_DOOR_ID); + PVP_WORLD.closeDoor(RED_DOOR_ID); + // Disable players. + for (Player participant : PLAYER_LIST) + { + participant.setInvul(true); + participant.setImmobilized(true); + participant.disableAllSkills(); + final Summon summon = participant.getSummon(); + if (summon != null) + { + summon.setInvul(true); + summon.setImmobilized(true); + summon.disableAllSkills(); + } + } + // Make sure noone is dead. + for (Player participant : PLAYER_LIST) + { + if (participant.isDead()) + { + participant.doRevive(); + } + } + // Team Blue wins. + if (BLUE_SCORE > RED_SCORE) + { + final Skill skill = CommonSkill.FIREWORK.getSkill(); + broadcastScreenMessageWithEffect("Team Blue won the event!", 7); + for (Player participant : BLUE_TEAM) + { + if ((participant != null) && (participant.getInstanceId() == PVP_WORLD.getInstanceId())) + { + participant.broadcastPacket(new MagicSkillUse(participant, participant, skill.getId(), skill.getLevel(), skill.getHitTime(), skill.getReuseDelay())); + participant.broadcastSocialAction(3); + giveItems(participant, REWARD); + } + } + } + // Team Red wins. + else if (RED_SCORE > BLUE_SCORE) + { + final Skill skill = CommonSkill.FIREWORK.getSkill(); + broadcastScreenMessageWithEffect("Team Red won the event!", 7); + for (Player participant : RED_TEAM) + { + if ((participant != null) && (participant.getInstanceId() == PVP_WORLD.getInstanceId())) + { + participant.broadcastPacket(new MagicSkillUse(participant, participant, skill.getId(), skill.getLevel(), skill.getHitTime(), skill.getReuseDelay())); + participant.broadcastSocialAction(3); + giveItems(participant, REWARD); + } + } + } + // Tie. + else + { + broadcastScreenMessageWithEffect("The event ended with a tie!", 7); + for (Player participant : PLAYER_LIST) + { + participant.broadcastSocialAction(13); + } + } + startQuestTimer("ScoreBoard", 3500, null, null); + startQuestTimer("TeleportOut", 7000, null, null); + break; + } + case "ScoreBoard": + { + PVP_WORLD.broadcastPacket(new ExPVPMatchCCRecord(ExPVPMatchCCRecord.FINISH, Util.sortByValue(PLAYER_SCORES, true))); + break; + } + case "TeleportOut": + { + // Remove event listeners. + for (Player participant : PLAYER_LIST) + { + removeListeners(participant); + participant.setTeam(Team.NONE); + participant.setOnEvent(false); + participant.leaveParty(); + PVP_WORLD.ejectPlayer(participant); + } + // Destroy world. + if (PVP_WORLD != null) + { + final Instance instance = InstanceManager.getInstance().getInstance(PVP_WORLD.getInstanceId()); + if (instance != null) + { + instance.setDuration(60000); + instance.setEmptyDestroyTime(0); + } + PVP_WORLD = null; + } + // Enable players. + for (Player participant : PLAYER_LIST) + { + participant.setInvul(false); + participant.setImmobilized(false); + participant.enableAllSkills(); + final Summon summon = participant.getSummon(); + if (summon != null) + { + summon.setInvul(true); + summon.setImmobilized(true); + summon.disableAllSkills(); + } + } + EVENT_ACTIVE = false; + break; + } + case "ResurrectPlayer": + { + if (player.isDead() && player.isOnEvent()) + { + if (BLUE_TEAM.contains(player)) + { + player.setIsPendingRevive(true); + player.teleToLocation(BLUE_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + // Make player invulnerable for 30 seconds. + GHOST_WALKING.getSkill().applyEffects(player, player); + // Reset existing activity timers. + resetActivityTimers(player); // In case player died in peace zone. + } + else if (RED_TEAM.contains(player)) + { + player.setIsPendingRevive(true); + player.teleToLocation(RED_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + // Make player invulnerable for 30 seconds. + GHOST_WALKING.getSkill().applyEffects(player, player); + // Reset existing activity timers. + resetActivityTimers(player); // In case player died in peace zone. + } + } + break; + } + case "10": + case "9": + case "8": + case "7": + case "6": + case "5": + case "4": + case "3": + case "2": + case "1": + { + broadcastScreenMessage(event, 4); + break; + } + } + // Activity timer. + if (event.startsWith("KickPlayer") && (player != null) && (player.getInstanceId() == PVP_WORLD.getInstanceId())) + { + if (event.contains("Warning")) + { + sendScreenMessage(player, "You have been marked as inactive!", 10); + } + else + { + player.setTeam(Team.NONE); + PVP_WORLD.ejectPlayer(player); + PLAYER_LIST.remove(player); + PLAYER_SCORES.remove(player); + BLUE_TEAM.remove(player); + RED_TEAM.remove(player); + player.setOnEvent(false); + removeListeners(player); + player.sendMessage("You have been kicked for been inactive."); + if (PVP_WORLD != null) + { + // Manage forfeit. + if ((BLUE_TEAM.isEmpty() && !RED_TEAM.isEmpty()) || // + (RED_TEAM.isEmpty() && !BLUE_TEAM.isEmpty())) + { + manageForfeit(); + } + else + { + broadcastScreenMessageWithEffect("Player " + player.getName() + " was kicked for been inactive!", 7); + } + } + } + } + return htmltext; + } + + @Override + public String onFirstTalk(Npc npc, Player player) + { + // Event not active. + if (!EVENT_ACTIVE) + { + return null; + } + + // Player has already registered. + if (PLAYER_LIST.contains(player)) + { + // Npc is in instance. + if (npc.getInstanceId() > 0) + { + return "manager-buffheal.html"; + } + return "manager-cancel.html"; + } + // Player is not registered. + return "manager-register.html"; + } + + @Override + public String onEnterZone(Creature creature, ZoneType zone) + { + if (creature.isPlayable() && creature.getActingPlayer().isOnEvent()) + { + // Kick enemy players. + if ((zone == BLUE_PEACE_ZONE) && (creature.getTeam() == Team.RED)) + { + creature.teleToLocation(RED_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + sendScreenMessage(creature.getActingPlayer(), "Entering the enemy headquarters is prohibited!", 10); + } + if ((zone == RED_PEACE_ZONE) && (creature.getTeam() == Team.BLUE)) + { + creature.teleToLocation(BLUE_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + sendScreenMessage(creature.getActingPlayer(), "Entering the enemy headquarters is prohibited!", 10); + } + // Start inactivity check. + if (creature.isPlayer() && // + (((zone == BLUE_PEACE_ZONE) && (creature.getTeam() == Team.BLUE)) || // + ((zone == RED_PEACE_ZONE) && (creature.getTeam() == Team.RED)))) + { + resetActivityTimers(creature.getActingPlayer()); + } + } + return null; + } + + @Override + public String onExitZone(Creature creature, ZoneType zone) + { + if (creature.isPlayer() && creature.getActingPlayer().isOnEvent()) + { + final Player player = creature.getActingPlayer(); + cancelQuestTimer("KickPlayer" + creature.getObjectId(), null, player); + cancelQuestTimer("KickPlayerWarning" + creature.getObjectId(), null, player); + // Removed invulnerability shield. + if (player.isAffectedBySkill(GHOST_WALKING.getSkillId())) + { + player.getEffectList().stopSkillEffects(SkillFinishType.REMOVED, GHOST_WALKING.getSkill()); + } + } + return super.onExitZone(creature, zone); + } + + private boolean canRegister(Player player) + { + if (PLAYER_LIST.contains(player)) + { + player.sendMessage("You are already registered on this event."); + return false; + } + if (player.getLevel() < MINIMUM_PARTICIPANT_LEVEL) + { + player.sendMessage("Your level is too low to participate."); + return false; + } + if (player.getLevel() > MAXIMUM_PARTICIPANT_LEVEL) + { + player.sendMessage("Your level is too high to participate."); + return false; + } + if (player.isRegisteredOnEvent() || (player.getBlockCheckerArena() > -1)) + { + player.sendMessage("You are already registered on an event."); + return false; + } + if (PLAYER_LIST.size() >= MAXIMUM_PARTICIPANT_COUNT) + { + player.sendMessage("There are too many players registered on the event."); + return false; + } + if (player.isFlyingMounted()) + { + player.sendMessage("You cannot register on the event while flying."); + return false; + } + if (player.isTransformed()) + { + player.sendMessage("You cannot register on the event while on a transformed state."); + return false; + } + if (!player.isInventoryUnder80(false)) + { + player.sendMessage("There are too many items in your inventory."); + player.sendMessage("Try removing some items."); + return false; + } + if ((player.getWeightPenalty() != 0)) + { + player.sendMessage("Your invetory weight has exceeded the normal limit."); + player.sendMessage("Try removing some items."); + return false; + } + if (player.isCursedWeaponEquipped() || (player.getKarma() > 0)) + { + player.sendMessage("People with bad reputation can't register."); + return false; + } + if (player.isInDuel()) + { + player.sendMessage("You cannot register while on a duel."); + return false; + } + if (player.isInOlympiadMode() || Olympiad.getInstance().isRegistered(player)) + { + player.sendMessage("You cannot participate while registered on the Olympiad."); + return false; + } + if (player.getInstanceId() > 0) + { + player.sendMessage("You cannot register while in an instance."); + return false; + } + if (player.isInSiege() || player.isInsideZone(ZoneId.SIEGE)) + { + player.sendMessage("You cannot register while on a siege."); + return false; + } + if (player.isFishing()) + { + player.sendMessage("You cannot register while fishing."); + return false; + } + return true; + } + + private void sendScreenMessage(Player player, String message, int duration) + { + player.sendPacket(new ExShowScreenMessage(message, ExShowScreenMessage.TOP_CENTER, duration * 1000, 0, true, false)); + } + + private void broadcastScreenMessage(String message, int duration) + { + PVP_WORLD.broadcastPacket(new ExShowScreenMessage(message, ExShowScreenMessage.TOP_CENTER, duration * 1000, 0, true, false)); + } + + private void broadcastScreenMessageWithEffect(String message, int duration) + { + PVP_WORLD.broadcastPacket(new ExShowScreenMessage(message, ExShowScreenMessage.TOP_CENTER, duration * 1000, 0, true, true)); + } + + private void broadcastScoreMessage() + { + PVP_WORLD.broadcastPacket(new ExShowScreenMessage("Blue: " + BLUE_SCORE + " - Red: " + RED_SCORE, ExShowScreenMessage.BOTTOM_RIGHT, 15000, 0, true, false)); + } + + private void addLogoutListener(Player player) + { + player.addListener(new ConsumerEventListener(player, EventType.ON_PLAYER_LOGOUT, (OnPlayerLogout event) -> onPlayerLogout(event), this)); + } + + private void addDeathListener(Player player) + { + player.addListener(new ConsumerEventListener(player, EventType.ON_CREATURE_DEATH, (OnCreatureDeath event) -> onPlayerDeath(event), this)); + } + + private void removeListeners(Player player) + { + for (AbstractEventListener listener : player.getListeners(EventType.ON_PLAYER_LOGOUT)) + { + if (listener.getOwner() == this) + { + listener.unregisterMe(); + } + } + for (AbstractEventListener listener : player.getListeners(EventType.ON_CREATURE_DEATH)) + { + if (listener.getOwner() == this) + { + listener.unregisterMe(); + } + } + } + + private void resetActivityTimers(Player player) + { + cancelQuestTimer("KickPlayer" + player.getObjectId(), null, player); + cancelQuestTimer("KickPlayerWarning" + player.getObjectId(), null, player); + startQuestTimer("KickPlayer" + player.getObjectId(), PVP_WORLD.getDoor(BLUE_DOOR_ID).isOpen() ? INACTIVITY_TIME * 60000 : (INACTIVITY_TIME * 60000) + (WAIT_TIME * 60000), null, player); + startQuestTimer("KickPlayerWarning" + player.getObjectId(), PVP_WORLD.getDoor(BLUE_DOOR_ID).isOpen() ? (INACTIVITY_TIME / 2) * 60000 : ((INACTIVITY_TIME / 2) * 60000) + (WAIT_TIME * 60000), null, player); + } + + private void manageForfeit() + { + cancelQuestTimer("10", null, null); + cancelQuestTimer("9", null, null); + cancelQuestTimer("8", null, null); + cancelQuestTimer("7", null, null); + cancelQuestTimer("6", null, null); + cancelQuestTimer("5", null, null); + cancelQuestTimer("4", null, null); + cancelQuestTimer("3", null, null); + cancelQuestTimer("2", null, null); + cancelQuestTimer("1", null, null); + cancelQuestTimer("EndFight", null, null); + startQuestTimer("EndFight", 10000, null, null); + broadcastScreenMessageWithEffect("Enemy team forfeit!", 7); + } + + @RegisterEvent(EventType.ON_PLAYER_LOGOUT) + private void onPlayerLogout(OnPlayerLogout event) + { + final Player player = event.getPlayer(); + // Remove player from lists. + PLAYER_LIST.remove(player); + PLAYER_SCORES.remove(player); + BLUE_TEAM.remove(player); + RED_TEAM.remove(player); + // Manage forfeit. + if ((BLUE_TEAM.isEmpty() && !RED_TEAM.isEmpty()) || // + (RED_TEAM.isEmpty() && !BLUE_TEAM.isEmpty())) + { + manageForfeit(); + } + } + + @RegisterEvent(EventType.ON_CREATURE_DEATH) + public void onPlayerDeath(OnCreatureDeath event) + { + if (event.getTarget().isPlayer()) + { + final Player killedPlayer = event.getTarget().getActingPlayer(); + final Player killer = event.getAttacker().getActingPlayer(); + // Confirm Blue team kill. + if ((killer.getTeam() == Team.BLUE) && (killedPlayer.getTeam() == Team.RED)) + { + PLAYER_SCORES.put(killer, PLAYER_SCORES.get(killer) + 1); + BLUE_SCORE++; + broadcastScoreMessage(); + PVP_WORLD.broadcastPacket(new ExPVPMatchCCRecord(ExPVPMatchCCRecord.UPDATE, Util.sortByValue(PLAYER_SCORES, true))); + } + // Confirm Red team kill. + if ((killer.getTeam() == Team.RED) && (killedPlayer.getTeam() == Team.BLUE)) + { + PLAYER_SCORES.put(killer, PLAYER_SCORES.get(killer) + 1); + RED_SCORE++; + broadcastScoreMessage(); + PVP_WORLD.broadcastPacket(new ExPVPMatchCCRecord(ExPVPMatchCCRecord.UPDATE, Util.sortByValue(PLAYER_SCORES, true))); + } + // Auto release after 10 seconds. + startQuestTimer("ResurrectPlayer", 10000, null, killedPlayer); + } + } + + @Override + public boolean eventStart(Player eventMaker) + { + if (EVENT_ACTIVE) + { + return false; + } + EVENT_ACTIVE = true; + + // Cancel timers. (In case event started immediately after another event was canceled.) + for (List timers : getQuestTimers().values()) + { + for (QuestTimer timer : timers) + { + timer.cancel(); + } + } + // Register the event at AntiFeedManager and clean it for just in case if the event is already registered + if (Config.DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP > 0) + { + AntiFeedManager.getInstance().registerEvent(AntiFeedManager.L2EVENT_ID); + AntiFeedManager.getInstance().clear(AntiFeedManager.L2EVENT_ID); + } + // Clear player lists. + PLAYER_LIST.clear(); + PLAYER_SCORES.clear(); + BLUE_TEAM.clear(); + RED_TEAM.clear(); + // Spawn event manager. + MANAGER_NPC_INSTANCE = addSpawn(MANAGER, MANAGER_SPAWN_LOC, false, REGISTRATION_TIME * 60000); + startQuestTimer("TeleportToArena", REGISTRATION_TIME * 60000, null, null); + // Send message to players. + Broadcast.toAllOnlinePlayers("TvT Event: Registration opened for " + REGISTRATION_TIME + " minutes."); + Broadcast.toAllOnlinePlayers("TvT Event: You can register at Giran TvT Event Manager."); + return true; + } + + @Override + public boolean eventStop() + { + if (!EVENT_ACTIVE) + { + return false; + } + EVENT_ACTIVE = false; + + // Despawn event manager. + MANAGER_NPC_INSTANCE.deleteMe(); + // Cancel timers. + for (List timers : getQuestTimers().values()) + { + for (QuestTimer timer : timers) + { + timer.cancel(); + } + } + // Remove participants. + for (Player participant : PLAYER_LIST) + { + removeListeners(participant); + participant.setTeam(Team.NONE); + participant.setRegisteredOnEvent(false); + participant.setOnEvent(false); + PVP_WORLD.ejectPlayer(participant); + } + if (PVP_WORLD != null) + { + final Instance instance = InstanceManager.getInstance().getInstance(PVP_WORLD.getInstanceId()); + if (instance != null) + { + instance.setDuration(60000); + instance.setEmptyDestroyTime(0); + } + PVP_WORLD = null; + } + // Send message to players. + Broadcast.toAllOnlinePlayers("TvT Event: Event was canceled."); + return true; + } + + @Override + public boolean eventBypass(Player player, String bypass) + { + return false; + } + + public static void main(String[] args) + { + new TvT(); + } +} diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/manager-buffheal.html b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/manager-buffheal.html new file mode 100644 index 0000000000..aebcd98bd4 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/manager-buffheal.html @@ -0,0 +1,42 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Need assistance?
+
+
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/manager-cancel.html b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/manager-cancel.html new file mode 100644 index 0000000000..c03057a1b4 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/manager-cancel.html @@ -0,0 +1,42 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Cancel your participation?
+
+
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/manager-combat.html b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/manager-combat.html new file mode 100644 index 0000000000..ce123dca6b --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/manager-combat.html @@ -0,0 +1,39 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
You are in combat, calm down...
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/manager-register.html b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/manager-register.html new file mode 100644 index 0000000000..f1724ed9ad --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/manager-register.html @@ -0,0 +1,42 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Do you want to join the event?
+
+
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/registration-canceled.html b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/registration-canceled.html new file mode 100644 index 0000000000..a371f68493 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/registration-canceled.html @@ -0,0 +1,39 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Registration canceled.
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/registration-failed.html b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/registration-failed.html new file mode 100644 index 0000000000..8b4b0663a6 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/registration-failed.html @@ -0,0 +1,39 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Registration failed.
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/registration-ip.html b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/registration-ip.html new file mode 100644 index 0000000000..074732b093 --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/registration-ip.html @@ -0,0 +1,39 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Too many registrations.
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/registration-success.html b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/registration-success.html new file mode 100644 index 0000000000..b38b178e7d --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/scripts/custom/events/TeamVsTeam/registration-success.html @@ -0,0 +1,39 @@ + + TvT Event + +
+ + + + +
+ + + + +
+

+ +
+
+ + + + +
+ Team vs Team + +
+
+ + + + +
Registration successful!
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/stats/npcs/custom/tvt_event.xml b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/stats/npcs/custom/tvt_event.xml new file mode 100644 index 0000000000..351b366c9f --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/stats/npcs/custom/tvt_event.xml @@ -0,0 +1,21 @@ + + + + HUMAN + MALE + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/stats/skills/custom/tvt_event.xml b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/stats/skills/custom/tvt_event.xml new file mode 100644 index 0000000000..a46e0437df --- /dev/null +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/data/stats/skills/custom/tvt_event.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/instances/Coliseum.xml b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/instances/Coliseum.xml new file mode 100644 index 0000000000..ec78182380 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/instances/Coliseum.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/TvT.java b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/TvT.java new file mode 100644 index 0000000000..adcd1ce2f5 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/TvT.java @@ -0,0 +1,954 @@ +/* + * 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 custom.events.TeamVsTeam; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.l2jmobius.Config; +import org.l2jmobius.gameserver.enums.PartyDistributionType; +import org.l2jmobius.gameserver.enums.SkillFinishType; +import org.l2jmobius.gameserver.enums.Team; +import org.l2jmobius.gameserver.instancemanager.AntiFeedManager; +import org.l2jmobius.gameserver.instancemanager.InstanceManager; +import org.l2jmobius.gameserver.instancemanager.ZoneManager; +import org.l2jmobius.gameserver.model.CommandChannel; +import org.l2jmobius.gameserver.model.Location; +import org.l2jmobius.gameserver.model.Party; +import org.l2jmobius.gameserver.model.actor.Creature; +import org.l2jmobius.gameserver.model.actor.Npc; +import org.l2jmobius.gameserver.model.actor.Player; +import org.l2jmobius.gameserver.model.actor.Summon; +import org.l2jmobius.gameserver.model.events.EventType; +import org.l2jmobius.gameserver.model.events.annotations.RegisterEvent; +import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureDeath; +import org.l2jmobius.gameserver.model.events.impl.creature.player.OnPlayerLogout; +import org.l2jmobius.gameserver.model.events.listeners.AbstractEventListener; +import org.l2jmobius.gameserver.model.events.listeners.ConsumerEventListener; +import org.l2jmobius.gameserver.model.holders.ItemHolder; +import org.l2jmobius.gameserver.model.holders.SkillHolder; +import org.l2jmobius.gameserver.model.instancezone.Instance; +import org.l2jmobius.gameserver.model.instancezone.InstanceWorld; +import org.l2jmobius.gameserver.model.olympiad.OlympiadManager; +import org.l2jmobius.gameserver.model.quest.Event; +import org.l2jmobius.gameserver.model.quest.QuestTimer; +import org.l2jmobius.gameserver.model.skill.CommonSkill; +import org.l2jmobius.gameserver.model.skill.Skill; +import org.l2jmobius.gameserver.model.zone.ZoneId; +import org.l2jmobius.gameserver.model.zone.ZoneType; +import org.l2jmobius.gameserver.network.serverpackets.ExPVPMatchCCRecord; +import org.l2jmobius.gameserver.network.serverpackets.ExShowScreenMessage; +import org.l2jmobius.gameserver.network.serverpackets.MagicSkillUse; +import org.l2jmobius.gameserver.util.Broadcast; +import org.l2jmobius.gameserver.util.Util; + +/** + * Team vs Team event. + * @author Mobius + */ +public class TvT extends Event +{ + // NPC + private static final int MANAGER = 70010; + // Skills + private static final SkillHolder[] FIGHTER_BUFFS = + { + new SkillHolder(4322, 1), // Wind Walk + new SkillHolder(4323, 1), // Shield + new SkillHolder(5637, 1), // Magic Barrier + new SkillHolder(4324, 1), // Bless the Body + new SkillHolder(4325, 1), // Vampiric Rage + new SkillHolder(4326, 1), // Regeneration + new SkillHolder(5632, 1), // Haste + }; + private static final SkillHolder[] MAGE_BUFFS = + { + new SkillHolder(4322, 1), // Wind Walk + new SkillHolder(4323, 1), // Shield + new SkillHolder(5637, 1), // Magic Barrier + new SkillHolder(4328, 1), // Bless the Soul + new SkillHolder(4329, 1), // Acumen + new SkillHolder(4330, 1), // Concentration + new SkillHolder(4331, 1), // Empower + }; + private static final SkillHolder GHOST_WALKING = new SkillHolder(100000, 1); // Custom Ghost Walking + // Others + private static final int INSTANCE_ID = 3049; + private static final int BLUE_DOOR_ID = 24190002; + private static final int RED_DOOR_ID = 24190003; + private static final Location MANAGER_SPAWN_LOC = new Location(83425, 148585, -3406, 32938); + private static final Location BLUE_BUFFER_SPAWN_LOC = new Location(147450, 46913, -3400, 49000); + private static final Location RED_BUFFER_SPAWN_LOC = new Location(151545, 46528, -3400, 16000); + private static final Location BLUE_SPAWN_LOC = new Location(147447, 46722, -3416); + private static final Location RED_SPAWN_LOC = new Location(151536, 46722, -3416); + private static final ZoneType BLUE_PEACE_ZONE = ZoneManager.getInstance().getZoneByName("colosseum_peace1"); + private static final ZoneType RED_PEACE_ZONE = ZoneManager.getInstance().getZoneByName("colosseum_peace2"); + // Settings + private static final int REGISTRATION_TIME = 10; // Minutes + private static final int WAIT_TIME = 1; // Minutes + private static final int FIGHT_TIME = 20; // Minutes + private static final int INACTIVITY_TIME = 2; // Minutes + private static final int MINIMUM_PARTICIPANT_LEVEL = 76; + private static final int MAXIMUM_PARTICIPANT_LEVEL = 200; + private static final int MINIMUM_PARTICIPANT_COUNT = 4; + private static final int MAXIMUM_PARTICIPANT_COUNT = 24; // Scoreboard has 25 slots + private static final int PARTY_MEMBER_COUNT = 8; + private static final ItemHolder REWARD = new ItemHolder(57, 100000); // Adena + // Misc + private static final Map PLAYER_SCORES = new ConcurrentHashMap<>(); + private static final Set PLAYER_LIST = ConcurrentHashMap.newKeySet(); + private static final Set BLUE_TEAM = ConcurrentHashMap.newKeySet(); + private static final Set RED_TEAM = ConcurrentHashMap.newKeySet(); + private static volatile int BLUE_SCORE; + private static volatile int RED_SCORE; + private static InstanceWorld PVP_WORLD = null; + private static Npc MANAGER_NPC_INSTANCE = null; + private static boolean EVENT_ACTIVE = false; + + private TvT() + { + addTalkId(MANAGER); + addStartNpc(MANAGER); + addFirstTalkId(MANAGER); + addExitZoneId(BLUE_PEACE_ZONE.getId(), RED_PEACE_ZONE.getId()); + addEnterZoneId(BLUE_PEACE_ZONE.getId(), RED_PEACE_ZONE.getId()); + + // Daily task to start event at 20:00. + // final Calendar calendar = Calendar.getInstance(); + // if ((calendar.get(Calendar.HOUR_OF_DAY) >= 20) && (calendar.get(Calendar.MINUTE) >= 0)) + // { + // calendar.add(Calendar.DAY_OF_YEAR, 1); + // } + // calendar.set(Calendar.HOUR_OF_DAY, 20); + // calendar.set(Calendar.MINUTE, 0); + // calendar.set(Calendar.SECOND, 0); + // ThreadPool.scheduleAtFixedRate(() -> eventStart(null), calendar.getTimeInMillis() - System.currentTimeMillis(), 86400000); // 86400000 = 1 day + } + + @Override + public String onAdvEvent(String event, Npc npc, Player player) + { + if (!EVENT_ACTIVE) + { + return null; + } + + String htmltext = null; + switch (event) + { + case "Participate": + { + if (canRegister(player)) + { + if ((Config.DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP == 0) || AntiFeedManager.getInstance().tryAddPlayer(AntiFeedManager.L2EVENT_ID, player, Config.DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP)) + { + PLAYER_LIST.add(player); + PLAYER_SCORES.put(player, 0); + player.setRegisteredOnEvent(true); + addLogoutListener(player); + htmltext = "registration-success.html"; + } + else + { + htmltext = "registration-ip.html"; + } + } + else + { + htmltext = "registration-failed.html"; + } + break; + } + case "CancelParticipation": + { + if (player.isOnEvent()) + { + return null; + } + // Remove the player from the IP count + if (Config.DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP > 0) + { + AntiFeedManager.getInstance().removePlayer(AntiFeedManager.L2EVENT_ID, player); + } + PLAYER_LIST.remove(player); + PLAYER_SCORES.remove(player); + removeListeners(player); + player.setRegisteredOnEvent(false); + htmltext = "registration-canceled.html"; + break; + } + case "BuffHeal": + { + if (player.isOnEvent() || player.isGM()) + { + if (player.isInCombat()) + { + htmltext = "manager-combat.html"; + } + else + { + if (player.isMageClass()) + { + for (SkillHolder skill : MAGE_BUFFS) + { + npc.setTarget(player); + npc.doCast(skill.getSkill()); + // No animation. + // skill.getSkill().applyEffects(npc, player); + } + } + else + { + for (SkillHolder skill : FIGHTER_BUFFS) + { + npc.setTarget(player); + npc.doCast(skill.getSkill()); + // No animation. + // skill.getSkill().applyEffects(npc, player); + } + } + player.setCurrentHp(player.getMaxHp()); + player.setCurrentMp(player.getMaxMp()); + player.setCurrentCp(player.getMaxCp()); + } + } + break; + } + case "TeleportToArena": + { + // Remove offline players. + for (Player participant : PLAYER_LIST) + { + if ((participant == null) || (participant.isOnlineInt() != 1)) + { + PLAYER_LIST.remove(participant); + PLAYER_SCORES.remove(participant); + } + } + // Check if there are enough players to start the event. + if (PLAYER_LIST.size() < MINIMUM_PARTICIPANT_COUNT) + { + Broadcast.toAllOnlinePlayers("TvT Event: Event was canceled, not enough participants."); + for (Player participant : PLAYER_LIST) + { + removeListeners(participant); + participant.setRegisteredOnEvent(false); + } + EVENT_ACTIVE = false; + return null; + } + // Create the instance. + final InstanceWorld world = new InstanceWorld(); + world.setInstance(InstanceManager.getInstance().createDynamicInstance(INSTANCE_ID)); + InstanceManager.getInstance().addWorld(world); + PVP_WORLD = world; + // Close doors. + PVP_WORLD.closeDoor(24190001); // Blue outer door. + PVP_WORLD.closeDoor(BLUE_DOOR_ID); + PVP_WORLD.closeDoor(RED_DOOR_ID); + PVP_WORLD.closeDoor(24190004); // Red outer door. + // Randomize player list and separate teams. + final List playerList = new ArrayList<>(PLAYER_LIST.size()); + playerList.addAll(PLAYER_LIST); + Collections.shuffle(playerList); + PLAYER_LIST.clear(); + PLAYER_LIST.addAll(playerList); + boolean team = getRandomBoolean(); // If teams are not even, randomize where extra player goes. + for (Player participant : PLAYER_LIST) + { + participant.setOnEvent(true); + participant.setRegisteredOnEvent(false); + if (team) + { + BLUE_TEAM.add(participant); + PVP_WORLD.addAllowed(participant); + participant.leaveParty(); + participant.setTeam(Team.BLUE); + participant.teleToLocation(BLUE_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + team = false; + } + else + { + RED_TEAM.add(participant); + PVP_WORLD.addAllowed(participant); + participant.leaveParty(); + participant.setTeam(Team.RED); + participant.teleToLocation(RED_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + team = true; + } + addDeathListener(participant); + } + // Make Blue CC. + if (BLUE_TEAM.size() > 1) + { + CommandChannel blueCC = null; + Party lastBlueParty = null; + int blueParticipantCounter = 0; + for (Player participant : BLUE_TEAM) + { + blueParticipantCounter++; + if (blueParticipantCounter == 1) + { + lastBlueParty = new Party(participant, PartyDistributionType.FINDERS_KEEPERS); + participant.joinParty(lastBlueParty); + if (BLUE_TEAM.size() > PARTY_MEMBER_COUNT) + { + if (blueCC == null) + { + blueCC = new CommandChannel(participant); + } + else + { + blueCC.addParty(lastBlueParty); + } + } + } + else + { + participant.joinParty(lastBlueParty); + } + if (blueParticipantCounter == PARTY_MEMBER_COUNT) + { + blueParticipantCounter = 0; + } + } + } + // Make Red CC. + if (RED_TEAM.size() > 1) + { + CommandChannel redCC = null; + Party lastRedParty = null; + int redParticipantCounter = 0; + for (Player participant : RED_TEAM) + { + redParticipantCounter++; + if (redParticipantCounter == 1) + { + lastRedParty = new Party(participant, PartyDistributionType.FINDERS_KEEPERS); + participant.joinParty(lastRedParty); + if (RED_TEAM.size() > PARTY_MEMBER_COUNT) + { + if (redCC == null) + { + redCC = new CommandChannel(participant); + } + else + { + redCC.addParty(lastRedParty); + } + } + } + else + { + participant.joinParty(lastRedParty); + } + if (redParticipantCounter == PARTY_MEMBER_COUNT) + { + redParticipantCounter = 0; + } + } + } + // Spawn managers. + addSpawn(MANAGER, BLUE_BUFFER_SPAWN_LOC, false, (WAIT_TIME + FIGHT_TIME) * 60000, false, PVP_WORLD.getInstanceId()); + addSpawn(MANAGER, RED_BUFFER_SPAWN_LOC, false, (WAIT_TIME + FIGHT_TIME) * 60000, false, PVP_WORLD.getInstanceId()); + // Initialize scores. + BLUE_SCORE = 0; + RED_SCORE = 0; + // Initialize scoreboard. + PVP_WORLD.broadcastPacket(new ExPVPMatchCCRecord(ExPVPMatchCCRecord.INITIALIZE, Util.sortByValue(PLAYER_SCORES, true))); + // Schedule start. + startQuestTimer("5", (WAIT_TIME * 60000) - 5000, null, null); + startQuestTimer("4", (WAIT_TIME * 60000) - 4000, null, null); + startQuestTimer("3", (WAIT_TIME * 60000) - 3000, null, null); + startQuestTimer("2", (WAIT_TIME * 60000) - 2000, null, null); + startQuestTimer("1", (WAIT_TIME * 60000) - 1000, null, null); + startQuestTimer("StartFight", WAIT_TIME * 60000, null, null); + break; + } + case "StartFight": + { + // Open doors. + PVP_WORLD.openDoor(BLUE_DOOR_ID); + PVP_WORLD.openDoor(RED_DOOR_ID); + // Send message. + broadcastScreenMessageWithEffect("The fight has began!", 5); + // Schedule finish. + startQuestTimer("10", (FIGHT_TIME * 60000) - 10000, null, null); + startQuestTimer("9", (FIGHT_TIME * 60000) - 9000, null, null); + startQuestTimer("8", (FIGHT_TIME * 60000) - 8000, null, null); + startQuestTimer("7", (FIGHT_TIME * 60000) - 7000, null, null); + startQuestTimer("6", (FIGHT_TIME * 60000) - 6000, null, null); + startQuestTimer("5", (FIGHT_TIME * 60000) - 5000, null, null); + startQuestTimer("4", (FIGHT_TIME * 60000) - 4000, null, null); + startQuestTimer("3", (FIGHT_TIME * 60000) - 3000, null, null); + startQuestTimer("2", (FIGHT_TIME * 60000) - 2000, null, null); + startQuestTimer("1", (FIGHT_TIME * 60000) - 1000, null, null); + startQuestTimer("EndFight", FIGHT_TIME * 60000, null, null); + break; + } + case "EndFight": + { + // Close doors. + PVP_WORLD.closeDoor(BLUE_DOOR_ID); + PVP_WORLD.closeDoor(RED_DOOR_ID); + // Disable players. + for (Player participant : PLAYER_LIST) + { + participant.setInvul(true); + participant.setImmobilized(true); + participant.disableAllSkills(); + final Summon summon = participant.getSummon(); + if (summon != null) + { + summon.setInvul(true); + summon.setImmobilized(true); + summon.disableAllSkills(); + } + } + // Make sure noone is dead. + for (Player participant : PLAYER_LIST) + { + if (participant.isDead()) + { + participant.doRevive(); + } + } + // Team Blue wins. + if (BLUE_SCORE > RED_SCORE) + { + final Skill skill = CommonSkill.FIREWORK.getSkill(); + broadcastScreenMessageWithEffect("Team Blue won the event!", 7); + for (Player participant : BLUE_TEAM) + { + if ((participant != null) && (participant.getInstanceId() == PVP_WORLD.getInstanceId())) + { + participant.broadcastPacket(new MagicSkillUse(participant, participant, skill.getId(), skill.getLevel(), skill.getHitTime(), skill.getReuseDelay())); + participant.broadcastSocialAction(3); + giveItems(participant, REWARD); + } + } + } + // Team Red wins. + else if (RED_SCORE > BLUE_SCORE) + { + final Skill skill = CommonSkill.FIREWORK.getSkill(); + broadcastScreenMessageWithEffect("Team Red won the event!", 7); + for (Player participant : RED_TEAM) + { + if ((participant != null) && (participant.getInstanceId() == PVP_WORLD.getInstanceId())) + { + participant.broadcastPacket(new MagicSkillUse(participant, participant, skill.getId(), skill.getLevel(), skill.getHitTime(), skill.getReuseDelay())); + participant.broadcastSocialAction(3); + giveItems(participant, REWARD); + } + } + } + // Tie. + else + { + broadcastScreenMessageWithEffect("The event ended with a tie!", 7); + for (Player participant : PLAYER_LIST) + { + participant.broadcastSocialAction(13); + } + } + startQuestTimer("ScoreBoard", 3500, null, null); + startQuestTimer("TeleportOut", 7000, null, null); + break; + } + case "ScoreBoard": + { + PVP_WORLD.broadcastPacket(new ExPVPMatchCCRecord(ExPVPMatchCCRecord.FINISH, Util.sortByValue(PLAYER_SCORES, true))); + break; + } + case "TeleportOut": + { + // Remove event listeners. + for (Player participant : PLAYER_LIST) + { + removeListeners(participant); + participant.setTeam(Team.NONE); + participant.setOnEvent(false); + participant.leaveParty(); + PVP_WORLD.ejectPlayer(participant); + } + // Destroy world. + if (PVP_WORLD != null) + { + final Instance instance = InstanceManager.getInstance().getInstance(PVP_WORLD.getInstanceId()); + if (instance != null) + { + instance.setDuration(60000); + instance.setEmptyDestroyTime(0); + } + PVP_WORLD = null; + } + // Enable players. + for (Player participant : PLAYER_LIST) + { + participant.setInvul(false); + participant.setImmobilized(false); + participant.enableAllSkills(); + final Summon summon = participant.getSummon(); + if (summon != null) + { + summon.setInvul(true); + summon.setImmobilized(true); + summon.disableAllSkills(); + } + } + EVENT_ACTIVE = false; + break; + } + case "ResurrectPlayer": + { + if (player.isDead() && player.isOnEvent()) + { + if (BLUE_TEAM.contains(player)) + { + player.setIsPendingRevive(true); + player.teleToLocation(BLUE_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + // Make player invulnerable for 30 seconds. + GHOST_WALKING.getSkill().applyEffects(player, player); + // Reset existing activity timers. + resetActivityTimers(player); // In case player died in peace zone. + } + else if (RED_TEAM.contains(player)) + { + player.setIsPendingRevive(true); + player.teleToLocation(RED_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + // Make player invulnerable for 30 seconds. + GHOST_WALKING.getSkill().applyEffects(player, player); + // Reset existing activity timers. + resetActivityTimers(player); // In case player died in peace zone. + } + } + break; + } + case "10": + case "9": + case "8": + case "7": + case "6": + case "5": + case "4": + case "3": + case "2": + case "1": + { + broadcastScreenMessage(event, 4); + break; + } + } + // Activity timer. + if (event.startsWith("KickPlayer") && (player != null) && (player.getInstanceId() == PVP_WORLD.getInstanceId())) + { + if (event.contains("Warning")) + { + sendScreenMessage(player, "You have been marked as inactive!", 10); + } + else + { + player.setTeam(Team.NONE); + PVP_WORLD.ejectPlayer(player); + PLAYER_LIST.remove(player); + PLAYER_SCORES.remove(player); + BLUE_TEAM.remove(player); + RED_TEAM.remove(player); + player.setOnEvent(false); + removeListeners(player); + player.sendMessage("You have been kicked for been inactive."); + if (PVP_WORLD != null) + { + // Manage forfeit. + if ((BLUE_TEAM.isEmpty() && !RED_TEAM.isEmpty()) || // + (RED_TEAM.isEmpty() && !BLUE_TEAM.isEmpty())) + { + manageForfeit(); + } + else + { + broadcastScreenMessageWithEffect("Player " + player.getName() + " was kicked for been inactive!", 7); + } + } + } + } + return htmltext; + } + + @Override + public String onFirstTalk(Npc npc, Player player) + { + // Event not active. + if (!EVENT_ACTIVE) + { + return null; + } + + // Player has already registered. + if (PLAYER_LIST.contains(player)) + { + // Npc is in instance. + if (npc.getInstanceId() > 0) + { + return "manager-buffheal.html"; + } + return "manager-cancel.html"; + } + // Player is not registered. + return "manager-register.html"; + } + + @Override + public String onEnterZone(Creature creature, ZoneType zone) + { + if (creature.isPlayable() && creature.getActingPlayer().isOnEvent()) + { + // Kick enemy players. + if ((zone == BLUE_PEACE_ZONE) && (creature.getTeam() == Team.RED)) + { + creature.teleToLocation(RED_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + sendScreenMessage(creature.getActingPlayer(), "Entering the enemy headquarters is prohibited!", 10); + } + if ((zone == RED_PEACE_ZONE) && (creature.getTeam() == Team.BLUE)) + { + creature.teleToLocation(BLUE_SPAWN_LOC, PVP_WORLD.getInstanceId(), 50); + sendScreenMessage(creature.getActingPlayer(), "Entering the enemy headquarters is prohibited!", 10); + } + // Start inactivity check. + if (creature.isPlayer() && // + (((zone == BLUE_PEACE_ZONE) && (creature.getTeam() == Team.BLUE)) || // + ((zone == RED_PEACE_ZONE) && (creature.getTeam() == Team.RED)))) + { + resetActivityTimers(creature.getActingPlayer()); + } + } + return null; + } + + @Override + public String onExitZone(Creature creature, ZoneType zone) + { + if (creature.isPlayer() && creature.getActingPlayer().isOnEvent()) + { + final Player player = creature.getActingPlayer(); + cancelQuestTimer("KickPlayer" + creature.getObjectId(), null, player); + cancelQuestTimer("KickPlayerWarning" + creature.getObjectId(), null, player); + // Removed invulnerability shield. + if (player.isAffectedBySkill(GHOST_WALKING.getSkillId())) + { + player.getEffectList().stopSkillEffects(SkillFinishType.REMOVED, GHOST_WALKING.getSkill()); + } + } + return super.onExitZone(creature, zone); + } + + private boolean canRegister(Player player) + { + if (PLAYER_LIST.contains(player)) + { + player.sendMessage("You are already registered on this event."); + return false; + } + if (player.getLevel() < MINIMUM_PARTICIPANT_LEVEL) + { + player.sendMessage("Your level is too low to participate."); + return false; + } + if (player.getLevel() > MAXIMUM_PARTICIPANT_LEVEL) + { + player.sendMessage("Your level is too high to participate."); + return false; + } + if (player.isRegisteredOnEvent() || (player.getBlockCheckerArena() > -1)) + { + player.sendMessage("You are already registered on an event."); + return false; + } + if (PLAYER_LIST.size() >= MAXIMUM_PARTICIPANT_COUNT) + { + player.sendMessage("There are too many players registered on the event."); + return false; + } + if (player.isFlyingMounted()) + { + player.sendMessage("You cannot register on the event while flying."); + return false; + } + if (player.isTransformed()) + { + player.sendMessage("You cannot register on the event while on a transformed state."); + return false; + } + if (!player.isInventoryUnder80(false)) + { + player.sendMessage("There are too many items in your inventory."); + player.sendMessage("Try removing some items."); + return false; + } + if ((player.getWeightPenalty() != 0)) + { + player.sendMessage("Your invetory weight has exceeded the normal limit."); + player.sendMessage("Try removing some items."); + return false; + } + if (player.isCursedWeaponEquipped() || (player.getKarma() > 0)) + { + player.sendMessage("People with bad reputation can't register."); + return false; + } + if (player.isInDuel()) + { + player.sendMessage("You cannot register while on a duel."); + return false; + } + if (player.isInOlympiadMode() || OlympiadManager.getInstance().isRegistered(player)) + { + player.sendMessage("You cannot participate while registered on the Olympiad."); + return false; + } + if (player.getInstanceId() > 0) + { + player.sendMessage("You cannot register while in an instance."); + return false; + } + if (player.isInSiege() || player.isInsideZone(ZoneId.SIEGE)) + { + player.sendMessage("You cannot register while on a siege."); + return false; + } + if (player.isFishing()) + { + player.sendMessage("You cannot register while fishing."); + return false; + } + return true; + } + + private void sendScreenMessage(Player player, String message, int duration) + { + player.sendPacket(new ExShowScreenMessage(message, ExShowScreenMessage.TOP_CENTER, duration * 1000, 0, true, false)); + } + + private void broadcastScreenMessage(String message, int duration) + { + PVP_WORLD.broadcastPacket(new ExShowScreenMessage(message, ExShowScreenMessage.TOP_CENTER, duration * 1000, 0, true, false)); + } + + private void broadcastScreenMessageWithEffect(String message, int duration) + { + PVP_WORLD.broadcastPacket(new ExShowScreenMessage(message, ExShowScreenMessage.TOP_CENTER, duration * 1000, 0, true, true)); + } + + private void broadcastScoreMessage() + { + PVP_WORLD.broadcastPacket(new ExShowScreenMessage("Blue: " + BLUE_SCORE + " - Red: " + RED_SCORE, ExShowScreenMessage.BOTTOM_RIGHT, 15000, 0, true, false)); + } + + private void addLogoutListener(Player player) + { + player.addListener(new ConsumerEventListener(player, EventType.ON_PLAYER_LOGOUT, (OnPlayerLogout event) -> onPlayerLogout(event), this)); + } + + private void addDeathListener(Player player) + { + player.addListener(new ConsumerEventListener(player, EventType.ON_CREATURE_DEATH, (OnCreatureDeath event) -> onPlayerDeath(event), this)); + } + + private void removeListeners(Player player) + { + for (AbstractEventListener listener : player.getListeners(EventType.ON_PLAYER_LOGOUT)) + { + if (listener.getOwner() == this) + { + listener.unregisterMe(); + } + } + for (AbstractEventListener listener : player.getListeners(EventType.ON_CREATURE_DEATH)) + { + if (listener.getOwner() == this) + { + listener.unregisterMe(); + } + } + } + + private void resetActivityTimers(Player player) + { + cancelQuestTimer("KickPlayer" + player.getObjectId(), null, player); + cancelQuestTimer("KickPlayerWarning" + player.getObjectId(), null, player); + startQuestTimer("KickPlayer" + player.getObjectId(), PVP_WORLD.getDoor(BLUE_DOOR_ID).isOpen() ? INACTIVITY_TIME * 60000 : (INACTIVITY_TIME * 60000) + (WAIT_TIME * 60000), null, player); + startQuestTimer("KickPlayerWarning" + player.getObjectId(), PVP_WORLD.getDoor(BLUE_DOOR_ID).isOpen() ? (INACTIVITY_TIME / 2) * 60000 : ((INACTIVITY_TIME / 2) * 60000) + (WAIT_TIME * 60000), null, player); + } + + private void manageForfeit() + { + cancelQuestTimer("10", null, null); + cancelQuestTimer("9", null, null); + cancelQuestTimer("8", null, null); + cancelQuestTimer("7", null, null); + cancelQuestTimer("6", null, null); + cancelQuestTimer("5", null, null); + cancelQuestTimer("4", null, null); + cancelQuestTimer("3", null, null); + cancelQuestTimer("2", null, null); + cancelQuestTimer("1", null, null); + cancelQuestTimer("EndFight", null, null); + startQuestTimer("EndFight", 10000, null, null); + broadcastScreenMessageWithEffect("Enemy team forfeit!", 7); + } + + @RegisterEvent(EventType.ON_PLAYER_LOGOUT) + private void onPlayerLogout(OnPlayerLogout event) + { + final Player player = event.getPlayer(); + // Remove player from lists. + PLAYER_LIST.remove(player); + PLAYER_SCORES.remove(player); + BLUE_TEAM.remove(player); + RED_TEAM.remove(player); + // Manage forfeit. + if ((BLUE_TEAM.isEmpty() && !RED_TEAM.isEmpty()) || // + (RED_TEAM.isEmpty() && !BLUE_TEAM.isEmpty())) + { + manageForfeit(); + } + } + + @RegisterEvent(EventType.ON_CREATURE_DEATH) + public void onPlayerDeath(OnCreatureDeath event) + { + if (event.getTarget().isPlayer()) + { + final Player killedPlayer = event.getTarget().getActingPlayer(); + final Player killer = event.getAttacker().getActingPlayer(); + // Confirm Blue team kill. + if ((killer.getTeam() == Team.BLUE) && (killedPlayer.getTeam() == Team.RED)) + { + PLAYER_SCORES.put(killer, PLAYER_SCORES.get(killer) + 1); + BLUE_SCORE++; + broadcastScoreMessage(); + PVP_WORLD.broadcastPacket(new ExPVPMatchCCRecord(ExPVPMatchCCRecord.UPDATE, Util.sortByValue(PLAYER_SCORES, true))); + } + // Confirm Red team kill. + if ((killer.getTeam() == Team.RED) && (killedPlayer.getTeam() == Team.BLUE)) + { + PLAYER_SCORES.put(killer, PLAYER_SCORES.get(killer) + 1); + RED_SCORE++; + broadcastScoreMessage(); + PVP_WORLD.broadcastPacket(new ExPVPMatchCCRecord(ExPVPMatchCCRecord.UPDATE, Util.sortByValue(PLAYER_SCORES, true))); + } + // Auto release after 10 seconds. + startQuestTimer("ResurrectPlayer", 10000, null, killedPlayer); + } + } + + @Override + public boolean eventStart(Player eventMaker) + { + if (EVENT_ACTIVE) + { + return false; + } + EVENT_ACTIVE = true; + + // Cancel timers. (In case event started immediately after another event was canceled.) + for (List timers : getQuestTimers().values()) + { + for (QuestTimer timer : timers) + { + timer.cancel(); + } + } + // Register the event at AntiFeedManager and clean it for just in case if the event is already registered + if (Config.DUALBOX_CHECK_MAX_L2EVENT_PARTICIPANTS_PER_IP > 0) + { + AntiFeedManager.getInstance().registerEvent(AntiFeedManager.L2EVENT_ID); + AntiFeedManager.getInstance().clear(AntiFeedManager.L2EVENT_ID); + } + // Clear player lists. + PLAYER_LIST.clear(); + PLAYER_SCORES.clear(); + BLUE_TEAM.clear(); + RED_TEAM.clear(); + // Spawn event manager. + MANAGER_NPC_INSTANCE = addSpawn(MANAGER, MANAGER_SPAWN_LOC, false, REGISTRATION_TIME * 60000); + startQuestTimer("TeleportToArena", REGISTRATION_TIME * 60000, null, null); + // Send message to players. + Broadcast.toAllOnlinePlayers("TvT Event: Registration opened for " + REGISTRATION_TIME + " minutes."); + Broadcast.toAllOnlinePlayers("TvT Event: You can register at Giran TvT Event Manager."); + return true; + } + + @Override + public boolean eventStop() + { + if (!EVENT_ACTIVE) + { + return false; + } + EVENT_ACTIVE = false; + + // Despawn event manager. + MANAGER_NPC_INSTANCE.deleteMe(); + // Cancel timers. + for (List timers : getQuestTimers().values()) + { + for (QuestTimer timer : timers) + { + timer.cancel(); + } + } + // Remove participants. + for (Player participant : PLAYER_LIST) + { + removeListeners(participant); + participant.setTeam(Team.NONE); + participant.setRegisteredOnEvent(false); + participant.setOnEvent(false); + PVP_WORLD.ejectPlayer(participant); + } + if (PVP_WORLD != null) + { + final Instance instance = InstanceManager.getInstance().getInstance(PVP_WORLD.getInstanceId()); + if (instance != null) + { + instance.setDuration(60000); + instance.setEmptyDestroyTime(0); + } + PVP_WORLD = null; + } + // Send message to players. + Broadcast.toAllOnlinePlayers("TvT Event: Event was canceled."); + return true; + } + + @Override + public boolean eventBypass(Player player, String bypass) + { + return false; + } + + public static void main(String[] args) + { + new TvT(); + } +} diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/manager-buffheal.html b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/manager-buffheal.html new file mode 100644 index 0000000000..65e814dd3a --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/manager-buffheal.html @@ -0,0 +1,38 @@ + + TvT Event + +
+ + + + +
+ + + + +
+ + + + +
+ Team vs Team + +
+
+ + + + +
Need assistance?
+
+
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/manager-cancel.html b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/manager-cancel.html new file mode 100644 index 0000000000..cc3649004f --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/manager-cancel.html @@ -0,0 +1,38 @@ + + TvT Event + +
+ + + + +
+ + + + +
+ + + + +
+ Team vs Team + +
+
+ + + + +
Cancel your participation?
+
+
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/manager-combat.html b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/manager-combat.html new file mode 100644 index 0000000000..40f9fa0e5b --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/manager-combat.html @@ -0,0 +1,35 @@ + + TvT Event + +
+ + + + +
+ + + + +
+ + + + +
+ Team vs Team + +
+
+ + + + +
You are in combat, calm down...
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/manager-register.html b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/manager-register.html new file mode 100644 index 0000000000..0a2c42f9f9 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/manager-register.html @@ -0,0 +1,38 @@ + + TvT Event + +
+ + + + +
+ + + + +
+ + + + +
+ Team vs Team + +
+
+ + + + +
Do you want to join the event?
+
+
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/registration-canceled.html b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/registration-canceled.html new file mode 100644 index 0000000000..cc29ee886d --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/registration-canceled.html @@ -0,0 +1,35 @@ + + TvT Event + +
+ + + + +
+ + + + +
+ + + + +
+ Team vs Team + +
+
+ + + + +
Registration canceled.
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/registration-failed.html b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/registration-failed.html new file mode 100644 index 0000000000..be9b3f710e --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/registration-failed.html @@ -0,0 +1,35 @@ + + TvT Event + +
+ + + + +
+ + + + +
+ + + + +
+ Team vs Team + +
+
+ + + + +
Registration failed.
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/registration-ip.html b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/registration-ip.html new file mode 100644 index 0000000000..7b3a458873 --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/registration-ip.html @@ -0,0 +1,35 @@ + + TvT Event + +
+ + + + +
+ + + + +
+ + + + +
+ Team vs Team + +
+
+ + + + +
Too many registrations.
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/registration-success.html b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/registration-success.html new file mode 100644 index 0000000000..3d08d6854d --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/scripts/custom/events/TeamVsTeam/registration-success.html @@ -0,0 +1,35 @@ + + TvT Event + +
+ + + + +
+ + + + +
+ + + + +
+ Team vs Team + +
+
+ + + + +
Registration successful!
+
+
+
+
+
+ + diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/stats/npcs/custom/tvt_event.xml b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/stats/npcs/custom/tvt_event.xml new file mode 100644 index 0000000000..351b366c9f --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/stats/npcs/custom/tvt_event.xml @@ -0,0 +1,21 @@ + + + + HUMAN + MALE + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/data/stats/skills/custom/tvt_event.xml b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/stats/skills/custom/tvt_event.xml new file mode 100644 index 0000000000..a46e0437df --- /dev/null +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/data/stats/skills/custom/tvt_event.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file