Упрощение написания квестов (с примером).

Теги:
 

Drac

разработчик l2j-сервера
★★★
Случайно наткнулся на написанный месяц назад мануал и решил его выложить. Он представляет собой упрощение написания квестов на языке Jython для сервера (особенно, если понять изложенные тут мысли).
Раньше, когда у СФ в Датапаке творилась неразбериха, материал был очень актуален, т.к. квесты писали очень коряво, даже не учитывая клиентсих данных, поэтому даже сейчас многие квесты не показывают правильно шаги в клиенте, т.к. являют собой зачаток нормального квеста (в них логика правильная, а вот посылка клиенту данных хромает сильно). Сейчас ситуация несколько изменилась, в Датапаке СФ появились более грамотные люди, и квесты пишутся почти так, как я задумывал (правда их стиль ох как похож на мой :), наводит на мысли).

В этом мануале я разбирал квест 297_GatekeepersFavor, переделывая его с написания. Не буду вдаваться в подробности, напишу как есть. Если возникнут вопросы - постараюсь ответить.

ПОЕХАЛИ:
Прямо с самого верха пойдем.

Зачем нужны окончания _ID при обозначении переменных в самом начале квеста (это особенно относится к предметам)? Перестал это понимать уже давно. Они только место занимают, и строку удлиняют.

def onEvent:
1. Описывается всегда жутко страшно в СФ, особенно в старых квестах. Эта строчка отвечает за ссылку в диалоге, т.е. пример [Quest 297_GatekeepersFavor 1|Say you will help], тут 1 - это ссылка. Уже вполне давно реализовано, что если вызывать ссылку на конце которой написано .htm , то ява будет искать файл диалога с таким именем. Притом мало того, что она будет искать диалог - так она ещё и запустит обработчик onEvent, что позволит убить сразу 2-х зайцев: вызвать нужный диалог, и запустить обработчик (в обычном случае нам бы пришлось сначала запускать обработчик потом показывать диалог).

2. st.set("id","0") - сакральный смысл этого мне до сих пор непонятен. Вероятно они думали про защиту от мошенничества. Но я боюсь, что она итак не пройдет.

3. Проверку на уровень имеет смысл делать в обработчике onTalk, все равно никому не надо будет настолько заморачиваться, чтобы вызвать диалог, потом сделать делевел и потом щелкать по ссылке.

def onTalk:
1.
if id == CREATED :
st.setState(STARTING)
st.set("cond","0")
st.set("onlyone","0")
st.set("id","0")
Вот эти 5 строчек - глупость полная. Можно обойтись и без них. Они при нажатии на диалог проверяют стадию (<state>) квеста, и если она в базе описана как Start, то будет запущена установка стадии в STARTING и добавление в таблицу character_quests в БД значений cond, onlyone, id и все в 0. Но зачем нам дергать итак нагруженную базу?! Это совершенно не нужно.
Расписываю почему:
а) cond - если делать st.getInt("cond") - то из базы будет возвращаться значение cond, а если cond нет в базе - то вернется 0. Значит нам вообще не надо его определять тут;
б) STARTING - некая промежуточная стадия, которая не нужна, потому как, обычно человек сразу кликает на ссылку и ему сразу же выдается стадия STARTED, т.к. смысла вообще не имеет;
в) onlyone - это, якобы, используется для того, чтобы определить многоразовость квеста. Если стоит 1 - значит квест уже был один раз пройден и при наличии проверки - не получится пройти его ещё раз. Но это же можно сделать если правильно поставить проверку на стадию st.getState() (<state> в БД) квеста. Что-то типа if id == COMPLETED : ...;
г) id - как и говорил, зачем используется я не понимаю, возможно пытались не дать поюзать баги.
Так что оно не нужно совершенно.

