Документация по КВЕСТОПИСАНИЮ. Не блещет оригинальностью и тем более не исключены ошибки, так что не судите строго:
Квестовый
скрипт определяет завершение или не полное завершение потока событий,
происходящих от начала до конца квеста. Он включает в себя, но не
ограничивает, начало и взятие квеста, последующий комплекс диалогов,
убиство мобов, поиск квестовых предметов, спавн квестовых мобов,
завершение квеста и получение награды.
Для написания и проверки
форматирования скрипта я использую программу Python-2.5.1, место
рождения: python-2.5.1.msi . Установка и описание ее работы - это
отдельная тема.
Для примера составим небольшой квест по условию
которого игрок сможет получить статус "Дворянин" взамен на выбитый из
мобов квестовый итем и на протяжении всего кода я буду добавлять свои
коментарии.
Для простоты назовем его как нибудь: "2007_noblesse",
таким названием мы убиваем сразу 2 зайца - иными словами 2007 - это
будет номер-идентификатор квеста, а 'noblesse' - это название квеста,
которое должно быть внесено в патч клиента для нормального отображения
информации о квесте в момент его выполнения гамером.
Хочу также
заметить, что для удобочитаемости квеста и более быстрого разбора кода
можно использовать коментарии. Для однострочного коментария
используется символ # перед строкой текста. Все что написано в коде
после этого символа не воспринимается компилятором как код программы.
Также можно использовать многострочный символ коментария тройные
кавычки: """ до и после комента """. Но этот коментарий используется
только до основного кода квеста.
Вначале необходимо определить так
сказать скелет будущего квеста, естественно не всегда все секции нашего
"скелета" используются при написании "квеста"
#~1~ Импорт библиотек
#~2~ Секция объявление переменных
##~3~ Начало основного кода
#~4~ + Секция onEvent()
#~5~ + Секция onTalk()
#~6~ + Секция onUseSkill()
#~7~ + Секция onAttack()
#~8~ + Секция onKill()
#~9~ + Секция onDeath()
###~10~ Инициализация квеста
#~11~ Регистрация добавления переменных
#~12~ + addStartNpc()
#~13~ + addTalk()
#~14~ + addAttack()
#~15~ + addSkillUse()
#~16~ + addKill()
#~17~ + addQuestDrop()
Для правильного функционирования скрипта, как минимум 3 класса должны быть импортированы из Java:
from net.sf.l2j.gameserver.model.quest import State def __init__(self,id,name,descr): JQuest.__init__(self,id,name,descr) |
Квестовые скрипты Jython-а по существу наследуются из классов Java net.sf.l2j.gameserver.model.quest.Quest. Разработчики, свободно владеющие языком Java могут посмотреть исходники этого класса, которые размещены на сайте www.l2jserver.com проекта l2jserver, для более детального изучения функций. Кроме того, кое-что вы сможете прочитать в этой документации.
#~~~~~~~~~~~~~~~ 2 ~~~~~~~~~~~~~~~~~~#
Часто полезно определять имя квеста в переменной, в начале скрипта (обычно используется "qn").Добавляем переменную названия квеста в секции описания переменных:
qn = "2007_noblesse" |
#~~~~~~~~~~~~~~~ 10 ~~~~~~~~~~~~~~~~~~#
QUEST = Quest(2007,qn,"custom") |
<a action="bypass -h npc_%objectId%_Quest 2007_noblesse">Nobless Quest</a> |
<a action="bypass -h npc_%objectId%_Quest">Quest</a> |
QUEST = Quest(626,qn,"A Dark Twilight") |
Статусы используются для слежением за сегментами квестов. Каждый статус имеет свой список квестовых предметов, которые могут быть найдены. По завершении квеста или отказа от него, или при переходе квеста от одного состояния к другому, STATES удаляют избыточные квестовые вещи из инвентаря игрока. Для определения state, можно использовать:
STATEVARIABLE = State('StateName', QUEST) |
STARTED = State('Started', QUEST) |
CREATED = State('Start', QUEST) |
COMPLETED = State('Completed', QUEST) |
В нашем случае код выглядит таким образом:
QUEST = Quest(2017,qn,"custom") CREATED = State('Start', QUEST) STARTED = State('Started', QUEST) COMPLETED = State('Completed', QUEST) |
Так как классы мы уже импортировали, далее необходимо зарегистрировать необходимых NPC и мобов для участия в нашем квесте. Проще говоря когда игрок взаимодействует с NPC - сервер получает уведомление об этом и если NPC не зарегистрирован, то сервер не будет вызывать соответствующие вспомагательные функции для обработки полученного уведомления.
Функции, описанные ниже, имеют единственную цель: регистрация NPC для инициализацииции событий. Короче говоря, NPC может быть зарегистрирован в квесте для конкретного события, для того, чтобы NPC ответил, когда событие произойдёт.
Функции РЕГИСТРАЦИИ не вызываются автоматически. Они должны быть добавлены внизу (в конце) квестового скрипта.
Любой квест начинается с разговора со стртовым NPC, а значит он должен быть зарегистрирован в секции:
QUEST.addStartNpc(npcId) QUEST.addTalkId(npcId) |
А параметр "npcId" содержит ID template для этого NPC.
В нашем случае он будет равен 31740:
QUEST.addStartNpc(31740) QUEST.addTalkId(31740) |
#NPC CARADINE = 31740 |
#~~~~~~~~~~~~~~~ 12 - 13 ~~~~~~~~~~~~~~~~~~#
QUEST.addStartNpc(CARADINE) QUEST.addTalkId(CARADINE) |
И так наш гамер подошел к NPC и начинает вести диалог. Если он нажмет на ссылку в окошке диалога с названием Задание (Quest), сервер получит уведомление о том что некий игрок вызвал функцию onTalk и так как это наш стартовый NPC - естественно, зарегистрировав это действие в таблице `character_quests`, активизирует случай onTalk().
onTalk(self,npc, player) |
Параметр "player" содержит ссылку на идентификатор игрока, который разговаривает с NPC.
Параметр "self" - ссылка на сам квест.
В нашем случае код будет выглядеть так:
def onTalk (self,npc,player): st = player.getQuestState(qn) # Присваиваем переменной st данные активного игрока htmltext = "<html><head><body>I have nothing to say you</body></html>" npcId = npc.getNpcId() # Присваиваем переменной npcId данные выбранного NPC if not st : return htmltext # Проверяем на состояние квеста, действительно ли у нашего игрока данный квест активен cond = st.getInt("cond") # Присваиваем переменной cond данные из таблицы `character_quests` onlyone = st.getInt("onlyone") if npcId == CARADINE: htmltext = "31740-01.htm" return htmltext |
Игрок автоматически получает статус CREATED для этого квеста и эта переменная записывается в талицу `character_quests`. Данные в этой таблице будут выглядеть следующим образом:
name | var | value
------------------------------------------
2007_noblesse | <state> | Start
------------------------------------------
*ПРИМЕЧАНИЕ ДЛЯ ТЕХ КТО В ТАНКЕ И БЕЗ ШЛЕМОФОНА:
Статуc квеста (QuestState) (состояние квеста):
QuestState – это не часть определения квеста как таковая, но в ней содержится информация, которая отслеживает весь процесс развития событий конкретного игрока в этом квесте. Возьмём пример с игроком, статус квеста игрока в этом квесте можно узнать, используя:
st = player.getQuestState("2007_noblesse") |
st = player.getQuestState(qn) |
Кроме того, "queststate" любого члена группы, который имеет конкретную переменную и значение сохранённое для этого квеста, можно узнать с использованием:
partyMember = self.getRandomPartyMember(player,"variable","value") st = partyMember.getQuestState("12345_questname") |
partyMember = self.getRandomPartyMemberState(player,STATE) st = partyMember.getQuestState("12345_questname") |
Если в диалоге вызваном игроком существует ссылка-приглашение продолжить квест или другого действия такого формата:
<a action="bypass -h Quest 2007_noblesse 31740-02.htm">Да</a> |
onEvent(self, event, st) |
Параметр "st" содержит ссылку на QuestState (статус квеста) игрока, который использует ссылку.
Параметр "event" содержит строковый идентификатор для события. В основном, эта строка находится прямо в ссылке, но она также может использоваться при установке так называемого таймера, названием которого и будет являться данный строковый идентификатор.
Параметр "self" - ссылка на сам квест. Вы можете использовать self.XXXX, где XXXX – любая функция, объявленная в родительском классе.
в нашем случае мы при помощи данного случая переводим квест в следующее состояние:
def onEvent (self,event,st) : htmltext = event # Присваиваем переменой htmltext значение строкового идентификатора event cond = st.getInt("cond") # Присваиваем переменой cond данные из таблицы `character_quests` if event == "31740-02.htm" : if cond == 0 : st.set("cond","1") # Присваиваем переменой cond значение 1 для и записываем ее в таблицу `character_quests` st.setState(STARTED) # Переводим состояние квеста в STARTED st.playSound("ItemSound.quest_accept") # Озвучка взятия квеста return htmltext |
Теперь, если обратить внимание на таблицу `character_quests` мы увидим что данные в этой таблице несколько изменились:
------------------------------------------ name | var | value ------------------------------------------ 2007_noblesse | <state> | Started ------------------------------------------ 2007_noblesse | cond | 1 ------------------------------------------ |
Пока QuestState (состояние квеста) у
игрока будет обнаруживаться, мы также будем иметь доступ к игроку, если
это понадобится, используя:
Код:
st.getPlayer()
Все другие общепринятые методы
реализации QuestState доступны из jython. Точно также, объекты,
извлекаемые из «st» могут быть в будущем использоваться, для того,
чтобы извлечь больше. Для примера, можно сделать приблизительно так:
Код:
st.getPlayer().getClan().getLeader().getPlayerInstance().getPet()
(этот пример как маленькая
демонстрация возможностей - как глубоко можно проникнуть в цепь
объектов, которые являются доступными. В этом случае, из QuestState, мы
получаем игрока, который имеет QuestState, затем получаем клан игрока,
затем лидера клана игрока, фактическое состояние лидера и уже там мы
находим вызванного лидером пета!) |
Теперь задание получено и игрок отправляется мочить какого нибудь монстра из которого должен выпасть квестовый итем к примеру Feastival Adena (Id == 6673), а значит нам необходимо зарегистрировать этот итем в разделе описания переменных и зарегистрировать МОНСТРА из которого этот итем будет выпадать, в случае если этого монстра замочить. В нашем случае мы добавляем строки :
CARADINE = 31740
# Items
FESTIVAL_ADENA_ID = 6673
# Mobs
KRANROT = 20650
и конечно же необходимо добавить квестовый итем в секцию регистрации квестового дропа:
STARTED.addQuestDrop(npcId,itemId,1) |
Параметр "itemId" - выпадающий квестовый итем
Параметр 1 - количество выпадающих итемов
STARTED.addQuestDrop(KRANROT,FESTIVAL_ADENA_ID,1)
#~~~~~~~~~~~~~~~ 16 ~~~~~~~~~~~~~~~~~~#
Регистрация NPC для события onKill.
Конечно же необходимо зарегистрировать все это в секции регистрации квеста :
addKillId(npcId) |
#~~~~~~~~~~~~~~~ 12 - 16 ~~~~~~~~~~~~~# QUEST.addTalkId(CARADINE) QUEST.addKillId(KRANROT) |
#~~~~~~~~~~~~~~~~~~~~ 8 ~~~~~~~~~~~~~~~~~~~~~~#
Итак наступает момент когда наш игрок наконец-то нашел и замочил необходимого (зарегистрированного в секции addKill() монстра, в этом случае сервер получает уведомление об убийстве квестового NPC и вызывает случай onKill() :
onKill(self, npc, player) |
Параметр "player" содержит ссылку на идентификатор игрока, который убил.
Параметр "self" работает так же как и в onEvent, т.е. "self" - ссылка на сам квест
Для нашего квеста код будет выглядеть следующим образом:
def onKill (self, npc, player) : st = player.getQuestState(qn) if not st : return if st.getState() != STARTED : return npcId = npc.getNpcId() cond = st.getInt("cond") if npcId == KRANROT : # Если NPC действительно соответствует зарегистрированному в секции регистрации addKill() if cond == 1 and st.getRandom(100)>70 : # Проверка состояние квеста ("cond") и на рандомное число st.giveItems(FESTIVAL_ADENA_ID,1) # Если предыдущие проверки возвращают истину - тогда игрок получает квестовый итем st.playSound("ItemSound.quest_middle") # Озвучка окончания этой части квеста st.set("cond","2") # Перевод состояния квеста на следующую ступень return |
------------------------------------------ name | var | value ------------------------------------------ 2007_noblesse | <state> | Started ------------------------------------------ 2007_noblesse | cond | 2 ------------------------------------------ |
#~~~~~~~~~~~~~~~~~ 5 ~~~~~~~~~~~~~~~~~~~#
Получив необходимый квестовый итем игрок возвращается к NPC который "заказал" ему убийство этого монстра и начинает вести диалог:
elif cond == 2 and st.getQuestItemsCount(FESTIVAL_ADENA_ID) >= 1 : #
Случай когда диалог начинает игрок уже выбивший квест.итем st.takeItems(FESTIVAL_ADENA_ID,-1) # Отбираем необходимый для квеста итем st.set("cond","0") # Обнуляем переменную "cond" st.getPlayer().setNoble(True) # Даем статус Дворянина st.giveItems(NOBLESS_TIARA,1) # Выдаем памятный подарок st.playSound("ItemSound.quest_finish") # Озвучка окончания квеста st.setState(COMPLETED) # Перевод квеста в состояние COMPLETED st.set("onlyone","1") # Добавляем переменную "onlyone" для одноразовости квеста. htmltext = "31740-04.htm" # Поздравления |
------------------------------------------ name | var | value ------------------------------------------ 2007_noblesse | <state> | Completed ------------------------------------------ 2007_noblesse | cond | 0 ------------------------------------------ 2007_noblesse | onlyone | 1 ------------------------------------------ |