Skip to content

Fenia: Examples

Kit Oliynyk edited this page Oct 13, 2021 · 6 revisions

Пример: мяукающий кот

Задача

В доме живет кот, и нам хочется, чтобы кот периодически мяукал и драл мебель.

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

1. Выбор триггера и пример простого сценария

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

Триггер onSpec мы повесим на прототип котенка с внумом 3090 - он есть и в локальной версии. Все экземпляры котенка, созданные, например, с помощью команды load mob 3090, также будут иметь этот триггер. Все триггеры, висящие на прототипах, всегда первым параметром имеют конкретный экземпляр моба. В нашем примере этот параметр называется mob и содержит, соответственно, котенка, от которого сейчас вызвался тригер onSpec.

С помощью встроенного редактора мобов medit можно присвоить отдельный триггер для прототипа моба.

medit 3090
fenia onSpec 

По этой команде откроется веб-редактор сценариев с уже готовым шаблоном для onSpec. Останется только дописать туда наш код и нажать на кнопку Run:

.get_mob_index(3090).onSpec = function(mob) {
    mob.interpret("эмоц громко и протяжно мяукает.");
}
load mob 3090
см

Черный Квадрат 
 Ты стоишь  на черном  игровом квадрате.  Ты видишь заброшенные ворота
недалеко на юге.

[Выходы: север юг запад]
Маленький верный котенок (kitten) здесь.

Котенок громко и протяжно мяукает.

2. Выполнение действия с какой-то вероятностью

Однако быстро становится понятно, что этот котенок будет не переставая мяукать каждые 4 секунды, что невыносимо. Сделаем это мяуканье более случайным. Для работы со случайными числами есть несколько функций в корневом объекте. Посмотрим по ним подсказку:

eval ptc(.api())
...
chanceOneOf: (x) true если .number_range(1, x) == 1
chance: (x) true если x < .number_percent()
number_percent: произвольное число от 1 до 100
number_range: (x, y) произвольное число в промежутке от x до y
dice: (x, y) x раз кинуть кубик с y гранями

Нам удобен метод .chance: так, .chance(50) вернет true в 50ти случаях из ста, т.е. с вероятностью 50%, .chance(1) вернет true с вероятностью 1% и так далее.

Перепишем сценарий:

.get_mob_index(3090).onSpec = function(mob) {
    if (.chance(20))
        mob.interpret("эмоц громко и протяжно мяукает.");
}

Теперь он мяукает не так часто, но достаточно часто для отладки. В окончательной версии шанс можно сделать равным 1.

3. Выполнение нескольких действий с равной вероятностью

Предположим, мы хотим добавить ему еще какие-то действия с равной вероятностью. Для этого пригодится метод .chanceOneOf. Вот как будет выглядеть новый сценарий с тремя случайными действиями котенка:

.get_mob_index(3090).onSpec = function(mob) {
    if (.chance(20)) {
        if (.chanceOneOf(3)) 
            mob.interpret("эмоц громко и протяжно мяукает.");
        else if (.chanceOneOf(2))
            mob.interpret("эмоц зевает и грациозно потягивается.");
        else
            mob.interpret("эмоц гоняется за собственным хвостом.");
    }
}

4. Поиск предмета и действия с ним

Следующий шаг - сделать, чтобы котенок драл мебель. Для начала выясним, есть ли рядом с ним в комнате мебель, т.е. предмет, лежащий на полу, и имеющий тип furniture. Все типы предметов описаны в таблице .tables.item_table, содержимое таблицы можно вывести на экран:

eval ptc(.tables.item_table.api())

Список всех доступных таблиц с флагами:

eval ptc(.tables.api())

Первая колонка содержит символическое название типа предмета, оно же используется в зонах и редакторе OLC. Для мебели это поле .tables.item_table.furniture.

Теперь надо найти в комнате предмет-мебель, используя метод комнаты get_obj_type. Вот его справка в API комнаты:

eval ptc(in_room.api())
...
get_obj_type: (type) поиск объекта в комнате по его типу, item type