2. Дальше я поубивал все проверки на номер НПЦ. Вопрос: а зачем они, если в квесте всего 1 НПЦ, с которым надо разговаривать?!Командами в конце:
QUEST.addStartNpc(30540)
STARTING.addTalkId(30540)
STARTED.addTalkId(30540)
этот НПЦ будет занесен в ХешМапы, и кроме него там никого больше не будет. Поэтому при выборе квеста ява сама будет понимать, что только у одного НПЦ можно по этому квесту разговаривать.
Соответственно, этот метод не подойдет, если в квесте разговор идет более, чем с одним НПЦ.

3. Вот кстати, чуток повыше надо было поставить: я int(st.get("cond")) переименовал в st.getInt("cond") (выше описано почему) и поставил прямо на входе в метод onTalk строку cond = st.getInt("cond"), чтобы можно было сокращенно использовать его. Очень полезно, когда квест длинный.

4. Собственно, перенес проверку на левел. Т.е. когда игрок нажмет у НПЦ на Quest в главном диалоге, то получит описание квеста и диалог в котором будет сказано, что ему ещё мало годиков для того, чтобы взять квест :)

5. На самом деле в квесте содержалась ошибка. Значение cond - при его изменении передается в обработчик явы, который посылает клиенту обновленные сведения о квесте. В конкретно этом квесте 2 шага (1 - получение задания и 2 - возвращение с нужными вещами). Они были заранее неправильно выставлены. И использовался всегда только шаг 1. Т.е. в окне квестов клиент всегда бы видел, что получил задание, даже тогда, когда все необходимые вещи были собраны.
Поэтому при возвращении с 20-ью Старстоунами я переписал, чтобы был шаг 2 (он дается в обработчике onKill, когда собраны 20 этих камней).

6. st.takeItems(STARSTONE2_ID,st.getQuestItemsCount(STARSTONE2_ID))
Очень противная строка. st.getQuestItemsCount(STARSTONE2_ID) - определяет количество заданных вещей в инвентаре персонажа. Это лишние вычисления, после которых количество все количество этих вещей изымается у персонажа. Можно поставить вместо st.getQuestItemsCount(STARSTONE2_ID) значение -1 (минус один) и тогда точно также будут забираться все вещи, только не нужны будут средние вычисления.

7. На оффе дается 2 Токена. Сам проверял. Поэтому st.giveItems(GATEKEEPER_TOKEN_ID,1) заменил 1 на 2.


def onKill:
1. Опять же используется всего лишь 1 моб, поэтому совершенно не нужна проверка на его номер. Если мобов больше и награды разные - надо будет уже отдельно описывать.

2. Система выдачи вещей выпавших такова, что определяется, можно ли ещё дать предмет. Если можно - дается и проверяется, что если этот предмет последний - ставится шаг 2 и играется соответствующая музыка. Если не все собрали ещё - играется другая музыка. Такая система эффективнее. Насчет шага 2 я писал в п.5 предыдущего раздела.


Все, что далее:
1. Все упоминания о STARTING удалил за ненадобностью.
2. Чтобы квест многоразовый был надо как раз было подставить COMPLETED.addTalkId(...) . Если мы после выполнения квеста поставим cond в 0 и стадию в COMPLETED , то можно будет на самом входе в def onTalk у первого НПЦ проверять на возможность повтора квеста. Если проверки на COMPLETED не добавлять, то квест будет многоразовым.

P.S. Лучше всего использовать для отступов ПРОБЕЛЫ, а не табуляцию, как сделано во многих квестах. Разные редакторы по разному отображают количество пробелов в табуляции, и поэтому у разных пользователей может по разному это отображаться (если они, конечно, не пользуются Блокнотом :) )

Вот и все. Надеюсь, что доступно и понятно.

P.S.S. Кстати, к рассмотрению принимаются diff для любых квестов (желательно с комментариями, что вы поправили). Можете присылать свои квесты, будем только рады. Помогу оформить квест в самом лучшем виде (естественно, я пришлю обратно то, что правил), размещу у нас в Датапаке, и конечно можно подумать о взятии в команду.
 

в начало страницы | новое
 
Поиск
Настройки
Твиттер сайта
Статистика
Рейтинг@Mail.ru