Skip to content

Example delayed action

Ruffina Koza edited this page Oct 28, 2018 · 8 revisions

Пример: реакция на реплику с задержками

Задача

Библиотекарша (внум 28800) реагирует на фразу 'кофе пожалуйста', наливает и дает тебе кофе. Это должно происходить реалистично, с паузами между ее действиями.

Перед началом работы просмотрите эту статью о том, как работать со сценариями из веб-клиента и не только.

Подход к решению

1. Сценарий без задержек

Сперва сделаем простую реакцию на эту фразу и все действия выполним подряд без задержек. В списке триггеров для мобов находим триггер onSpeech. Внутри него нужно проверить, то ли сказали, что нам нужно, и если да - выполнить набор каких-то действий.

Так как триггер onSpeech вешается на прототип моба, первым параметром ему передастся конкретный экземпляр библиотекарши, которая услышала реплику. В нашем примере это переменная ch. Параметр vict - тот, кто произнес реплику.

Вот как может начинаться такой сценарий:

librarian = function () {
    .get_mob_index(28800).onSpeech = function (ch, vict, msg) {
        if (msg != "кофе пожалуйста")
            return;

	ch.interpret("emote снимает с печки чайник.");
	ch.interpret("emote наливает чашку {Dкофе{x.");
    };
}

Теперь нужно, собственно, создать и вручить говорящему чашку кофе. Для создания экземпляров предметов служит метод create на прототипе предмета. Чашка кофе имеет внум 3101. Свежесозданный предмет надо тут же вручить кому-то в руки или положить в комнату, иначе он зависнет в 'лимбо'.

var coffee;

coffee = .get_obj_index(3101).create();
coffee.obj_to_char(ch);

Избавившись от промежуточной переменной, получим одну строку для того, чтобы создать экземпляр чашки и положить его библиотекарше в инвентарь:

.get_obj_index(3101).create().obj_to_char(ch);

Вот полный сценарий без задержек между действиями:

librarian = function () {
    .get_mob_index(28800).onSpeech = function (ch, vict, msg) {
        if (msg != "кофе пожалуйста")
            return;

	ch.interpret("emote снимает с печки чайник.");
	ch.interpret("emote наливает чашку {Dкофе{x.");
        .get_obj_index(3101).create().obj_to_char(ch);
	ch.interpret("дать чашка " + vict.getName());
	ch.interpret("эмоц тепло улыбается.");
    };
}

Конечно, тут не все идеально: библиотекарша может не видеть говорящего, у того может быть слишком много всего в инвентаре и дать чашку не получится. Сценарий можно усложнить проверкой, оказалась ли чашка в итоге в инвентаре у говорящего, но мы для простоты примера этого делать не будем. Проверка, например, может выглядеть так:

// запомнить чашку в переменной coffee, как в примере выше
if (coffee.carried_by != vict) {
    ...
}

2. Набор триггеров с префиксом post

Все триггера, начинающиеся с on, выполняются сразу, прямо изнутри обработчика той или иной команды (сказать, дать). Поэтому все действия, которые моб производит внутри такого триггера, выполнятся единым блоком.

Если внутри триггера хочется выполнять действия с задержками, необходимо использовать набор триггеров, начинающийся с post: postSpeech, postGive и так далее. Такой триггер выполнится не сразу, но почти мгновенно после того, как завершится вызвавшая его команда. И так как результатов выполнения триггера уже никто не ждет, можно провести в нем сколько угодно времени. С технической точки зрения, каждый такой post-триггер будет выполняться внутри отдельного феневого потока.

В этом примере библиотекарша отреагирует на фразу не мгновенно, а в следующий "пульс" (четверть секунды), и это будет заметно:

librarian = function () {
    .get_mob_index(28800).postSpeech = function (ch, vict, msg) {
        if (msg != "кофе пожалуйста")
            return;
	ch.interpret("emote снимает с печки чайник.");
    };
}

Не забудем обнулить триггер onSpeech, иначе они выполнятся оба:

eval .get_mob_index(28800).onSpeech = null;

3. Паузы в выполнении сценария

Однако такой задержки в четверть секунды может оказаться мало. К тому же, нам хочется делать паузу и между остальными ее действиями. Поэтому воспользуемся методом .scheduler.sleep, который прерывает выполнение текущего потока (триггера) на указанное число пульсов. В одной секунде - 4 пульса.

Теперь можно вызвать задержки перед каждым действием библиотекарши, добившись более-менее реалистичной реакции:

librarian = function () {
    .get_mob_index(28800).postSpeech = function (ch, vict, msg) {
        if (msg != "кофе пожалуйста")
            return;

        .scheduler.sleep(2);
	ch.interpret("эмоц снимает с печки чайник.");
	.scheduler.sleep(4);
	    
	ch.interpret("эмоц наливает чашку {Dкофе{x.");
	.scheduler.sleep(4);
	    
        .get_obj_index(3101).create().obj_to_char(ch);
	ch.interpret("дать чашка " + vict.getName());
	.scheduler.sleep(4);
	    
	ch.interpret("эмоц тепло улыбается.");
    };
}

4. Улучшаем код: вспомогательная функция

Если приведенный выше код режет глаза, его можно немного улучшить, вынеся понятие "действие с задержкой" в отдельную функцию. Видно, что чаще всего нужно подождать 1 секунду и выполнить interpret. Для удобства эту новую функцию присвоим тому же прототипу моба. Тогда она сразу станет доступной изнутри postSpeech:

    .get_mob_index(28800).delay = function (ch, action) {
        .scheduler.sleep(4);
        ch.interpret(action);
    };

Полный сценарий с использованием этой функции см. в конце статьи.

5. Улучшаем код: как гибче реагировать на реплики

Библиотекарше должно быть все равно, произнесли рядом с ней фразу кофе пожалуйста или кофе, пожалуйста, или даже пожалуйста, кофе мне. Однако наш код позволяет ей реагировать на одну жестко заданную фразу. Для более гибкой реакции воспользуемся регулярными выражениями и методом строки match. По сути нас интересует наличе во фразе слова кофе и волшебного слова пожалуйста, пусть даже написанного с ошибками:

    if (!msg.match("кофе"))
	return;
    if (!msg.match("пожалуй*ста"))
	return;

Для первой проверки, конечно, можно было воспользоваться и поиском подстроки (метод contains), однако если мы захотим научить библиотекаршу понимать и по-английски, это удобнее делать с помощью регулярных выражений, перечисляя варианты через |:

    if (!msg.match("кофе|coffee"))
	return;
    if (!msg.match("пожалуй*ста|please|pls"))
	return;

Это просто пример, и улучшать тут можно до бесконечности. Список всех методов у строки можно посмотреть так:

eval ptc("".api())

Готовый сценарий

librarian = function() {
    .get_mob_index(28800).delay = function (ch, action) {
        .scheduler.sleep(4);
        ch.interpret(action);
    };
    
    .get_mob_index(28800).postSpeech = function (ch, vict, msg) {
	if (!msg.match("кофе|coffee"))
	    return;
	if (!msg.match("пожалуй*ста|please|pls"))
	    return;

        delay(ch, "эмоц снимает с печки чайник.");
	delay(ch, "эмоц наливает чашку {Dкофе{x.");

        .get_obj_index(3101).create().obj_to_char(ch);
	delay(ch, "дать чашка " + vict.getName());
	delay(ch, "эмоц тепло улыбается.");
    };
}
You can’t perform that action at this time.