Для отладки создадим рядом с котенком какую-то мебель (на локальном мире можно поискать мебель командой vnum type furniture).

load obj 9822
Ты создаешь диван!

А затем найдем эту мебель рядом с нами в комнате и отпечатаем ее имя:

eval sofa=in_room.get_obj_type(.tables.item_table.furniture)
eval ptc(sofa.name)
sofa диван

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

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

Вот как теперь выглядит сценарий. Дабы избежать вытягивания кода в "спагетти", сделаны некоторые изменения:

.get_mob_index(3090).onSpec = function(mob) {
        if (!.chance(1))
            return;

        if (.chanceOneOf(4)) 
            mob.interpret("эмоц громко и протяжно мяукает.");
        else if (.chanceOneOf(3))
            mob.interpret("эмоц зевает и грациозно потягивается.");
        else if (.chanceOneOf(2))
            mob.interpret("эмоц гоняется за собственным хвостом.");
        else {
            var furniture;
                
            furniture = mob.in_room.get_obj_type(.tables.item_table.furniture);
            if (furniture == null)
                return;
            
            mob.recho("%^C1 дерет когтями %O4.", mob, furniture);
        }
}

Пример: перемешивать выходы из комнаты ночью

Задача

В зоне есть несколько комнат "зловещей рощи", в дневное время там обычные выходы, а ночью в роще можно заблудиться - идешь в одном направлении, а попадаешь совсем в другое.

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

1. Как определить время суток

Время суток содержится в поле корневого объекта .sunlight. Можно отпечатать список всех полей корневого объекта и посмотреть по нему справку:

eval ptc(.api())
...
sunlight: время суток: 0=ночь, 1=рассвет, 2=день, 3=закат
...

Поле .hour содержит конкретный час, однако в разное время года темнота наступает в разное время, поэтому поле .sunlight для нашей задачи удобнее.

2. Как выбрать случайный выход

Список полей и методов комнаты можно посмотреть командой

eval ptc(in_room.api())

где in_room - комната, где находится this, т.е. персонаж выполняющий команду eval. Нам понадобятся методы exits - список всех видимых персонажу выходов и getRoom - получить комнату, в которую ведет какая-то дверь.

Метод exits создает и возвращает список. Также новый список можно создать с помощью метода корневого объекта .List(). Поэтому методы списка можно отпечатать себе такими способами:

eval ptc(in_room.exits(this).api())
eval ptc(.List().api())

Нам пригодится метод random - получить случайный элемент списка.

Комбинируя все это вместе, получаем такой код для поиска случайной двери для персонажа ch, затем поиска комнаты, в которую эта дверь ведет и, наконец, внума (номера) этой комнаты:

var mydoors, randomDoor, randomRoom, randomDoorVnum;
mydoors = exits(ch);
randomDoor = mydoors.random();
randomRoom = getRoom(randomDoor);
randomDoorVnum = randomRoom.vnum;
return randomDoorVnum;

Избавляясь от промежуточных переменных:

return getRoom(exits(ch).random()).vnum;

3. Как перемешать выходы в комнате при ходьбе

Oбычно комнаты со случайными выходами задаются в редакторе зон, с помощью особого вида reset-a, 'randomize exits'. Для нашей задачи мы можем перемешивать выходы при попытке персонажа выйти или зайти в эту комнату. Для этого есть такие триггера:

onEntryLocation(walker,sourceRoom,door)
onExitLocation(walker,targetRoom,door)

Они подробно описаны в списке триггеров комнат.

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

.get_room_index(4000).onExitLocation = function(walker, targetRoom, door) {
    if (.sunlight == 0) 
        return getRoom(exits(walker).random()).vnum;
};

Примечание: в общем случае метод exits может вернуть пустой список (например, в комнате без выходов), тогда метод random вернет null и при попытке вызова getRoom с таким аргументом произойдет исключение. Однако в нашем случае мы уверены, что хотя бы один выход из комнаты есть: выход под номером door, ведущий в targetRoom - именно в него бы попал персонаж, если бы мы не вмешались.

