/* * This file is part of the L2J Mobius project. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.l2jmobius.gameserver.model.instancezone; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import com.l2jmobius.Config; import com.l2jmobius.commons.util.Rnd; import com.l2jmobius.gameserver.enums.GroupType; import com.l2jmobius.gameserver.enums.InstanceReenterType; import com.l2jmobius.gameserver.enums.InstanceRemoveBuffType; import com.l2jmobius.gameserver.enums.InstanceTeleportType; import com.l2jmobius.gameserver.instancemanager.InstanceManager; import com.l2jmobius.gameserver.model.AbstractPlayerGroup; import com.l2jmobius.gameserver.model.Location; import com.l2jmobius.gameserver.model.PcCondOverride; import com.l2jmobius.gameserver.model.StatsSet; import com.l2jmobius.gameserver.model.actor.L2Npc; import com.l2jmobius.gameserver.model.actor.L2Playable; import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; import com.l2jmobius.gameserver.model.actor.templates.L2DoorTemplate; import com.l2jmobius.gameserver.model.events.ListenersContainer; import com.l2jmobius.gameserver.model.holders.InstanceReenterTimeHolder; import com.l2jmobius.gameserver.model.instancezone.conditions.Condition; import com.l2jmobius.gameserver.model.instancezone.conditions.ConditionCommandChannel; import com.l2jmobius.gameserver.model.instancezone.conditions.ConditionGroupMax; import com.l2jmobius.gameserver.model.instancezone.conditions.ConditionGroupMin; import com.l2jmobius.gameserver.model.interfaces.IIdentifiable; import com.l2jmobius.gameserver.model.interfaces.INamable; import com.l2jmobius.gameserver.model.skills.BuffInfo; import com.l2jmobius.gameserver.model.skills.Skill; import com.l2jmobius.gameserver.model.spawns.SpawnTemplate; import com.l2jmobius.gameserver.model.variables.PlayerVariables; /** * Template holder for instances. * @author malyelfik */ public class InstanceTemplate extends ListenersContainer implements IIdentifiable, INamable { // Basic instance parameters private int _templateId = -1; private String _name = "UnknownInstance"; private int _duration = -1; private long _emptyDestroyTime = -1; private int _ejectTime = Config.EJECT_DEAD_PLAYER_TIME; private int _maxWorldCount = -1; private boolean _isPvP = false; private boolean _allowPlayerSummon = false; private float _expRate = Config.RATE_INSTANCE_XP; private float _spRate = Config.RATE_INSTANCE_SP; private float _expPartyRate = Config.RATE_INSTANCE_PARTY_XP; private float _spPartyRate = Config.RATE_INSTANCE_PARTY_SP; private StatsSet _parameters = StatsSet.EMPTY_STATSET; private final Map _doors = new HashMap<>(); private final List _spawns = new ArrayList<>(); // Locations private InstanceTeleportType _enterLocationType = InstanceTeleportType.NONE; private List _enterLocations = null; private InstanceTeleportType _exitLocationType = InstanceTeleportType.NONE; private List _exitLocations = null; // Reenter data private InstanceReenterType _reenterType = InstanceReenterType.NONE; private List _reenterData = Collections.emptyList(); // Buff remove data private InstanceRemoveBuffType _removeBuffType = InstanceRemoveBuffType.NONE; private List _removeBuffExceptions = Collections.emptyList(); // Conditions private List _conditions = Collections.emptyList(); private int _groupMask = GroupType.NONE.getMask(); /** * @param set */ public InstanceTemplate(StatsSet set) { _templateId = set.getInt("id", 0); _name = set.getString("name", null); _maxWorldCount = set.getInt("maxWorlds", -1); } // ------------------------------------------------------------- // Setters // ------------------------------------------------------------- /** * Set name of instance world. * @param name instance name */ public void setName(String name) { if ((name != null) && !name.isEmpty()) { _name = name; } } /** * Set instance world duration. * @param duration time in minutes */ public void setDuration(int duration) { if (duration > 0) { _duration = duration; } } /** * Set time after empty instance will be destroyed. * @param emptyDestroyTime time in minutes */ public void setEmptyDestroyTime(long emptyDestroyTime) { if (emptyDestroyTime >= 0) { _emptyDestroyTime = TimeUnit.MINUTES.toMillis(emptyDestroyTime); } } /** * Set time after death player will be ejected from instance world.
* Default: {@link Config#EJECT_DEAD_PLAYER_TIME} * @param ejectTime time in minutes */ public void setEjectTime(int ejectTime) { if (ejectTime >= 0) { _ejectTime = ejectTime; } } /** * Allow summoning players (that are out of instance) to instance world by players inside. * @param val {@code true} means summon is allowed, {@code false} means summon is prohibited */ public void allowPlayerSummon(boolean val) { _allowPlayerSummon = val; } /** * Set instance as PvP world. * @param val {@code true} world is PvP zone, {@code false} world use classic zones */ public void setIsPvP(boolean val) { _isPvP = val; } /** * Set parameters shared between instances with same template id. * @param set map containing parameters */ public void setParameters(Map set) { if (!set.isEmpty()) { _parameters = new StatsSet(Collections.unmodifiableMap(set)); } } /** * Add door into instance world. * @param templateId template id of door * @param template door template */ public void addDoor(int templateId, L2DoorTemplate template) { _doors.put(templateId, template); } /** * Add new group of NPC spawns into instance world.
* Group with name "general" will be spawned on instance world create. * @param spawns list of NPC spawn data */ public void addSpawns(List spawns) { _spawns.addAll(spawns); } /** * Set enter locations for instance world. * @param type type of teleport ({@link InstanceTeleportType#FIXED} or {@link InstanceTeleportType#RANDOM} are supported) * @param locations list of locations used for determining final enter location */ public void setEnterLocation(InstanceTeleportType type, List locations) { _enterLocationType = type; _enterLocations = locations; } /** * Set exit locations for instance world. * @param type type of teleport (see {@link InstanceTeleportType} for all possible types) * @param locations list of locations used for determining final exit location */ public void setExitLocation(InstanceTeleportType type, List locations) { _exitLocationType = type; _exitLocations = locations; } /** * Set re-enter data for instance world.
* This method also enable re-enter condition for instance world. * @param type reenter type means when reenter restriction should be applied (see {@link InstanceReenterType} for more info) * @param holder data which are used to calculate reenter time */ public void setReenterData(InstanceReenterType type, List holder) { _reenterType = type; _reenterData = holder; } /** * Set remove buff list for instance world.
* These data are used to restrict player buffs when he enters into instance. * @param type type of list like blacklist, whitelist, ... (see {@link InstanceRemoveBuffType} for more info) * @param exceptionList */ public void setRemoveBuff(InstanceRemoveBuffType type, List exceptionList) { _removeBuffType = type; _removeBuffExceptions = exceptionList; } /** * Register conditions to instance world.
* This method also set new enter group mask according to given conditions. * @param conditions list of conditions */ public void setConditions(List conditions) { // Set conditions _conditions = conditions; // Now iterate over conditions and determine enter group data boolean onlyCC = false; int min = 1, max = 1; for (Condition cond : _conditions) { if (cond instanceof ConditionCommandChannel) { onlyCC = true; } else if (cond instanceof ConditionGroupMin) { min = ((ConditionGroupMin) cond).getLimit(); } else if (cond instanceof ConditionGroupMax) { max = ((ConditionGroupMax) cond).getLimit(); } } // Reset group mask before setting new group _groupMask = 0; // Check if player can enter in other group then Command channel if (!onlyCC) { // Player if (min == 1) { _groupMask |= GroupType.NONE.getMask(); } // Party final int partySize = Config.ALT_PARTY_MAX_MEMBERS; if (((max > 1) && (max <= partySize)) || ((min <= partySize) && (max > partySize))) { _groupMask |= GroupType.PARTY.getMask(); } } // Command channel if (onlyCC || (max > 7)) { _groupMask |= GroupType.COMMAND_CHANNEL.getMask(); } } // ------------------------------------------------------------- // Getters // ------------------------------------------------------------- @Override public int getId() { return _templateId; } @Override public String getName() { return _name; } /** * Get all enter locations defined in XML template. * @return list of enter locations */ public List getEnterLocations() { return _enterLocations; } /** * Get enter location to instance world. * @return enter location if instance has any, otherwise {@code null} */ public Location getEnterLocation() { Location loc = null; switch (_enterLocationType) { case RANDOM: loc = _enterLocations.get(Rnd.get(_enterLocations.size())); break; case FIXED: loc = _enterLocations.get(0); break; } return loc; } /** * Get type of exit location. * @return exit location type (see {@link InstanceTeleportType} for possible values) */ public InstanceTeleportType getExitLocationType() { return _exitLocationType; } /** * Get exit location from instance world. * @param player player who wants to leave instance * @return exit location if instance has any, otherwise {@code null} */ public Location getExitLocation(L2PcInstance player) { Location location = null; switch (_exitLocationType) { case RANDOM: location = _exitLocations.get(Rnd.get(_exitLocations.size())); break; case FIXED: location = _exitLocations.get(0); break; case ORIGIN: { final PlayerVariables vars = player.getVariables(); final int[] loc = vars.getIntArray("INSTANCE_ORIGIN", ";"); if (loc.length == 3) { location = new Location(loc[0], loc[1], loc[2]); vars.remove("INSTANCE_ORIGIN"); } break; } } return location; } /** * Get time after empty instance is destroyed. * @return time in milliseconds */ public long getEmptyDestroyTime() { return _emptyDestroyTime; } /** * Get instance duration time. * @return time in minutes */ public int getDuration() { return _duration; } /** * Get time after dead player is ejected from instance world. * @return time in minutes */ public int getEjectTime() { return _ejectTime; } /** * Check if summoning player into instance is allowed. * @return {@code true} if summon is allowed, otherwise {@code false} */ public boolean isPlayerSummonAllowed() { return _allowPlayerSummon; } /** * Check if instance is PvP zone. * @return {@code true} if instance is PvP, otherwise {@code false} */ public boolean isPvP() { return _isPvP; } /** * Get doors data for instance world. * @return map in form doorId, door template */ public Map getDoors() { return _doors; } /** * @return list of all spawn templates */ public List getSpawns() { return _spawns; } /** * Get count of instance worlds which can run concurrently with same template ID. * @return count of worlds */ public int getMaxWorlds() { return _maxWorldCount; } /** * Get instance template parameters. * @return parameters of template */ public StatsSet getParameters() { return _parameters; } /** * Check if buffs are removed upon instance enter. * @return {@code true} if any buffs should be removed, otherwise {@code false} */ public boolean isRemoveBuffEnabled() { return _removeBuffType != InstanceRemoveBuffType.NONE; } /** * Remove buffs from player according to remove buff data * @param player player which loose buffs */ public void removePlayerBuff(L2PcInstance player) { // Make list of affected playable objects final List affected = new ArrayList<>(); affected.add(player); player.getServitors().values().forEach(affected::add); if (player.hasPet()) { affected.add(player.getPet()); } // Now remove buffs by type if (_removeBuffType.equals(InstanceRemoveBuffType.ALL)) { for (L2Playable playable : affected) { playable.stopAllEffectsExceptThoseThatLastThroughDeath(); } } else { for (L2Playable playable : affected) { for (BuffInfo info : playable.getEffectList().getBuffs()) { if (hasRemoveBuffException(info.getSkill())) { playable.stopSkillEffects(info.getSkill()); } } } } } /** * Check if given buff {@code skill} should be removed. * @param skill buff which should be removed * @return {@code true} if buff will be removed, otherwise {@code false} */ private boolean hasRemoveBuffException(Skill skill) { final boolean containsSkill = _removeBuffExceptions.contains(skill.getId()); return (_removeBuffType == InstanceRemoveBuffType.BLACKLIST) ? containsSkill : !containsSkill; } /** * Get type of re-enter data. * @return type of re-enter (see {@link InstanceReenterType} for possible values) */ public InstanceReenterType getReenterType() { return _reenterType; } /** * Calculate re-enter time for instance world. * @return re-enter time in milliseconds */ public long calculateReenterTime() { long time = -1; for (InstanceReenterTimeHolder data : _reenterData) { if (data.getTime() > 0) { time = System.currentTimeMillis() + data.getTime(); break; } final Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR, data.getHour()); calendar.set(Calendar.MINUTE, data.getMinute()); calendar.set(Calendar.SECOND, 0); // If calendar time is lower than current, add one more day if (calendar.getTimeInMillis() <= System.currentTimeMillis()) { calendar.add(Calendar.DAY_OF_MONTH, 1); } // Modify calendar day if (data.getDay() != null) { // DayOfWeek starts with Monday(1) but Calendar starts with Sunday(1) int day = data.getDay().getValue() + 1; if (day > 7) { day = 1; } // Set exact day. If modified date is before current, add one more week. calendar.set(Calendar.DAY_OF_WEEK, day); if (calendar.getTimeInMillis() <= System.currentTimeMillis()) { calendar.add(Calendar.WEEK_OF_MONTH, 1); } } if ((time == -1) || (calendar.getTimeInMillis() < time)) { time = calendar.getTimeInMillis(); } } return time; } /** * Check if enter group mask contains given group type {@code type}. * @param type type of group * @return {@code true} if mask contains given group, otherwise {@code false} */ private final boolean groupMaskContains(GroupType type) { final int flag = type.getMask(); return (_groupMask & flag) == flag; } /** * Get enter group which can enter into instance world based on player's group. * @param player player who wants to enter * @return group type which can enter if any can enter, otherwise {@code null} */ private final GroupType getEnterGroupType(L2PcInstance player) { // If mask doesn't contain any group if (_groupMask == 0) { return null; } // If player can override instance conditions then he can enter alone if (player.canOverrideCond(PcCondOverride.INSTANCE_CONDITIONS)) { return GroupType.NONE; } // Check if mask contains player's group final GroupType playerGroup = player.getGroupType(); if (groupMaskContains(playerGroup)) { return playerGroup; } // Check if mask contains only one group final GroupType type = GroupType.getByMask(_groupMask); if (type != null) { return type; } // When mask contains more group types but without player's group, choose nearest one // player < party < command channel for (GroupType t : GroupType.values()) { if (!t.equals(playerGroup) && groupMaskContains(t)) { return t; } } // nothing found? then player cannot enter return null; } /** * Get player's group based on result of {@link InstanceTemplate#getEnterGroupType(L2PcInstance)}. * @param player player who wants to enter into instance * @return list of players (first player in list is player who make enter request) */ public List getEnterGroup(L2PcInstance player) { final GroupType type = getEnterGroupType(player); if (type == null) { return null; } // Make list of players which can enter into instance world final List group = new ArrayList<>(); group.add(player); // Put player who made request at first position inside list // Check if player has group in which he can enter AbstractPlayerGroup pGroup = null; if (type.equals(GroupType.PARTY)) { pGroup = player.getParty(); } else if (type.equals(GroupType.COMMAND_CHANNEL)) { pGroup = player.getCommandChannel(); } // If any group found then put them into enter group list if (pGroup != null) { pGroup.getMembers().stream().filter(p -> !p.equals(player)).forEach(group::add); } return group; } /** * Validate instance conditions for given group. * @param group group of players which want to enter instance world * @param npc instance of NPC used to enter to instance * @param htmlCallback callback function used to display fail HTML when condition validate failed * @return {@code true} when all condition are met, otherwise {@code false} */ public boolean validateConditions(List group, L2Npc npc, BiConsumer htmlCallback) { for (Condition cond : _conditions) { if (!cond.validate(npc, group, htmlCallback)) { return false; } } return true; } /** * Apply condition effects for each player from enter group. * @param group players from enter group */ public void applyConditionEffects(List group) { _conditions.forEach(c -> c.applyEffect(group)); } /** * @return the exp rate of the instance **/ public float getExpRate() { return _expRate; } /** * Sets the exp rate of the instance * @param expRate **/ public void setExpRate(float expRate) { _expRate = expRate; } /** * @return the sp rate of the instance */ public float getSPRate() { return _spRate; } /** * Sets the sp rate of the instance * @param spRate **/ public void setSPRate(float spRate) { _spRate = spRate; } /** * @return the party exp rate of the instance */ public float getExpPartyRate() { return _expPartyRate; } /** * Sets the party exp rate of the instance * @param expRate **/ public void setExpPartyRate(float expRate) { _expPartyRate = expRate; } /** * @return the party sp rate of the instance */ public float getSPPartyRate() { return _spPartyRate; } /** * Sets the party sp rate of the instance * @param spRate **/ public void setSPPartyRate(float spRate) { _spPartyRate = spRate; } /** * Get count of created instance worlds. * @return count of created instances */ public long getWorldCount() { return InstanceManager.getInstance().getWorldCount(getId()); } @Override public String toString() { return "ID: " + getId() + " Name: " + getName(); } }