/* * Copyright (C) 2004-2015 L2J Server * * This file is part of L2J Server. * * L2J Server is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * L2J Server is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.l2jserver.gameserver.model.quest; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import com.l2jserver.Config; import com.l2jserver.L2DatabaseFactory; import com.l2jserver.gameserver.enums.QuestSound; import com.l2jserver.gameserver.enums.QuestType; import com.l2jserver.gameserver.instancemanager.PcCafePointsManager; import com.l2jserver.gameserver.instancemanager.QuestManager; import com.l2jserver.gameserver.model.actor.L2Character; import com.l2jserver.gameserver.model.actor.L2Npc; import com.l2jserver.gameserver.model.actor.instance.L2PcInstance; import com.l2jserver.gameserver.model.events.AbstractScript; import com.l2jserver.gameserver.model.holders.ItemHolder; import com.l2jserver.gameserver.network.NpcStringId; import com.l2jserver.gameserver.network.serverpackets.ExShowQuestMark; import com.l2jserver.gameserver.network.serverpackets.PlaySound; import com.l2jserver.gameserver.network.serverpackets.QuestList; import com.l2jserver.gameserver.network.serverpackets.TutorialShowQuestionMark; import com.l2jserver.gameserver.util.Util; /** * Quest state class. * @author Luis Arias */ public final class QuestState { protected static final Logger _log = Logger.getLogger(QuestState.class.getName()); /** The name of the quest of this QuestState */ private final String _questName; /** The "owner" of this QuestState object */ private final L2PcInstance _player; /** The current state of the quest */ private byte _state; /** A map of key->value pairs containing the quest state variables and their values */ private Map<String, String> _vars; /** * boolean flag letting QuestStateManager know to exit quest when cleaning up */ private boolean _isExitQuestOnCleanUp = false; /** * Constructor of the QuestState. Creates the QuestState object and sets the player's progress of the quest to this QuestState. * @param quest the {@link Quest} object associated with the QuestState * @param player the owner of this {@link QuestState} object * @param state the initial state of the quest */ public QuestState(Quest quest, L2PcInstance player, byte state) { _questName = quest.getName(); _player = player; _state = state; player.setQuestState(this); } /** * @return the name of the quest of this QuestState */ public String getQuestName() { return _questName; } /** * @return the {@link Quest} object of this QuestState */ public Quest getQuest() { return QuestManager.getInstance().getQuest(_questName); } /** * @return the {@link L2PcInstance} object of the owner of this QuestState */ public L2PcInstance getPlayer() { return _player; } /** * @return the current State of this QuestState * @see com.l2jserver.gameserver.model.quest.State */ public byte getState() { return _state; } /** * @return {@code true} if the State of this QuestState is CREATED, {@code false} otherwise * @see com.l2jserver.gameserver.model.quest.State */ public boolean isCreated() { return (_state == State.CREATED); } /** * @return {@code true} if the State of this QuestState is STARTED, {@code false} otherwise * @see com.l2jserver.gameserver.model.quest.State */ public boolean isStarted() { return (_state == State.STARTED); } /** * @return {@code true} if the State of this QuestState is COMPLETED, {@code false} otherwise * @see com.l2jserver.gameserver.model.quest.State */ public boolean isCompleted() { return (_state == State.COMPLETED); } /** * @param state the new state of the quest to set * @return {@code true} if state was changed, {@code false} otherwise * @see #setState(byte state, boolean saveInDb) * @see com.l2jserver.gameserver.model.quest.State */ public boolean setState(byte state) { return setState(state, true); } /** * Change the state of this quest to the specified value. * @param state the new state of the quest to set * @param saveInDb if {@code true}, will save the state change in the database * @return {@code true} if state was changed, {@code false} otherwise * @see com.l2jserver.gameserver.model.quest.State */ public boolean setState(byte state, boolean saveInDb) { if (_state == state) { return false; } final boolean newQuest = isCreated(); _state = state; if (saveInDb) { if (newQuest) { Quest.createQuestInDb(this); } else { Quest.updateQuestInDb(this); } } _player.sendPacket(new QuestList()); return true; } /** * Add parameter used in quests. * @param var String pointing out the name of the variable for quest * @param val String pointing out the value of the variable for quest * @return String (equal to parameter "val") */ public String setInternal(String var, String val) { if (_vars == null) { _vars = new HashMap<>(); } if (val == null) { val = ""; } _vars.put(var, val); return val; } public String set(String var, int val) { return set(var, Integer.toString(val)); } /** * Return value of parameter "val" after adding the couple (var,val) in class variable "vars".<br> * Actions:<br> * <ul> * <li>Initialize class variable "vars" if is null.</li> * <li>Initialize parameter "val" if is null</li> * <li>Add/Update couple (var,val) in class variable HashMap "vars"</li> * <li>If the key represented by "var" exists in HashMap "vars", the couple (var,val) is updated in the database.<br> * The key is known as existing if the preceding value of the key (given as result of function put()) is not null.<br> * If the key doesn't exist, the couple is added/created in the database</li> * <ul> * @param var String indicating the name of the variable for quest * @param val String indicating the value of the variable for quest * @return String (equal to parameter "val") */ public String set(String var, String val) { if (_vars == null) { _vars = new HashMap<>(); } if (val == null) { val = ""; } String old = _vars.put(var, val); if (old != null) { Quest.updateQuestVarInDb(this, var, val); } else { Quest.createQuestVarInDb(this, var, val); } if ("cond".equals(var)) { try { int previousVal = 0; try { previousVal = Integer.parseInt(old); } catch (Exception ex) { previousVal = 0; } setCond(Integer.parseInt(val), previousVal); } catch (Exception e) { _log.log(Level.WARNING, _player.getName() + ", " + getQuestName() + " cond [" + val + "] is not an integer. Value stored, but no packet was sent: " + e.getMessage(), e); } } return val; } /** * Internally handles the progression of the quest so that it is ready for sending appropriate packets to the client.<br> * <u><i>Actions :</i></u><br> * <ul> * <li>Check if the new progress number resets the quest to a previous (smaller) step.</li> * <li>If not, check if quest progress steps have been skipped.</li> * <li>If skipped, prepare the variable completedStateFlags appropriately to be ready for sending to clients.</li> * <li>If no steps were skipped, flags do not need to be prepared...</li> * <li>If the passed step resets the quest to a previous step, reset such that steps after the parameter are not considered, while skipped steps before the parameter, if any, maintain their info.</li> * </ul> * @param cond the current quest progress condition (0 - 31 including) * @param old the previous quest progress condition to check against */ private void setCond(int cond, int old) { if (cond == old) { return; } int completedStateFlags = 0; // cond 0 and 1 do not need completedStateFlags. Also, if cond > 1, the 1st step must // always exist (i.e. it can never be skipped). So if cond is 2, we can still safely // assume no steps have been skipped. // Finally, more than 31 steps CANNOT be supported in any way with skipping. if ((cond < 3) || (cond > 31)) { unset("__compltdStateFlags"); } else { completedStateFlags = getInt("__compltdStateFlags"); } // case 1: No steps have been skipped so far... if (completedStateFlags == 0) { // check if this step also doesn't skip anything. If so, no further work is needed // also, in this case, no work is needed if the state is being reset to a smaller value // in those cases, skip forward to informing the client about the change... // ELSE, if we just now skipped for the first time...prepare the flags!!! if (cond > (old + 1)) { // set the most significant bit to 1 (indicates that there exist skipped states) // also, ensure that the least significant bit is an 1 (the first step is never skipped, no matter // what the cond says) completedStateFlags = 0x80000001; // since no flag had been skipped until now, the least significant bits must all // be set to 1, up until "old" number of bits. completedStateFlags |= ((1 << old) - 1); // now, just set the bit corresponding to the passed cond to 1 (current step) completedStateFlags |= (1 << (cond - 1)); set("__compltdStateFlags", String.valueOf(completedStateFlags)); } } // case 2: There were exist previously skipped steps // if this is a push back to a previous step, clear all completion flags ahead else if (cond < old) { // note, this also unsets the flag indicating that there exist skips completedStateFlags &= ((1 << cond) - 1); // now, check if this resulted in no steps being skipped any more if (completedStateFlags == ((1 << cond) - 1)) { unset("__compltdStateFlags"); } else { // set the most significant bit back to 1 again, to correctly indicate that this skips states. // also, ensure that the least significant bit is an 1 (the first step is never skipped, no matter // what the cond says) completedStateFlags |= 0x80000001; set("__compltdStateFlags", String.valueOf(completedStateFlags)); } } // If this moves forward, it changes nothing on previously skipped steps. // Just mark this state and we are done. else { completedStateFlags |= (1 << (cond - 1)); set("__compltdStateFlags", String.valueOf(completedStateFlags)); } // send a packet to the client to inform it of the quest progress (step change) _player.sendPacket(new QuestList()); final Quest q = getQuest(); if (!q.isCustomQuest() && (cond > 0)) { _player.sendPacket(new ExShowQuestMark(q.getId(), getCond())); } } /** * Removes a quest variable from the list of existing quest variables. * @param var the name of the variable to remove * @return the previous value of the variable or {@code null} if none were found */ public String unset(String var) { if (_vars == null) { return null; } String old = _vars.remove(var); if (old != null) { Quest.deleteQuestVarInDb(this, var); } return old; } /** * Insert (or update) in the database variables that need to stay persistent for this player after a reboot. This function is for storage of values that are not related to a specific quest but are global instead, i.e. can be used by any script. * @param var the name of the variable to save * @param value the value of the variable */ // TODO: these methods should not be here, they could be used by other classes to save some variables, but they can't because they require to create a QuestState first. public final void saveGlobalQuestVar(String var, String value) { try (Connection con = L2DatabaseFactory.getInstance().getConnection(); PreparedStatement statement = con.prepareStatement("REPLACE INTO character_quest_global_data (charId, var, value) VALUES (?, ?, ?)")) { statement.setInt(1, _player.getObjectId()); statement.setString(2, var); statement.setString(3, value); statement.executeUpdate(); } catch (Exception e) { _log.log(Level.WARNING, "Could not insert player's global quest variable: " + e.getMessage(), e); } } /** * Read from the database a previously saved variable for this quest.<br> * Due to performance considerations, this function should best be used only when the quest is first loaded.<br> * Subclasses of this class can define structures into which these loaded values can be saved.<br> * However, on-demand usage of this function throughout the script is not prohibited, only not recommended.<br> * Values read from this function were entered by calls to "saveGlobalQuestVar". * @param var the name of the variable whose value to get * @return the value of the variable or an empty string if the variable does not exist in the database */ // TODO: these methods should not be here, they could be used by other classes to save some variables, but they can't because they require to create a QuestState first. public final String getGlobalQuestVar(String var) { String result = ""; try (Connection con = L2DatabaseFactory.getInstance().getConnection(); PreparedStatement ps = con.prepareStatement("SELECT value FROM character_quest_global_data WHERE charId = ? AND var = ?")) { ps.setInt(1, _player.getObjectId()); ps.setString(2, var); try (ResultSet rs = ps.executeQuery()) { if (rs.first()) { result = rs.getString(1); } } } catch (Exception e) { _log.log(Level.WARNING, "Could not load player's global quest variable: " + e.getMessage(), e); } return result; } /** * Permanently delete a global quest variable from the database. * @param var the name of the variable to delete */ public final void deleteGlobalQuestVar(String var) { try (Connection con = L2DatabaseFactory.getInstance().getConnection(); PreparedStatement statement = con.prepareStatement("DELETE FROM character_quest_global_data WHERE charId = ? AND var = ?")) { statement.setInt(1, _player.getObjectId()); statement.setString(2, var); statement.executeUpdate(); } catch (Exception e) { _log.log(Level.WARNING, "could not delete player's global quest variable; charId = " + _player.getObjectId() + ", variable name = " + var + ". Exception: " + e.getMessage(), e); } } /** * @param var the name of the variable to get * @return the value of the variable from the list of quest variables */ public String get(String var) { if (_vars == null) { return null; } return _vars.get(var); } /** * @param var the name of the variable to get * @return the integer value of the variable or 0 if the variable does not exist or its value is not an integer */ public int getInt(String var) { if (_vars == null) { return 0; } final String variable = _vars.get(var); if ((variable == null) || variable.isEmpty()) { return 0; } int varint = 0; try { varint = Integer.parseInt(variable); } catch (NumberFormatException nfe) { _log.log(Level.INFO, "Quest " + getQuestName() + ", method getInt(" + var + "), tried to parse a non-integer value (" + variable + "). Char Id: " + _player.getObjectId(), nfe); } return varint; } /** * Checks if the quest state progress ({@code cond}) is at the specified step. * @param condition the condition to check against * @return {@code true} if the quest condition is equal to {@code condition}, {@code false} otherwise * @see #getInt(String var) */ public boolean isCond(int condition) { return (getInt("cond") == condition); } /** * Sets the quest state progress ({@code cond}) to the specified step. * @param value the new value of the quest state progress * @return this {@link QuestState} object * @see #set(String var, String val) * @see #setCond(int, boolean) */ public QuestState setCond(int value) { if (isStarted()) { set("cond", Integer.toString(value)); } return this; } /** * @return the current quest progress ({@code cond}) */ public int getCond() { if (isStarted()) { return getInt("cond"); } return 0; } /** * Check if a given variable is set for this quest. * @param variable the variable to check * @return {@code true} if the variable is set, {@code false} otherwise * @see #get(String) * @see #getInt(String) * @see #getCond() */ public boolean isSet(String variable) { return (get(variable) != null); } /** * Sets the quest state progress ({@code cond}) to the specified step. * @param value the new value of the quest state progress * @param playQuestMiddle if {@code true}, plays "ItemSound.quest_middle" * @return this {@link QuestState} object * @see #setCond(int value) * @see #set(String var, String val) */ public QuestState setCond(int value, boolean playQuestMiddle) { if (!isStarted()) { return this; } set("cond", String.valueOf(value)); if (playQuestMiddle) { AbstractScript.playSound(_player, QuestSound.ITEMSOUND_QUEST_MIDDLE); } return this; } public QuestState setMemoState(int value) { set("memoState", String.valueOf(value)); return this; } /** * @return the current Memo State */ public int getMemoState() { if (isStarted()) { return getInt("memoState"); } return 0; } public boolean isMemoState(int memoState) { return (getInt("memoState") == memoState); } /** * Gets the memo state ex. * @param slot the slot where the value was saved * @return the memo state ex */ public int getMemoStateEx(int slot) { if (isStarted()) { return getInt("memoStateEx" + slot); } return 0; } /** * Sets the memo state ex. * @param slot the slot where the value will be saved * @param value the value * @return this QuestState */ public QuestState setMemoStateEx(int slot, int value) { set("memoStateEx" + slot, String.valueOf(value)); return this; } /** * Verifies if the given value is equal to the current memos state ex. * @param slot the slot where the value was saved * @param memoStateEx the value to verify * @return {@code true} if the values are equal, {@code false} otherwise */ public boolean isMemoStateEx(int slot, int memoStateEx) { return (getMemoStateEx(slot) == memoStateEx); } /** * Add player to get notification of characters death * @param character the {@link L2Character} object of the character to get notification of death */ public void addNotifyOfDeath(L2Character character) { if (!(character instanceof L2PcInstance)) { return; } ((L2PcInstance) character).addNotifyQuestOfDeath(this); } // TODO: This all remains because of backward compatibility, should be cleared when all scripts are rewritten in java /** * Return the quantity of one sort of item hold by the player * @param itemId the Id of the item wanted to be count * @return long */ public long getQuestItemsCount(int itemId) { return AbstractScript.getQuestItemsCount(_player, itemId); } /** * @param itemId the Id of the item required * @return true if item exists in player's inventory, false - if not */ public boolean hasQuestItems(int itemId) { return AbstractScript.hasQuestItems(_player, itemId); } /** * @param itemIds list of items that are required * @return true if all items exists in player's inventory, false - if not */ public boolean hasQuestItems(int... itemIds) { return AbstractScript.hasQuestItems(_player, itemIds); } /** * Return the level of enchantment on the weapon of the player(Done specifically for weapon SA's) * @param itemId Id of the item to check enchantment * @return int */ public int getEnchantLevel(int itemId) { return AbstractScript.getEnchantLevel(_player, itemId); } /** * Give adena to the player * @param count * @param applyRates */ public void giveAdena(long count, boolean applyRates) { AbstractScript.giveAdena(_player, count, applyRates); } /** * Give reward to player using multiplier's * @param item */ public void rewardItems(ItemHolder item) { AbstractScript.rewardItems(_player, item); } /** * Give reward to player using multiplier's * @param itemId * @param count */ public void rewardItems(int itemId, long count) { AbstractScript.rewardItems(_player, itemId, count); } /** * Give item/reward to the player * @param itemId * @param count */ public void giveItems(int itemId, long count) { AbstractScript.giveItems(_player, itemId, count, 0); } public void giveItems(ItemHolder holder) { AbstractScript.giveItems(_player, holder.getId(), holder.getCount(), 0); } public void giveItems(int itemId, long count, int enchantlevel) { AbstractScript.giveItems(_player, itemId, count, enchantlevel); } public void giveItems(int itemId, long count, byte attributeId, int attributeLevel) { AbstractScript.giveItems(_player, itemId, count, attributeId, attributeLevel); } public boolean giveItemRandomly(int itemId, long amount, long limit, double dropChance, boolean playSound) { return AbstractScript.giveItemRandomly(_player, null, itemId, amount, amount, limit, dropChance, playSound); } public boolean giveItemRandomly(L2Npc npc, int itemId, long amount, long limit, double dropChance, boolean playSound) { return AbstractScript.giveItemRandomly(_player, npc, itemId, amount, amount, limit, dropChance, playSound); } public boolean giveItemRandomly(L2Npc npc, int itemId, long minAmount, long maxAmount, long limit, double dropChance, boolean playSound) { return AbstractScript.giveItemRandomly(_player, npc, itemId, minAmount, maxAmount, limit, dropChance, playSound); } // TODO: More radar functions need to be added when the radar class is complete. // BEGIN STUFF THAT WILL PROBABLY BE CHANGED public void addRadar(int x, int y, int z) { _player.getRadar().addMarker(x, y, z); } public void removeRadar(int x, int y, int z) { _player.getRadar().removeMarker(x, y, z); } public void clearRadar() { _player.getRadar().removeAllMarkers(); } // END STUFF THAT WILL PROBABLY BE CHANGED /** * Remove items from player's inventory when talking to NPC in order to have rewards.<br> * Actions:<br> * <ul> * <li>Destroy quantity of items wanted</li> * <li>Send new inventory list to player</li> * </ul> * @param itemId Identifier of the item * @param count Quantity of items to destroy */ public void takeItems(int itemId, long count) { AbstractScript.takeItems(_player, itemId, count); } /** * Send a packet in order to play a sound to the player. * @param sound the name of the sound to play */ public void playSound(String sound) { AbstractScript.playSound(_player, sound); } /** * Send a packet in order to play a sound to the player. * @param sound the {@link QuestSound} object of the sound to play */ public void playSound(QuestSound sound) { AbstractScript.playSound(_player, sound); } /** * Add XP and SP as quest reward * @param exp * @param sp */ public void addExpAndSp(int exp, int sp) { AbstractScript.addExpAndSp(_player, exp, sp); PcCafePointsManager.getInstance().givePcCafePoint(getPlayer(), (long) (exp * Config.RATE_QUEST_REWARD_XP)); } /** * @param loc * @return number of ticks from GameTimeController */ public int getItemEquipped(int loc) { return AbstractScript.getItemEquipped(_player, loc); } /** * @return {@code true} if quest is to be exited on clean up by QuestStateManager, {@code false} otherwise */ public final boolean isExitQuestOnCleanUp() { return _isExitQuestOnCleanUp; } /** * @param isExitQuestOnCleanUp {@code true} if quest is to be exited on clean up by QuestStateManager, {@code false} otherwise */ public void setIsExitQuestOnCleanUp(boolean isExitQuestOnCleanUp) { _isExitQuestOnCleanUp = isExitQuestOnCleanUp; } /** * Start a timed event for a quest.<br> * Will call an event in onEvent/onAdvEvent. * @param name the name of the timer/event * @param time time in milliseconds till the event is executed */ public void startQuestTimer(String name, long time) { getQuest().startQuestTimer(name, time, null, _player, false); } /** * Start a timed event for a quest.<br> * Will call an event in onEvent/onAdvEvent. * @param name the name of the timer/event * @param time time in milliseconds till the event is executed * @param npc the L2Npc associated with this event */ public void startQuestTimer(String name, long time, L2Npc npc) { getQuest().startQuestTimer(name, time, npc, _player, false); } /** * Start a repeating timed event for a quest.<br> * Will call an event in onEvent/onAdvEvent. * @param name the name of the timer/event * @param time time in milliseconds till the event is executed/repeated */ public void startRepeatingQuestTimer(String name, long time) { getQuest().startQuestTimer(name, time, null, _player, true); } /** * Start a repeating timed event for a quest.<br> * Will call an event in onEvent/onAdvEvent. * @param name the name of the timer/event * @param time time in milliseconds till the event is executed/repeated * @param npc the L2Npc associated with this event */ public void startRepeatingQuestTimer(String name, long time, L2Npc npc) { getQuest().startQuestTimer(name, time, npc, _player, true); } /** * @param name the name of the QuestTimer required * @return the {@link QuestTimer} object with the specified name or {@code null} if it doesn't exist */ public final QuestTimer getQuestTimer(String name) { return getQuest().getQuestTimer(name, null, _player); } // --- Spawn methods --- /** * Add a temporary spawn of the specified npc.<br> * Player's coordinates will be used for the spawn. * @param npcId the Id of the npc to spawn * @return the {@link L2Npc} object of the newly spawned npc or {@code null} if the npc doesn't exist * @see #addSpawn(int npcId, int x, int y, int z, int heading, boolean randomOffset, int despawnDelay, boolean isSummonSpawn) */ public L2Npc addSpawn(int npcId) { return addSpawn(npcId, _player.getX(), _player.getY(), _player.getZ(), 0, false, 0, false); } /** * Add a temporary spawn of the specified npc.<br> * Player's coordinates will be used for the spawn. * @param npcId the Id of the npc to spawn * @param despawnDelay time in milliseconds till the npc is despawned (default: 0) * @return the {@link L2Npc} object of the newly spawned npc or {@code null} if the npc doesn't exist * @see #addSpawn(int npcId, int x, int y, int z, int heading, boolean randomOffset, int despawnDelay, boolean isSummonSpawn) */ public L2Npc addSpawn(int npcId, int despawnDelay) { return addSpawn(npcId, _player.getX(), _player.getY(), _player.getZ(), 0, false, despawnDelay, false); } /** * Add a temporary spawn of the specified npc. * @param npcId the Id of the npc to spawn * @param x the X coordinate of the npc spawn location * @param y the Y coordinate of the npc spawn location * @param z the Z coordinate (height) of the npc spawn location * @return the {@link L2Npc} object of the newly spawned npc or {@code null} if the npc doesn't exist * @see #addSpawn(int npcId, int x, int y, int z, int heading, boolean randomOffset, int despawnDelay, boolean isSummonSpawn) */ public L2Npc addSpawn(int npcId, int x, int y, int z) { return addSpawn(npcId, x, y, z, 0, false, 0, false); } /** * Add a temporary spawn of the specified npc. * @param npcId the Id of the npc to spawn * @param x the X coordinate of the npc spawn location * @param y the Y coordinate of the npc spawn location * @param z the Z coordinate (height) of the npc spawn location * @param despawnDelay time in milliseconds till the npc is despawned (default: 0) * @return the {@link L2Npc} object of the newly spawned npc or {@code null} if the npc doesn't exist * @see #addSpawn(int npcId, int x, int y, int z, int heading, boolean randomOffset, int despawnDelay, boolean isSummonSpawn) */ public L2Npc addSpawn(int npcId, int x, int y, int z, int despawnDelay) { return addSpawn(npcId, x, y, z, 0, false, despawnDelay, false); } /** * Add a temporary spawn of the specified npc. * @param npcId the Id of the npc to spawn * @param cha the character whose coordinates will be used for the npc spawn * @return the {@link L2Npc} object of the newly spawned npc or {@code null} if the npc doesn't exist * @see #addSpawn(int npcId, int x, int y, int z, int heading, boolean randomOffset, int despawnDelay, boolean isSummonSpawn) */ public L2Npc addSpawn(int npcId, L2Character cha) { return addSpawn(npcId, cha.getX(), cha.getY(), cha.getZ(), cha.getHeading(), true, 0, false); } /** * Add a temporary spawn of the specified npc. * @param npcId the Id of the npc to spawn * @param cha the character whose coordinates will be used for the npc spawn * @param despawnDelay time in milliseconds till the npc is despawned (default: 0) * @return the {@link L2Npc} object of the newly spawned npc or {@code null} if the npc doesn't exist * @see #addSpawn(int npcId, int x, int y, int z, int heading, boolean randomOffset, int despawnDelay, boolean isSummonSpawn) */ public L2Npc addSpawn(int npcId, L2Character cha, int despawnDelay) { return addSpawn(npcId, cha.getX(), cha.getY(), cha.getZ(), cha.getHeading(), true, despawnDelay, false); } /** * Add a temporary spawn of the specified npc. * @param npcId the Id of the npc to spawn * @param cha the character whose coordinates will be used for the npc spawn * @param randomOffset if {@code true}, adds +/- 50~100 to X/Y coordinates of the spawn location * @param despawnDelay time in milliseconds till the npc is despawned (default: 0) * @return the {@link L2Npc} object of the newly spawned npc or {@code null} if the npc doesn't exist * @see #addSpawn(int npcId, int x, int y, int z, int heading, boolean randomOffset, int despawnDelay, boolean isSummonSpawn) */ public L2Npc addSpawn(int npcId, L2Character cha, boolean randomOffset, int despawnDelay) { return addSpawn(npcId, cha.getX(), cha.getY(), cha.getZ(), cha.getHeading(), randomOffset, despawnDelay, false); } /** * Add a temporary spawn of the specified npc. * @param npcId the Id of the npc to spawn * @param x the X coordinate of the npc spawn location * @param y the Y coordinate of the npc spawn location * @param z the Z coordinate (height) of the npc spawn location * @param heading the heading of the npc * @param randomOffset if {@code true}, adds +/- 50~100 to X/Y coordinates of the spawn location * @param despawnDelay time in milliseconds till the npc is despawned (default: 0) * @return the {@link L2Npc} object of the newly spawned npc or {@code null} if the npc doesn't exist * @see #addSpawn(int, int, int, int, int, boolean, int, boolean) */ public L2Npc addSpawn(int npcId, int x, int y, int z, int heading, boolean randomOffset, int despawnDelay) { return addSpawn(npcId, x, y, z, heading, randomOffset, despawnDelay, false); } /** * Add a temporary spawn of the specified npc. * @param npcId the Id of the npc to spawn * @param x the X coordinate of the npc spawn location * @param y the Y coordinate of the npc spawn location * @param z the Z coordinate (height) of the npc spawn location * @param heading the heading of the npc * @param randomOffset if {@code true}, adds +/- 50~100 to X/Y coordinates of the spawn location * @param despawnDelay time in milliseconds till the npc is despawned (default: 0) * @param isSummonSpawn if {@code true}, displays a summon animation on npc spawn (default: {@code false}) * @return the {@link L2Npc} object of the newly spawned npc or {@code null} if the npc doesn't exist * @see #addSpawn(int) * @see #addSpawn(int, int) * @see #addSpawn(int, L2Character) * @see #addSpawn(int, L2Character, int) * @see #addSpawn(int, int, int, int) * @see #addSpawn(int, L2Character, boolean, int) * @see #addSpawn(int, int, int, int, int) * @see #addSpawn(int, int, int, int, int, boolean, int) * @see #addSpawn(int, int, int, int, int, boolean, int, boolean) */ public L2Npc addSpawn(int npcId, int x, int y, int z, int heading, boolean randomOffset, int despawnDelay, boolean isSummonSpawn) { return AbstractScript.addSpawn(npcId, x, y, z, heading, randomOffset, despawnDelay, isSummonSpawn); } /** * Send an HTML file to the specified player. * @param filename the name of the HTML file to show * @return the contents of the HTML file that was sent to the player * @see #showHtmlFile(String, L2Npc) * @see Quest#showHtmlFile(L2PcInstance, String) * @see Quest#showHtmlFile(L2PcInstance, String, L2Npc) */ public String showHtmlFile(String filename) { return showHtmlFile(filename, null); } /** * Send an HTML file to the specified player. * @param filename the name of the HTML file to show * @param npc the NPC that is showing the HTML file * @return the contents of the HTML file that was sent to the player * @see Quest#showHtmlFile(L2PcInstance, String) * @see Quest#showHtmlFile(L2PcInstance, String, L2Npc) */ public String showHtmlFile(String filename, L2Npc npc) { return getQuest().showHtmlFile(_player, filename, npc); } /** * Set condition to 1, state to STARTED and play the "ItemSound.quest_accept".<br> * Works only if state is CREATED and the quest is not a custom quest. * @return the newly created {@code QuestState} object */ public QuestState startQuest() { if (isCreated() && !getQuest().isCustomQuest()) { set("cond", "1"); setState(State.STARTED); playSound(QuestSound.ITEMSOUND_QUEST_ACCEPT); } return this; } /** * Finishes the quest and removes all quest items associated with this quest from the player's inventory.<br> * If {@code type} is {@code QuestType.ONE_TIME}, also removes all other quest data associated with this quest. * @param type the {@link QuestType} of the quest * @return this {@link QuestState} object * @see #exitQuest(QuestType type, boolean playExitQuest) * @see #exitQuest(boolean repeatable) * @see #exitQuest(boolean repeatable, boolean playExitQuest) */ public QuestState exitQuest(QuestType type) { switch (type) { case DAILY: { exitQuest(false); setRestartTime(); break; } // case ONE_TIME: // case REPEATABLE: default: { exitQuest(type == QuestType.REPEATABLE); break; } } return this; } /** * Finishes the quest and removes all quest items associated with this quest from the player's inventory.<br> * If {@code type} is {@code QuestType.ONE_TIME}, also removes all other quest data associated with this quest. * @param type the {@link QuestType} of the quest * @param playExitQuest if {@code true}, plays "ItemSound.quest_finish" * @return this {@link QuestState} object * @see #exitQuest(QuestType type) * @see #exitQuest(boolean repeatable) * @see #exitQuest(boolean repeatable, boolean playExitQuest) */ public QuestState exitQuest(QuestType type, boolean playExitQuest) { exitQuest(type); if (playExitQuest) { playSound(QuestSound.ITEMSOUND_QUEST_FINISH); } return this; } /** * Finishes the quest and removes all quest items associated with this quest from the player's inventory.<br> * If {@code repeatable} is set to {@code false}, also removes all other quest data associated with this quest. * @param repeatable if {@code true}, deletes all data and variables of this quest, otherwise keeps them * @return this {@link QuestState} object * @see #exitQuest(QuestType type) * @see #exitQuest(QuestType type, boolean playExitQuest) * @see #exitQuest(boolean repeatable, boolean playExitQuest) */ public QuestState exitQuest(boolean repeatable) { _player.removeNotifyQuestOfDeath(this); if (!isStarted()) { return this; } // Clean registered quest items getQuest().removeRegisteredQuestItems(_player); Quest.deleteQuestInDb(this, repeatable); if (repeatable) { _player.delQuestState(getQuestName()); _player.sendPacket(new QuestList()); } else { setState(State.COMPLETED); } _vars = null; return this; } /** * Finishes the quest and removes all quest items associated with this quest from the player's inventory.<br> * If {@code repeatable} is set to {@code false}, also removes all other quest data associated with this quest. * @param repeatable if {@code true}, deletes all data and variables of this quest, otherwise keeps them * @param playExitQuest if {@code true}, plays "ItemSound.quest_finish" * @return this {@link QuestState} object * @see #exitQuest(QuestType type) * @see #exitQuest(QuestType type, boolean playExitQuest) * @see #exitQuest(boolean repeatable) */ public QuestState exitQuest(boolean repeatable, boolean playExitQuest) { exitQuest(repeatable); if (playExitQuest) { playSound(QuestSound.ITEMSOUND_QUEST_FINISH); } return this; } public void showQuestionMark(int number) { _player.sendPacket(new TutorialShowQuestionMark(number)); } // TODO make tutorial voices the same as quest sounds public void playTutorialVoice(String voice) { _player.sendPacket(new PlaySound(2, voice, 0, 0, _player.getX(), _player.getY(), _player.getZ())); } /** * Set the restart time for the daily quests.<br> * The time is hardcoded at {@link Quest#getResetHour()} hours, {@link Quest#getResetMinutes()} minutes of the following day.<br> * It can be overridden in scripts (quests). */ public void setRestartTime() { final Calendar reDo = Calendar.getInstance(); if (reDo.get(Calendar.HOUR_OF_DAY) >= getQuest().getResetHour()) { reDo.add(Calendar.DATE, 1); } reDo.set(Calendar.HOUR_OF_DAY, getQuest().getResetHour()); reDo.set(Calendar.MINUTE, getQuest().getResetMinutes()); set("restartTime", String.valueOf(reDo.getTimeInMillis())); } /** * Check if a daily quest is available to be started over. * @return {@code true} if the quest is available, {@code false} otherwise. */ public boolean isNowAvailable() { final String val = get("restartTime"); return ((val == null) || !Util.isDigit(val)) || (Long.parseLong(val) <= System.currentTimeMillis()); } /** * @return returns {@link NpcStringId} that is will order all teleports along the way as first option for player when he is on a quest. */ public NpcStringId getQuestLocation() { return NpcStringId.getNpcStringIdOrDefault(getInt("LOCATION_ID"), null); } /** * Sets {@link NpcStringId} that is will order all teleports along the way as first option for player when he is on a quest. * @param id */ public void setQuestLocation(NpcStringId id) { if (id == null) { throw new NullPointerException("Attempting to set null quest location string for quest: " + getQuestName()); } set("LOCATION_ID", id.getId()); } /** * Removes the {@link NpcStringId} that is will order all teleports along the way as first option for player when he is on a quest. */ public void unsetQuestLocation() { unset("LOCATION_ID"); } }