4. Как присвоить один и тот же триггер нескольким комнатам сразу

В примере выше триггер onExitLocation присвоится комнате с номером 4000. Но что если таких комнат несколько, например, наша "зловещая роща" состоит из 4х комнат: 4208, 4216, 4207, 4215. Ленивый способ - присвоить триггер какой-то переменной и затем сделать четыре присваивания подряд:

var myfunc;
myfunc = function(walker, targetRoom, door) {
...
};

.get_room_index(4208).onExitLocation = myfunc;
.get_room_index(4216).onExitLocation = myfunc;
...

Более продвинутый способ - организовать все номера этих комнат в список и выполнить присваивание всем элементам списка, используя метод forEach. Этот метод принимает в параметры функцию, которую надо выполнить для каждого элемента списка. Переменная this внутри функции будет указывать на каждый элемент по очереди:

.List().add(4208, 4216, 4207, 4215).forEach(
    function() {
        .get_room_index(this).onExitLocation = function(walker, targetRoom, door) {
        ...
        };
    }
};

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

Подробнее о работе со сценариями читайте в этой статье.

.apply(function() {
    .List().add(4208, 4216, 4207, 4215).forEach(function() {
       .get_room_index(this).onExitLocation = function(walker,targetRoom,door) {
           if (.sunlight == 0) 
              return getRoom(exits(walker).random()).vnum;
       };
    });
})

Посмотреть, успешно ли присвоился триггер какой-то комнате, можно так:

eval ptc(.get_room_index(4215).rtapi())

Runtime fields:
onExitLocation

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

Задача

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

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

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

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

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

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

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

medit 28800
fenia onSpeech

Откроется редактор сценариев с уже готовым шаблоном для onSpeech:

.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);

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

.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-триггер будет выполняться внутри отдельного феневого потока.

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

.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 пульса.

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

.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. Улучшаем код: как гибче реагировать на реплики

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

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

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

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

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

eval ptc("".api())

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

.get_mob_index(28800).postSpeech = function (ch, vict, msg) {
	if (!msg.match("кофе|coffee"))
	    return;
	if (!msg.match("пожалуй*ста|please|pls"))
	    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("эмоц тепло улыбается.");
}

Мелкие задачи

Свалка примеров, как выполнить ту или иную задачу на Фене.

Как проверить, находится ли персонаж под защитой святилища

Для проверки этого или любого другого битового флага используется метод .isset_bit:

if (.isset_bit(ch.affected_by, .tables.affect_flags.sanctuary)) {

}

Также можно воспользоваться битовым оператором и, &:

// Типичный набор сопротивляемостей у сатира:
eval ptc(.tables.res_flags.names(res_flags))
disease wood

// Проверяем что битовая маска не нулевая
eval ptc(res_flags & .tables.res_flags.wood)
8388608

// А в этом случае - нулевая.
eval ptc(res_flags & .tables.res_flags.holy)
0

// Поэтому в сценарии можно писать:
if (ch.res_flags & .tables.res_flags.wood) {
    ch.act("Палки тебе не страшны!");
}

Проверить, что предмет находится внутри данного контейнера

Предмет в 99.9% случаев будет в одном из трех мест: внутри другого предмета, в руках у персонажа или на полу в комнате.

if (item.in_obj == container) {
    item.getRoom().echo("%^O1 находится внутри %O2.", item, container);
} else if (item.carried_by != null) {
    item.getRoom().echo("%^O4 несёт %C1.", item, item.carried_by);
} else if (item.in_room != null) {
    item.getRoom().echo("%^O1 просто лежит на полу в комнате '%s'.", item, item.in_room.name);
}

Выбор случайного персонажа в комнате

Поле комнаты ppl хранит список (List) всех персонажей в этой комнате. Поэтому для работы с ним доступны все методы списка, в том числе random:

eval ptc(in_room.ppl.api())      - вывести API для списка

eval ptc(in_room.ppl.random().name)  - вывести имя случайного персонажа в комнате

Если необходим любой случайный персонаж, кроме ch, этот элемент можно вычесть из списка методом sub и продолжить работать так же:

