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

Если же вы хотите сделать сделать своих персонажей более живыми, оригинальными и необычными, то добро пожаловать! Также после прочтения рекомендуется ознакомиться с реализацией стандартных действий в `Ren-Engine/rpy/rpg/actions.rpy`.

## Постановка задачи

Итак, предположим, у нас есть патрульный `ch`, он обходит локацию "склад" (store).
Начиная с места `point-1` идёт до `point-2`, затем до `point-3` и т. д.
В конце же возвращается к `point-1`.
И пусть доходя до каждой точки он с некоторой вероятностью оглядывается по сторонам.

## Начало - появление

Для начала рассмотрим функцию появления.

    def patrol_spawn(character, state):
        if state != 'end':
            return 'end'

        point_nums = []
        for place_name in rpg_locations['store'].places.keys():
            if not place_name.startswith('point-'): continue
            num = place_name[len('point-'):]
            if num.isdigit():
                point_nums.append(num)
        point_nums.sort()

        index = random.randint(0, len(point_nums) - 1)
        show_character(character, 'point-' + str(point_nums[index]), 'store')

        actions = character.get_actions()
        actions.point_nums = point_nums
        actions.next_index = index + 1

        return 'end'

Трюк с конечным состоянием (`end`) нужен для того, чтобы код выполнился:

- только 1 раз,
- но в обязательном порядке.

Строка `actions.start('spawn')` не запускает это действие сразу, оно начнёт выполнение лишь в ближайшем обновлении кадра.
Т. е. если сразу же после этой строки будет запущено другое действие, то предыдущее (`spawn`) будет завершено (ему будет передано состояние `end`), и основной код этой функции будет выполнен, как и было задумано.
В обычном же случае мы получим на вход состояние `start`, из которого сразу вернём `end`, а при возвращении именно `end` эта функция сразу же будет вызвана снова, но уже с состоянием `end`, и всё снова будет хорошо.
Перейдём к самому коду.

В начале мы берём все подходящие места, берём их число-окончание, добавляем их в массив и сортируем его.
Этот подход хорош тем, что он не требует отсутствия "дыр", т. е. если у нас есть 10 мест вида `point-N` и мы удаляем `point-4`, то после `point-3` список будет продолжаться.
Кроме того, из-за сортировки по числам-окончаниям `point-10` будет идти после `point-2`, тогда как сортировка по строкам покажет другой, нежелательный результат.

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

## Ходьба

Теперь разберём функцию ходьбы между отмеченными точками по кругу.

    def patrol_walking(character, state):
        actions = character.get_actions()

        if state == 'start':
            num = actions.point_nums[actions.next_index % len(actions.point_nums)]
            character.move_to_place('point-' + str(num))
            actions.next_index += 1
            return 'moving'

        if state == 'moving':
            if character.ended_move_waiting():
                return 'end'
            return 'moving'

        if state == 'end':
            if not character.ended_move_waiting():
                actions.next_index -= 1
                character.move_to_place(None)
            return 'end'

В начале мы направляем персонажа к следующей точке, не забывая её после этого обновлять (`+= 1`).
Остаток от деления (`%`) на длину списка позволяет не думать о сбрасывании счётчика в `0` при достижении последней точки в этом списке.

В состоянии `moving` мы просто ждём, пока персонаж дойдёт.

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

## Запуск

И наконец, соединим это всё вместе и отправим в работу.

    patrol_actions = RpgActions()
    patrol_actions.set_action('spawn', patrol_spawn, 0)
    patrol_actions.set_action('walking', patrol_walking, 4)
    patrol_actions.set_action('look_around', rpg_action_look_around, 1)

    ch = Character(...)
    ch.make_rpg(...)
    ch.set_auto(True)
    ch_actions = ch.set_actions(patrol_actions)
    ch_actions.start('spawn')

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

- их сумма равна `5`, она не обязана равняться `100`,
- `spawn` не может быть запущен случайно (вероятность этого равна `0`), это нужно сделать вручную,
- вероятность запуска `walking` - `80%` (`4/5`), а `look_around` - `20%` (`1/5`).

Строки с `...` подразумевают, что вы уже достаточно хорошо знаете (или можете найти), как делается создание персонажа и его инициализация для RPG-игры.

Наконец, мы позволяем автопилоту управлять данным персонажем, устанавливаем ему RPG-действия и запускаем у него действие `spawn`.

Вот и всё.
Этот автопилот не так уж и сложен, но при этом довольно гибок и расширяем.
По крайней мере, он таким задумывался.

## Действие как уточнение общего действия

Иногда бывает удобно сделать одно общее действие, которое будет использоваться другими действиями.
Например, `other_place` принимает дополнительным параметром список локаций, в которых и будет осуществляться поиск случайного места. По умолчанию это `None` - текущая локация, но также туда можно передать, к примеру, список "интересных" локаций.

Вот реализация функции перехода к соседней локации:

    def rpg_action_near_location(character, state):
        location_names = [exit.to_location_name for exit in character.location.exits]
        character.get_actions().cur_action = rpg_action_other_place
        return rpg_action_other_place(character, state, location_names)

Сначала берём все соседние локации (локации, в которые ведут выходы текущей локации персонажа).
Затем меняем текущую функцию автопилота с `rpg_action_near_location` на `rpg_action_other_place`.
И наконец, просто запускаем эту функцию, передавая ей в качестве доп. параметра список нужных локаций.
В качестве результата не забываем вернуть результат этой функции.

При следующем обновлении уже будет запускаться функция `rpg_action_other_place`, словно именно она и запускалась с самого начала, за исключением того, что место, выбранное в начале, было среди определённого списка локаций.

Также особое внимание здесь стоит уделить тому, что состояние передаётся как есть: переданное `state`, а не просто `start`, который на первый взгляд кажется проще и яснее.
Опять же дело в том, что действие может быть запущено и тут же остановлено, но запуск не осуществляется сразу, сразу происходит лишь запоминание функции, которая будет вызвана при следующем обновлении.
В этом случае при остановке будет передано значение `end`, которое должно быть передано и в общую функцию.
Если же в неё всегда будет передаваться состояние `start`, то результат, очевидно, будет неадекватным.

Далее ->
<- Назад