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

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

***

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

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

***

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

Для начала рассмотрим функцию появления.
```python
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`,
тогда как сортировка по строкам покажет другой, нежелательный результат.

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

***

## Ходьба

Теперь разберём функцию ходьбы между отмеченными точками по кругу.
```python
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` мы просто ждём, пока персонаж дойдёт.

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

***

## Запуск

И наконец, соединим это всё вместе и отправим в работу.
```python
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` - текущая локация,
но также туда можно передать, к примеру, список "интересных" локаций.

Вот реализация функции перехода к соседней локации:
```python
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`, то результат, очевидно, будет неадекватным.

***

[Далее ->](./Объект-наблюдения)  
[<- Назад](./Автопилот)