var vict;

vict = ch.in_room.ppl.sub(ch).random();
if (vict != null) {

}

Цикл по всем персонажам в комнате

Поле комнаты ppl содержит список персонажей в комнате, и для работы с ним удобно использовать синтаксис for (element in list). Предварительно из этого списка можно вычесть ch, чтобы не делать эту проверку внутри самого цикла.

// Переменную mob не надо предварительно объявлять с помощью var.
for (mob in ch.in_room.ppl.sub(ch)) {
    if (.chance(10)) {
        mob.interpret("эмоц глупо хихикает.");
    }
}

Подсчет согруппников в комнате

var group;

group = 0;
for (gch in ch.in_room.ppl) {
    if (ch.is_same_group(gch))
        group = group + 1;
}

ch.act("Рядом с тобой %d членов твоей группы, включая тебя.", group);

Как проверить погоду и флаги помещения

Пусть у нас есть персонаж ch, и ему надо вывести сводку о погоде, если он снаружи помещения.

if (.isset_bit(ch.in_room.room_flags, .tables.room_flags.indoors))
    ch.act("Сперва выйди на улицу.");
else if (.sky == 0) 
    ch.act("Над тобой ясное синее небо.");
else if (.sky == 1)
    ch.act("Небо затянуто тучами.");
else if (.sky == 2)
    ch.act("Идёт дождь.");
else
    ch.act("Гремит гром и сверкают молнии, лучше поищи укрытие!");

Работа с типами местности комнаты

С полем комнаты sector_type можно работать как численно, так и превратив его в строку для наглядности.

if (ch.in_room.sector_type == .tables.sector_table.forest)
    ch.act("Наконец-то ты в лесу!");
var s;

s = .tables.sector_table.name(
            ch.in_room.sector_type);

if (s == "forest")
    ch.act("Вокруг тебя лес.");
else if (s == "field");
    ch.act("Степь да степь кругом.");
else if (s == "city")
    ch.act("Ты в городской местности.");

Проверить, есть ли у персонажа какое-то оружие

if (ch.get_eq_char("wield") != null) {
    // ...
}

Выяснить параметры оружия персонажа

Для этого найдем предмет в локации wield, проверим, что это оружие, и проанализируем его параметры.

var w, damtype, ave;

w = ch.get_eq_char("wield");
if (w != null && w.item_type == .tables.item_table.weapon) {
    ch.act("%^O1 имеет тип удара %s и среднее повреждение %d.",
                     w,
                     .tables.weapon_flags.name(w.value3),
                     w.ave);

    if (w.value3 == .tables.weapon_flags.pierce
        || w.value3 == .tables.weapon_flags.stab)
    {
        ch.act("Это оружие отлично подходит для удара в спину.");
    }
}

Повесить аффект, изменяющий особенности оружия, например, winters touch

Этот аффект сделает оружие обмораживающим на 10 тиков.

var af;

af = .Affect();
af.type = "winters touch";
af.where = .tables.affwhere_flags.weapon;
af.level = 50;
af.duration = 10;
af.bitvector = .tables.weapon_type2.frost;

obj.affectAdd(af);

Предварительно неплохо бы проверить, не обладает ли оружие уже какими-то необычными свойствами:

if (obj.hasWeaponFlag("flaming shocking frost"))
    return;

Повесить простой аффект на персонажа, например, backguard

Этот аффект ничего не изменяет (не задействованы поля location, modifier и т.д.), но на него есть проверка из умений blackjack и ему подобных.

var af;

af = .Affect();
af.type = "backguard";
af.level = ch.modifyLevel;
af.duration = 2;
ch.affectAdd(af);

Более компактная запись:

// Создать и повесить на персонажа аффект на 2 тика.
ch.affectAdd(
         .Affect("backguard", ch.modifyLevel, 2));

Как нанести зависящие от уровня повреждения и выдать сообщение

var dam, vict;

vict = ch.fighting;
ch.act("Ты бьешь по %C3 со страшной силой.", vict);
ch.rvecho(vict, "%^C1 бьет по %C3 со страшной силой.", ch, vict);
vict.act("%^C1 бьет по тебе со страшной силой.", ch);

dam = .dice(ch.modifyLevel, 18);
// Повреждение с сообщением "Твое землятрясение" и типом повреждений 'none'.
ch.damage(vict, dam, "earthquake", "none");

Гибкая реакция на реплики с помощью регулярных выражений

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

.get_mob_index(3333).onSpeech = function(mob, speaker, message) {
    var pattern, reply;

    // Регулярное выражение, которому должна соответствовать реплика персонажа:
    pattern = "можешь (дать|вернуть|отдать|подарить) ([^ ]+)";
    // Шаблон ответа моба. $1 будет заменено на первое совпадение из скобок, $2 - на второе и так далее.
    reply = "я и не собирался тебе $1 $2";

    if (message.match(pattern))
        mob.say(reply.matchAndReplace(pattern, message));        
};

Как проверить религию персонажа

Поле religion у персонажа ссылается на структуру Religion, которую также можно получить через конструктор .Religion("god name").

Вывод всех полей и методов религии:

eval ptc(.Religion("ares").api())

Проверка доступности религии персонажу this:

eval ptc(.Religion("enki").available(this))

Пример onGreet тригера, где моб по-разному приветствует входящего в зависимости от религии:

.get_mob_index(xxxx).onGreet = function(mob, ch) {
    if (ch.is_npc())
        return;

    if (ch.religion.name == "none")
       mob.say("Приветствую тебя, безбожн%Gик|ик|ица.", ch);
    else
       mob.say("Приветствую тебя, идущ%Gее|ий|ая по пути %N2.", ch, ch.religion.nameRus);
};

Проверка иммунитета к заклинаниям

Предположим, мы хотим отравить противника, но не хотим зря тратить "заряды" - если у него иммунитет к яду или заклинаниям в целом, то пытаться бесполезно. Вот как выглядит эта проверка:

if (!victim.isImmune(.tables.damage_table.poison, .tables.damage_flags.magic)) {
    if (!victim.isAffected("poison")) {
        ch.spell("poison", ch.modifyLevel, victim);
        return;
    }
}

// Альтернативная запись, через строки.
if (!victim.isImmune("disease", "magic")) {
    if (!victim.isAffected("plague")) {
        ch.spell("plague", ch.modifyLevel, victim);
        return;
    }
}

Как выводить разное описание предмета, в зависимости от условий

Для этоо используйте триггер onExtraDescr. Он вызывается каждый раз, как персонаж смотрит на какое-то ключевое слово рядом с предметом или на сам предмет. Из триггера нужно вернуть текст или null. В примере ниже при выполнении команды look telescope (см телескоп) персонаж увидит то или иное описание, в зависимости от поля state телескопа (это поле устанавливается в другом триггере).

telescope.onExtraDescr = function(obj, char, keyword) {
    if (keyword.is_name("telescope телескоп")) {
        if (obj.state == "galaxy") 
	    return "Краем глаза ты замечаешь в окуляре дивное изображение таинственной галактики.";
	else if (obj.state == "arcadia") 
	    return "Мельком взглянув в окуляр ты видишь цветущие луга, притягивающие и манящие.";
			
        return null;
    }
};

Доступ к аттрибутам персонажа

Из фени легко получить значение аттрибутов, которые представляют из себя строку или число.

    var warcry;

    warcry = ch.attribute("warcry");
    if (warcry != null)
        ch.interpret_raw("yell", warcry);
    else {
        ch.pecho("Ты издаешь боевой клич и чувствуешь, что теперь тебе все по плечу!");
        ch.recho("%^C1 издает боевой клич!", ch);
    }

Также доступны некоторые более сложные аттрибуты, представляющие из себя ассоциативный массив.

   var restrings;

   restrings = ch.attribute("create food");
   if (restrings != null && restrings["default"] != null) {
       ch.act("Созданная тобой еда получит имя %s и шорт %s.", restrings["default"].name, restrings["default"].shortDescr);
   }
Clone this wiki locally