@euhenio/pi-buddy 0.0.0-stage → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -2
- package/extensions/buddy.ts +2345 -0
- package/package.json +38 -4
- package/preview.png +0 -0
|
@@ -0,0 +1,2345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-buddy — тамагочи для pi coding agent (по мотивам /buddy из Claude Code).
|
|
3
|
+
*
|
|
4
|
+
* Установка: ~/.pi/agent/extensions/buddy.ts (Windows: %USERPROFILE%\.pi\agent\extensions\buddy.ts)
|
|
5
|
+
* Затем в pi: /reload
|
|
6
|
+
*
|
|
7
|
+
* Команды:
|
|
8
|
+
* /buddy показать/спрятать
|
|
9
|
+
* /buddy feed|pet|play|nap
|
|
10
|
+
* /buddy choose [вид] выбрать питомца (без аргумента — меню)
|
|
11
|
+
* /buddy name <имя>
|
|
12
|
+
* /buddy side|top сбоку от поля ввода (по умолчанию) или виджетом сверху
|
|
13
|
+
* /buddy dock полный арт в полосе справа, ничего не сдвигается (по умолчанию)
|
|
14
|
+
* /buddy compact|big мини-мордочка без потери ширины / большой арт с ростом поля ввода
|
|
15
|
+
* /buddy mute|unmute выключить/включить болтовню
|
|
16
|
+
* /buddy pxcolor fg|bg|auto как красить пиксельного пса (fg — для терминала IntelliJ)
|
|
17
|
+
* /buddy anim on|off|auto анимация; auto — в простое не пишет в терминал вне fullscreen
|
|
18
|
+
* /buddy learn [тема|пожелание] модель pi придумает новые фразы (сохраняются в buddy-phrases.json)
|
|
19
|
+
* /buddy phrases создать файл со своими фразами
|
|
20
|
+
* В чате: «добавь Сальсе фраз про Kafka» — агент сам вызовет инструмент buddy_phrases
|
|
21
|
+
* /buddy stats
|
|
22
|
+
* /buddy hatch вылупить нового (сброс!)
|
|
23
|
+
*/
|
|
24
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
25
|
+
import * as fs from "node:fs";
|
|
26
|
+
import * as os from "node:os";
|
|
27
|
+
import * as path from "node:path";
|
|
28
|
+
|
|
29
|
+
// ---------- модель ----------
|
|
30
|
+
|
|
31
|
+
type Mood = "idle" | "happy" | "sad" | "sleep" | "work" | "eat" | "love";
|
|
32
|
+
type Rarity = "common" | "uncommon" | "rare" | "epic" | "legendary";
|
|
33
|
+
|
|
34
|
+
interface BuddyState {
|
|
35
|
+
name: string;
|
|
36
|
+
species: string;
|
|
37
|
+
rarity: Rarity;
|
|
38
|
+
shiny: boolean;
|
|
39
|
+
born: number;
|
|
40
|
+
food: number; // 0..100, 100 = сыт
|
|
41
|
+
joy: number; // 0..100
|
|
42
|
+
energy: number; // 0..100
|
|
43
|
+
xp: number;
|
|
44
|
+
lastTick: number;
|
|
45
|
+
lastActivity: number;
|
|
46
|
+
visible: boolean;
|
|
47
|
+
toolCalls: number;
|
|
48
|
+
errors: number;
|
|
49
|
+
generations?: number; // ответов модели (turn_end)
|
|
50
|
+
tokensOut?: number; // сгенерировано токенов
|
|
51
|
+
placement?: Placement;
|
|
52
|
+
size?: Size;
|
|
53
|
+
muted?: boolean;
|
|
54
|
+
anim?: "auto" | "on" | "off";
|
|
55
|
+
pxColor?: "auto" | PxMode; // как красить пиксельного пса
|
|
56
|
+
cleanRuns?: number; // запросов подряд без ошибок
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type Placement = "side" | "top";
|
|
60
|
+
/**
|
|
61
|
+
* dock — полноразмерный питомец в полосе справа; чат и ввод сужаются по ширине,
|
|
62
|
+
* по высоте ничего не сдвигается (по умолчанию)
|
|
63
|
+
* compact — мини-мордочка, ширина чата не трогается, строк не добавляет
|
|
64
|
+
* big — полный арт рядом с полем ввода, поле растёт по высоте
|
|
65
|
+
*/
|
|
66
|
+
type Size = "dock" | "compact" | "big";
|
|
67
|
+
|
|
68
|
+
const STATE_FILE = path.join(os.homedir(), ".pi", "agent", "buddy.json");
|
|
69
|
+
const WIDGET_ID = "buddy";
|
|
70
|
+
/**
|
|
71
|
+
* Статы самовосполняемые: в простое плавно тянутся к базовому уровню,
|
|
72
|
+
* а работа агента их двигает. Кормить и развлекать вручную не нужно.
|
|
73
|
+
*/
|
|
74
|
+
const BASE = { food: 60, joy: 60, energy: 100 };
|
|
75
|
+
const RELAX_H = { food: 3, joy: 2, energy: 1 }; // постоянная времени возврата к базе, часов
|
|
76
|
+
const GAIN = {
|
|
77
|
+
genFood: 5, // каждая генерация кормит
|
|
78
|
+
tokFood: 1 / 400, // и немного за объём ответа (токены)
|
|
79
|
+
tokFoodMax: 8,
|
|
80
|
+
toolJoy: 1.5, // успешный tool call радует
|
|
81
|
+
runJoy: 4, // завершённый запрос — ещё больше
|
|
82
|
+
errJoy: -4, // ошибка огорчает, но база вернёт
|
|
83
|
+
genEnergy: -1.5, // генерации утомляют
|
|
84
|
+
toolEnergy: -0.4,
|
|
85
|
+
genXp: 2,
|
|
86
|
+
toolXp: 1,
|
|
87
|
+
tokXp: 1 / 1000,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const SIDE_W = 20; // ширина колонки питомца в режиме big
|
|
91
|
+
const MINI_W = 18; // ширина колонки в компактном режиме
|
|
92
|
+
const DOCK_W = 20; // ширина полосы справа в режиме dock
|
|
93
|
+
const MIN_TERM_W = 70; // в узком терминале колонка прячется, чтобы не душить ввод
|
|
94
|
+
const ANSI = /\x1b\[[0-9;?]*[ -\/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b_[^\x1b]*\x1b\\/g;
|
|
95
|
+
|
|
96
|
+
/** Пакеты pi переименовывались (@mariozechner -> @earendil-works), пробуем оба */
|
|
97
|
+
async function importPi(sub: string): Promise<any> {
|
|
98
|
+
for (const scope of ["@earendil-works", "@mariozechner"]) {
|
|
99
|
+
try {
|
|
100
|
+
return await import(`${scope}/${sub}`);
|
|
101
|
+
} catch {
|
|
102
|
+
/* следующий */
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function wrapWords(text: string, w: number, maxLines: number): string[] {
|
|
109
|
+
const out: string[] = [];
|
|
110
|
+
let cur = "";
|
|
111
|
+
for (const word of text.split(/\s+/)) {
|
|
112
|
+
if (!cur) cur = word;
|
|
113
|
+
else if (cur.length + 1 + word.length <= w) cur += " " + word;
|
|
114
|
+
else {
|
|
115
|
+
out.push(cur);
|
|
116
|
+
cur = word;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (cur) out.push(cur);
|
|
120
|
+
return out.slice(0, maxLines).map((l) => (l.length > w ? l.slice(0, w - 1) + "…" : l));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
// ---------- реплики ----------
|
|
125
|
+
// Держим фразы короткими (до ~34 символов): облачко — две строки по 18.
|
|
126
|
+
|
|
127
|
+
type Topic =
|
|
128
|
+
| "hello" | "back" | "morning" | "night" | "monday" | "friday" | "weekend"
|
|
129
|
+
| "done" | "streak" | "bigGen" | "think" | "levelup"
|
|
130
|
+
| "error" | "errStreak"
|
|
131
|
+
| "bash" | "edit" | "read" | "java" | "pom" | "bpmn" | "yaml" | "sql"
|
|
132
|
+
| "testOk" | "testFail" | "buildOk" | "buildFail" | "gitCommit" | "gitPush" | "gitFail"
|
|
133
|
+
| "docker" | "k8s" | "rm"
|
|
134
|
+
| "idle" | "feed" | "full" | "pet" | "play" | "tired" | "nap"
|
|
135
|
+
// дежурные (тихие, серым, вместо пустоты): меняются раз в несколько минут
|
|
136
|
+
| "stbIdle" | "stbWork" | "stbSleep" | "stbSad" | "stbMute";
|
|
137
|
+
|
|
138
|
+
const SAY: Record<Topic, string[]> = {
|
|
139
|
+
hello: ["привет!", "снова кодим?", "я тут", "к работе готов", "погнали!", "что строим сегодня?", "я соскучился"],
|
|
140
|
+
back: ["ты вернулся!", "о, а я ждал", "долго тебя не было", "кофе попил?", "с возвращением!", "я тут всё охранял"],
|
|
141
|
+
morning: ["доброе утро!", "утро. кофе. код.", "свежая голова — свежий код", "утренний деплой? смело"],
|
|
142
|
+
night: ["уже ночь...", "может, спать?", "ночной коммит — смелый коммит", "в такое время баги злее", "завтра перечитаем?"],
|
|
143
|
+
monday: ["понедельник, держимся", "новая неделя, новые баги", "ретро уже было?"],
|
|
144
|
+
friday: ["пятница! не деплоим", "в пятницу только рефакторинг", "прод в пятницу? ну-ну", "скоро выходные!"],
|
|
145
|
+
weekend: ["выходные же...", "пет-проект?", "я никому не скажу"],
|
|
146
|
+
|
|
147
|
+
done: ["готово!", "сделали!", "ещё задачку?", "красиво вышло", "я помогал!", "принято", "так-так, неплохо",
|
|
148
|
+
"запушим?", "работает же?", "это было быстро", "одобряю", "ещё по одной?", "чистая работа", "пять баллов"],
|
|
149
|
+
streak: ["серия без ошибок!", "ты в ударе", "не сглазить бы", "чистый поток", "вот это ритм!"],
|
|
150
|
+
bigGen: ["вот это простыня", "много букв!", "кто-то разговорился", "это всё прочитать?", "роман написали"],
|
|
151
|
+
think: ["думаем...", "хмм...", "модель скрипит", "жду, жду...", "сейчас что-то будет", "токены пошли", "крутится, вертится"],
|
|
152
|
+
levelup: ["новый уровень!", "я вырос!", "level up!", "+1 к мудрости"],
|
|
153
|
+
|
|
154
|
+
error: ["ой...", "упало :(", "стектрейс страшный", "не плачь, починим", "опять NPE?", "бывает", "читаем лог",
|
|
155
|
+
"попробуем ещё", "ну почти", "красное — к удаче", "это не баг, это фича", "а локально работало"],
|
|
156
|
+
errStreak: ["может, перерыв?", "что-то не идёт", "давай по шагам", "откатимся?", "дебаггер в помощь", "я с тобой"],
|
|
157
|
+
|
|
158
|
+
bash: ["шелл, люблю", "ого, команды", "только не rm -rf", "в терминале как дома", "запускаем..."],
|
|
159
|
+
edit: ["правим", "минус строчка, плюс строчка", "чистенько", "диф красивый", "аккуратнее там"],
|
|
160
|
+
read: ["читаем код", "что тут у нас?", "изучаем", "ищем истину", "легаси, да?"],
|
|
161
|
+
java: ["Java! уважаю", "главное без NPE", "бин нашёлся?", "@Transactional на месте?", "прокси, это прокси!",
|
|
162
|
+
"CGLIB или JDK proxy?", "LazyInitialization не пробегал?", "Optional, а не null", "final где можно", "стримы красивые"],
|
|
163
|
+
pom: ["pom.xml... держись", "версии зависимостей, ух", "dependency hell?"],
|
|
164
|
+
bpmn: ["BPMN! квадратики!", "стрелочки, гейтвеи...", "процесс красивый", "токен побежал"],
|
|
165
|
+
yaml: ["отступы, отступы!", "YAML коварен", "пробелы, не табы"],
|
|
166
|
+
sql: ["SQL, классика", "индекс есть?", "N+1 не завёлся?", "EXPLAIN глянем?"],
|
|
167
|
+
|
|
168
|
+
testOk: ["тесты зелёные!", "всё зелёное!", "покрытие растёт", "тесты прошли", "можно мержить"],
|
|
169
|
+
testFail: ["тест упал", "красный тест :(", "assert не прошёл", "а тест прав?", "ищем, что сломали"],
|
|
170
|
+
buildOk: ["собралось!", "BUILD SUCCESS", "компилятор доволен"],
|
|
171
|
+
buildFail: ["не собралось", "BUILD FAILURE", "компилятор ругается", "точка с запятой?"],
|
|
172
|
+
gitCommit: ["коммит!", "сохранились", "хорошее сообщение?", "+1 в историю", "атомарный, надеюсь"],
|
|
173
|
+
gitPush: ["запушили!", "улетело", "CI, лови", "теперь ревью", "ушло на сервер"],
|
|
174
|
+
gitFail: ["git ругается", "конфликт?", "rebase, держись", "а ветка та?"],
|
|
175
|
+
docker: ["контейнеры!", "кит плывёт", "образ толстый?", "docker, поехали"],
|
|
176
|
+
k8s: ["кубер...", "поды живы?", "yaml-инженерия", "CrashLoopBackOff?"],
|
|
177
|
+
rm: ["ой, удаляем...", "точно не нужно?", "надеюсь, есть git"],
|
|
178
|
+
|
|
179
|
+
idle: ["тишина...", "я тут, если что", "задумался?", "можно перерыв", "потянись, разомнись", "водички попей",
|
|
180
|
+
"глаза отдохнут", "что дальше?", "идеи есть?", "я жду"],
|
|
181
|
+
feed: ["ням-ням!", "вкусно!", "спасибо!", "ещё!", "хрум"],
|
|
182
|
+
full: ["я сыт!", "больше не лезет", "потом, ладно?"],
|
|
183
|
+
pet: ["мррр", "<3", "ещё!", "приятно", "обожаю"],
|
|
184
|
+
play: ["уиии!", "догони!", "ещё раунд!", "весело!"],
|
|
185
|
+
tired: ["сил нет... дай поспать", "устал, потом", "зарядка нужна"],
|
|
186
|
+
nap: ["zzz...", "сплю, не будить", "минутку вздремну"],
|
|
187
|
+
|
|
188
|
+
stbIdle: ["жду задачу", "наблюдаю", "на связи", "готов помочь", "слежу за кодом", "смотрю в терминал",
|
|
189
|
+
"думаю о рефакторинге", "считаю строчки", "сторожу ветку", "отдыхаю", "мечтаю о зелёных тестах"],
|
|
190
|
+
stbWork: ["работаем...", "слежу за агентом", "смотрю, что он делает", "держу кулачки", "читаю вывод",
|
|
191
|
+
"болею за тесты", "не мешаю", "вникаю"],
|
|
192
|
+
stbSleep: ["zzz...", "спит", "дремлет", "видит сны о коде", "тихо сопит"],
|
|
193
|
+
stbSad: ["немного грустит", "переживает", "отходит от ошибок"],
|
|
194
|
+
stbMute: ["молчу", "тихий режим", "просто рядом"],
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/** Расширение набора: дописывается к темам выше */
|
|
198
|
+
const SAY_EXTRA: Partial<Record<Topic, string[]>> = {
|
|
199
|
+
hello: ["с чего начнём?", "открываем IDE", "новый день — новый PR", "кофе есть? начинаем"],
|
|
200
|
+
back: ["я скучал", "тут без тебя тихо", "продолжим?"],
|
|
201
|
+
morning: ["сначала кофе, потом код", "утренний стендап скоро?", "бодрое утро!"],
|
|
202
|
+
night: ["глаза устали?", "сохранись и спать", "ночью баги не видны"],
|
|
203
|
+
friday: ["пятничный рефакторинг?", "до выходных чуть-чуть"],
|
|
204
|
+
weekend: ["отдыхать тоже надо", "только по чуть-чуть"],
|
|
205
|
+
done: ["едем дальше", "задача закрыта", "так держать", "аккуратно сделано", "можно в ревью"],
|
|
206
|
+
streak: ["всё получается!", "без единой ошибки", "ты сегодня огонь"],
|
|
207
|
+
bigGen: ["целая статья", "это на вечер чтения", "TL;DR будет?"],
|
|
208
|
+
think: ["модель думает", "ищем решение", "погоди, ещё чуть", "шестерёнки крутятся"],
|
|
209
|
+
error: ["эх...", "давай ещё раз", "лог подскажет", "не сдаёмся", "почти получилось"],
|
|
210
|
+
errStreak: ["выдохни", "отдохни пять минут", "начнём с малого"],
|
|
211
|
+
edit: ["меняем аккуратно", "рефакторим", "строчка к строчке"],
|
|
212
|
+
read: ["разбираемся", "погружаемся в код", "кто это писал?.."],
|
|
213
|
+
java: ["@Autowired через конструктор", "equals и hashCode!", "а record не подойдёт?", "check-исключения, ох", "Stream или цикл?", "Lombok тут уместен?", "final — наше всё"],
|
|
214
|
+
pom: ["mvn dependency:tree?", "версия плагина свежая?", "кто притащил эту либу?"],
|
|
215
|
+
bpmn: ["Camunda заработает", "user task или service?", "гейтвей не забыли?", "диаграмма как картина"],
|
|
216
|
+
yaml: ["пробел не тот?", "отступ съехал?"],
|
|
217
|
+
sql: ["JOIN или подзапрос?", "транзакция закрыта?", "миграция готова?"],
|
|
218
|
+
testOk: ["зелёный — мой любимый цвет", "тесты довольны", "без регрессий"],
|
|
219
|
+
testFail: ["тест знает, что говорит", "почини меня", "красненько..."],
|
|
220
|
+
buildOk: ["сборка прошла", "артефакт готов"],
|
|
221
|
+
buildFail: ["что-то не сходится", "зависимости?", "clean install?"],
|
|
222
|
+
gitCommit: ["в историю!", "коммит засчитан", "сообщение понятное?"],
|
|
223
|
+
gitPush: ["полетело в remote", "пайплайн пошёл"],
|
|
224
|
+
gitFail: ["pull сначала?", "merge-конфликт, ох"],
|
|
225
|
+
docker: ["слои кешируются?", "compose up!"],
|
|
226
|
+
k8s: ["kubectl get pods", "манифесты, манифесты"],
|
|
227
|
+
idle: ["может, чаю?", "размяться бы", "что-то тихо", "окно проветрить?"],
|
|
228
|
+
stbIdle: ["жду команду", "присматриваю за кодом", "на посту"],
|
|
229
|
+
stbWork: ["наблюдаю за процессом", "смотрю в оба"],
|
|
230
|
+
};
|
|
231
|
+
for (const [k, v] of Object.entries(SAY_EXTRA)) SAY[k as Topic].push(...v!);
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Фразы с позой: «@lie:…», «@stand:…», «@walk:…» звучат, только когда пёс (пиксельный)
|
|
235
|
+
* в этой позе, — чтобы не «сидел рядом», пока стоит. Фразы без метки годятся всегда.
|
|
236
|
+
* У ASCII-питомцев позы нет — им подходят все фразы.
|
|
237
|
+
*/
|
|
238
|
+
const DOG_SAY: Partial<Record<Topic, string[]>> = {
|
|
239
|
+
hello: ["гав! привет!", "охраняю репозиторий", "к работе готов!", "@stand:виляет хвостом", "гав-гав, погнали"],
|
|
240
|
+
back: ["гав! ты вернулся!", "я ждал у двери", "@stand:прыгает от радости", "где ты был?!", "нюхает руки: кофе?"],
|
|
241
|
+
done: ["гав!", "служу!", "задача выполнена!", "апорт принёс!", "гав-гав :)", "хороший код, хороший",
|
|
242
|
+
"@stand:виляет хвостом", "ещё палочку?", "принёс результат", "можно вкусняшку?"],
|
|
243
|
+
streak: ["гав-гав-гав!", "лучший хозяин", "@stand:хвост пропеллером"],
|
|
244
|
+
think: ["нюхает воздух...", "навострил уши", "@walk:идёт по следу", "@walk:трусит рядом"],
|
|
245
|
+
error: ["рррр...", "скулит", "ищу баг по следу", "взял след ошибки", "гав! (тревожно)", "прижал уши",
|
|
246
|
+
"рычит на стектрейс", "это не я!"],
|
|
247
|
+
errStreak: ["скулит, поддерживает", "пойдём погуляем?", "@lie:положил морду на лапы"],
|
|
248
|
+
idle: ["пойдём гулять?", "кинь мячик", "@lie:лежит у ног", "@lie:грызёт мышку от ноута", "чешет за ухом",
|
|
249
|
+
"@lie:тихо сопит", "@stand:переминается с лапы на лапу"],
|
|
250
|
+
bash: ["нюхает терминал", "гав на команду"],
|
|
251
|
+
rm: ["рррр! (не надо)", "охраняю файлы!"],
|
|
252
|
+
gitPush: ["гав! улетело!", "догоню CI!"],
|
|
253
|
+
feed: ["хрум-хрум!", "мясо!", "ещё косточку?", "вылизал миску"],
|
|
254
|
+
full: ["пузо полное", "сыт и доволен"],
|
|
255
|
+
pet: ["@stand:виляет хвостом", "лизь!", "*тыкается носом*", "@lie:подставляет пузо", "закрыл глаза"],
|
|
256
|
+
play: ["апорт!", "догоню!", "тяни канат!", "где мячик?!"],
|
|
257
|
+
nap: ["@lie:свернулся клубком", "zzz... дёргает лапой", "сопит"],
|
|
258
|
+
stbIdle: ["сторожит репозиторий", "смотрит на тебя", "ушки на макушке", "охраняет прод", "ждёт команду",
|
|
259
|
+
"нюхает воздух", "@stand:стоит рядом", "@stand:виляет хвостиком", "@stand:переминается с лапы на лапу",
|
|
260
|
+
"@lie:лежит у ног", "@lie:грызёт косточку", "@lie:прилёг рядом", "@lie:положил морду на лапы"],
|
|
261
|
+
stbWork: ["следит за агентом", "навострил уши", "нюхает логи", "внимательно смотрит", "охраняет процесс",
|
|
262
|
+
"@walk:идёт по следу", "@walk:трусит рядом с агентом", "@walk:обходит владения"],
|
|
263
|
+
stbSleep: ["спит у ног", "дёргает лапой во сне", "сопит в клубочке", "видит сны про мячик"],
|
|
264
|
+
stbSad: ["прижал уши", "@lie:положил морду на лапы", "вздыхает"],
|
|
265
|
+
stbMute: ["@lie:тихо лежит рядом", "@stand:тихо стоит рядом", "молча сторожит"],
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/** Собачьи версии технических тем и новые позы */
|
|
269
|
+
const DOG_EXTRA: Partial<Record<Topic, string[]>> = {
|
|
270
|
+
hello: ["гав! за работу!", "хвост наготове", "нюхает клавиатуру", "@stand:потягивается"],
|
|
271
|
+
back: ["гав-гав-гав!", "@stand:крутится от радости", "принёс тапки"],
|
|
272
|
+
morning: ["утренняя прогулка?", "гав! доброе утро", "сначала миска, потом код"],
|
|
273
|
+
night: ["пора спать, гав", "@lie:зевает", "ночной дозор"],
|
|
274
|
+
monday: ["гав, понедельник", "снова в строй"],
|
|
275
|
+
friday: ["гав! пятница!", "в выходные гулять?"],
|
|
276
|
+
weekend: ["выходные! в парк?", "гулять, а не кодить!"],
|
|
277
|
+
done: ["апорт! готово", "гав! сделано", "@stand:гордо стоит", "заслужил косточку", "служба выполнена"],
|
|
278
|
+
streak: ["лучший день!", "гав! так держать", "@stand:носится кругами"],
|
|
279
|
+
bigGen: ["гав, сколько букв", "даже не дочитал", "@lie:уснул на середине"],
|
|
280
|
+
think: ["принюхивается...", "ждёт, навострив уши", "@walk:обнюхивает углы"],
|
|
281
|
+
levelup: ["гав! вырос!", "новый трюк выучил!", "теперь я взрослее"],
|
|
282
|
+
error: ["гав! что-то упало", "нашёл косяк", "чует неладное", "рычит на ошибку"],
|
|
283
|
+
errStreak: ["тыкается носом: держись", "лизнул руку", "мы справимся, гав"],
|
|
284
|
+
bash: ["гав на терминал", "команда принята", "сидит у консоли"],
|
|
285
|
+
edit: ["следит за правками", "нюхает диф", "аккуратно, гав"],
|
|
286
|
+
read: ["нюхает код", "читает вместе с тобой", "след в коде"],
|
|
287
|
+
java: ["гав на NPE", "чует утечку бинов", "прокси пахнет CGLIB", "любит чистые сервисы", "охраняет транзакции", "нюхает стектрейс"],
|
|
288
|
+
pom: ["закопал лишнюю либу", "гав на конфликт версий"],
|
|
289
|
+
bpmn: ["пасёт процессы", "гонит токен по схеме", "охраняет гейтвей"],
|
|
290
|
+
sql: ["нюхает запрос", "гав на N+1"],
|
|
291
|
+
testOk: ["гав! зелёные!", "апорт: тесты прошли", "@stand:виляет на зелёный"],
|
|
292
|
+
testFail: ["рычит на красный тест", "гав! тест упал", "ищет, кто сломал"],
|
|
293
|
+
buildOk: ["гав! собралось", "сборка в зубах"],
|
|
294
|
+
buildFail: ["гав! не собралось", "рычит на компилятор"],
|
|
295
|
+
gitCommit: ["закопал коммит", "гав! сохранили", "метит историю"],
|
|
296
|
+
gitPush: ["понёс в remote", "гав! в облако!"],
|
|
297
|
+
gitFail: ["рычит на конфликт", "гав! git ругается"],
|
|
298
|
+
docker: ["гав на кита", "охраняет контейнер"],
|
|
299
|
+
k8s: ["пасёт поды", "гав на кубер"],
|
|
300
|
+
rm: ["не отдам файлы!", "гав! осторожно"],
|
|
301
|
+
idle: ["принёс мячик", "@lie:грызёт палочку", "смотрит в окно", "ждёт прогулку", "@stand:тянется за рукой", "@lie:дремлет вполглаза"],
|
|
302
|
+
feed: ["чавк-чавк", "миска пуста — спасибо!"],
|
|
303
|
+
pet: ["счастлив!", "@stand:крутит хвостом", "@lie:перевернулся на спину"],
|
|
304
|
+
play: ["гав-гав! ещё!", "@stand:припал на лапы"],
|
|
305
|
+
nap: ["@lie:устроился поудобнее", "@lie:прикрыл нос хвостом"],
|
|
306
|
+
stbIdle: ["@stand:смотрит в монитор", "@stand:наклонил голову", "@lie:дремлет у ног", "@lie:лапы вытянул", "бережёт покой", "слушает клавиши"],
|
|
307
|
+
stbWork: ["@walk:бежит по следу", "@walk:патрулирует логи", "сторожит сборку", "не сводит глаз"],
|
|
308
|
+
stbSleep: ["снятся кошки", "спит без задних лап", "сопит носом"],
|
|
309
|
+
stbSad: ["@lie:ткнулся носом в лапы", "грустит, но рядом"],
|
|
310
|
+
stbMute: ["тихо сторожит", "молчит, но всё видит"],
|
|
311
|
+
};
|
|
312
|
+
for (const [k, v] of Object.entries(DOG_EXTRA)) (DOG_SAY[k as Topic] ??= []).push(...v!);
|
|
313
|
+
|
|
314
|
+
const VEO_SAY: Partial<Record<Topic, string[]>> = {
|
|
315
|
+
...DOG_SAY,
|
|
316
|
+
hello: [...DOG_SAY.hello!, "ВЕО на посту", "охрана включена"],
|
|
317
|
+
done: [...DOG_SAY.done!, "служу трудовому коду!", "надёжно, как ВЕО"],
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const GSD_SAY: Partial<Record<Topic, string[]>> = {
|
|
321
|
+
...DOG_SAY,
|
|
322
|
+
hello: ["wuff! ты вернулся!", "bereit!", "guten Morgen!", "охраняю репозиторий"],
|
|
323
|
+
done: ["jawohl!", "wuff!", "gut gemacht!", "ordnung!", "sehr gut!", "чётко, по-немецки", "fertig!"],
|
|
324
|
+
error: ["nein!", "рррр...", "achtung, баг!", "скулит", "беру след ошибки"],
|
|
325
|
+
idle: ["spazieren?", "wirf den Ball!", "langweilig...", "wartet..."],
|
|
326
|
+
feed: ["danke!", "хрум-хрум!", "lecker!"],
|
|
327
|
+
play: ["fass!", "апорт!", "noch mal!"],
|
|
328
|
+
nap: ["platz... zzz", "zzz... дёргает лапой"],
|
|
329
|
+
stbIdle: [...DOG_SAY.stbIdle!, "wacht", "bei Fuß"],
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
/** Темы, где у вида только свои фразы, без общих: тело, позы, дежурные */
|
|
333
|
+
const OWN_ONLY = new Set<Topic>(["feed", "full", "pet", "play", "tired", "nap", "idle", "stbIdle", "stbWork", "stbSleep", "stbSad", "stbMute"]);
|
|
334
|
+
|
|
335
|
+
/** Свои фразы: ~/.pi/agent/buddy-phrases.json, например
|
|
336
|
+
* { "done": ["моя фраза"], "veo": { "hello": ["..."] } }
|
|
337
|
+
* Дополняют встроенные, применяются после /reload. */
|
|
338
|
+
const PHRASES_FILE = path.join(os.homedir(), ".pi", "agent", "buddy-phrases.json");
|
|
339
|
+
|
|
340
|
+
// E — место для глаз, подставляется в зависимости от настроения.
|
|
341
|
+
// grown — арт после взросления (с уровня GROW_LEVEL), если есть.
|
|
342
|
+
interface Species {
|
|
343
|
+
label: string;
|
|
344
|
+
rarity: Rarity;
|
|
345
|
+
art: string[];
|
|
346
|
+
grown?: string[];
|
|
347
|
+
grownLabel?: string;
|
|
348
|
+
mini: string; // однострочная мордочка для компактного режима, E — глаз
|
|
349
|
+
// доп. кадры анимации (кадр 0 — сам art/grown). Нет — покачивание на 1 символ
|
|
350
|
+
frames?: string[][];
|
|
351
|
+
framesGrown?: string[][];
|
|
352
|
+
miniGrown?: string;
|
|
353
|
+
say?: Partial<Record<Topic, string[]>>;
|
|
354
|
+
px?: Record<PxFrame, string[]>; // пиксельные кадры (полублоки, truecolor); art — ASCII-запаска
|
|
355
|
+
items?: PxItem[]; // два любимых предмета
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Пиксельный ВЕО (по фото): 24×16 пикселей → 24 колонки × 8 строк полублоками ▀▄.
|
|
360
|
+
* . пусто K шерсть D тень G блик по контуру A янтарный глаз N нос P/p язык
|
|
361
|
+
*/
|
|
362
|
+
const PX_PAL: Record<string, [number, number, number]> = {
|
|
363
|
+
K: [58, 56, 64],
|
|
364
|
+
D: [26, 26, 30],
|
|
365
|
+
G: [118, 115, 128],
|
|
366
|
+
A: [222, 150, 48],
|
|
367
|
+
N: [8, 8, 10],
|
|
368
|
+
P: [232, 120, 140],
|
|
369
|
+
p: [180, 80, 100],
|
|
370
|
+
// остальные питомцы
|
|
371
|
+
"E": [18, 18, 22],
|
|
372
|
+
"e": [240, 240, 236],
|
|
373
|
+
"w": [240, 240, 236],
|
|
374
|
+
"g": [190, 190, 196],
|
|
375
|
+
"Y": [250, 208, 60],
|
|
376
|
+
"y": [205, 150, 35],
|
|
377
|
+
"O": [240, 128, 40],
|
|
378
|
+
"C": [80, 205, 175],
|
|
379
|
+
"c": [35, 140, 125],
|
|
380
|
+
"B": [150, 98, 55],
|
|
381
|
+
"b": [95, 60, 32],
|
|
382
|
+
"k": [245, 160, 190],
|
|
383
|
+
"m": [205, 95, 145],
|
|
384
|
+
"V": [95, 185, 85],
|
|
385
|
+
"v": [45, 115, 55],
|
|
386
|
+
"R": [225, 70, 60],
|
|
387
|
+
"T": [205, 145, 75],
|
|
388
|
+
"t": [150, 98, 48],
|
|
389
|
+
"L": [120, 190, 255],
|
|
390
|
+
"s": [110, 110, 118],
|
|
391
|
+
"S": [160, 160, 168],
|
|
392
|
+
};
|
|
393
|
+
type PxFrame = "S" | "SB" | "SP" | "SE" | "WM" | "WU" | "WA" | "WB" | "L" | "LB" | "LP" | "LT" | "SAD" | "SADB" | "Z" | "ZB";
|
|
394
|
+
/** Кадры пиксельного ВЕО, 24×16 пикселей → 24 колонки × 8 строк */
|
|
395
|
+
const VEO_PX: Record<PxFrame, string[]> = {
|
|
396
|
+
// стоит
|
|
397
|
+
S: [
|
|
398
|
+
".................G..G...",
|
|
399
|
+
"................GKGGKG..",
|
|
400
|
+
"................GKKKKG..",
|
|
401
|
+
"...............GKAKKKKG.",
|
|
402
|
+
"..............GKKKKKKKKN",
|
|
403
|
+
".............GKKKKKKKDDN",
|
|
404
|
+
"............GKKKKKKD.PP.",
|
|
405
|
+
"...GGGGGGGGGKKKKKK...p..",
|
|
406
|
+
"..GKKKKKKKKKKKKKKK......",
|
|
407
|
+
".GKKKKKKKKKKKKKKKKG.....",
|
|
408
|
+
"GKKKKKKKKKKKKKKKKG......",
|
|
409
|
+
"GKGKKKKKDDDDDKKKKG......",
|
|
410
|
+
"GKG.GKKKD....GKDKK......",
|
|
411
|
+
"GKG..GKK.D...GKDKK......",
|
|
412
|
+
".GK..GKK.D...GKDKK......",
|
|
413
|
+
".....DDD.DD..DD.DDD.....",
|
|
414
|
+
],
|
|
415
|
+
// моргнул
|
|
416
|
+
SB: [
|
|
417
|
+
".................G..G...",
|
|
418
|
+
"................GKGGKG..",
|
|
419
|
+
"................GKKKKG..",
|
|
420
|
+
"...............GKDKKKKG.",
|
|
421
|
+
"..............GKKKKKKKKN",
|
|
422
|
+
".............GKKKKKKKDDN",
|
|
423
|
+
"............GKKKKKKD.PP.",
|
|
424
|
+
"...GGGGGGGGGKKKKKK...p..",
|
|
425
|
+
"..GKKKKKKKKKKKKKKK......",
|
|
426
|
+
".GKKKKKKKKKKKKKKKKG.....",
|
|
427
|
+
"GKKKKKKKKKKKKKKKKG......",
|
|
428
|
+
"GKGKKKKKDDDDDKKKKG......",
|
|
429
|
+
"GKG.GKKKD....GKDKK......",
|
|
430
|
+
"GKG..GKK.D...GKDKK......",
|
|
431
|
+
".GK..GKK.D...GKDKK......",
|
|
432
|
+
".....DDD.DD..DD.DDD.....",
|
|
433
|
+
],
|
|
434
|
+
// дышит, язык короче
|
|
435
|
+
SP: [
|
|
436
|
+
".................G..G...",
|
|
437
|
+
"................GKGGKG..",
|
|
438
|
+
"................GKKKKG..",
|
|
439
|
+
"...............GKAKKKKG.",
|
|
440
|
+
"..............GKKKKKKKKN",
|
|
441
|
+
".............GKKKKKKKDDN",
|
|
442
|
+
"............GKKKKKKDD.P.",
|
|
443
|
+
"...GGGGGGGGGKKKKKK......",
|
|
444
|
+
"..GKKKKKKKKKKKKKKK......",
|
|
445
|
+
".GKKKKKKKKKKKKKKKKG.....",
|
|
446
|
+
"GKKKKKKKKKKKKKKKKG......",
|
|
447
|
+
"GKGKKKKKDDDDDKKKKG......",
|
|
448
|
+
"GKG.GKKKD....GKDKK......",
|
|
449
|
+
"GKG..GKK.D...GKDKK......",
|
|
450
|
+
".GK..GKK.D...GKDKK......",
|
|
451
|
+
".....DDD.DD..DD.DDD.....",
|
|
452
|
+
],
|
|
453
|
+
// дёрнул ухом
|
|
454
|
+
SE: [
|
|
455
|
+
".................G...G..",
|
|
456
|
+
"................GKGGKKG.",
|
|
457
|
+
"................GKKKKG..",
|
|
458
|
+
"...............GKAKKKKG.",
|
|
459
|
+
"..............GKKKKKKKKN",
|
|
460
|
+
".............GKKKKKKKDDN",
|
|
461
|
+
"............GKKKKKKD.PP.",
|
|
462
|
+
"...GGGGGGGGGKKKKKK...p..",
|
|
463
|
+
"..GKKKKKKKKKKKKKKK......",
|
|
464
|
+
".GKKKKKKKKKKKKKKKKG.....",
|
|
465
|
+
"GKKKKKKKKKKKKKKKKG......",
|
|
466
|
+
"GKGKKKKKDDDDDKKKKG......",
|
|
467
|
+
"GKG.GKKKD....GKDKK......",
|
|
468
|
+
"GKG..GKK.D...GKDKK......",
|
|
469
|
+
".GK..GKK.D...GKDKK......",
|
|
470
|
+
".....DDD.DD..DD.DDD.....",
|
|
471
|
+
],
|
|
472
|
+
// хвост ровно
|
|
473
|
+
WM: [
|
|
474
|
+
".................G..G...",
|
|
475
|
+
"................GKGGKG..",
|
|
476
|
+
"................GKKKKG..",
|
|
477
|
+
"...............GKAKKKKG.",
|
|
478
|
+
"..............GKKKKKKKKN",
|
|
479
|
+
".............GKKKKKKKDDN",
|
|
480
|
+
"............GKKKKKKD.PP.",
|
|
481
|
+
"...GGGGGGGGGKKKKKK...p..",
|
|
482
|
+
"GGGKKKKKKKKKKKKKKK......",
|
|
483
|
+
"GKKKKKKKKKKKKKKKKKG.....",
|
|
484
|
+
".GKKKKKKKKKKKKKKKG......",
|
|
485
|
+
"..GKKKKKDDDDDKKKKG......",
|
|
486
|
+
"....GKKKD....GKDKK......",
|
|
487
|
+
".....GKK.D...GKDKK......",
|
|
488
|
+
".....GKK.D...GKDKK......",
|
|
489
|
+
".....DDD.DD..DD.DDD.....",
|
|
490
|
+
],
|
|
491
|
+
// хвост вверх
|
|
492
|
+
WU: [
|
|
493
|
+
".................G..G...",
|
|
494
|
+
"................GKGGKG..",
|
|
495
|
+
"................GKKKKG..",
|
|
496
|
+
"...............GKAKKKKG.",
|
|
497
|
+
"GG............GKKKKKKKKN",
|
|
498
|
+
"GKG..........GKKKKKKKDDN",
|
|
499
|
+
".GKG........GKKKKKKD.PP.",
|
|
500
|
+
"..GKGGGGGGGGKKKKKK...p..",
|
|
501
|
+
"..GKKKKKKKKKKKKKKK......",
|
|
502
|
+
".GKKKKKKKKKKKKKKKKG.....",
|
|
503
|
+
".GKKKKKKKKKKKKKKKG......",
|
|
504
|
+
"..GKKKKKDDDDDKKKKG......",
|
|
505
|
+
"....GKKKD....GKDKK......",
|
|
506
|
+
".....GKK.D...GKDKK......",
|
|
507
|
+
".....GKK.D...GKDKK......",
|
|
508
|
+
".....DDD.DD..DD.DDD.....",
|
|
509
|
+
],
|
|
510
|
+
// шаг, фаза 1
|
|
511
|
+
WA: [
|
|
512
|
+
".................G..G...",
|
|
513
|
+
"................GKGGKG..",
|
|
514
|
+
"................GKKKKG..",
|
|
515
|
+
"...............GKAKKKKG.",
|
|
516
|
+
"..............GKKKKKKKKN",
|
|
517
|
+
".............GKKKKKKKDDN",
|
|
518
|
+
"............GKKKKKKD.PP.",
|
|
519
|
+
"...GGGGGGGGGKKKKKK...p..",
|
|
520
|
+
"..GKKKKKKKKKKKKKKK......",
|
|
521
|
+
".GKKKKKKKKKKKKKKKKG.....",
|
|
522
|
+
"GKKKKKKKKKKKKKKKKG......",
|
|
523
|
+
"GKGKKKKKDDDDDKKKKG......",
|
|
524
|
+
"GKG.GKKKDD...GKDKK......",
|
|
525
|
+
"GKG..GKK..D.GKD..GK.....",
|
|
526
|
+
".GK.GKK....GKD....GK....",
|
|
527
|
+
"...DDD....DD.......DD...",
|
|
528
|
+
],
|
|
529
|
+
// шаг, фаза 2
|
|
530
|
+
WB: [
|
|
531
|
+
".................G..G...",
|
|
532
|
+
"................GKGGKG..",
|
|
533
|
+
"................GKKKKG..",
|
|
534
|
+
"...............GKAKKKKG.",
|
|
535
|
+
"..............GKKKKKKKKN",
|
|
536
|
+
".............GKKKKKKKDDN",
|
|
537
|
+
"............GKKKKKKD.PP.",
|
|
538
|
+
"...GGGGGGGGGKKKKKK...p..",
|
|
539
|
+
"..GKKKKKKKKKKKKKKK......",
|
|
540
|
+
".GKKKKKKKKKKKKKKKKG.....",
|
|
541
|
+
"GKKKKKKKKKKKKKKKKG......",
|
|
542
|
+
"GKGKKKKKDDDDDKKKKG......",
|
|
543
|
+
"GKG.GKKKD...GKDKK.......",
|
|
544
|
+
"GKG...GKKD..GKD.GK......",
|
|
545
|
+
".GK....GKKD.GK...GK.....",
|
|
546
|
+
".......DDD.DDD....DDD...",
|
|
547
|
+
],
|
|
548
|
+
// лежит
|
|
549
|
+
L: [
|
|
550
|
+
"........................",
|
|
551
|
+
"........................",
|
|
552
|
+
"........................",
|
|
553
|
+
".................G..G...",
|
|
554
|
+
"................GKGGKG..",
|
|
555
|
+
"................GKKKKG..",
|
|
556
|
+
"...............GKAKKKKG.",
|
|
557
|
+
"..............GKKKKKKKKN",
|
|
558
|
+
".............GKKKKKKKDDN",
|
|
559
|
+
"............GKKKKKKD.PP.",
|
|
560
|
+
".....GGGGGGGKKKKKK...p..",
|
|
561
|
+
"..GGGKKKKKKKKKKKKKG.....",
|
|
562
|
+
".GKKKKKKKKKKKKKKKKKG....",
|
|
563
|
+
"GKKKKKKKKKKKKKKKKKKKG...",
|
|
564
|
+
"GKKKKKKKKKKKKKKKKKKKKKG.",
|
|
565
|
+
"GGDDDDDDDDDDDDDDDDDDDDD.",
|
|
566
|
+
],
|
|
567
|
+
// лежит, моргнул
|
|
568
|
+
LB: [
|
|
569
|
+
"........................",
|
|
570
|
+
"........................",
|
|
571
|
+
"........................",
|
|
572
|
+
".................G..G...",
|
|
573
|
+
"................GKGGKG..",
|
|
574
|
+
"................GKKKKG..",
|
|
575
|
+
"...............GKDKKKKG.",
|
|
576
|
+
"..............GKKKKKKKKN",
|
|
577
|
+
".............GKKKKKKKDDN",
|
|
578
|
+
"............GKKKKKKD.PP.",
|
|
579
|
+
".....GGGGGGGKKKKKK...p..",
|
|
580
|
+
"..GGGKKKKKKKKKKKKKG.....",
|
|
581
|
+
".GKKKKKKKKKKKKKKKKKG....",
|
|
582
|
+
"GKKKKKKKKKKKKKKKKKKKG...",
|
|
583
|
+
"GKKKKKKKKKKKKKKKKKKKKKG.",
|
|
584
|
+
"GGDDDDDDDDDDDDDDDDDDDDD.",
|
|
585
|
+
],
|
|
586
|
+
// лежит, дышит
|
|
587
|
+
LP: [
|
|
588
|
+
"........................",
|
|
589
|
+
"........................",
|
|
590
|
+
"........................",
|
|
591
|
+
".................G..G...",
|
|
592
|
+
"................GKGGKG..",
|
|
593
|
+
"................GKKKKG..",
|
|
594
|
+
"...............GKAKKKKG.",
|
|
595
|
+
"..............GKKKKKKKKN",
|
|
596
|
+
".............GKKKKKKKDDN",
|
|
597
|
+
"............GKKKKKKDD.P.",
|
|
598
|
+
".....GGGGGGGKKKKKK......",
|
|
599
|
+
"..GGGKKKKKKKKKKKKKG.....",
|
|
600
|
+
".GKKKKKKKKKKKKKKKKKG....",
|
|
601
|
+
"GKKKKKKKKKKKKKKKKKKKG...",
|
|
602
|
+
"GKKKKKKKKKKKKKKKKKKKKKG.",
|
|
603
|
+
"GGDDDDDDDDDDDDDDDDDDDDD.",
|
|
604
|
+
],
|
|
605
|
+
// лежит, дёрнул хвостом
|
|
606
|
+
LT: [
|
|
607
|
+
"........................",
|
|
608
|
+
"........................",
|
|
609
|
+
"........................",
|
|
610
|
+
".................G..G...",
|
|
611
|
+
"................GKGGKG..",
|
|
612
|
+
"................GKKKKG..",
|
|
613
|
+
"...............GKAKKKKG.",
|
|
614
|
+
"..............GKKKKKKKKN",
|
|
615
|
+
".............GKKKKKKKDDN",
|
|
616
|
+
"............GKKKKKKD.PP.",
|
|
617
|
+
".....GGGGGGGKKKKKK...p..",
|
|
618
|
+
"..GGGKKKKKKKKKKKKKG.....",
|
|
619
|
+
".GKKKKKKKKKKKKKKKKKG....",
|
|
620
|
+
"GKKKKKKKKKKKKKKKKKKKG...",
|
|
621
|
+
"GGKKKKKKKKKKKKKKKKKKKKG.",
|
|
622
|
+
"G.DDDDDDDDDDDDDDDDDDDDD.",
|
|
623
|
+
],
|
|
624
|
+
// грустит: уши прижаты
|
|
625
|
+
SAD: [
|
|
626
|
+
"........................",
|
|
627
|
+
"........................",
|
|
628
|
+
"........................",
|
|
629
|
+
"........................",
|
|
630
|
+
"................GGGGGG..",
|
|
631
|
+
"................GKKKKG..",
|
|
632
|
+
"...............GKAKKKKG.",
|
|
633
|
+
"..............GKKKKKKKKN",
|
|
634
|
+
".............GKKKKKKKDDN",
|
|
635
|
+
"............GKKKKKKDD...",
|
|
636
|
+
".....GGGGGGGKKKKKK......",
|
|
637
|
+
"..GGGKKKKKKKKKKKKKG.....",
|
|
638
|
+
".GKKKKKKKKKKKKKKKKKG....",
|
|
639
|
+
"GKKKKKKKKKKKKKKKKKKKG...",
|
|
640
|
+
"GKKKKKKKKKKKKKKKKKKKKKG.",
|
|
641
|
+
"GGDDDDDDDDDDDDDDDDDDDDD.",
|
|
642
|
+
],
|
|
643
|
+
// грустит, моргнул
|
|
644
|
+
SADB: [
|
|
645
|
+
"........................",
|
|
646
|
+
"........................",
|
|
647
|
+
"........................",
|
|
648
|
+
"........................",
|
|
649
|
+
"................GGGGGG..",
|
|
650
|
+
"................GKKKKG..",
|
|
651
|
+
"...............GKDKKKKG.",
|
|
652
|
+
"..............GKKKKKKKKN",
|
|
653
|
+
".............GKKKKKKKDDN",
|
|
654
|
+
"............GKKKKKKDD...",
|
|
655
|
+
".....GGGGGGGKKKKKK......",
|
|
656
|
+
"..GGGKKKKKKKKKKKKKG.....",
|
|
657
|
+
".GKKKKKKKKKKKKKKKKKG....",
|
|
658
|
+
"GKKKKKKKKKKKKKKKKKKKG...",
|
|
659
|
+
"GKKKKKKKKKKKKKKKKKKKKKG.",
|
|
660
|
+
"GGDDDDDDDDDDDDDDDDDDDDD.",
|
|
661
|
+
],
|
|
662
|
+
// спит
|
|
663
|
+
Z: [
|
|
664
|
+
"........................",
|
|
665
|
+
"........................",
|
|
666
|
+
"........................",
|
|
667
|
+
"........................",
|
|
668
|
+
"........................",
|
|
669
|
+
"........................",
|
|
670
|
+
"........................",
|
|
671
|
+
"................G.G.....",
|
|
672
|
+
"...............GKGKG....",
|
|
673
|
+
"....GGGGGGG...GKKKKKG...",
|
|
674
|
+
"..GGKKKKKKKGGGKKDDKKKG..",
|
|
675
|
+
".GKKKKKKKKKKKKKKKKKKKKKN",
|
|
676
|
+
"GKKKKKKKKKKKKKKKKKKKKDDN",
|
|
677
|
+
"GKKKKKKKKKKKKKKKKKKKKKG.",
|
|
678
|
+
"GKKKKKKKKKKKKKKKKDKKKKKG",
|
|
679
|
+
"GGDDDDDDDDDDDDDDDDDDDDD.",
|
|
680
|
+
],
|
|
681
|
+
// спит, вдох
|
|
682
|
+
ZB: [
|
|
683
|
+
"........................",
|
|
684
|
+
"........................",
|
|
685
|
+
"........................",
|
|
686
|
+
"........................",
|
|
687
|
+
"........................",
|
|
688
|
+
"........................",
|
|
689
|
+
"........................",
|
|
690
|
+
"................G.G.....",
|
|
691
|
+
"....GGGGGGG....GKGKG....",
|
|
692
|
+
"..GGKKKKKKKGGGGKKKKKG...",
|
|
693
|
+
".GKKKKKKKKKKKKKKDDKKKG..",
|
|
694
|
+
".KKKKKKKKKKKKKKKKKKKKKKN",
|
|
695
|
+
"GKKKKKKKKKKKKKKKKKKKKDDN",
|
|
696
|
+
"GKKKKKKKKKKKKKKKKKKKKKG.",
|
|
697
|
+
"GKKKKKKKKKKKKKKKKDKKKKKG",
|
|
698
|
+
"GGDDDDDDDDDDDDDDDDDDDDD.",
|
|
699
|
+
],
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Анимация по состоянию: every — каждые N тиков по 500 мс, seq — кадры по кругу.
|
|
704
|
+
* В простое редко и в основном «стоит», чтобы не отвлекать; в работе — шаг.
|
|
705
|
+
*/
|
|
706
|
+
type PxState = "work" | "happy" | "love" | "eat" | "idle" | "rest" | "sad" | "sleep" | "toss" | "chew";
|
|
707
|
+
const PX_ANIM: Record<PxState, { every: number; seq: PxFrame[] }> = {
|
|
708
|
+
work: { every: 1, seq: ["S", "WA", "S", "WB"] }, // идёт
|
|
709
|
+
happy: { every: 1, seq: ["S", "WM", "WU", "WM"] }, // виляет хвостом
|
|
710
|
+
love: { every: 1, seq: ["WM", "WU"] }, // виляет вовсю
|
|
711
|
+
eat: { every: 2, seq: ["S", "SP"] },
|
|
712
|
+
idle: { every: 2, seq: ["S", "S", "S", "SB", "S", "SP", "S", "SP", "S", "S", "SE", "S", "WM", "S", "S", "SP"] },
|
|
713
|
+
rest: { every: 2, seq: ["L", "L", "L", "LB", "L", "LP", "L", "LP", "L", "L", "LT", "L"] }, // прилёг
|
|
714
|
+
sad: { every: 2, seq: ["SAD", "SAD", "SAD", "SADB", "SAD", "SAD"] },
|
|
715
|
+
sleep: { every: 3, seq: ["Z", "ZB"] }, // дышит во сне
|
|
716
|
+
toss: { every: 1, seq: ["S", "WM", "WU", "WM"] }, // играет: подпрыгивает за предметом
|
|
717
|
+
chew: { every: 2, seq: ["L", "LP", "L", "LP", "L", "LB"] }, // лёжа возится с предметом
|
|
718
|
+
};
|
|
719
|
+
/** Состояния «лёжа»: переход между ними идёт через L, а не через стойку */
|
|
720
|
+
const PX_LYING = new Set<PxState>(["rest", "sad", "sleep", "chew"]);
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Любимые предметы. toss — прыгает в руки (мячик скачет), chew — лежит у лап.
|
|
724
|
+
* Рисуются поверх кадра у земли рядом с питомцем; say — дежурные фразы, пока идёт игра.
|
|
725
|
+
*/
|
|
726
|
+
interface PxItem {
|
|
727
|
+
name: string;
|
|
728
|
+
mode: "toss" | "chew";
|
|
729
|
+
art: string[];
|
|
730
|
+
say: string[];
|
|
731
|
+
}
|
|
732
|
+
const IT: Record<string, PxItem> = {
|
|
733
|
+
ball: { name: "мячик", mode: "toss", art: [".RR.", "RwRR", "RRRR", ".RR."], say: ["гоняет мячик", "ловит мячик", "апорт!", "мячик! мячик!"] },
|
|
734
|
+
bone: { name: "косточка", mode: "chew", art: ["g...g", "wwwww", "g...g"], say: ["грызёт косточку", "охраняет косточку", "хрум-хрум"] },
|
|
735
|
+
frisbee: { name: "фрисби", mode: "toss", art: [".RRR.", "ROOOR", ".RRR."], say: ["ловит фрисби", "фрисби! ещё!", "прыжок за тарелкой"] },
|
|
736
|
+
rope: { name: "канат", mode: "chew", art: ["T.T.T.", "wTwTwT", ".T.T.T"], say: ["треплет канат", "тянет канат", "рррр, канат!"] },
|
|
737
|
+
cherry: { name: "вишенка", mode: "toss", art: ["..v", ".v.", "RvR", "RR."], say: ["подбрасывает вишенку", "ням, вишенка"] },
|
|
738
|
+
sugar: { name: "сахарок", mode: "chew", art: ["ww", "wg"], say: ["тает от сахарка", "облизывает сахарок"] },
|
|
739
|
+
yarn: { name: "клубок", mode: "toss", art: [".mm.", "mkmm", "mmkm", ".mm."], say: ["гоняет клубок", "запутался в нитках", "катает клубок"] },
|
|
740
|
+
mouse: { name: "мышка", mode: "chew", art: ["..gg", "gEgg", "sggg"], say: ["охотится на мышку", "мышка моя!", "трогает мышку лапкой"] },
|
|
741
|
+
rubber: { name: "уточка-пищалка", mode: "toss", art: [".YO", "YYY", "YY."], say: ["пищит уточкой", "играет с пищалкой"] },
|
|
742
|
+
bread: { name: "хлебушек", mode: "chew", art: [".TT.", "TTTT", "tTTt"], say: ["клюёт хлебушек", "крошки!", "ням-ням хлеб"] },
|
|
743
|
+
lantern: { name: "фонарик", mode: "toss", art: [".b.", "YYY", "YOY", "bbb"], say: ["светит фонариком", "парит с фонариком"] },
|
|
744
|
+
cookie: { name: "печенька", mode: "chew", art: [".TT.", "TtTT", "TTtT", ".TT."], say: ["хрустит печенькой", "призрачная печенька"] },
|
|
745
|
+
feather: { name: "пёрышко", mode: "toss", art: ["..w", ".w.", ".w.", "w.."], say: ["ловит пёрышко", "чистит пёрышко"] },
|
|
746
|
+
book: { name: "книжка", mode: "chew", art: ["RRRR", "Rwww", "RRRR"], say: ["читает книжку", "изучает документацию", "листает страницы"] },
|
|
747
|
+
bubbles: { name: "пузырьки", mode: "toss", art: ["L..", "..L", ".L."], say: ["пускает пузырьки", "буль-буль"] },
|
|
748
|
+
shell: { name: "ракушка", mode: "chew", art: [".k.", "kwk", "kkk"], say: ["любуется ракушкой", "слушает ракушку"] },
|
|
749
|
+
coin: { name: "монетка", mode: "toss", art: [".Y.", "YyY", ".Y."], say: ["подкидывает монетку", "орёл или решка?"] },
|
|
750
|
+
gold: { name: "сокровище", mode: "chew", art: ["..Y...", ".YyY..", "YyYyYy"], say: ["лежит на сокровищах", "пересчитывает золото", "моё золото!"] },
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
/** Пиксельные кадры остальных питомцев (те же 16 кадров, что у Сальсы) */
|
|
755
|
+
const ZOO_PX: Record<string, Record<PxFrame, string[]>> = {
|
|
756
|
+
blob: {
|
|
757
|
+
S: ["........................", "........................", "........................", "........................", "..........cccc..........", "........ccCCCCcc........", ".......cCCwCCCCCc.......", "......cCCwCCCCCCCc......", "......cCCCECCCECCc......", ".....cCCCCECCCECCCc.....", ".....cCCCCCCCCCCCCc.....", ".....cCCCCCkCkCCCCc.....", ".....cCCCCCCCCCCCCc.....", "......cCCCCCCCCCCc......", ".......cccccccccc.......", "........................"],
|
|
758
|
+
SB: ["........................", "........................", "........................", "........................", "..........cccc..........", "........ccCCCCcc........", ".......cCCwCCCCCc.......", "......cCCwCCCCCCCc......", "......cCCCCCCCCCCc......", ".....cCCCCCCCCCCCCc.....", ".....cCCCCCCCCCCCCc.....", ".....cCCCCCkCkCCCCc.....", ".....cCCCCCCCCCCCCc.....", "......cCCCCCCCCCCc......", ".......cccccccccc.......", "........................"],
|
|
759
|
+
SP: ["........................", "........................", "........................", "........................", "...........cccc.........", "........ccCCCCcc........", "........cCCwCCCCCc......", "......cCCwCCCCCCCc......", ".......cCCCECCCECCc.....", ".....cCCCCECCCECCCc.....", "......cCCCCCCCCCCCCc....", ".....cCCCCCkCkCCCCc.....", "......cCCCCCCCCCCCCc....", "......cCCCCCCCCCCc......", "........cccccccccc......", "........................"],
|
|
760
|
+
SE: ["........................", "........................", "..........cccc..........", ".........cCCCCc.........", "........cCwCCCCc........", "........cCwCCCCc........", ".......cCCCCCCCCc.......", ".......cCCECCECCc.......", ".......cCCECCECCc.......", ".......cCCCCCCCCc.......", ".......cCCCkkCCCc.......", ".......cCCCCCCCCc.......", ".......cCCCCCCCCc.......", "......cCCCCCCCCCCc......", "......cccccccccccc......", "........................"],
|
|
761
|
+
WM: ["........................", "..........cccc..........", ".........cCCCCc.........", "........cCwCCCCc........", "........cCwCCCCc........", ".......cCCCCCCCCc.......", ".......cCCECCECCc.......", ".......cCCECCECCc.......", ".......cCCCCCCCCc.......", ".......cCCCkkCCCc.......", ".......cCCCCCCCCc.......", ".......cCCCCCCCCc.......", "......cCCCCCCCCCCc......", "......cccccccccccc......", "........................", "........................"],
|
|
762
|
+
WU: ["..........cccc..........", ".........cCCCCc.........", "........cCwCCCCc........", "........cCwCCCCc........", ".......cCCCCCCCCc.......", ".......cCCECCECCc.......", ".......cCCECCECCc.......", ".......cCCCCCCCCc.......", ".......cCCCkkCCCc.......", ".......cCCCCCCCCc.......", ".......cCCCCCCCCc.......", "......cCCCCCCCCCCc......", "......cccccccccccc......", "........................", "........................", "........................"],
|
|
763
|
+
WA: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "..........cccc..........", ".......ccCCCCCCcc.......", "......cCCwCCCCCCCc......", ".....cCCCCECCCECCCc.....", ".....cCCCCCCCCCCCCc.....", "....cCCCCCCkCkCCCCCc....", "....cCCCCCCCCCCCCCCc....", ".....cccccccccccccc.....", "........................"],
|
|
764
|
+
WB: ["........................", "........................", "..........cccc..........", ".........cCCCCc.........", "........cCwCCCCc........", "........cCwCCCCc........", ".......cCCCCCCCCc.......", ".......cCCECCECCc.......", ".......cCCECCECCc.......", ".......cCCCCCCCCc.......", ".......cCCCkkCCCc.......", ".......cCCCCCCCCc.......", ".......cCCCCCCCCc.......", "......cCCCCCCCCCCc......", "......cccccccccccc......", "........................"],
|
|
765
|
+
L: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "..........cccc..........", ".......ccCCCCCCcc.......", "......cCCwCCCCCCCc......", ".....cCCCCECCCECCCc.....", ".....cCCCCCCCCCCCCc.....", "....cCCCCCCkCkCCCCCc....", "....cCCCCCCCCCCCCCCc....", ".....cccccccccccccc.....", "........................"],
|
|
766
|
+
LB: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "..........cccc..........", ".......ccCCCCCCcc.......", "......cCCwCCCCCCCc......", ".....cCCCCCCCCCCCCc.....", ".....cCCCCCCCCCCCCc.....", "....cCCCCCCkCkCCCCCc....", "....cCCCCCCCCCCCCCCc....", ".....cccccccccccccc.....", "........................"],
|
|
767
|
+
LP: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "..........cccc..........", "........ccCCCCCCcc......", "......cCCwCCCCCCCc......", "......cCCCCECCCECCCc....", ".....cCCCCCCCCCCCCc.....", ".....cCCCCCCkCkCCCCCc...", "....cCCCCCCCCCCCCCCc....", "......cccccccccccccc....", "........................"],
|
|
768
|
+
LT: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "..........cccc..........", ".......ccCCCCCCcc.......", "......cCCwCCCCCCCc......", ".....cCCCCECCCECCCc.....", ".....cCCCCCCCCCCCCc.....", "....cCCCCCCkCkCCCCCc....", "....cCCCCCCCCCCCCCCc....", ".....cccccccccccccc.....", "........................"],
|
|
769
|
+
SAD: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "..........cccc..........", ".......ccCCCCCCcc.......", "......cCCwCCCCCCCc......", ".....cCCCCECCCECCCc.....", ".....cCCCCLCCCLCCCc.....", "....cCCCCCCkCkCCCCCc....", "....cCCCCCCCCCCCCCCc....", ".....cccccccccccccc.....", "........................"],
|
|
770
|
+
SADB: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "..........cccc..........", ".......ccCCCCCCcc.......", "......cCCwCCCCCCCc......", ".....cCCCCCCCCCCCCc.....", ".....cCCCCCCCCCCCCc.....", "....cCCCCCCkCkCCCCCc....", "....cCCCCCCCCCCCCCCc....", ".....cccccccccccccc.....", "........................"],
|
|
771
|
+
Z: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "..........cccc..........", ".......ccCCCCCCcc.......", "......cCCwCCCCCCCc......", ".....cCCCCCCCCCCCCc.....", ".....cCCCCCCCCCCCCc.....", "....cCCCCCCkCkCCCCCc....", "....cCCCCCCCCCCCCCCc....", ".....cccccccccccccc.....", "........................"],
|
|
772
|
+
ZB: ["........................", "........................", "........................", "........................", "........................", "........................", "..........cccc..........", ".........Ccccc..........", ".......ccCCCCCCcc.......", "......cCCwCCCCCCCc......", ".....cCCCCCCCCCCCCc.....", ".....cCCCCCCCCCCCCc.....", "....cCCCCCCkCkCCCCCc....", "....cCCCCCCCCCCCCCCc....", ".....cccccccccccccc.....", "........................"],
|
|
773
|
+
},
|
|
774
|
+
cat: {
|
|
775
|
+
S: ["........................", "........................", "......s......s..........", "......sS....sS..........", "......sSS..sSS..........", "......sSSSSSSS..........", ".....sSSSSSSSSs.........", ".....sSESSSSESs.........", ".....sSSSkkSSSs.........", "......sSSSSSSs..........", "......sSSSSSSs....s.....", ".....sSSSwwSSSs...sS....", ".....sSSwwwwSSs..sS.....", ".....sSSwwwwSSssSS......", ".....sSSSSSSSSSSs.......", "......ss.ss.ss.........."],
|
|
776
|
+
SB: ["........................", "........................", "......s......s..........", "......sS....sS..........", "......sSS..sSS..........", "......sSSSSSSS..........", ".....sSSSSSSSSs.........", ".....sSSSSSSSSs.........", ".....sSSSkkSSSs.........", "......sSSSSSSs..........", "......sSSSSSSs....s.....", ".....sSSSwwSSSs...sS....", ".....sSSwwwwSSs..sS.....", ".....sSSwwwwSSssSS......", ".....sSSSSSSSSSSs.......", "......ss.ss.ss.........."],
|
|
777
|
+
SP: ["........................", "........................", "......s......s..........", "......sS....sS..........", "......sSS..sSS..........", "......sSSSSSSS..........", ".....sSSSSSSSSs.........", ".....sSESSSSESs.........", ".....sSSSkkSSSs..s......", "......sSSSSSSs...sS.....", "......sSSSSSSs...sS.....", ".....sSSSwwSSS..sS......", ".....sSSwwwwSS.sS.......", ".....sSSwwwwSSss........", ".....sSSSSSSSSSSs.......", "......ss.ss.ss.........."],
|
|
778
|
+
SE: ["........................", "........................", "......s.......s.........", "......sS....sSS.........", "......sSS..sSS..........", "......sSSSSSSS..........", ".....sSSSSSSSSs.........", ".....sSESSSSESs.........", ".....sSSSkkSSSs.........", "......sSSSSSSs..........", "......sSSSSSSs....s.....", ".....sSSSwwSSSs...sS....", ".....sSSwwwwSSs..sS.....", ".....sSSwwwwSSssSS......", ".....sSSSSSSSSSSs.......", "......ss.ss.ss.........."],
|
|
779
|
+
WM: ["........................", "......s......s..........", "......sS....sS..........", "......sSS..sSS..........", "......sSSSSSSS..........", ".....sSSSSSSSSs.........", ".....sSESSSSESs.........", ".....sSSSkkSSSs..s......", "......sSSSSSSs...sS.....", "......sSSSSSSs...sS.....", ".....sSSSwwSSS..sS......", ".....sSSwwwwSS.sS.......", ".....sSSwwwwSSss........", ".....sSSSSSSSSSSs.......", "......ss.ss.ss..........", "........................"],
|
|
780
|
+
WU: ["......s......s..........", "......sS....sS..........", "......sSS..sSS..........", "......sSSSSSSS..........", ".....sSSSSSSSSs.........", ".....sSESSSSESs.........", ".....sSSSkkSSSs..s......", "......sSSSSSSs...sS.....", "......sSSSSSSs...sS.....", ".....sSSSwwSSS..sS......", ".....sSSwwwwSS.sS.......", ".....sSSwwwwSSss........", ".....sSSSSSSSSSSs.......", "......ss.ss.ss..........", "........................", "........................"],
|
|
781
|
+
WA: ["........................", "........................", "......s......s..........", "......sS....sS..........", "......sSS..sSS..........", "......sSSSSSSS..........", ".....sSSSSSSSSs.........", ".....sSESSSSESs.........", ".....sSSSkkSSSs..s......", "......sSSSSSSs...sS.....", "......sSSSSSSs...sS.....", ".....sSSSwwSSS..sS......", ".....sSSwwwwSS.sS.......", ".....sSSwwwwSSss........", ".....sSSSSSSSSSSs.......", "......ss.ss.ss.........."],
|
|
782
|
+
WB: ["........................", "........................", "......s......s..........", "......sS....sS..........", "......sSS..sSS..........", "......sSSSSSSS..........", ".....sSSSSSSSSs.........", ".....sSESSSSESs.........", ".....sSwSkkSSSs.........", "......swwSSSSs..........", "......sSSSSSSs....s.....", ".....sSSSwwSSSs...sS....", ".....sSSwwwwSSs..sS.....", ".....sSSwwwwSSssSS......", ".....sSSSSSSSSSSs.......", "......ss.ss.ss.........."],
|
|
783
|
+
L: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "......s......s..........", "......sS....sS..........", "......sSSSSSSS..........", ".....sSESSSSESs.........", ".....sSSSkkSSSssssss....", "....sSSSSSSSSSSSSSSSs...", "....sSSSSSSSSSSSSSSSs...", ".....ssssssssssssss.....", "........................"],
|
|
784
|
+
LB: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "......s......s..........", "......sS....sS..........", "......sSSSSSSS..........", ".....sSSSSSSSSs.........", ".....sSSSkkSSSssssss....", "....sSSSSSSSSSSSSSSSs...", "....sSSSSSSSSSSSSSSSs...", ".....ssssssssssssss.....", "........................"],
|
|
785
|
+
LP: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "......s......s..........", "......sS....sS..........", "......sSSSSSSS..........", ".....sSESSSSESs...s.....", ".....sSSSkkSSSssssSs....", "....sSSSSSSSSSSSSSSSs...", "....sSSSSSSSSSSSSSSSs...", ".....ssssssssssssss.....", "........................"],
|
|
786
|
+
LT: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "......s......s..........", "......sS....sS..........", "......sSSSSSSS..........", ".....sSESSSSESs.........", ".....sSSSkkSSSssssss....", "....sSSSSSSSSSSSSSSSs...", "....sSSSSSSSSSSSSSSSs...", ".....ssssssssssssss.....", "........................"],
|
|
787
|
+
SAD: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "......s......s..........", "......sS....sS..........", "......sSSSSSSS..........", ".....sSESSSSESs.........", ".....sSLSkkSLSssssss....", "....sSSSSSSSSSSSSSSSs...", "....sSSSSSSSSSSSSSSSs...", ".....ssssssssssssss.....", "........................"],
|
|
788
|
+
SADB: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "......s......s..........", "......sS....sS..........", "......sSSSSSSS..........", ".....sSSSSSSSSs.........", ".....sSSSkkSSSssssss....", "....sSSSSSSSSSSSSSSSs...", "....sSSSSSSSSSSSSSSSs...", ".....ssssssssssssss.....", "........................"],
|
|
789
|
+
Z: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "........................", "........ss..ss..........", ".......sSSssSSs.........", "......sSSSSSSSSs........", "......sSsSSsSSSSs.......", ".....sSSSSkSSSSSSs......", ".....sSSSSSSSSSSSSs.....", "......ssSSSSSSSSSss.....", "........ssssssssss......"],
|
|
790
|
+
ZB: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", ".........s..ss..........", "........ssssss..........", ".......sSSssSSs.........", "......sSSSSSSSSs........", "......sSsSSsSSSSs.......", ".....sSSSSkSSSSSSs......", ".....sSSSSSSSSSSSSs.....", "......ssSSSSSSSSSss.....", "........ssssssssss......"],
|
|
791
|
+
},
|
|
792
|
+
duck: {
|
|
793
|
+
S: ["........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYEYYOO.....", "...........YYYYYOOOO....", "............YYYY........", ".......Y...YYYYY........", ".......YYYYYYYYYY.......", ".......YYYyyyyYYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "...........O..O.........", "..........OO.OO........."],
|
|
794
|
+
SB: ["........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYYYYOO.....", "...........YYYYYOOOO....", "............YYYY........", ".......Y...YYYYY........", ".......YYYYYYYYYY.......", ".......YYYyyyyYYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "...........O..O.........", "..........OO.OO........."],
|
|
795
|
+
SP: ["........................", "........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYEYYOO.....", "...........YYYYYOOOO....", ".......Y...YYYYY........", ".......YYYYYYYYYY.......", ".......YYYyyyyYYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "...........O..O.........", "..........OO.OO........."],
|
|
796
|
+
SE: ["........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYEYYOO.....", "...........YYYYYOOOO....", ".........yy.YYYY........", ".......YyyyYYYYY........", ".......YYYYYYYYYY.......", ".......YYYyyyyYYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "...........O..O.........", "..........OO.OO........."],
|
|
797
|
+
WM: ["........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYEYYOO.....", "...........YYYYYOOOO....", ".........yy.YYYY........", ".......YyyyYYYYY........", ".......YYYYYYYYYY.......", ".......YYYyyyyYYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "...........O..O.........", "..........OO.OO.........", "........................"],
|
|
798
|
+
WU: ["........................", "............YYYY........", "...........YYYYYY.......", "...........YYYEYYOO.....", "...........YYYYYOOOO....", ".........yy.YYYY........", ".......YyyyYYYYY........", ".......YYYYYYYYYY.......", ".......YYYyyyyYYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "...........O..O.........", "..........OO.OO.........", "........................", "........................"],
|
|
799
|
+
WA: ["........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYEYYOO.....", "...........YYYYYOOOO....", "............YYYY........", ".......Y...YYYYY........", ".......YYYYYYYYYY.......", ".......YYYyyyyYYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "..........O....O........", ".........OO....OO......."],
|
|
800
|
+
WB: ["........................", "........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYEYYOO.....", "...........YYYYYOOOO....", ".......Y...YYYYY........", ".......YYYYYYYYYY.......", ".......YYYyyyyYYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "...........O..O.........", "..........OO.OO........."],
|
|
801
|
+
L: ["........................", "........................", "........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYEYYOO.....", "...........YYYYYOOOO....", ".......Y....YYYY........", ".......YYYYYYYYYYY......", ".......YYYyyyyyYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "........................"],
|
|
802
|
+
LB: ["........................", "........................", "........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYYYYOO.....", "...........YYYYYOOOO....", ".......Y....YYYY........", ".......YYYYYYYYYYY......", ".......YYYyyyyyYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "........................"],
|
|
803
|
+
LP: ["........................", "........................", "........................", "........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYEYYOO.....", ".......Y...YYYYYOOOO....", ".......YYYYYYYYYYY......", ".......YYYyyyyyYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "........................"],
|
|
804
|
+
LT: ["........................", "........................", "........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYEYYOO.....", "...........YYYYYOOOO....", ".......Y....YYYY........", ".......YYYYYYYYYYY......", ".......YYYyyyyyYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "........................"],
|
|
805
|
+
SAD: ["........................", "........................", "........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYEYYOO.....", "...........YYYLYOOOO....", ".......Y....YYYY........", ".......YYYYYYYYYYY......", ".......YYYyyyyyYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "........................"],
|
|
806
|
+
SADB: ["........................", "........................", "........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYYYYOO.....", "...........YYYYYOOOO....", ".......Y....YYYY........", ".......YYYYYYYYYYY......", ".......YYYyyyyyYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "........................"],
|
|
807
|
+
Z: ["........................", "........................", "........................", "........................", "........................", "............YYYY........", "...........YYYYYY.......", "...........YYYYYY.......", "...........YYYYYO.......", ".......Y....YYYYyy......", ".......YYYYYYYYYYY......", ".......YYYyyyyyYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "........................"],
|
|
808
|
+
ZB: ["........................", "........................", "........................", "........................", "............YY..........", "...........YYYYY........", "...........YYYYYY.......", "...........YYYYYY.......", "...........YYYYYO.......", ".......Y..Y.YYYYyy......", ".......YYYYYYYYYYY......", ".......YYYyyyyyYYY......", ".......YYYyyyyyYYY......", "........YYYYYYYYY.......", ".........YYYYYYY........", "........................"],
|
|
809
|
+
},
|
|
810
|
+
ghost: {
|
|
811
|
+
S: ["........................", "........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwEwwEwwg.......", ".......gwwEwwEwwg.......", ".......gwwwwwwwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwgwwgwwgw.......", ".......g.g.gg.g.g.......", "........................"],
|
|
812
|
+
SB: ["........................", "........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwgwwgwwg.......", ".......gwwgwwgwwg.......", ".......gwwwwwwwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwgwwgwwgw.......", ".......g.g.gg.g.g.......", "........................"],
|
|
813
|
+
SP: ["........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwEwwEwwg.......", ".......gwwEwwEwwg.......", ".......gwwwwwwwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwgwwgwwgw.......", ".......g.g.gg.g.g.......", "........................", "........................"],
|
|
814
|
+
SE: ["........................", "........................", "........................", ".........gggggg.........", "........gggggggg........", ".......gggggggggg.......", ".......gggEggEggg.......", ".......gggEggEggg.......", ".......gggggggggg.......", ".......ggggkkgggg.......", ".......gggggggggg.......", ".......gggggggggg.......", ".......gggggggggg.......", ".......gggggggggg.......", ".......g.g.gg.g.g.......", "........................"],
|
|
815
|
+
WM: ["........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwEwwEwwg.......", ".......gwwEwwEwwg.......", ".......gwwwwwwwwg.......", ".......gwwwEEwwwg.......", ".......gwwwEEwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwgwwgwwgw.......", ".......g.g.gg.g.g.......", "........................", "........................"],
|
|
816
|
+
WU: ["........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwEwwEwwg.......", ".......gwwEwwEwwg.......", ".......gwwwwwwwwg.......", ".......gwwwEEwwwg.......", ".......gwwwEEwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwgwwgwwgw.......", ".......g.g.gg.g.g.......", "........................", "........................", "........................"],
|
|
817
|
+
WA: ["........................", "........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwEwwEwwg.......", ".......gwwEwwEwwg.......", ".......gwwwwwwwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......wgwwgwwgwg.......", "........g.gg.g.g........", "........................"],
|
|
818
|
+
WB: ["........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwEwwEwwg.......", ".......gwwEwwEwwg.......", ".......gwwwwwwwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......wgwwgwwgwg.......", "........g.gg.g.g........", "........................", "........................"],
|
|
819
|
+
L: ["........................", "........................", "........................", "........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwEwwEwwg.......", ".......gwwEwwEwwg.......", ".......gwwwwwwwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwgwwgwwgw......."],
|
|
820
|
+
LB: ["........................", "........................", "........................", "........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwgwwgwwg.......", ".......gwwgwwgwwg.......", ".......gwwwwwwwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwgwwgwwgw......."],
|
|
821
|
+
LP: ["........................", "........................", "........................", "........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwEwwEwwg.......", ".......gwwEwwEwwg.......", ".......gwwwwwwwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......wgwwgwwgwg......."],
|
|
822
|
+
LT: ["........................", "........................", "........................", "........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwEwwEwwg.......", ".......gwwEwwEwwg.......", ".......gwwwwwwwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwgwwgwwgw......."],
|
|
823
|
+
SAD: ["........................", "........................", "........................", "........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwEwwEwwg.......", ".......gwwEwwEwwg.......", ".......gwwLwwLwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwgwwgwwgw......."],
|
|
824
|
+
SADB: ["........................", "........................", "........................", "........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwgwwgwwg.......", ".......gwwgwwgwwg.......", ".......gwwwwwwwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwgwwgwwgw......."],
|
|
825
|
+
Z: ["........................", "........................", "........................", "........................", "........................", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwgwwgwwg.......", ".......gwwgwwgwwg.......", ".......gwwwwwwwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwgwwgwwgw......."],
|
|
826
|
+
ZB: ["........................", "........................", "........................", "........................", "..........www...........", ".........gwwwwg.........", "........gwwwwwwg........", ".......gwwwwwwwwg.......", ".......gwwgwwgwwg.......", ".......gwwgwwgwwg.......", ".......gwwwwwwwwg.......", ".......gwwwkkwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwwwwwwwwg.......", ".......gwgwwgwwgw......."],
|
|
827
|
+
},
|
|
828
|
+
owl: {
|
|
829
|
+
S: ["........................", "........................", ".......b........b.......", ".......bB......Bb.......", ".......bBBBBBBBBb.......", "......bBeeeBBeeeBb......", "......bBeEeBBeEeBb......", "......bBeeeOOeeeBb......", "......bBBBBOOBBBBb......", "......bBTTTTTTTTBb......", "......bBTtTTtTTTBb......", "......bBTTTtTTtTBb......", "......bBTTTTTTTTBb......", ".......bBBBBBBBBb.......", "........OO....OO........", "........................"],
|
|
830
|
+
SB: ["........................", "........................", ".......b........b.......", ".......bB......Bb.......", ".......bBBBBBBBBb.......", "......bBBBBBBBBBBb......", "......bBBBBBBBBBBb......", "......bBBBBOOBBBBb......", "......bBBBBOOBBBBb......", "......bBTTTTTTTTBb......", "......bBTtTTtTTTBb......", "......bBTTTtTTtTBb......", "......bBTTTTTTTTBb......", ".......bBBBBBBBBb.......", "........OO....OO........", "........................"],
|
|
831
|
+
SP: ["........................", "........................", "........b........b......", "........bB......Bb......", "........bBBBBBBBBb......", ".......bBeeeBBeeeBb.....", ".......bBeEeBBeEeBb.....", ".......bBeeeOOeeeBb.....", ".......bBBBBOOBBBBb.....", "......bBTTTTTTTTBb......", "......bBTtTTtTTTBb......", "......bBTTTtTTtTBb......", "......bBTTTTTTTTBb......", ".......bBBBBBBBBb.......", "........OO....OO........", "........................"],
|
|
832
|
+
SE: ["........................", "........................", ".......b........b.......", ".......bB......Bb.......", ".......bBBBBBBBBb.......", "......bBBBBBBeeeBb......", "......bBBBBBBeEeBb......", "......bBBBBOOeeeBb......", "......bBBBBOOBBBBb......", "......bBTTTTTTTTBb......", "......bBTtTTtTTTBb......", "......bBTTTtTTtTBb......", "......bBTTTTTTTTBb......", ".......bBBBBBBBBb.......", "........OO....OO........", "........................"],
|
|
833
|
+
WM: ["........................", ".......b........b.......", ".......bB......Bb.......", ".......bBBBBBBBBb.......", "....b.bBeeeBBeeeBb.b....", "....bbbBeEeBBeEeBbbb....", ".....bbBeeeOOeeeBbb.....", "......bBBBBOOBBBBb......", "......bBTTTTTTTTBb......", "......bBTtTTtTTTBb......", "......bBTTTtTTtTBb......", "......bBTTTTTTTTBb......", ".......bBBBBBBBBb.......", "........OO....OO........", "........................", "........................"],
|
|
834
|
+
WU: [".......b........b.......", ".......bB......Bb.......", ".......bBBBBBBBBb.......", "....b.bBeeeBBeeeBb.b....", "....bbbBeEeBBeEeBbbb....", ".....bbBeeeOOeeeBbb.....", "......bBBBBOOBBBBb......", "......bBTTTTTTTTBb......", "......bBTtTTtTTTBb......", "......bBTTTtTTtTBb......", "......bBTTTTTTTTBb......", ".......bBBBBBBBBb.......", "........OO....OO........", "........................", "........................", "........................"],
|
|
835
|
+
WA: ["........................", "........................", ".......b........b.......", ".......bB......Bb.......", ".......bBBBBBBBBb.......", "......bBeeeBBeeeBb......", "......bBeEeBBeEeBb......", "......bBeeeOOeeeBb......", "......bBBBBOOBBBBb......", "......bBTTTTTTTTBb......", "......bBTtTTtTTTBb......", "......bBTTTtTTtTBb......", "......bBTTTTTTTTBb......", ".......bBBBBBBBBb.......", ".......OO......OO.......", "........................"],
|
|
836
|
+
WB: ["........................", "........................", "........b........b......", "........bB......Bb......", "........bBBBBBBBBb......", ".......bBeeeBBeeeBb.....", ".......bBeEeBBeEeBb.....", ".......bBeeeOOeeeBb.....", ".......bBBBBOOBBBBb.....", "......bBTTTTTTTTBb......", "......bBTtTTtTTTBb......", "......bBTTTtTTtTBb......", "......bBTTTTTTTTBb......", ".......bBBBBBBBBb.......", "........OO....OO........", "........................"],
|
|
837
|
+
L: ["........................", "........................", "........................", "........................", ".......b........b.......", ".......bBBBBBBBBb.......", "......bBBBBBBBBBBb......", ".....bBeEeBBBeEeBBb.....", ".....bBBBBBOOBBBBBb.....", ".....bBTTTTTTTTTTBb.....", ".....bBTtTTtTTtTTBb.....", ".....bBTTTtTTtTTTBb.....", ".....bBTTTTTTTTTTBb.....", "......bBBBBBBBBBBb......", "........OO....OO........", "........................"],
|
|
838
|
+
LB: ["........................", "........................", "........................", "........................", ".......b........b.......", ".......bBBBBBBBBb.......", "......bBBBBBBBBBBb......", ".....bBBBBBBBBBBBBb.....", ".....bBBBBBOOBBBBBb.....", ".....bBTTTTTTTTTTBb.....", ".....bBTtTTtTTtTTBb.....", ".....bBTTTtTTtTTTBb.....", ".....bBTTTTTTTTTTBb.....", "......bBBBBBBBBBBb......", "........OO....OO........", "........................"],
|
|
839
|
+
LP: ["........................", "........................", "........................", "........................", ".......b.BBBBB..b.......", ".......bBBBBBBBBb.......", "......bBBBBBBBBBBb......", ".....bBeEeBBBeEeBBb.....", ".....bBBBBBOOBBBBBb.....", ".....bBTTTTTTTTTTBb.....", ".....bBTtTTtTTtTTBb.....", ".....bBTTTtTTtTTTBb.....", ".....bBTTTTTTTTTTBb.....", "......bBBBBBBBBBBb......", "........OO....OO........", "........................"],
|
|
840
|
+
LT: ["........................", "........................", "........................", "........................", ".......b........b.......", ".......bBBBBBBBBb.......", "......bBBBBBBBBBBb......", ".....bBeEeBBBeEeBBb.....", ".....bBBBBBOOBBBBBb.....", ".....bBTTTTTTTTTTBb.....", ".....bBTtTTtTTtTTBb.....", ".....bBTTTtTTtTTTBb.....", ".....bBTTTTTTTTTTBb.....", "......bBBBBBBBBBBb......", "........OO....OO........", "........................"],
|
|
841
|
+
SAD: ["........................", "........................", "........................", "........................", ".......b........b.......", ".......bBBBBBBBBb.......", "......bBBBBBBBBBBb......", ".....bBeEeBBBeEeBBb.....", ".....bBBLBBOOBLBBBb.....", ".....bBTTTTTTTTTTBb.....", ".....bBTtTTtTTtTTBb.....", ".....bBTTTtTTtTTTBb.....", ".....bBTTTTTTTTTTBb.....", "......bBBBBBBBBBBb......", "........OO....OO........", "........................"],
|
|
842
|
+
SADB: ["........................", "........................", "........................", "........................", ".......b........b.......", ".......bBBBBBBBBb.......", "......bBBBBBBBBBBb......", ".....bBBBBBBBBBBBBb.....", ".....bBBBBBOOBBBBBb.....", ".....bBTTTTTTTTTTBb.....", ".....bBTtTTtTTtTTBb.....", ".....bBTTTtTTtTTTBb.....", ".....bBTTTTTTTTTTBb.....", "......bBBBBBBBBBBb......", "........OO....OO........", "........................"],
|
|
843
|
+
Z: ["........................", "........................", "........................", "........................", ".......b........b.......", ".......bBBBBBBBBb.......", "......bBBBBBBBBBBb......", ".....bBBbbbBBbbbBBb.....", ".....bBBBBBOOBBBBBb.....", ".....bBTTTTTTTTTTBb.....", ".....bBTtTTtTTtTTBb.....", ".....bBTTTtTTtTTTBb.....", ".....bBTTTTTTTTTTBb.....", "......bBBBBBBBBBBb......", "........OO....OO........", "........................"],
|
|
844
|
+
ZB: ["........................", "........................", "........................", "........................", ".......b.BBBBB..b.......", ".......bBBBBBBBBb.......", "......bBBBBBBBBBBb......", ".....bBBbbbBBbbbBBb.....", ".....bBBBBBOOBBBBBb.....", ".....bBTTTTTTTTTTBb.....", ".....bBTtTTtTTtTTBb.....", ".....bBTTTtTTtTTTBb.....", ".....bBTTTTTTTTTTBb.....", "......bBBBBBBBBBBb......", "........OO....OO........", "........................"],
|
|
845
|
+
},
|
|
846
|
+
axolotl: {
|
|
847
|
+
S: ["........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "............mkkkkkkm....", "..........m.kkEkkEkkm...", ".........mkmkkkkkkkkm...", "..........m.kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", ".......kk..kk..kk.......", ".......mm..mm..mm.......", "........................"],
|
|
848
|
+
SB: ["........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "............mkkkkkkm....", "..........m.kkkkkkkkm...", ".........mkmkkkkkkkkm...", "..........m.kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", ".......kk..kk..kk.......", ".......mm..mm..mm.......", "........................"],
|
|
849
|
+
SP: ["........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "..........m.mkkkkkkm....", ".........mkmkkEkkEkkm...", "..........m.kkkkkkkkm...", "............kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", ".......kk..kk..kk.......", ".......mm..mm..mm.......", "........................"],
|
|
850
|
+
SE: ["........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "............mkkkkkkm....", "..........m.kkEkkEkkm...", ".........mkmkkkkkkkkm...", "..m.......m.kkkmmkkk....", "...m........kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", ".......kk..kk..kk.......", ".......mm..mm..mm.......", "........................"],
|
|
851
|
+
WM: ["........................", "........................", "..............m..m......", ".............mkmmkm.....", "..........m.mkkkkkkm....", ".........mkmkkEkkEkkm...", "..........m.kkkkkkkkm...", "............kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", ".......kk..kk..kk.......", ".......mm..mm..mm.......", "........................", "........................"],
|
|
852
|
+
WU: ["........................", "..............m..m......", ".............mkmmkm.....", "............mkkkkkkm....", "..........m.kkEkkEkkm...", ".........mkmkkkkkkkkm...", "..m.......m.kkkmmkkk....", "...m........kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", ".......kk..kk..kk.......", ".......mm..mm..mm.......", "........................", "........................", "........................"],
|
|
853
|
+
WA: ["........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "............mkkkkkkm....", "..........m.kkEkkEkkm...", ".........mkmkkkkkkkkm...", "..........m.kkkmmkkk....", ".....m......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", "......kk...kk...kk......", "......mm...mm...mm......", "........................"],
|
|
854
|
+
WB: ["........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "..........m.mkkkkkkm....", ".........mkmkkEkkEkkm...", "..........m.kkkkkkkkm...", "............kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", ".......kk..kk..kk.......", ".......mm..mm..mm.......", "........................"],
|
|
855
|
+
L: ["........................", "........................", "........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "............mkkkkkkm....", "..........m.kkEkkEkkm...", ".........mkmkkkkkkkkm...", "..........m.kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", "........................"],
|
|
856
|
+
LB: ["........................", "........................", "........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "............mkkkkkkm....", "..........m.kkkkkkkkm...", ".........mkmkkkkkkkkm...", "..........m.kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", "........................"],
|
|
857
|
+
LP: ["........................", "........................", "........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "..........m.mkkkkkkm....", ".........mkmkkEkkEkkm...", "..........m.kkkkkkkkm...", "............kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", "........................"],
|
|
858
|
+
LT: ["........................", "........................", "........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "............mkkkkkkm....", "..........m.kkEkkEkkm...", ".........mkmkkkkkkkkm...", "..........m.kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", "........................"],
|
|
859
|
+
SAD: ["........................", "........................", "........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "............mkkkkkkm....", "..........m.kkEkkEkkm...", ".........mkmkkLkkLkkm...", "..........m.kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", "........................"],
|
|
860
|
+
SADB: ["........................", "........................", "........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "............mkkkkkkm....", "..........m.kkkkkkkkm...", ".........mkmkkkkkkkkm...", "..........m.kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", "........................"],
|
|
861
|
+
Z: ["........................", "........................", "........................", "........................", "........................", "..............m..m......", ".............mkmmkm.....", "............mkkkkkkm....", "..........m.kkkkkkkkm...", ".........mkmkkkkkkkkm...", "..........m.kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", "........................"],
|
|
862
|
+
ZB: ["........................", "........................", "........................", "........................", "..............m.........", ".............mm..m......", "............mmkmmkm.....", "..........m.mkkkkkkm....", ".........mmmkkkkkkkkm...", ".........mkmkkkkkkkkm...", "..........m.kkkmmkkk....", "....m.......kkkkkkk.....", "....kkkkkkkkkkkkkk......", ".....kkkkkkkkkkkkk......", "......kkkkkkkkkkk.......", "........................"],
|
|
863
|
+
},
|
|
864
|
+
dragon: {
|
|
865
|
+
S: ["........................", "........................", "..............v...v.....", "..............VV.VV.....", ".......v.......VVVVVV...", "......vVv......VVEVVVVR.", ".....vVVVv....VVVVVVVVVv", "....vVVVVVv..VVVVVYYY...", "...vVVvVVVVVVVVVVYY.....", "..vv..vVVVVVVVVVYY......", ".vR....VVVYYYYYYVV......", ".v.....VVVYYYYYVVV......", "..v.....VVVVVVVVV.......", "...vvvvVVVVVVVVV........", "........VV....VV........", ".......vvv...vvv........"],
|
|
866
|
+
SB: ["........................", "........................", "..............v...v.....", "..............VV.VV.....", ".......v.......VVVVVV...", "......vVv......VVVVVVVR.", ".....vVVVv....VVVVVVVVVv", "....vVVVVVv..VVVVVYYY...", "...vVVvVVVVVVVVVVYY.....", "..vv..vVVVVVVVVVYY......", ".vR....VVVYYYYYYVV......", ".v.....VVVYYYYYVVV......", "..v.....VVVVVVVVV.......", "...vvvvVVVVVVVVV........", "........VV....VV........", ".......vvv...vvv........"],
|
|
867
|
+
SP: ["........................", "........................", "..............v...v...g.", "..............VV.VV....s", ".......v.......VVVVVV.s.", "......vVv......VVEVVVVR.", ".....vVVVv....VVVVVVVVVv", "....vVVVVVv..VVVVVYYY...", "...vVVvVVVVVVVVVVYY.....", "..vv..vVVVVVVVVVYY......", ".vR....VVVYYYYYYVV......", ".v.....VVVYYYYYVVV......", "..v.....VVVVVVVVV.......", "...vvvvVVVVVVVVV........", "........VV....VV........", ".......vvv...vvv........"],
|
|
868
|
+
SE: ["........................", "........................", "..............v...v.....", "..............VV.VV.....", "...............VVVVVV...", "...............VEVVVVVR.", "..............VVVVVVVVVv", "....vvvvvvv..VVVVVYYY...", "...vVVVVVVVVVVVVVYY.....", "..vv.vVVVVVVVVVVYY......", ".vR...vVVYYYYYYYVV......", ".v.....VVVYYYYYVVV......", "..v.....VVVVVVVVV.......", "...vvvvVVVVVVVVV........", ".......VV......VV.......", "......vvv.....vvv......."],
|
|
869
|
+
WM: ["........................", "..............v...v.....", "..............VV.VV....R", ".......v.......VVVVVV.OY", "......vVv......VVEVVVVRO", ".....vVVVv....VVVVVVVVVv", "....vVVVVVv..VVVVVYYY...", "...vVVvVVVVVVVVVVYY.....", "..vv..vVVVVVVVVVYY......", ".vR....VVVYYYYYYVV......", ".v.....VVVYYYYYVVV......", "..v.....VVVVVVVVV.......", "...vvvvVVVVVVVVV........", "........VV....VV........", ".......vvv...vvv........", "........................"],
|
|
870
|
+
WU: ["..............v...v.....", "..............VV.VV.....", "...............VVVVVV...", "...............VEVVVVVR.", "..............VVVVVVVVVv", "....vvvvvvv..VVVVVYYY...", "...vVVVVVVVVVVVVVYY.....", "..vv.vVVVVVVVVVVYY......", ".vR...vVVYYYYYYYVV......", ".v.....VVVYYYYYVVV......", "..v.....VVVVVVVVV.......", "...vvvvVVVVVVVVV........", ".......VV......VV.......", "......vvv.....vvv.......", "........................", "........................"],
|
|
871
|
+
WA: ["........................", "........................", "..............v...v.....", "..............VV.VV.....", "...............VVVVVV...", "...............VEVVVVVR.", "..............VVVVVVVVVv", "....vvvvvvv..VVVVVYYY...", "...vVVVVVVVVVVVVVYY.....", "..vv.vVVVVVVVVVVYY......", ".vR...vVVYYYYYYYVV......", ".v.....VVVYYYYYVVV......", "..v.....VVVVVVVVV.......", "...vvvvVVVVVVVVV........", ".......VV......VV.......", "......vvv.....vvv......."],
|
|
872
|
+
WB: ["........................", "........................", "..............v...v.....", "..............VV.VV.....", ".......v.......VVVVVV...", "......vVv......VVEVVVVR.", ".....vVVVv....VVVVVVVVVv", "....vVVVVVv..VVVVVYYY...", "...vVVvVVVVVVVVVVYY.....", "..vv..vVVVVVVVVVYY......", ".vR....VVVYYYYYYVV......", ".v.....VVVYYYYYVVV......", "..v.....VVVVVVVVV.......", "...vvvvVVVVVVVVV........", "........VV....VV........", ".......vvv...vvv........"],
|
|
873
|
+
L: ["........................", "........................", "........................", "........................", "........................", "..............v...v.....", "..............VV.VV.....", "...............VVVVVV...", "...............VEVVVVVR.", "....vvvvvvv...VVVVVVVVVv", "...vVVVVVVVVVVVVVVYYY...", "..vv.VVVVVVVVVVVVYY.....", ".vR..VVVYYYYYYYYVV......", ".v...VVVVYYYYYYVVVVV....", "..vvvVVVVVVVVVVVVVVVVv..", "........................"],
|
|
874
|
+
LB: ["........................", "........................", "........................", "........................", "........................", "..............v...v.....", "..............VV.VV.....", "...............VVVVVV...", "...............VVVVVVVR.", "....vvvvvvv...VVVVVVVVVv", "...vVVVVVVVVVVVVVVYYY...", "..vv.VVVVVVVVVVVVYY.....", ".vR..VVVYYYYYYYYVV......", ".v...VVVVYYYYYYVVVVV....", "..vvvVVVVVVVVVVVVVVVVv..", "........................"],
|
|
875
|
+
LP: ["........................", "........................", "........................", "........................", "........................", "..............v...v.....", "..............VV.VV....s", "...............VVVVVV.s.", "...............VEVVVVVR.", "....vvvvvvv...VVVVVVVVVv", "...vVVVVVVVVVVVVVVYYY...", "..vv.VVVVVVVVVVVVYY.....", ".vR..VVVYYYYYYYYVV......", ".v...VVVVYYYYYYVVVVV....", "..vvvVVVVVVVVVVVVVVVVv..", "........................"],
|
|
876
|
+
LT: ["........................", "........................", "........................", "........................", "........................", "..............v...v.....", "..............VV.VV.....", "...............VVVVVV...", "...............VEVVVVVR.", "....vvvvvvv...VVVVVVVVVv", "...vVVVVVVVVVVVVVVYYY...", "..vv.VVVVVVVVVVVVYY.....", ".vR..VVVYYYYYYYYVV......", ".v...VVVVYYYYYYVVVVV....", "..vvvVVVVVVVVVVVVVVVVv..", "........................"],
|
|
877
|
+
SAD: ["........................", "........................", "........................", "........................", "........................", "..............v...v.....", "..............VV.VV.....", "...............VVVVVV...", "...............VEVVVVVR.", "....vvvvvvv...VVLVVVVVVv", "...vVVVVVVVVVVVVVVYYY...", "..vv.VVVVVVVVVVVVYY.....", ".vR..VVVYYYYYYYYVV......", ".v...VVVVYYYYYYVVVVV....", "..vvvVVVVVVVVVVVVVVVVv..", "........................"],
|
|
878
|
+
SADB: ["........................", "........................", "........................", "........................", "........................", "..............v...v.....", "..............VV.VV.....", "...............VVVVVV...", "...............VVVVVVVR.", "....vvvvvvv...VVVVVVVVVv", "...vVVVVVVVVVVVVVVYYY...", "..vv.VVVVVVVVVVVVYY.....", ".vR..VVVYYYYYYYYVV......", ".v...VVVVYYYYYYVVVVV....", "..vvvVVVVVVVVVVVVVVVVv..", "........................"],
|
|
879
|
+
Z: ["........................", "........................", "........................", "........................", "........................", "..............v...v.....", "..............VV.VV.....", "...............VVVVVV...", "...............VVVVVVVR.", "....vvvvvvv...VVVVVVVVVv", "...vVVVVVVVVVVVVVVYYY...", "..vv.VVVVVVVVVVVVYY.....", ".vR..VVVYYYYYYYYVV......", ".v...VVVVYYYYYYVVVVV....", "..vvvVVVVVVVVVVVVVVVVv..", "........................"],
|
|
880
|
+
ZB: ["........................", "........................", "........................", "........................", "..............v.........", "..............vV..v.....", "..............VV.VV.....", "...............VVVVVV...", "........vvv....VVVVVVVR.", "....vvvvvvvVVVVVVVVVVVVv", "...vVVVVVVVVVVVVVVYYY...", "..vv.VVVVVVVVVVVVYY.....", ".vR..VVVYYYYYYYYVV......", ".v...VVVVYYYYYYVVVVV....", "..vvvVVVVVVVVVVVVVVVVv..", "........................"],
|
|
881
|
+
},
|
|
882
|
+
gsd: {
|
|
883
|
+
S: [".................G..G...", "................GKGGKG..", "................GTKKTG..", "...............GTETTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTD.PP.", "...GGGGGGGGGKTTTTT...p..", "..GKKKKKKKKKKTTTTT......", ".GKKKKKKKKKKKTTTTTG.....", "GKKKKKKKKKKKTTTTTG......", "GKGTTTTTDDDDDTTTTG......", "GKG.GTTTD....GTDTT......", "GKG..GTT.D...GTDTT......", ".GK..GTT.D...GTDTT......", ".....DDD.DD..DD.DDD....."],
|
|
884
|
+
SB: [".................G..G...", "................GKGGKG..", "................GTKKTG..", "...............GTDTTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTD.PP.", "...GGGGGGGGGKTTTTT...p..", "..GKKKKKKKKKKTTTTT......", ".GKKKKKKKKKKKTTTTTG.....", "GKKKKKKKKKKKTTTTTG......", "GKGTTTTTDDDDDTTTTG......", "GKG.GTTTD....GTDTT......", "GKG..GTT.D...GTDTT......", ".GK..GTT.D...GTDTT......", ".....DDD.DD..DD.DDD....."],
|
|
885
|
+
SP: [".................G..G...", "................GKGGKG..", "................GTKKTG..", "...............GTETTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTDD.P.", "...GGGGGGGGGKTTTTT......", "..GKKKKKKKKKKTTTTT......", ".GKKKKKKKKKKKTTTTTG.....", "GKKKKKKKKKKKTTTTTG......", "GKGTTTTTDDDDDTTTTG......", "GKG.GTTTD....GTDTT......", "GKG..GTT.D...GTDTT......", ".GK..GTT.D...GTDTT......", ".....DDD.DD..DD.DDD....."],
|
|
886
|
+
SE: [".................G...G..", "................GKGGKKG.", "................GTKKKG..", "...............GTETTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTD.PP.", "...GGGGGGGGGKTTTTT...p..", "..GKKKKKKKKKKTTTTT......", ".GKKKKKKKKKKKTTTTTG.....", "GKKKKKKKKKKKTTTTTG......", "GKGTTTTTDDDDDTTTTG......", "GKG.GTTTD....GTDTT......", "GKG..GTT.D...GTDTT......", ".GK..GTT.D...GTDTT......", ".....DDD.DD..DD.DDD....."],
|
|
887
|
+
WM: [".................G..G...", "................GKGGKG..", "................GTKKTG..", "...............GTETTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTD.PP.", "...GGGGGGGGGKTTTTT...p..", "GGGKKKKKKKKKKTTTTT......", "GKKKKKKKKKKKKTTTTTG.....", ".GKKKKKKKKKKTTTTTG......", "..GTTTTTDDDDDTTTTG......", "....GTTTD....GTDTT......", ".....GTT.D...GTDTT......", ".....GTT.D...GTDTT......", ".....DDD.DD..DD.DDD....."],
|
|
888
|
+
WU: [".................G..G...", "................GKGGKG..", "................GTKKTG..", "...............GTETTKKG.", "GG............GKTTTTKKKN", "GKG..........GKTTTTTKDDN", ".GKG........GKTTTTTD.PP.", "..GKGGGGGGGGKTTTTT...p..", "..GKKKKKKKKKKTTTTT......", ".GKKKKKKKKKKKTTTTTG.....", ".GKTKKKKKKKKTTTTTG......", "..GTTTTTDDDDDTTTTG......", "....GTTTD....GTDTT......", ".....GTT.D...GTDTT......", ".....GTT.D...GTDTT......", ".....DDD.DD..DD.DDD....."],
|
|
889
|
+
WA: [".................G..G...", "................GKGGKG..", "................GTKKTG..", "...............GTETTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTD.PP.", "...GGGGGGGGGKTTTTT...p..", "..GKKKKKKKKKKTTTTT......", ".GKKKKKKKKKKKTTTTTG.....", "GKKKKKKKKKKKTTTTTG......", "GKGTTTTTDDDDDTTTTG......", "GKG.GTTTDD...GTDTT......", "GKG..GTT..D.GTD..GT.....", ".GK.GTT....GTD....GT....", "...DDD....DD.......DD..."],
|
|
890
|
+
WB: [".................G..G...", "................GKGGKG..", "................GTKKTG..", "...............GTETTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTD.PP.", "...GGGGGGGGGKTTTTT...p..", "..GKKKKKKKKKKTTTTT......", ".GKKKKKKKKKKKTTTTTG.....", "GKKKKKKKKKKKTTTTTG......", "GKGTTTTTDDDDDTTTTG......", "GKG.GTTTD...GTDTT.......", "GKG...GTTD..GTD.GT......", ".GK....GTTD.GT...GT.....", ".......DDD.DDD....DDD..."],
|
|
891
|
+
L: ["........................", "........................", "........................", ".................G..G...", "................GKGGKG..", "................GTKKTG..", "...............GTETTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTD.PP.", ".....GGGGGGGKTTTTT...p..", "..GGGKKKKKKKKTTTTTG.....", ".GKKKKKKKKKKKTTTTTTG....", "GKKKKKKKKKKKTTTTTTTTG...", "GKKKKTTTTTTTTTTTTTTTTTG.", "GGDDDDDDDDDDDDDDDDDDDDD."],
|
|
892
|
+
LB: ["........................", "........................", "........................", ".................G..G...", "................GKGGKG..", "................GTKKTG..", "...............GTDTTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTD.PP.", ".....GGGGGGGKTTTTT...p..", "..GGGKKKKKKKKTTTTTG.....", ".GKKKKKKKKKKKTTTTTTG....", "GKKKKKKKKKKKTTTTTTTTG...", "GKKKKTTTTTTTTTTTTTTTTTG.", "GGDDDDDDDDDDDDDDDDDDDDD."],
|
|
893
|
+
LP: ["........................", "........................", "........................", ".................G..G...", "................GKGGKG..", "................GTKKTG..", "...............GTETTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTDD.P.", ".....GGGGGGGKTTTTT......", "..GGGKKKKKKKKTTTTTG.....", ".GKKKKKKKKKKKTTTTTTG....", "GKKKKKKKKKKKTTTTTTTTG...", "GKKKKTTTTTTTTTTTTTTTTTG.", "GGDDDDDDDDDDDDDDDDDDDDD."],
|
|
894
|
+
LT: ["........................", "........................", "........................", ".................G..G...", "................GKGGKG..", "................GTKKTG..", "...............GTETTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTD.PP.", ".....GGGGGGGKTTTTT...p..", "..GGGKKKKKKKKTTTTTG.....", ".GKKKKKKKKKKKTTTTTTG....", "GKKKKKKKKKKKTTTTTTTTG...", "GGKKKTTTTTTTTTTTTTTTTTG.", "G.DDDDDDDDDDDDDDDDDDDDD."],
|
|
895
|
+
SAD: ["........................", "........................", "........................", "........................", "................GGGGGG..", "................GKKKKG..", "...............GTETTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTDD...", ".....GGGGGGGKTTTTT......", "..GGGKKKKKKKTTTTTTG.....", ".GKKKKKKKKKKTTTTTTTG....", "GKKKKKKKKKKKTTTTTTTTG...", "GKKKKTTTTTTTTTTTTTTTTTG.", "GGDDDDDDDDDDDDDDDDDDDDD."],
|
|
896
|
+
SADB: ["........................", "........................", "........................", "........................", "................GGGGGG..", "................GKKKKG..", "...............GTDTTKKG.", "..............GKTTTTKKKN", ".............GKTTTTTKDDN", "............GKTTTTTDD...", ".....GGGGGGGKTTTTT......", "..GGGKKKKKKKTTTTTTG.....", ".GKKKKKKKKKKTTTTTTTG....", "GKKKKKKKKKKKTTTTTTTTG...", "GKKKKTTTTTTTTTTTTTTTTTG.", "GGDDDDDDDDDDDDDDDDDDDDD."],
|
|
897
|
+
Z: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "................G.G.....", "...............GKGKG....", "....GGGGGGG...GKTKTKG...", "..GGKKKKKKKGGGKTDDTTKG..", ".GKKKKKKKKTKKKTTTTTTKKKN", "GKKKKKKKKKTTTTTTTTTTKDDN", "GKKKTTTTTTTTTTTTTTTTKKG.", "GKKTTTTTTTTTTTTTTDTTTTTG", "GGDDDDDDDDDDDDDDDDDDDDD."],
|
|
898
|
+
ZB: ["........................", "........................", "........................", "........................", "........................", "........................", "........................", "................G.G.....", "....GGGGGGG....GKGKG....", "..GGKKKKKKKGGGGKTKTKG...", ".GKKKKKKKKTKKKKTDDTTKG..", ".KKKKKKKKKTTTTTTTTTTKKKN", "GKKKTTTTTTTTTTTTTTTTKDDN", "GKKTTTTTTTTTTTTTTTTTKKG.", "GKKTTTTTTTTTTTTTTDTTTTTG", "GGDDDDDDDDDDDDDDDDDDDDD."],
|
|
899
|
+
},
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
const PX_W = 24; // ширина пиксельного спрайта в колонках
|
|
903
|
+
const PX_MIN_TERM = 86; // уже — пиксельный вид показывается своей ASCII-запаской
|
|
904
|
+
|
|
905
|
+
/** Пиксели → строки полублоков: верхний пиксель — цвет символа ▀, нижний — фон */
|
|
906
|
+
const pxCache = new Map<string, string[]>();
|
|
907
|
+
/**
|
|
908
|
+
* Как красить пиксели:
|
|
909
|
+
* bg — полублок ▀: верх цветом символа, низ цветом фона. Чётко, но нужен 24-битный ФОН.
|
|
910
|
+
* fg — только цвет символа (█ ▀ ▄), без фона, палитра 256. Для терминалов, где truecolor не работает
|
|
911
|
+
* (терминал IntelliJ/JediTerm): иначе низ каждой клетки пропадает — пёс «полосатый».
|
|
912
|
+
*/
|
|
913
|
+
type PxMode = "bg" | "fg";
|
|
914
|
+
/**
|
|
915
|
+
* Те же цвета в палитре 256 (38;5;N) — для режима fg. Терминал IntelliJ понижает 24-битные
|
|
916
|
+
* цвета до базовых 16: угольная шерсть становится чёрной, розовый язык — пурпурным.
|
|
917
|
+
* 256-цветную палитру он рисует как есть.
|
|
918
|
+
*/
|
|
919
|
+
const PX_256: Record<string, number> = {
|
|
920
|
+
K: 237, D: 234, G: 243, A: 172, N: 232, P: 211, p: 132,
|
|
921
|
+
"E": 233, "e": 255, "w": 255, "g": 250, "Y": 221, "y": 172, "O": 208, "C": 79, "c": 30, "B": 137, "b": 94, "k": 217, "m": 168, "V": 71, "v": 28, "R": 167, "T": 172, "t": 130, "L": 111, "s": 242, "S": 247,
|
|
922
|
+
};
|
|
923
|
+
/** Детали важнее шерсти: если в клетке два разных цвета, а фон рисовать нельзя */
|
|
924
|
+
const PX_PRIO: Record<string, number> = {
|
|
925
|
+
A: 5, N: 4, P: 4, p: 3, G: 2, K: 1, D: 1,
|
|
926
|
+
"E": 5, "e": 3, "w": 1, "g": 2, "Y": 1, "y": 2, "O": 3, "C": 1, "c": 2, "B": 1, "b": 2, "k": 2, "m": 2, "V": 1, "v": 2, "R": 3, "T": 1, "t": 2, "L": 4, "s": 2, "S": 1,
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
function autoPxMode(): PxMode {
|
|
930
|
+
const te = `${process.env.TERMINAL_EMULATOR ?? ""} ${process.env.TERM_PROGRAM ?? ""}`;
|
|
931
|
+
return /jetbrains|jediterm/i.test(te) ? "fg" : "bg";
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function pxRender(grid: string[], blink: boolean, mode: PxMode = "bg"): string[] {
|
|
935
|
+
const key = grid.join("|") + (blink ? "~" : "") + mode;
|
|
936
|
+
const hit = pxCache.get(key);
|
|
937
|
+
if (hit) return hit;
|
|
938
|
+
const rows = blink ? grid.map((r) => r.replace(/A/g, "D")) : grid;
|
|
939
|
+
const fg = (c: [number, number, number]) => `\x1b[38;2;${c[0]};${c[1]};${c[2]}m`;
|
|
940
|
+
const bg = (c: [number, number, number]) => `\x1b[48;2;${c[0]};${c[1]};${c[2]}m`;
|
|
941
|
+
const out: string[] = [];
|
|
942
|
+
for (let y = 0; y < rows.length; y += 2) {
|
|
943
|
+
let line = "";
|
|
944
|
+
for (let x = 0; x < PX_W; x++) {
|
|
945
|
+
const tc = rows[y][x];
|
|
946
|
+
const bc = rows[y + 1]?.[x] ?? ".";
|
|
947
|
+
const t = PX_PAL[tc];
|
|
948
|
+
const b = PX_PAL[bc];
|
|
949
|
+
if (mode === "fg") {
|
|
950
|
+
// только цвет символа, палитра 256
|
|
951
|
+
const c = (k: string) => `\x1b[38;5;${PX_256[k]}m`;
|
|
952
|
+
if (t && b) line += `${c((PX_PRIO[bc] ?? 0) > (PX_PRIO[tc] ?? 0) ? bc : tc)}█\x1b[0m`; // цвет поважнее
|
|
953
|
+
else if (t) line += `${c(tc)}▀\x1b[0m`;
|
|
954
|
+
else if (b) line += `${c(bc)}▄\x1b[0m`;
|
|
955
|
+
else line += " ";
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
if (t && b && t === b) line += `${bg(b)} \x1b[0m`;
|
|
959
|
+
else if (t && b) line += `${fg(t)}${bg(b)}▀\x1b[0m`;
|
|
960
|
+
else if (t) line += `${fg(t)}▀\x1b[0m`;
|
|
961
|
+
else if (b) line += `${fg(b)}▄\x1b[0m`;
|
|
962
|
+
else line += " ";
|
|
963
|
+
}
|
|
964
|
+
out.push(line);
|
|
965
|
+
}
|
|
966
|
+
pxCache.set(key, out);
|
|
967
|
+
return out;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const GROW_LEVEL = 5;
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
const SPECIES: Record<string, Species> = {
|
|
975
|
+
blob: { px: ZOO_PX.blob, items: [IT.cherry, IT.sugar], mini: "(E_E)", label: "Блоб", rarity: "common", art: [" .---. ", " ( E E ) ", " `---' "] },
|
|
976
|
+
cat: { px: ZOO_PX.cat, items: [IT.yarn, IT.mouse], mini: "(=E.E=)", label: "Кот", rarity: "common", art: [" /\\_/\\ ", "( E.E ) ", " > ^ < "] },
|
|
977
|
+
duck: { px: ZOO_PX.duck, items: [IT.rubber, IT.bread], mini: "<(E )", label: "Утка", rarity: "uncommon", art: [" __ ", " <(E )___", " ( ._> /", " `---' "] },
|
|
978
|
+
ghost: { px: ZOO_PX.ghost, items: [IT.lantern, IT.cookie], mini: "(EoE)", label: "Призрак", rarity: "uncommon", art: [" .-. ", " (E E) ", " | O | ", " '~~~' "] },
|
|
979
|
+
owl: { px: ZOO_PX.owl, items: [IT.feather, IT.book], mini: "{E,E}", label: "Сова", rarity: "rare", art: [" ,___, ", " [E,E] ", " /)__) ", " -\"--\"- "] },
|
|
980
|
+
axolotl: { px: ZOO_PX.axolotl, items: [IT.bubbles, IT.shell], mini: "}(EvE){", label: "Аксолотль", rarity: "epic", art: ["}~(___)~{", "}~(E E)~{", " ( v ) ", " ^^ ^^ "] },
|
|
981
|
+
dragon: { px: ZOO_PX.dragon, items: [IT.coin, IT.gold], mini: "<(EwE)>~", label: "Дракон", rarity: "legendary", art: [" /\\ /\\ ", " ( E E )~", " \\ ww / ", " `--' "] },
|
|
982
|
+
veo: {
|
|
983
|
+
mini: "U(E.E)U", // щенок: уши висят
|
|
984
|
+
frames: [[" _/\\ /\\_ ", " / /______\\ \\", " \\_| E E |_/", " | ,--, | ", " \\ (__) / ", " \\_\\U/_/ "], [" /\\ /\\_ ", " / /______\\ \\", " \\_| E E |_/", " | ,--, | ", " \\ (__) / ", " \\_\\/_/ "]], // язык, дёрнул ухом
|
|
985
|
+
framesGrown: [[" /\\ /\\ ", " / \\____/ \\", " \\ E E /", " \\ .. / ", " \\ (__) / ", " \\_\\/_/ ", " U "], [" /\\ |\\ ", " / \\____/ \\", " \\ E E /", " \\ .. / ", " \\ (__) / ", " \\_\\/_/ ", " \\U/ "]], // язык качается, дёрнул ухом
|
|
986
|
+
miniGrown: "V(E.E)V", // взрослый: торчком
|
|
987
|
+
label: "Овчарёнок ВЕО",
|
|
988
|
+
grownLabel: "ВЕО",
|
|
989
|
+
rarity: "legendary",
|
|
990
|
+
// щенок: уши ещё полустоячие, морда короткая
|
|
991
|
+
art: [" _/\\ /\\_ ", " / /______\\ \\", " \\_| E E |_/", " | ,--, | ", " \\ (__) / ", " \\_\\/_/ "],
|
|
992
|
+
// взрослый: по фото — высокие стоячие уши, длинная морда, язык наружу
|
|
993
|
+
grown: [" /\\ /\\ ", " / \\____/ \\", " \\ E E /", " \\ .. / ", " \\ (__) / ", " \\_\\/_/ ", " \\U/ "],
|
|
994
|
+
say: VEO_SAY,
|
|
995
|
+
},
|
|
996
|
+
gsd: {
|
|
997
|
+
px: ZOO_PX.gsd, // спрайт Сальсы в чепрачном окрасе
|
|
998
|
+
items: [IT.frisbee, IT.rope],
|
|
999
|
+
mini: "V(E.E)U", // одно ухо стоит, второе висит
|
|
1000
|
+
frames: [[" /\\ __ ", " / \\______/ \\_ ", " | E E |\\__/", " \\ ## / ", " \\#(__)#/ ", " \\_\\U/_/ "], [" /\\ /\\ ", " / \\______/ \\", " | E E | ", " \\ ## / ", " \\#(__)#/ ", " \\_\\/_/ "]], // язык; висячее ухо на миг встало
|
|
1001
|
+
framesGrown: [[" /\\ /\\ ", " / \\ / \\", " | \\____/ |", " \\ E ## E / ", " \\ ##||## / ", " \\#(__)#/ ", " \\_\\U/_/ "], [" /\\ |\\ ", " / \\ / \\", " | \\____/ |", " \\ E ## E / ", " \\ ##||## / ", " \\#(__)#/ ", " \\_\\/_/ "]],
|
|
1002
|
+
miniGrown: "V(E.E)V",
|
|
1003
|
+
label: "Щенок немецкой овчарки",
|
|
1004
|
+
grownLabel: "Немецкая овчарка",
|
|
1005
|
+
rarity: "epic",
|
|
1006
|
+
// щенок: одно ухо уже стоит, второе ещё висит; # — тёмная маска на морде
|
|
1007
|
+
art: [" /\\ __ ", " / \\______/ \\_ ", " | E E |\\__/", " \\ ## / ", " \\#(__)#/ ", " \\_\\/_/ "],
|
|
1008
|
+
// взрослая: огромные стоячие уши, чёрная маска вокруг носа
|
|
1009
|
+
grown: [" /\\ /\\ ", " / \\ / \\", " | \\____/ |", " \\ E ## E / ", " \\ ##||## / ", " \\#(__)#/ ", " \\_\\/_/ "],
|
|
1010
|
+
say: GSD_SAY,
|
|
1011
|
+
},
|
|
1012
|
+
veo_px: {
|
|
1013
|
+
label: "ВЕО (пиксели)",
|
|
1014
|
+
rarity: "legendary",
|
|
1015
|
+
mini: "V(E.E)V",
|
|
1016
|
+
// в узком терминале или в режимах compact/top — ASCII взрослого ВЕО
|
|
1017
|
+
art: [" /\\ /\\ ", " / \\____/ \\", " \\ E E /", " \\ .. / ", " \\ (__) / ", " \\_\\/_/ ", " \\U/ "],
|
|
1018
|
+
frames: [[" /\\ /\\ ", " / \\____/ \\", " \\ E E /", " \\ .. / ", " \\ (__) / ", " \\_\\/_/ ", " U "], [" /\\ |\\ ", " / \\____/ \\", " \\ E E /", " \\ .. / ", " \\ (__) / ", " \\_\\/_/ ", " \\U/ "]],
|
|
1019
|
+
px: VEO_PX,
|
|
1020
|
+
items: [IT.ball, IT.bone],
|
|
1021
|
+
say: VEO_SAY,
|
|
1022
|
+
},
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
const RARITY_WEIGHTS: [Rarity, number][] = [
|
|
1026
|
+
["common", 60],
|
|
1027
|
+
["uncommon", 25],
|
|
1028
|
+
["rare", 10],
|
|
1029
|
+
["epic", 4],
|
|
1030
|
+
["legendary", 1],
|
|
1031
|
+
];
|
|
1032
|
+
|
|
1033
|
+
const RARITY_STARS: Record<Rarity, string> = {
|
|
1034
|
+
common: "*",
|
|
1035
|
+
uncommon: "**",
|
|
1036
|
+
rare: "***",
|
|
1037
|
+
epic: "****",
|
|
1038
|
+
legendary: "*****",
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
const EYES: Record<Mood, string> = {
|
|
1042
|
+
idle: "•",
|
|
1043
|
+
happy: "^",
|
|
1044
|
+
sad: ";",
|
|
1045
|
+
sleep: "-",
|
|
1046
|
+
work: "•",
|
|
1047
|
+
eat: "•",
|
|
1048
|
+
love: "*",
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
const NAMES = ["Байт", "Кеш", "Бином", "Гоша", "Пиксель", "Тапок", "Монада", "Лямбда", "Бобик", "Спринг"];
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
// ---------- утилиты ----------
|
|
1055
|
+
|
|
1056
|
+
const clamp = (v: number) => Math.max(0, Math.min(100, v));
|
|
1057
|
+
const pick = <T>(a: T[]) => a[Math.floor(Math.random() * a.length)];
|
|
1058
|
+
const SPECIES_KEYS = Object.keys(SPECIES);
|
|
1059
|
+
|
|
1060
|
+
function hash(s: string): number {
|
|
1061
|
+
let h = 2166136261;
|
|
1062
|
+
for (let i = 0; i < s.length; i++) h = Math.imul(h ^ s.charCodeAt(i), 16777619);
|
|
1063
|
+
return h >>> 0;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function rollRarity(r: number): Rarity {
|
|
1067
|
+
let acc = 0;
|
|
1068
|
+
const x = r % 100;
|
|
1069
|
+
for (const [rar, w] of RARITY_WEIGHTS) {
|
|
1070
|
+
acc += w;
|
|
1071
|
+
if (x < acc) return rar;
|
|
1072
|
+
}
|
|
1073
|
+
return "common";
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function hatch(seed: string): BuddyState {
|
|
1077
|
+
const h = hash(seed);
|
|
1078
|
+
const rarity = rollRarity(h);
|
|
1079
|
+
const pool = Object.entries(SPECIES).filter(([, s]) => s.rarity === rarity);
|
|
1080
|
+
const species = pool[(h >>> 8) % pool.length][0];
|
|
1081
|
+
const now = Date.now();
|
|
1082
|
+
return {
|
|
1083
|
+
name: NAMES[(h >>> 16) % NAMES.length],
|
|
1084
|
+
species,
|
|
1085
|
+
rarity,
|
|
1086
|
+
shiny: (h >>> 24) % 100 === 0,
|
|
1087
|
+
born: now,
|
|
1088
|
+
food: 80,
|
|
1089
|
+
joy: 80,
|
|
1090
|
+
energy: 100,
|
|
1091
|
+
xp: 0,
|
|
1092
|
+
lastTick: now,
|
|
1093
|
+
lastActivity: now,
|
|
1094
|
+
visible: true,
|
|
1095
|
+
toolCalls: 0,
|
|
1096
|
+
errors: 0,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function load(): BuddyState {
|
|
1101
|
+
try {
|
|
1102
|
+
const st = JSON.parse(fs.readFileSync(STATE_FILE, "utf8")) as BuddyState;
|
|
1103
|
+
// старые варианты ВЕО (профиль, чепрачный) убраны — переводим на основной
|
|
1104
|
+
if (!SPECIES[st.species] && st.species.startsWith("veo")) st.species = "veo";
|
|
1105
|
+
return st;
|
|
1106
|
+
} catch {
|
|
1107
|
+
// первый запуск: питомец детерминирован по пользователю, как в Claude Code
|
|
1108
|
+
return hatch(os.userInfo().username + "@" + os.hostname());
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function save(s: BuddyState) {
|
|
1113
|
+
try {
|
|
1114
|
+
fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
|
|
1115
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(s, null, 2));
|
|
1116
|
+
} catch {
|
|
1117
|
+
/* не критично */
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
const level = (xp: number) => Math.floor(Math.sqrt(xp / 10)) + 1;
|
|
1122
|
+
|
|
1123
|
+
const spec = (st: BuddyState) => SPECIES[st.species] ?? SPECIES.blob;
|
|
1124
|
+
const grown = (st: BuddyState) => !!spec(st).grown && level(st.xp) >= GROW_LEVEL;
|
|
1125
|
+
const miniFor = (st: BuddyState) => (grown(st) ? spec(st).miniGrown ?? spec(st).mini : spec(st).mini);
|
|
1126
|
+
/** Кадры анимации: [основной, ...доп]. Без своих кадров — лёгкое покачивание */
|
|
1127
|
+
const framesFor = (st: BuddyState): string[][] => {
|
|
1128
|
+
const base = artFor(st);
|
|
1129
|
+
const extra = grown(st) ? spec(st).framesGrown : spec(st).frames;
|
|
1130
|
+
return [base, ...(extra ?? [base.map((l) => " " + l)])];
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Темп анимации по настроению: every — каждые N тиков по 500 мс, seq — номера кадров.
|
|
1135
|
+
* В простое редко и в основном кадр 0, чтобы не отвлекать; в работе живее.
|
|
1136
|
+
*/
|
|
1137
|
+
const ANIM: Record<Mood, { every: number; seq: number[] }> = {
|
|
1138
|
+
idle: { every: 2, seq: [0, 0, 0, 1, 0, 0, 0, 2] },
|
|
1139
|
+
work: { every: 1, seq: [0, 1, 0, 2] },
|
|
1140
|
+
happy: { every: 1, seq: [0, 1, 0, 2] },
|
|
1141
|
+
love: { every: 1, seq: [0, 1] },
|
|
1142
|
+
eat: { every: 1, seq: [0, 1] },
|
|
1143
|
+
sad: { every: 4, seq: [0] },
|
|
1144
|
+
sleep: { every: 4, seq: [0] },
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
const artFor = (st: BuddyState) => (grown(st) ? spec(st).grown! : spec(st).art);
|
|
1148
|
+
const labelFor = (st: BuddyState) => (grown(st) ? spec(st).grownLabel ?? spec(st).label : spec(st).label);
|
|
1149
|
+
|
|
1150
|
+
function bar(v: number, w = 10) {
|
|
1151
|
+
const n = Math.round((v / 100) * w);
|
|
1152
|
+
return "[" + "=".repeat(n) + "-".repeat(w - n) + "]";
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
function age(born: number) {
|
|
1156
|
+
const d = Math.floor((Date.now() - born) / 86_400_000);
|
|
1157
|
+
return d === 0 ? "сегодня" : `${d} дн.`;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// ---------- расширение ----------
|
|
1161
|
+
|
|
1162
|
+
export default function buddy(pi: ExtensionAPI) {
|
|
1163
|
+
let s = load();
|
|
1164
|
+
let ui: any = null;
|
|
1165
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
1166
|
+
let tempMood: Mood | null = null;
|
|
1167
|
+
let tempUntil = 0;
|
|
1168
|
+
let bubble = "";
|
|
1169
|
+
let bubbleUntil = 0;
|
|
1170
|
+
let blink = false;
|
|
1171
|
+
let working = false;
|
|
1172
|
+
let tuiRef: any = null;
|
|
1173
|
+
let sideReady = false; // удалось ли встроиться в редактор
|
|
1174
|
+
let vw: (t: string) => number = (t) => t.replace(ANSI, "").length;
|
|
1175
|
+
let truncW: ((t: string, w: number, ellipsis?: string) => string) | null = null;
|
|
1176
|
+
let dockReady = false;
|
|
1177
|
+
const placement = (): Placement => (sideReady ? s.placement ?? "side" : "top");
|
|
1178
|
+
|
|
1179
|
+
/** Пассивное изменение статов со временем */
|
|
1180
|
+
function tick() {
|
|
1181
|
+
const now = Date.now();
|
|
1182
|
+
const hours = (now - s.lastTick) / 3_600_000;
|
|
1183
|
+
if (hours <= 0) return;
|
|
1184
|
+
// экспоненциальный возврат к базе: голод и скука не накапливаются
|
|
1185
|
+
const relax = (v: number, base: number, tau: number) => clamp(base + (v - base) * Math.exp(-hours / tau));
|
|
1186
|
+
s.food = relax(s.food, BASE.food, RELAX_H.food);
|
|
1187
|
+
s.joy = relax(s.joy, BASE.joy, RELAX_H.joy);
|
|
1188
|
+
s.energy = relax(s.energy, BASE.energy, RELAX_H.energy);
|
|
1189
|
+
s.lastTick = now;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// ---- речь ----
|
|
1193
|
+
let custom: any = {};
|
|
1194
|
+
try {
|
|
1195
|
+
custom = JSON.parse(fs.readFileSync(PHRASES_FILE, "utf8")) ?? {};
|
|
1196
|
+
} catch {
|
|
1197
|
+
/* своих фраз нет — ок */
|
|
1198
|
+
}
|
|
1199
|
+
const bags = new Map<string, number[]>();
|
|
1200
|
+
let lastChat = 0;
|
|
1201
|
+
let lastErrSay = 0;
|
|
1202
|
+
const CHAT_COOLDOWN = 25_000; // болтовня не чаще раза в 25 с
|
|
1203
|
+
const ERR_COOLDOWN = 6_000;
|
|
1204
|
+
|
|
1205
|
+
/** Поза пиксельного пса прямо сейчас; у ASCII-питомцев позы нет */
|
|
1206
|
+
function pose(): "stand" | "walk" | "lie" | null {
|
|
1207
|
+
if (!pxOn()) return null;
|
|
1208
|
+
const st = curState(mood());
|
|
1209
|
+
return st === "work" ? "walk" : PX_LYING.has(st) ? "lie" : "stand";
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
function pool(t: Topic): string[] {
|
|
1213
|
+
const own = (v: unknown) => (Array.isArray(v) ? v.filter((x) => typeof x === "string") : []);
|
|
1214
|
+
const mine = spec(s).say?.[t];
|
|
1215
|
+
// технические темы смешиваем (собака тоже радуется зелёным тестам по-человечески),
|
|
1216
|
+
// а реплики про тело и дежурные — только свои
|
|
1217
|
+
const base = !mine ? SAY[t] : OWN_ONLY.has(t) ? mine : [...mine, ...SAY[t]];
|
|
1218
|
+
const all = [...base, ...own(custom[t]), ...own(custom[s.species]?.[t])];
|
|
1219
|
+
const ps = pose();
|
|
1220
|
+
// фраза с меткой позы — только в своей позе; без позы (ASCII) метка просто снимается
|
|
1221
|
+
const fit = all.flatMap((x) => {
|
|
1222
|
+
const m = /^@(\w+):(.*)$/.exec(x);
|
|
1223
|
+
if (!m) return [x];
|
|
1224
|
+
return !ps || m[1] === ps ? [m[2]] : [];
|
|
1225
|
+
});
|
|
1226
|
+
return fit.length ? fit : all.map((x) => x.replace(/^@\w+:/, ""));
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/** Фраза темы без повторов подряд: перемешанный «мешок», пока не кончится */
|
|
1230
|
+
function line(t: Topic): string {
|
|
1231
|
+
const p = pool(t);
|
|
1232
|
+
const key = `${s.species}:${t}:${pose() ?? ""}`;
|
|
1233
|
+
let bag = bags.get(key);
|
|
1234
|
+
if (!bag?.length || bag.some((i) => i >= p.length)) {
|
|
1235
|
+
bag = [...p.keys()].sort(() => Math.random() - 0.5);
|
|
1236
|
+
bags.set(key, bag);
|
|
1237
|
+
}
|
|
1238
|
+
return p[bag.pop()!];
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/** Необязательная болтовня: с вероятностью, кулдауном и молчит в mute */
|
|
1242
|
+
function chat(t: Topic, prob = 1, ms = 5000) {
|
|
1243
|
+
if (s.muted) return;
|
|
1244
|
+
const now = Date.now();
|
|
1245
|
+
if (now - lastChat < CHAT_COOLDOWN || Math.random() > prob) return;
|
|
1246
|
+
lastChat = now;
|
|
1247
|
+
say(line(t), ms);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/** Важное (ответ на команду, уровень): без кулдауна; force — даже в mute */
|
|
1251
|
+
function shout(t: Topic, ms = 5000, force = false) {
|
|
1252
|
+
if (s.muted && !force) return;
|
|
1253
|
+
lastChat = Date.now();
|
|
1254
|
+
say(line(t), ms);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/** Дежурная фраза: держится несколько минут, меняется при смене состояния */
|
|
1258
|
+
const STANDBY_ROTATE = 3 * 60_000;
|
|
1259
|
+
let stbTopic = ""; // вид:тема:поза — при смене питомца или позы фраза сразу меняется
|
|
1260
|
+
let stbText = "";
|
|
1261
|
+
let stbSince = 0;
|
|
1262
|
+
function standby(): string {
|
|
1263
|
+
if (sceneOn()) return scene!.text; // играет — и подпись про игру
|
|
1264
|
+
const m = mood();
|
|
1265
|
+
const t: Topic =
|
|
1266
|
+
m === "work" ? "stbWork" : m === "sleep" ? "stbSleep" : m === "sad" ? "stbSad" : s.muted ? "stbMute" : "stbIdle";
|
|
1267
|
+
const now = Date.now();
|
|
1268
|
+
const key = `${s.species}:${t}:${pose() ?? ""}`; // сменилась поза — сменилась и фраза
|
|
1269
|
+
if (key !== stbTopic || now - stbSince > STANDBY_ROTATE) {
|
|
1270
|
+
stbTopic = key;
|
|
1271
|
+
stbText = line(t);
|
|
1272
|
+
stbSince = now;
|
|
1273
|
+
}
|
|
1274
|
+
return stbText;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/** Приглушённый цвет из темы pi; если не вышло — просто dim */
|
|
1278
|
+
function dim(t: string): string {
|
|
1279
|
+
try {
|
|
1280
|
+
const out = ui?.theme?.fg?.("muted", t);
|
|
1281
|
+
if (typeof out === "string") return out;
|
|
1282
|
+
} catch {
|
|
1283
|
+
/* старая тема */
|
|
1284
|
+
}
|
|
1285
|
+
return `\x1b[2m${t}\x1b[22m`;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function greetingTopic(): Topic {
|
|
1289
|
+
const d = new Date();
|
|
1290
|
+
const h = d.getHours();
|
|
1291
|
+
const wd = d.getDay();
|
|
1292
|
+
if (Date.now() - s.lastActivity > 2 * 3_600_000) return "back";
|
|
1293
|
+
if (h >= 23 || h < 5) return "night";
|
|
1294
|
+
if (wd === 0 || wd === 6) return "weekend";
|
|
1295
|
+
if (wd === 5 && h >= 15) return "friday";
|
|
1296
|
+
if (wd === 1 && h < 12) return "monday";
|
|
1297
|
+
if (h < 11) return "morning";
|
|
1298
|
+
return "hello";
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
/** Тема по инструменту и его аргументам. Для ошибок — своя тема или null */
|
|
1302
|
+
function classify(name: string, input: any, ok: boolean): { t: Topic; p: number } | null {
|
|
1303
|
+
const cmd = String(input?.command ?? input?.script ?? "");
|
|
1304
|
+
const file = String(input?.path ?? input?.file_path ?? input?.pattern ?? "").toLowerCase();
|
|
1305
|
+
if (name === "bash" || name === "powershell") {
|
|
1306
|
+
if (/\brm\s+-[a-z]*r|remove-item\b.*-recurse|\brd\s+\/s|\bdel\s+\/s/i.test(cmd)) return ok ? { t: "rm", p: 1 } : null;
|
|
1307
|
+
if (/\b(mvnw?|gradlew?|npm|pnpm|yarn|pytest|jest|vitest|go|dotnet|cargo)\b.*\btest/i.test(cmd))
|
|
1308
|
+
return { t: ok ? "testOk" : "testFail", p: ok ? 0.8 : 1 };
|
|
1309
|
+
if (/\b(mvnw?|gradlew?|javac|tsc)\b|\b(npm run build|cargo build|dotnet build)\b/i.test(cmd))
|
|
1310
|
+
return { t: ok ? "buildOk" : "buildFail", p: ok ? 0.5 : 1 };
|
|
1311
|
+
if (/\bgit\b/i.test(cmd)) {
|
|
1312
|
+
if (!ok) return { t: "gitFail", p: 1 };
|
|
1313
|
+
if (/\bgit\s+commit\b/i.test(cmd)) return { t: "gitCommit", p: 0.7 };
|
|
1314
|
+
if (/\bgit\s+push\b/i.test(cmd)) return { t: "gitPush", p: 0.8 };
|
|
1315
|
+
}
|
|
1316
|
+
if (!ok) return null;
|
|
1317
|
+
if (/\bdocker\b/i.test(cmd)) return { t: "docker", p: 0.4 };
|
|
1318
|
+
if (/\b(kubectl|helm|k9s)\b/i.test(cmd)) return { t: "k8s", p: 0.4 };
|
|
1319
|
+
return { t: "bash", p: 0.1 };
|
|
1320
|
+
}
|
|
1321
|
+
if (!ok) return null;
|
|
1322
|
+
const writing = name === "edit" || name === "write";
|
|
1323
|
+
if (/\.bpmn$/.test(file)) return { t: "bpmn", p: writing ? 0.6 : 0.3 };
|
|
1324
|
+
if (/pom\.xml$|build\.gradle(\.kts)?$/.test(file)) return { t: "pom", p: 0.5 };
|
|
1325
|
+
if (/\.java$|\.kt$/.test(file)) return { t: "java", p: writing ? 0.25 : 0.1 };
|
|
1326
|
+
if (/\.ya?ml$/.test(file)) return { t: "yaml", p: 0.3 };
|
|
1327
|
+
if (/\.sql$/.test(file)) return { t: "sql", p: 0.4 };
|
|
1328
|
+
if (writing) return { t: "edit", p: 0.08 };
|
|
1329
|
+
if (["read", "grep", "find", "ls"].includes(name)) return { t: "read", p: 0.06 };
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function say(text: string, ms = 5000) {
|
|
1334
|
+
bubble = text;
|
|
1335
|
+
bubbleUntil = Date.now() + ms;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function react(m: Mood, ms = 4000) {
|
|
1339
|
+
tempMood = m;
|
|
1340
|
+
tempUntil = Date.now() + ms;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
function mood(): Mood {
|
|
1344
|
+
const now = Date.now();
|
|
1345
|
+
if (tempMood && now < tempUntil) return tempMood;
|
|
1346
|
+
if (working) return "work";
|
|
1347
|
+
if (now - s.lastActivity > 10 * 60_000 || s.energy < 10) return "sleep";
|
|
1348
|
+
if (s.joy < 30) return "sad";
|
|
1349
|
+
if (s.joy > 80 && s.food > 60) return "happy";
|
|
1350
|
+
return "idle";
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
let animN = 0; // тики таймера (500 мс)
|
|
1354
|
+
let termW = 0; // последняя известная ширина терминала
|
|
1355
|
+
|
|
1356
|
+
/** Пиксельный вид активен: у питомца есть px, режим dock и терминал достаточно широкий */
|
|
1357
|
+
const pxOn = () => !!spec(s).px && (s.size ?? "dock") === "dock" && placement() === "side" && termW >= PX_MIN_TERM;
|
|
1358
|
+
const dockW = () => (pxOn() ? PX_W + 1 : DOCK_W);
|
|
1359
|
+
|
|
1360
|
+
const pxMode = (): PxMode => (!s.pxColor || s.pxColor === "auto" ? autoPxMode() : s.pxColor);
|
|
1361
|
+
|
|
1362
|
+
/** Состояние пиксельного пса: что он сейчас делает */
|
|
1363
|
+
function pxState(m: Mood): PxState {
|
|
1364
|
+
if (m === "sleep") return "sleep";
|
|
1365
|
+
if (m === "sad") return "sad";
|
|
1366
|
+
if (m === "work" || m === "happy" || m === "love" || m === "eat") return m;
|
|
1367
|
+
return Date.now() - s.lastActivity > 3 * 60_000 ? "rest" : "idle"; // заскучал — прилёг
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// ---- игра с любимым предметом ----
|
|
1371
|
+
let scene: { item: PxItem; until: number; text: string } | null = null;
|
|
1372
|
+
const sceneOn = () => !!scene && Date.now() < scene.until && pxOn() && !working;
|
|
1373
|
+
|
|
1374
|
+
function startScene(item?: PxItem, ms = 12_000) {
|
|
1375
|
+
const items = spec(s).items;
|
|
1376
|
+
const it = item ?? (items?.length ? items[Math.floor(Math.random() * items.length)] : undefined);
|
|
1377
|
+
if (!it) return false;
|
|
1378
|
+
scene = { item: it, until: Date.now() + ms, text: pick(it.say) };
|
|
1379
|
+
return true;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/** Предмет поверх кадра: у земли, в ближайшем свободном месте справа; мячик подпрыгивает */
|
|
1383
|
+
function withItem(grid: string[], it: PxItem, t: number): string[] {
|
|
1384
|
+
const h = it.art.length;
|
|
1385
|
+
const w = Math.max(...it.art.map((r) => r.length));
|
|
1386
|
+
const free = (x: number) => {
|
|
1387
|
+
for (let y = 16 - h; y < 16; y++) for (let i = 0; i < w; i++) if ((grid[y]?.[x + i] ?? ".") !== ".") return false;
|
|
1388
|
+
return true;
|
|
1389
|
+
};
|
|
1390
|
+
let x0 = PX_W - w;
|
|
1391
|
+
for (let x = PX_W - w; x >= 0; x--) if (free(x)) { x0 = x; break; }
|
|
1392
|
+
const up = it.mode === "toss" ? [0, 2, 3, 2][t % 4] : 0;
|
|
1393
|
+
const out = grid.map((r) => r.padEnd(PX_W, ".").split(""));
|
|
1394
|
+
it.art.forEach((row, i) => {
|
|
1395
|
+
const y = 16 - h + i - up;
|
|
1396
|
+
if (y >= 0) [...row].forEach((c, j) => { if (c !== "." && x0 + j < PX_W) out[y][x0 + j] = c; });
|
|
1397
|
+
});
|
|
1398
|
+
return out.map((r) => r.join(""));
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* Переходы: при смене состояния питомец сначала возвращается в базовую стойку
|
|
1403
|
+
* (S — стоит, L — лежит), а если меняется поза — проходит через обе. Затем новая анимация
|
|
1404
|
+
* играется с первого кадра. Машина состояний сдвигается раз в тик (500 мс).
|
|
1405
|
+
*/
|
|
1406
|
+
let mState: PxState | "" = "";
|
|
1407
|
+
let mStart = 0;
|
|
1408
|
+
let mQueue: PxFrame[] = [];
|
|
1409
|
+
const baseOf = (st: PxState): PxFrame => (PX_LYING.has(st) ? "L" : "S");
|
|
1410
|
+
|
|
1411
|
+
function curState(m: Mood): PxState {
|
|
1412
|
+
if (sceneOn() && m !== "sleep" && m !== "sad") return scene!.item.mode;
|
|
1413
|
+
return pxState(m);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
function pxTick() {
|
|
1417
|
+
const st = curState(mood());
|
|
1418
|
+
if (st !== mState) {
|
|
1419
|
+
mQueue = mState && baseOf(mState) !== baseOf(st) ? [baseOf(mState), baseOf(st)] : [baseOf(st)];
|
|
1420
|
+
mState = st;
|
|
1421
|
+
mStart = animN + mQueue.length;
|
|
1422
|
+
} else if (mQueue.length) mQueue.shift();
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
/** Текущий кадр: в тихом режиме — первый кадр состояния, без анимации и переходов */
|
|
1426
|
+
function pxFrame(m: Mood): PxFrame {
|
|
1427
|
+
const st = curState(m);
|
|
1428
|
+
const a = PX_ANIM[st];
|
|
1429
|
+
if (quiet()) return a.seq[0];
|
|
1430
|
+
if (st === mState && mQueue.length) return mQueue[0];
|
|
1431
|
+
return a.seq[Math.floor(Math.max(0, animN - mStart) / a.every) % a.seq.length];
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
/** Сетка кадра с предметом, если идёт игра */
|
|
1435
|
+
function pxGrid(m: Mood): string[] {
|
|
1436
|
+
const grid = spec(s).px![pxFrame(m)];
|
|
1437
|
+
return sceneOn() ? withItem(grid, scene!.item, animN - mStart) : grid;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* «Тихий» режим: в простое ничего не пишем в терминал.
|
|
1442
|
+
* В обычном (не полноэкранном) режиме pi история — это прокрутка самого терминала,
|
|
1443
|
+
* а консоль Windows при любом выводе прыгает вниз к курсору: анимация раз в 0,5 с
|
|
1444
|
+
* не давала бы листать длинные ответы. Пока агент работает, вывод идёт и так.
|
|
1445
|
+
*/
|
|
1446
|
+
function quiet(): boolean {
|
|
1447
|
+
const a = s.anim ?? "auto";
|
|
1448
|
+
if (a === "on") return false;
|
|
1449
|
+
if (a === "off") return !working;
|
|
1450
|
+
return !working && tuiRef?.mode !== "fullscreen";
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
function frameIndex(m: Mood, count: number): number {
|
|
1454
|
+
const a = ANIM[m];
|
|
1455
|
+
return a.seq[Math.floor(animN / a.every) % a.seq.length] % count;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function eyesNow(): { m: Mood; lines: string[] } {
|
|
1459
|
+
const m = mood();
|
|
1460
|
+
const eye = blink && (m === "idle" || m === "work") ? "-" : EYES[m];
|
|
1461
|
+
if (pxOn()) return { m, lines: pxRender(pxGrid(m), false, pxMode()) }; // моргание — в самих кадрах
|
|
1462
|
+
const fr = framesFor(s);
|
|
1463
|
+
const idx = quiet() ? 0 : frameIndex(m, fr.length);
|
|
1464
|
+
return { m, lines: fr[idx].map((l) => l.replace(/E/g, eye)) };
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// во сне «z» дышит, в работе бегут точки
|
|
1468
|
+
const decoFor = (m: Mood) =>
|
|
1469
|
+
quiet()
|
|
1470
|
+
? m === "sleep" ? " z" : m === "love" ? " <3" : ""
|
|
1471
|
+
: m === "sleep"
|
|
1472
|
+
? [" z", " zZ", " zZz"][Math.floor(animN / 4) % 3]
|
|
1473
|
+
: m === "love"
|
|
1474
|
+
? " <3"
|
|
1475
|
+
: m === "work"
|
|
1476
|
+
? [" .", " ..", " ..."][animN % 3]
|
|
1477
|
+
: "";
|
|
1478
|
+
const bubbleOn = () => !!bubble && Date.now() < bubbleUntil;
|
|
1479
|
+
|
|
1480
|
+
/** Колонка справа от поля ввода: реплика, арт, имя. Статы уходят в футер. */
|
|
1481
|
+
function sideColumn(): string[] {
|
|
1482
|
+
const { m, lines } = eyesNow();
|
|
1483
|
+
const col: string[] = [];
|
|
1484
|
+
// зона реплики всегда 2 строки: живая реплика или дежурная серым — высота не прыгает
|
|
1485
|
+
const cw = Math.max(SIDE_W, dockW() - 1);
|
|
1486
|
+
const talk = bubbleOn() ? wrapWords(bubble, cw - 2, 2) : wrapWords(standby(), cw - 2, 2).map(dim);
|
|
1487
|
+
while (talk.length < 2) talk.unshift("");
|
|
1488
|
+
col.push(...talk.map((l) => (l ? " " + l : "")));
|
|
1489
|
+
col.push(...lines);
|
|
1490
|
+
const nm = `${s.shiny ? "~" : ""}${s.name}${s.shiny ? "~" : ""} lv.${level(s.xp)}${decoFor(m)}`;
|
|
1491
|
+
col.push(nm.length > cw ? nm.slice(0, cw - 1) + "…" : nm);
|
|
1492
|
+
const p = (v: number) => String(Math.round(v)).padStart(3);
|
|
1493
|
+
col.push(`ед${p(s.food)} рд${p(s.joy)} сл${p(s.energy)}`);
|
|
1494
|
+
return col;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
/** Подмена render() у редактора: редактор сужается, питомец встаёт справа, низы выровнены */
|
|
1498
|
+
function sideRender(orig: (w: number) => string[], width: number): string[] {
|
|
1499
|
+
if (!s.visible || placement() !== "side" || width < MIN_TERM_W) return orig(width);
|
|
1500
|
+
const size = s.size ?? "dock";
|
|
1501
|
+
termW = width;
|
|
1502
|
+
if (size === "dock") return dockReady ? orig(width - dockW() - 1) : sideCompact(orig, width);
|
|
1503
|
+
return size === "big" ? sideBig(orig, width) : sideCompact(orig, width);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
/**
|
|
1507
|
+
* Компактный режим (по умолчанию): НЕ добавляет ни одной строки.
|
|
1508
|
+
* Реплика вписывается в верхнюю рамку поля ввода, справа — мордочка, имя и статы.
|
|
1509
|
+
*/
|
|
1510
|
+
function sideCompact(orig: (w: number) => string[], width: number): string[] {
|
|
1511
|
+
const leftW = width - MINI_W - 1;
|
|
1512
|
+
const rows = orig(leftW);
|
|
1513
|
+
if (!rows.length) return rows;
|
|
1514
|
+
const { m } = eyesNow();
|
|
1515
|
+
const eye = blink && (m === "idle" || m === "work") ? "-" : EYES[m];
|
|
1516
|
+
const live = bubbleOn();
|
|
1517
|
+
let talk = live ? bubble : standby();
|
|
1518
|
+
const room = leftW - 12; // слева на рамке оставляем место под служебные пометки pi
|
|
1519
|
+
if (talk.length + 2 > room) talk = talk.slice(0, Math.max(0, room - 3)) + "…";
|
|
1520
|
+
if (talk && truncW) {
|
|
1521
|
+
const head = truncW(rows[0], leftW - vw(talk) - 2, "");
|
|
1522
|
+
rows[0] = head + " ".repeat(Math.max(0, leftW - vw(talk) - 2 - vw(head))) + " " + (live ? talk : dim(talk)) + " ";
|
|
1523
|
+
}
|
|
1524
|
+
const cut = (t: string) => (t.length > MINI_W ? t.slice(0, MINI_W - 1) + "…" : t);
|
|
1525
|
+
const face = miniFor(s).replace(/E/g, eye);
|
|
1526
|
+
const n = (v: number) => String(Math.round(v)).padStart(3);
|
|
1527
|
+
const cells = [
|
|
1528
|
+
cut(`${face}${decoFor(m)}`),
|
|
1529
|
+
cut(`${s.name} lv.${level(s.xp)}`),
|
|
1530
|
+
dim(cut(`ед${n(s.food)} рд${n(s.joy)} сл${n(s.energy)}`)),
|
|
1531
|
+
];
|
|
1532
|
+
// без truncateToWidth реплику кладём в колонку вместо мордочки
|
|
1533
|
+
if (talk && !truncW) cells[0] = live ? cut(talk) : dim(cut(talk));
|
|
1534
|
+
return rows.map((l, i) => l + " ".repeat(Math.max(0, leftW - vw(l))) + " " + (cells[i] ?? ""));
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
/** Большой режим: полный арт справа, поле ввода растёт на высоту арта */
|
|
1538
|
+
function sideBig(orig: (w: number) => string[], width: number): string[] {
|
|
1539
|
+
const leftW = width - SIDE_W - 1;
|
|
1540
|
+
const left = orig(leftW);
|
|
1541
|
+
const col = sideColumn();
|
|
1542
|
+
const h = Math.max(left.length, col.length);
|
|
1543
|
+
const lpad = h - left.length;
|
|
1544
|
+
const cpad = h - col.length;
|
|
1545
|
+
const out: string[] = [];
|
|
1546
|
+
for (let i = 0; i < h; i++) {
|
|
1547
|
+
const l = i < lpad ? "" : left[i - lpad];
|
|
1548
|
+
const c = i < cpad ? "" : col[i - cpad];
|
|
1549
|
+
out.push(l + " ".repeat(Math.max(0, leftW - vw(l))) + " " + c);
|
|
1550
|
+
}
|
|
1551
|
+
return out;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
function render() {
|
|
1555
|
+
if (!ui) return;
|
|
1556
|
+
if (!s.visible) {
|
|
1557
|
+
ui.setWidget(WIDGET_ID, undefined);
|
|
1558
|
+
ui.setStatus?.(WIDGET_ID, undefined);
|
|
1559
|
+
tuiRef?.requestRender?.();
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
if (placement() === "side") {
|
|
1563
|
+
ui.setWidget(WIDGET_ID, undefined);
|
|
1564
|
+
ui.setStatus?.(WIDGET_ID, undefined);
|
|
1565
|
+
tuiRef?.requestRender?.();
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
ui.setStatus?.(WIDGET_ID, undefined);
|
|
1569
|
+
tuiRef?.requestRender?.();
|
|
1570
|
+
|
|
1571
|
+
const { m, lines } = eyesNow();
|
|
1572
|
+
const w = Math.max(...lines.map((l) => l.length)) + 2;
|
|
1573
|
+
const info = [
|
|
1574
|
+
`${s.shiny ? "~" : ""}${s.name}${s.shiny ? "~" : ""} (${labelFor(s)}) lv.${level(s.xp)} ${RARITY_STARS[s.rarity]}${decoFor(m)}`,
|
|
1575
|
+
`еда ${bar(s.food)} радость ${bar(s.joy)}`,
|
|
1576
|
+
`сил ${bar(s.energy)} xp ${s.xp}`,
|
|
1577
|
+
];
|
|
1578
|
+
const rows = Math.max(lines.length, info.length);
|
|
1579
|
+
const out: string[] = [];
|
|
1580
|
+
out.push(" ".repeat(Math.max(0, w - 4)) + (bubbleOn() ? `( ${bubble} )` : dim(` ${standby()}`)));
|
|
1581
|
+
for (let i = 0; i < rows; i++) out.push((lines[i] ?? "").padEnd(w) + (info[i] ?? ""));
|
|
1582
|
+
ui.setWidget(WIDGET_ID, out);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// ---- режим dock: полоса справа через оверлей ----
|
|
1586
|
+
let overlay: any = null;
|
|
1587
|
+
let footerRows = -1;
|
|
1588
|
+
const dockOn = (termW: number) =>
|
|
1589
|
+
s.visible && placement() === "side" && (s.size ?? "dock") === "dock" && termW >= MIN_TERM_W;
|
|
1590
|
+
|
|
1591
|
+
/** Содержимое полосы: реплика, полный арт, имя, статы — всё шириной DOCK_W */
|
|
1592
|
+
const dockComponent = {
|
|
1593
|
+
render: (_w: number) => sideColumn().map((l) => l + " ".repeat(Math.max(0, dockW() - vw(l)))),
|
|
1594
|
+
invalidate: () => {},
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
let shownW = 0;
|
|
1598
|
+
function showDock() {
|
|
1599
|
+
if (!tuiRef?.showOverlay) return;
|
|
1600
|
+
shownW = dockW();
|
|
1601
|
+
try {
|
|
1602
|
+
overlay?.hide();
|
|
1603
|
+
} catch {
|
|
1604
|
+
/* уже скрыт */
|
|
1605
|
+
}
|
|
1606
|
+
// прижат к правому нижнему углу, но над футером (там путь и модель — их не закрываем)
|
|
1607
|
+
overlay = tuiRef.showOverlay(dockComponent, {
|
|
1608
|
+
anchor: "bottom-right",
|
|
1609
|
+
width: shownW,
|
|
1610
|
+
offsetY: -Math.max(0, footerRows),
|
|
1611
|
+
nonCapturing: true,
|
|
1612
|
+
visible: (tw: number) => dockOn(tw),
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/**
|
|
1617
|
+
* Сужаем по ширине все корневые блоки, кроме футера, — справа освобождается полоса.
|
|
1618
|
+
* Футер не трогаем, только меряем его высоту, чтобы встать ровно над ним.
|
|
1619
|
+
*/
|
|
1620
|
+
function installDock(tui: any, editor: any): boolean {
|
|
1621
|
+
const kids: any[] = Array.isArray(tui?.children) ? tui.children : [];
|
|
1622
|
+
if (!kids.length || typeof tui.showOverlay !== "function") return false;
|
|
1623
|
+
// Обёртки ставятся один раз на TUI, а логику берут из актуального экземпляра расширения:
|
|
1624
|
+
// после /reload старые замыкания не должны управлять раскладкой
|
|
1625
|
+
tui.__buddyHook = {
|
|
1626
|
+
cut: (w: number) => (dockOn(w) ? dockW() + 1 : 0),
|
|
1627
|
+
editor,
|
|
1628
|
+
footer: (w: number, rows: number) => {
|
|
1629
|
+
termW = w;
|
|
1630
|
+
if (rows !== footerRows || (overlay && shownW !== dockW())) {
|
|
1631
|
+
footerRows = rows;
|
|
1632
|
+
queueMicrotask(showDock); // футер поменял высоту или полоса ширину — переставляем
|
|
1633
|
+
}
|
|
1634
|
+
},
|
|
1635
|
+
};
|
|
1636
|
+
const hook = () => tui.__buddyHook;
|
|
1637
|
+
const footer = kids.find((k) => k?.children?.some((c: any) => c?.constructor?.name === "FooterComponent")) ?? kids[kids.length - 1];
|
|
1638
|
+
for (const k of kids) {
|
|
1639
|
+
if (!k || k.__buddyDock || typeof k.render !== "function") continue;
|
|
1640
|
+
const orig = k.render.bind(k);
|
|
1641
|
+
if (k.children?.includes(editor)) {
|
|
1642
|
+
// контейнер поля ввода: сам редактор сужается в sideRender, а диалоги pi
|
|
1643
|
+
// (select, confirm), которые временно встают на его место, сужаем здесь
|
|
1644
|
+
k.render = (w: number) => {
|
|
1645
|
+
const h = hook();
|
|
1646
|
+
return h && !k.children.includes(h.editor) ? orig(w - h.cut(w)) : orig(w);
|
|
1647
|
+
};
|
|
1648
|
+
} else if (k === footer) {
|
|
1649
|
+
k.render = (w: number) => {
|
|
1650
|
+
const r = orig(w);
|
|
1651
|
+
hook()?.footer(w, r.length);
|
|
1652
|
+
return r;
|
|
1653
|
+
};
|
|
1654
|
+
} else {
|
|
1655
|
+
k.render = (w: number) => orig(w - (hook()?.cut(w) ?? 0));
|
|
1656
|
+
}
|
|
1657
|
+
k.__buddyDock = true;
|
|
1658
|
+
}
|
|
1659
|
+
return true;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
/**
|
|
1663
|
+
* Встраиваемся в поле ввода, как /buddy в Claude Code. Если другое расширение
|
|
1664
|
+
* уже поставило свой редактор (vim-режим и т.п.) — оборачиваем его, а не заменяем.
|
|
1665
|
+
*/
|
|
1666
|
+
async function installSide(ctx: any) {
|
|
1667
|
+
if (!ctx.ui?.setEditorComponent) return;
|
|
1668
|
+
const tuiMod = await importPi("pi-tui");
|
|
1669
|
+
if (tuiMod?.visibleWidth) vw = tuiMod.visibleWidth;
|
|
1670
|
+
if (tuiMod?.truncateToWidth) truncW = tuiMod.truncateToWidth;
|
|
1671
|
+
|
|
1672
|
+
let base = ctx.ui.getEditorComponent?.();
|
|
1673
|
+
if (base?.__buddyBase !== undefined) base = base.__buddyBase; // после /reload не оборачиваем дважды
|
|
1674
|
+
let make = base;
|
|
1675
|
+
if (!make) {
|
|
1676
|
+
const Editor = (await importPi("pi-coding-agent"))?.CustomEditor;
|
|
1677
|
+
if (!Editor) return; // не нашли редактор — остаёмся виджетом сверху
|
|
1678
|
+
// те же опции, что у встроенного редактора pi: индикатор «Working» остаётся
|
|
1679
|
+
// в верхней рамке поля ввода, а не выносится отдельной строкой над ним
|
|
1680
|
+
const opts: Record<string, unknown> = { embedWorkingStatus: true };
|
|
1681
|
+
for (const f of [
|
|
1682
|
+
path.join(os.homedir(), ".pi", "agent", "settings.json"),
|
|
1683
|
+
path.join(String(ctx.cwd ?? process.cwd()), ".pi", "settings.json"),
|
|
1684
|
+
]) {
|
|
1685
|
+
try {
|
|
1686
|
+
const st = JSON.parse(fs.readFileSync(f, "utf8"));
|
|
1687
|
+
if (st.editorPaddingX !== undefined) opts.paddingX = st.editorPaddingX;
|
|
1688
|
+
if (st.autocompleteMaxVisible !== undefined) opts.autocompleteMaxVisible = st.autocompleteMaxVisible;
|
|
1689
|
+
} catch {
|
|
1690
|
+
/* нет файла — значения по умолчанию */
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
make = (tui: any, theme: any, kb: any) => new Editor(tui, theme, kb, opts);
|
|
1694
|
+
}
|
|
1695
|
+
const factory: any = (tui: any, theme: any, kb: any) => {
|
|
1696
|
+
tuiRef = tui;
|
|
1697
|
+
const ed = make(tui, theme, kb);
|
|
1698
|
+
const orig = ed.render.bind(ed);
|
|
1699
|
+
ed.render = (width: number) => sideRender(orig, width);
|
|
1700
|
+
// корневые контейнеры появляются после монтирования редактора
|
|
1701
|
+
setTimeout(() => {
|
|
1702
|
+
try {
|
|
1703
|
+
dockReady = installDock(tui, ed);
|
|
1704
|
+
if (dockReady) showDock();
|
|
1705
|
+
} catch {
|
|
1706
|
+
dockReady = false; // не вышло — работаем в compact
|
|
1707
|
+
}
|
|
1708
|
+
tui.requestRender?.();
|
|
1709
|
+
}, 0);
|
|
1710
|
+
return ed;
|
|
1711
|
+
};
|
|
1712
|
+
factory.__buddyBase = base ?? null;
|
|
1713
|
+
ctx.ui.setEditorComponent(factory);
|
|
1714
|
+
sideReady = true;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
function startTimer() {
|
|
1718
|
+
if (timer) return;
|
|
1719
|
+
let n = 0;
|
|
1720
|
+
timer = setInterval(() => {
|
|
1721
|
+
n++;
|
|
1722
|
+
// кадр анимации/декор сменился — перерисовываем (только тогда, чтобы не дёргать TUI зря)
|
|
1723
|
+
const m0 = mood();
|
|
1724
|
+
const fc = framesFor(s).length;
|
|
1725
|
+
const sig = () => `${pxOn() ? pxFrame(m0) + (sceneOn() ? animN - mStart : "") : frameIndex(m0, fc)}${decoFor(m0)}`;
|
|
1726
|
+
const before = sig();
|
|
1727
|
+
animN = n;
|
|
1728
|
+
if (pxOn()) pxTick();
|
|
1729
|
+
const animChanged = sig() !== before;
|
|
1730
|
+
// моргаем ~раз в 4 секунды
|
|
1731
|
+
const nextBlink = n % 8 === 0;
|
|
1732
|
+
const bubbleExpired = bubble && Date.now() >= bubbleUntil;
|
|
1733
|
+
const tempExpired = tempMood && Date.now() >= tempUntil;
|
|
1734
|
+
if (bubbleExpired) bubble = "";
|
|
1735
|
+
if (tempExpired) tempMood = null;
|
|
1736
|
+
if (quiet()) {
|
|
1737
|
+
// только разовые перерисовки по событиям (реплика ушла), без анимации
|
|
1738
|
+
blink = false;
|
|
1739
|
+
if (bubbleExpired || tempExpired) render();
|
|
1740
|
+
} else if (nextBlink !== blink || bubbleExpired || tempExpired || animChanged) {
|
|
1741
|
+
blink = nextBlink;
|
|
1742
|
+
render();
|
|
1743
|
+
}
|
|
1744
|
+
const now = Date.now();
|
|
1745
|
+
if (working && n % 60 === 0 && now - workStart > 20_000) chat("think", 0.3, 4000);
|
|
1746
|
+
// в простое иногда сам берёт любимую игрушку (не во сне и не в грусти)
|
|
1747
|
+
if (!working && !quiet() && pxOn() && n % 120 === 60 && !sceneOn()) {
|
|
1748
|
+
const md = mood();
|
|
1749
|
+
if ((md === "idle" || md === "happy") && Math.random() < 0.35) startScene();
|
|
1750
|
+
}
|
|
1751
|
+
if (!working && !quiet() && n % 360 === 0) {
|
|
1752
|
+
const idleMs = now - s.lastActivity;
|
|
1753
|
+
if (idleMs > 3 * 60_000 && idleMs < 30 * 60_000) chat("idle", 0.35, 6000);
|
|
1754
|
+
}
|
|
1755
|
+
if (n % 120 === 0) {
|
|
1756
|
+
tick();
|
|
1757
|
+
if (!quiet()) render();
|
|
1758
|
+
}
|
|
1759
|
+
}, 500);
|
|
1760
|
+
(timer as any).unref?.();
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
function activity() {
|
|
1764
|
+
s.lastActivity = Date.now();
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
// ---- события агента ----
|
|
1768
|
+
|
|
1769
|
+
// ---- обучение фразам через модель pi ----
|
|
1770
|
+
|
|
1771
|
+
/** Подсказки модели: когда звучит каждая тема */
|
|
1772
|
+
const TOPIC_HINT: Record<Topic, string> = {
|
|
1773
|
+
hello: "приветствие при запуске pi",
|
|
1774
|
+
back: "хозяин вернулся после долгого перерыва",
|
|
1775
|
+
morning: "утро, начало работы",
|
|
1776
|
+
night: "поздняя ночь, хозяин всё ещё кодит",
|
|
1777
|
+
monday: "понедельник утром",
|
|
1778
|
+
friday: "пятница вечером, шутки про деплой",
|
|
1779
|
+
weekend: "работа в выходные",
|
|
1780
|
+
done: "агент закончил ход",
|
|
1781
|
+
streak: "много запросов подряд без ошибок",
|
|
1782
|
+
bigGen: "модель выдала очень длинный ответ",
|
|
1783
|
+
think: "агент долго думает",
|
|
1784
|
+
levelup: "питомец получил новый уровень",
|
|
1785
|
+
error: "инструмент агента упал с ошибкой",
|
|
1786
|
+
errStreak: "несколько ошибок подряд, поддержка",
|
|
1787
|
+
bash: "агент запускает команду в терминале",
|
|
1788
|
+
edit: "агент правит файл",
|
|
1789
|
+
read: "агент читает или ищет код",
|
|
1790
|
+
java: "работа с Java/Kotlin-кодом, Spring, JPA",
|
|
1791
|
+
pom: "правка pom.xml или build.gradle",
|
|
1792
|
+
bpmn: "работа с BPMN-схемами, Camunda",
|
|
1793
|
+
yaml: "правка YAML",
|
|
1794
|
+
sql: "работа с SQL",
|
|
1795
|
+
testOk: "тесты прошли",
|
|
1796
|
+
testFail: "тесты упали",
|
|
1797
|
+
buildOk: "сборка прошла",
|
|
1798
|
+
buildFail: "сборка упала",
|
|
1799
|
+
gitCommit: "git commit",
|
|
1800
|
+
gitPush: "git push",
|
|
1801
|
+
gitFail: "git выдал ошибку",
|
|
1802
|
+
docker: "команды docker",
|
|
1803
|
+
k8s: "команды kubectl/helm",
|
|
1804
|
+
rm: "агент удаляет файлы",
|
|
1805
|
+
idle: "редкая реплика в простое",
|
|
1806
|
+
feed: "хозяин покормил",
|
|
1807
|
+
full: "покормили, а питомец сыт",
|
|
1808
|
+
pet: "хозяин погладил",
|
|
1809
|
+
play: "поиграли",
|
|
1810
|
+
tired: "нет сил играть",
|
|
1811
|
+
nap: "лёг вздремнуть",
|
|
1812
|
+
stbIdle: "тихая подпись-ремарка в простое: что делает питомец (3-е лицо)",
|
|
1813
|
+
stbWork: "тихая ремарка, пока агент работает",
|
|
1814
|
+
stbSleep: "тихая ремарка, пока питомец спит",
|
|
1815
|
+
stbSad: "тихая ремарка, когда питомцу грустно",
|
|
1816
|
+
stbMute: "тихая ремарка в режиме тишины",
|
|
1817
|
+
};
|
|
1818
|
+
const TOPICS = Object.keys(TOPIC_HINT) as Topic[];
|
|
1819
|
+
|
|
1820
|
+
/**
|
|
1821
|
+
* В какой позе бывает пиксельный пёс, когда звучит тема (см. события и pxState):
|
|
1822
|
+
* приветствия — на старте (стоит), рабочие — пока агент идёт, ошибки — грустит лёжа и т. д.
|
|
1823
|
+
* Фраза с меткой другой позы не прозвучала бы никогда.
|
|
1824
|
+
*/
|
|
1825
|
+
type Pose = "stand" | "walk" | "lie";
|
|
1826
|
+
const TOPIC_POSES: Record<Topic, Pose[]> = {
|
|
1827
|
+
hello: ["stand"], back: ["stand"], morning: ["stand"], night: ["stand"], monday: ["stand"],
|
|
1828
|
+
friday: ["stand"], weekend: ["stand"],
|
|
1829
|
+
done: ["stand"], streak: ["stand"], levelup: ["stand"], bigGen: ["walk"], think: ["walk"],
|
|
1830
|
+
error: ["lie"], errStreak: ["lie"], testFail: ["lie"], buildFail: ["lie"], gitFail: ["lie"],
|
|
1831
|
+
bash: ["walk"], edit: ["walk"], read: ["walk"], java: ["walk"], pom: ["walk"], bpmn: ["walk"],
|
|
1832
|
+
yaml: ["walk"], sql: ["walk"], testOk: ["walk"], buildOk: ["walk"], gitCommit: ["walk"],
|
|
1833
|
+
gitPush: ["walk"], docker: ["walk"], k8s: ["walk"], rm: ["walk"],
|
|
1834
|
+
idle: ["lie"], feed: ["stand"], full: ["stand", "lie"], pet: ["stand"], play: ["stand"],
|
|
1835
|
+
tired: ["stand", "lie"], nap: ["lie"],
|
|
1836
|
+
stbIdle: ["stand", "lie"], stbWork: ["walk"], stbSleep: ["lie"], stbSad: ["lie"], stbMute: ["stand", "lie"],
|
|
1837
|
+
};
|
|
1838
|
+
const POSE_RU: Record<Pose, string> = { stand: "стоит", walk: "идёт", lie: "лежит" };
|
|
1839
|
+
const poseNote = (t: Topic) => TOPIC_POSES[t].map((p) => `${POSE_RU[p]} (@${p}:)`).join(" или ");
|
|
1840
|
+
|
|
1841
|
+
/** Фраза осмысленна для темы: длина в норме и метка позы (если есть) возможна */
|
|
1842
|
+
function phraseProblem(t: Topic, x: string): string | null {
|
|
1843
|
+
const tag = /^@(\w+):/.exec(x)?.[1];
|
|
1844
|
+
const body = x.replace(/^@\w+:/, "").trim();
|
|
1845
|
+
if (body.length < 2) return "пустая";
|
|
1846
|
+
if (body.length > MAX_LEN) return "длинная";
|
|
1847
|
+
if (tag && !TOPIC_POSES[t].includes(tag as Pose)) return `пёс тут ${TOPIC_POSES[t].map((p) => POSE_RU[p]).join("/")}, а не ${POSE_RU[tag as Pose] ?? tag}`;
|
|
1848
|
+
return null;
|
|
1849
|
+
}
|
|
1850
|
+
const MAX_LEN = 34;
|
|
1851
|
+
|
|
1852
|
+
/** Чистим и проверяем фразы от модели/агента; дубликаты отсекаем */
|
|
1853
|
+
function cleanPhrases(t: Topic, list: unknown): string[] {
|
|
1854
|
+
if (!Array.isArray(list)) return [];
|
|
1855
|
+
const seen = new Set(pool(t).map((x) => x.toLowerCase()));
|
|
1856
|
+
const out: string[] = [];
|
|
1857
|
+
for (const raw of list) {
|
|
1858
|
+
if (typeof raw !== "string") continue;
|
|
1859
|
+
let x = raw.replace(/[\r\n]+/g, " ").trim().replace(/^["«']+|["»']+$/g, "").trim();
|
|
1860
|
+
const tag = /^@(stand|walk|lie):/.exec(x)?.[0] ?? "";
|
|
1861
|
+
const body = x.slice(tag.length).trim();
|
|
1862
|
+
x = tag + body;
|
|
1863
|
+
if (phraseProblem(t, x)) continue; // длинная или поза, в которой пёс тут не бывает
|
|
1864
|
+
if (seen.has(body.toLowerCase())) continue;
|
|
1865
|
+
seen.add(body.toLowerCase());
|
|
1866
|
+
out.push(x);
|
|
1867
|
+
}
|
|
1868
|
+
return out;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
/** Дописать фразы в buddy-phrases.json (для текущего вида) и сразу начать их использовать */
|
|
1872
|
+
function savePhrases(add: Partial<Record<Topic, string[]>>): number {
|
|
1873
|
+
let file: any = {};
|
|
1874
|
+
try {
|
|
1875
|
+
file = JSON.parse(fs.readFileSync(PHRASES_FILE, "utf8")) ?? {};
|
|
1876
|
+
} catch {
|
|
1877
|
+
/* файла ещё нет */
|
|
1878
|
+
}
|
|
1879
|
+
const sp = (file[s.species] ??= {});
|
|
1880
|
+
let n = 0;
|
|
1881
|
+
for (const [t, list] of Object.entries(add)) {
|
|
1882
|
+
if (!list?.length) continue;
|
|
1883
|
+
sp[t] = [...(Array.isArray(sp[t]) ? sp[t] : []), ...list];
|
|
1884
|
+
n += list.length;
|
|
1885
|
+
}
|
|
1886
|
+
if (!n) return 0;
|
|
1887
|
+
fs.mkdirSync(path.dirname(PHRASES_FILE), { recursive: true });
|
|
1888
|
+
fs.writeFileSync(PHRASES_FILE, JSON.stringify(file, null, 2));
|
|
1889
|
+
custom = file;
|
|
1890
|
+
bags.clear();
|
|
1891
|
+
return n;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
/** Ответ модели → объект {тема: [фразы]} (модели любят оборачивать JSON в ```) */
|
|
1895
|
+
function parseJsonObject(text: string): Record<string, unknown> | null {
|
|
1896
|
+
const m = /\{[\s\S]*\}/.exec(text);
|
|
1897
|
+
if (!m) return null;
|
|
1898
|
+
try {
|
|
1899
|
+
const o = JSON.parse(m[0]);
|
|
1900
|
+
return o && typeof o === "object" && !Array.isArray(o) ? o : null;
|
|
1901
|
+
} catch {
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
/**
|
|
1907
|
+
* /buddy learn [тема | пожелание] — текущая модель pi придумывает новые фразы в характере
|
|
1908
|
+
* питомца. Отдельный вызов модели: в историю сессии ничего не попадает.
|
|
1909
|
+
*/
|
|
1910
|
+
async function learn(ctx: any, arg: string) {
|
|
1911
|
+
const model = ctx.model;
|
|
1912
|
+
if (!model || !ctx.modelRegistry?.complete) {
|
|
1913
|
+
ctx.ui.notify("Нет активной модели pi — выбери её через /model", "warning");
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
const one = TOPICS.find((t) => t.toLowerCase() === arg.toLowerCase());
|
|
1917
|
+
const topics = one ? [one] : TOPICS;
|
|
1918
|
+
const perTopic = one ? 10 : 2;
|
|
1919
|
+
const wish = one ? "" : arg.trim();
|
|
1920
|
+
const dog = !!spec(s).say?.done?.some((x) => /гав/i.test(x));
|
|
1921
|
+
const examples = topics
|
|
1922
|
+
.map((t) => {
|
|
1923
|
+
const ex = pool(t).sort(() => Math.random() - 0.5).slice(0, 3).map((x) => JSON.stringify(x)).join(", ");
|
|
1924
|
+
return `- ${t}: ${TOPIC_HINT[t]}; пёс в этот момент ${poseNote(t)}. Уже есть: ${ex}`;
|
|
1925
|
+
})
|
|
1926
|
+
.join("\n");
|
|
1927
|
+
const prompt = [
|
|
1928
|
+
`Придумай новые короткие реплики для питомца в терминале программиста.`,
|
|
1929
|
+
`Питомец: ${labelFor(s)} по кличке ${s.name}.` +
|
|
1930
|
+
(dog
|
|
1931
|
+
? ` Это собака: часть реплик — короткая речь («гав!», «апорт!»), часть — ремарки в 3-м лице («виляет хвостом»).`
|
|
1932
|
+
: ""),
|
|
1933
|
+
`Хозяин — Java/Spring-разработчик, работает с AI-агентом pi в терминале.`,
|
|
1934
|
+
wish ? `Пожелание хозяина: ${wish}. Подбери подходящие темы сам, всего до 15 реплик.` : `По ${perTopic} новых реплики на тему.`,
|
|
1935
|
+
``,
|
|
1936
|
+
`Правила: по-русски; не длиннее ${MAX_LEN} символов; без эмодзи и без кавычек вокруг; не повторяй примеры;`,
|
|
1937
|
+
`добрый юмор, без грубости. Ремарку про позу тела помечай префиксом позы, и только той позы,`,
|
|
1938
|
+
`что указана у темы (@stand: стоит, @walk: идёт, @lie: лежит). Пёс стоит на четырёх лапах, не сидит.`,
|
|
1939
|
+
``,
|
|
1940
|
+
`Темы:`,
|
|
1941
|
+
examples,
|
|
1942
|
+
``,
|
|
1943
|
+
`Ответь ТОЛЬКО JSON-объектом {"тема": ["реплика", ...]} с темами из списка.`,
|
|
1944
|
+
].join("\n");
|
|
1945
|
+
|
|
1946
|
+
say("думаю над новыми фразами...", 120_000);
|
|
1947
|
+
render();
|
|
1948
|
+
try {
|
|
1949
|
+
const res = await ctx.modelRegistry.complete(
|
|
1950
|
+
model,
|
|
1951
|
+
{ messages: [{ role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() }] },
|
|
1952
|
+
{ cacheRetention: "none" },
|
|
1953
|
+
);
|
|
1954
|
+
const text = (res?.content ?? [])
|
|
1955
|
+
.filter((c: any) => c?.type === "text")
|
|
1956
|
+
.map((c: any) => c.text)
|
|
1957
|
+
.join("\n");
|
|
1958
|
+
const obj = parseJsonObject(text);
|
|
1959
|
+
if (!obj) throw new Error("модель ответила не JSON");
|
|
1960
|
+
const add: Partial<Record<Topic, string[]>> = {};
|
|
1961
|
+
for (const [t, list] of Object.entries(obj)) {
|
|
1962
|
+
if (!TOPICS.includes(t as Topic)) continue;
|
|
1963
|
+
if (one && t !== one) continue;
|
|
1964
|
+
add[t as Topic] = cleanPhrases(t as Topic, list);
|
|
1965
|
+
}
|
|
1966
|
+
const n = savePhrases(add);
|
|
1967
|
+
const sample = Object.values(add).flat().slice(0, 6).map((x) => x!.replace(/^@\w+:/, ""));
|
|
1968
|
+
say(n ? `выучил ${n} новых фраз!` : "ничего нового не придумал", 6000);
|
|
1969
|
+
react(n ? "happy" : "sad", 4000);
|
|
1970
|
+
ctx.ui.notify(
|
|
1971
|
+
n
|
|
1972
|
+
? `${s.name} выучил ${n} фраз (${Object.values(add).filter((v) => v?.length).length} тем) — ${model.id}.\n` +
|
|
1973
|
+
`Например: ${sample.join(" · ")}\nВсе свои фразы: ${PHRASES_FILE}`
|
|
1974
|
+
: "Модель не предложила ничего нового (или всё отсеялось как повторы/длинное).",
|
|
1975
|
+
n ? "info" : "warning",
|
|
1976
|
+
);
|
|
1977
|
+
} catch (e: any) {
|
|
1978
|
+
say("не вышло выучить :(", 5000);
|
|
1979
|
+
ctx.ui.notify(`Не получилось: ${e?.message ?? e}`, "error");
|
|
1980
|
+
}
|
|
1981
|
+
render();
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
// ---- инструмент для агента: «добавь Сальсе фраз про Kafka» прямо в чате ----
|
|
1985
|
+
let toolReady = false;
|
|
1986
|
+
async function registerPhraseTool() {
|
|
1987
|
+
if (toolReady || typeof (pi as any).registerTool !== "function") return;
|
|
1988
|
+
let Type: any = null;
|
|
1989
|
+
for (const m of ["typebox", "@sinclair/typebox"]) {
|
|
1990
|
+
try {
|
|
1991
|
+
Type = (await import(m)).Type;
|
|
1992
|
+
if (Type) break;
|
|
1993
|
+
} catch {
|
|
1994
|
+
/* следующий */
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
if (!Type) return; // без схем инструмент не зарегистрировать — остаётся /buddy learn
|
|
1998
|
+
toolReady = true;
|
|
1999
|
+
(pi as any).registerTool({
|
|
2000
|
+
name: "buddy_phrases",
|
|
2001
|
+
label: "Buddy phrases",
|
|
2002
|
+
description:
|
|
2003
|
+
"Add new speech phrases to the user's terminal pet (buddy extension). Use ONLY when the user asks to " +
|
|
2004
|
+
"teach/extend the pet's phrases. Phrases must be Russian, at most 34 characters, in the pet's character. " +
|
|
2005
|
+
`Topics: ${TOPICS.map((t) => `${t} (${TOPIC_HINT[t]})`).join("; ")}. ` +
|
|
2006
|
+
"Body/pose remarks may be prefixed with @stand:, @walk: or @lie:, but only with the pose the pet is in for that topic: " +
|
|
2007
|
+
TOPICS.map((t) => `${t}=${TOPIC_POSES[t].join("/")}`).join(", ") +
|
|
2008
|
+
". The pet stands on four legs and never sits.",
|
|
2009
|
+
promptSnippet: "Teach the user's terminal pet new phrases",
|
|
2010
|
+
parameters: Type.Object({
|
|
2011
|
+
topic: Type.String({ description: "One of the topics listed in the tool description" }),
|
|
2012
|
+
phrases: Type.Array(Type.String(), { description: "New phrases, each at most 34 characters" }),
|
|
2013
|
+
}),
|
|
2014
|
+
async execute(_id: string, params: { topic: string; phrases: string[] }) {
|
|
2015
|
+
const t = TOPICS.find((x) => x === params.topic);
|
|
2016
|
+
if (!t) {
|
|
2017
|
+
return { content: [{ type: "text", text: `Unknown topic "${params.topic}". Valid: ${TOPICS.join(", ")}` }], details: {} };
|
|
2018
|
+
}
|
|
2019
|
+
const list = cleanPhrases(t, params.phrases);
|
|
2020
|
+
const n = savePhrases({ [t]: list });
|
|
2021
|
+
if (n) {
|
|
2022
|
+
say(`новые фразы: ${n}!`, 5000);
|
|
2023
|
+
react("happy", 4000);
|
|
2024
|
+
render();
|
|
2025
|
+
}
|
|
2026
|
+
return {
|
|
2027
|
+
content: [
|
|
2028
|
+
{
|
|
2029
|
+
type: "text",
|
|
2030
|
+
text: n
|
|
2031
|
+
? `Added ${n} phrase(s) to topic ${t} for ${s.name}: ${list.join(" | ")}`
|
|
2032
|
+
: "Nothing added: all phrases were duplicates, empty or longer than 34 characters.",
|
|
2033
|
+
},
|
|
2034
|
+
],
|
|
2035
|
+
details: { added: n },
|
|
2036
|
+
};
|
|
2037
|
+
},
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
pi.on("session_start", async (_e, ctx) => {
|
|
2042
|
+
registerPhraseTool().catch(() => {});
|
|
2043
|
+
if ((ctx as any).hasUI === false) return;
|
|
2044
|
+
ui = ctx.ui;
|
|
2045
|
+
try {
|
|
2046
|
+
await installSide(ctx);
|
|
2047
|
+
} catch {
|
|
2048
|
+
sideReady = false;
|
|
2049
|
+
}
|
|
2050
|
+
const greet = greetingTopic(); // до activity(): нужно знать, сколько тебя не было
|
|
2051
|
+
tick();
|
|
2052
|
+
activity();
|
|
2053
|
+
shout(greet, 4000);
|
|
2054
|
+
render();
|
|
2055
|
+
startTimer();
|
|
2056
|
+
});
|
|
2057
|
+
|
|
2058
|
+
let levelAtStart = 1;
|
|
2059
|
+
let workStart = 0;
|
|
2060
|
+
let runErrors = 0;
|
|
2061
|
+
let errRow = 0;
|
|
2062
|
+
|
|
2063
|
+
pi.on("agent_start", async () => {
|
|
2064
|
+
levelAtStart = level(s.xp);
|
|
2065
|
+
workStart = Date.now();
|
|
2066
|
+
runErrors = 0;
|
|
2067
|
+
working = true;
|
|
2068
|
+
activity();
|
|
2069
|
+
render();
|
|
2070
|
+
});
|
|
2071
|
+
|
|
2072
|
+
/** Каждая генерация модели: кормит, утомляет, даёт XP пропорционально объёму */
|
|
2073
|
+
pi.on("turn_end", async (e: any) => {
|
|
2074
|
+
tick();
|
|
2075
|
+
const out = Number(e?.message?.usage?.output) || 0;
|
|
2076
|
+
s.generations = (s.generations ?? 0) + 1;
|
|
2077
|
+
s.tokensOut = (s.tokensOut ?? 0) + out;
|
|
2078
|
+
s.food = clamp(s.food + GAIN.genFood + Math.min(GAIN.tokFoodMax, out * GAIN.tokFood));
|
|
2079
|
+
s.energy = clamp(s.energy + GAIN.genEnergy);
|
|
2080
|
+
s.xp += GAIN.genXp + Math.floor(out * GAIN.tokXp);
|
|
2081
|
+
if (out > 3000) chat("bigGen", 0.6);
|
|
2082
|
+
render();
|
|
2083
|
+
});
|
|
2084
|
+
|
|
2085
|
+
pi.on("tool_result", async (e: any) => {
|
|
2086
|
+
s.toolCalls++;
|
|
2087
|
+
s.xp += GAIN.toolXp;
|
|
2088
|
+
s.energy = clamp(s.energy + GAIN.toolEnergy);
|
|
2089
|
+
const ok = !e?.isError;
|
|
2090
|
+
const c = classify(String(e?.toolName ?? ""), e?.input, ok);
|
|
2091
|
+
if (ok) {
|
|
2092
|
+
errRow = 0;
|
|
2093
|
+
s.joy = clamp(s.joy + GAIN.toolJoy);
|
|
2094
|
+
if (c) chat(c.t, c.p);
|
|
2095
|
+
} else {
|
|
2096
|
+
s.errors++;
|
|
2097
|
+
runErrors++;
|
|
2098
|
+
errRow++;
|
|
2099
|
+
s.joy = clamp(s.joy + GAIN.errJoy);
|
|
2100
|
+
react("sad", 4000);
|
|
2101
|
+
const now = Date.now();
|
|
2102
|
+
if (now - lastErrSay > ERR_COOLDOWN) {
|
|
2103
|
+
lastErrSay = now;
|
|
2104
|
+
shout(c?.t ?? (errRow >= 3 ? "errStreak" : "error"));
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
render();
|
|
2108
|
+
});
|
|
2109
|
+
|
|
2110
|
+
pi.on("agent_end", async () => {
|
|
2111
|
+
working = false;
|
|
2112
|
+
activity();
|
|
2113
|
+
tick();
|
|
2114
|
+
s.joy = clamp(s.joy + GAIN.runJoy);
|
|
2115
|
+
s.cleanRuns = runErrors ? 0 : (s.cleanRuns ?? 0) + 1;
|
|
2116
|
+
if (level(s.xp) > levelAtStart) {
|
|
2117
|
+
const grewUp = spec(s).grown && level(s.xp) >= GROW_LEVEL && levelAtStart < GROW_LEVEL;
|
|
2118
|
+
say(grewUp ? `${s.name} вырос! теперь это ${labelFor(s)}` : `${line("levelup")} lv.${level(s.xp)}`, 8000);
|
|
2119
|
+
react("love", 6000);
|
|
2120
|
+
} else {
|
|
2121
|
+
if ([5, 10, 25, 50, 100].includes(s.cleanRuns)) shout("streak", 6000);
|
|
2122
|
+
else chat("done", 0.6);
|
|
2123
|
+
react("happy", 4000);
|
|
2124
|
+
}
|
|
2125
|
+
save(s);
|
|
2126
|
+
render();
|
|
2127
|
+
});
|
|
2128
|
+
|
|
2129
|
+
pi.on("session_shutdown", async () => {
|
|
2130
|
+
try {
|
|
2131
|
+
overlay?.hide();
|
|
2132
|
+
} catch {
|
|
2133
|
+
/* ок */
|
|
2134
|
+
}
|
|
2135
|
+
tick();
|
|
2136
|
+
save(s);
|
|
2137
|
+
if (timer) clearInterval(timer);
|
|
2138
|
+
timer = null;
|
|
2139
|
+
});
|
|
2140
|
+
|
|
2141
|
+
// ---- команды ----
|
|
2142
|
+
|
|
2143
|
+
pi.registerCommand("buddy", {
|
|
2144
|
+
description: "Тамагочи: /buddy [feed|pet|play|nap|choose|side|top|dock|compact|big|mute|unmute|anim|phrases|name <имя>|stats|hatch]",
|
|
2145
|
+
getArgumentCompletions: (prefix: string) => {
|
|
2146
|
+
const parts = prefix.split(/\s+/);
|
|
2147
|
+
if (parts[0] === "choose" && parts.length > 1)
|
|
2148
|
+
return SPECIES_KEYS.filter((k) => k.startsWith(parts[1])).map((k) => ({
|
|
2149
|
+
value: `choose ${k}`,
|
|
2150
|
+
label: k,
|
|
2151
|
+
description: `${SPECIES[k].label} (${SPECIES[k].rarity})`,
|
|
2152
|
+
}));
|
|
2153
|
+
return ["feed", "pet", "play", "nap", "choose", "side", "top", "mute", "unmute", "anim", "pxcolor", "learn", "phrases", "dock", "compact", "big", "name", "stats", "hatch"]
|
|
2154
|
+
.filter((c) => c.startsWith(parts[0] ?? ""))
|
|
2155
|
+
.map((c) => ({ value: c, label: c }));
|
|
2156
|
+
},
|
|
2157
|
+
handler: async (args: string, ctx: any) => {
|
|
2158
|
+
ui = ctx.ui ?? ui;
|
|
2159
|
+
startTimer();
|
|
2160
|
+
tick();
|
|
2161
|
+
activity();
|
|
2162
|
+
const [cmd, ...rest] = (args ?? "").trim().split(/\s+/);
|
|
2163
|
+
|
|
2164
|
+
switch ((cmd ?? "").toLowerCase()) {
|
|
2165
|
+
case "":
|
|
2166
|
+
s.visible = !s.visible;
|
|
2167
|
+
break;
|
|
2168
|
+
case "feed":
|
|
2169
|
+
if (s.food > 95) shout("full", 4000, true);
|
|
2170
|
+
else {
|
|
2171
|
+
s.food = clamp(s.food + 30);
|
|
2172
|
+
s.energy = clamp(s.energy + 5);
|
|
2173
|
+
react("eat");
|
|
2174
|
+
shout("feed", 4000, true);
|
|
2175
|
+
}
|
|
2176
|
+
break;
|
|
2177
|
+
case "pet":
|
|
2178
|
+
s.joy = clamp(s.joy + 15);
|
|
2179
|
+
react("love");
|
|
2180
|
+
shout("pet", 4000, true);
|
|
2181
|
+
break;
|
|
2182
|
+
case "play":
|
|
2183
|
+
if (s.energy < 15) shout("tired", 4000, true);
|
|
2184
|
+
else {
|
|
2185
|
+
const want = rest.join(" ").toLowerCase();
|
|
2186
|
+
const its = spec(s).items ?? [];
|
|
2187
|
+
const it = its.find((x) => want && x.name.startsWith(want)) ?? its.find((x) => x.mode === "toss");
|
|
2188
|
+
if (it && startScene(it, 10_000)) say(scene!.text, 4000);
|
|
2189
|
+
s.joy = clamp(s.joy + 25);
|
|
2190
|
+
s.energy = clamp(s.energy - 15);
|
|
2191
|
+
s.food = clamp(s.food - 5);
|
|
2192
|
+
s.xp += 2;
|
|
2193
|
+
react("happy");
|
|
2194
|
+
shout("play", 4000, true);
|
|
2195
|
+
}
|
|
2196
|
+
break;
|
|
2197
|
+
case "nap":
|
|
2198
|
+
s.energy = clamp(s.energy + 40);
|
|
2199
|
+
react("sleep", 8000);
|
|
2200
|
+
shout("nap", 4000, true);
|
|
2201
|
+
break;
|
|
2202
|
+
case "choose": {
|
|
2203
|
+
let key = rest[0]?.toLowerCase();
|
|
2204
|
+
if (!key) {
|
|
2205
|
+
const opts = SPECIES_KEYS.map(
|
|
2206
|
+
(k) => `${SPECIES[k].label} (${SPECIES[k].rarity})${k === s.species ? " <- сейчас" : ""}`,
|
|
2207
|
+
);
|
|
2208
|
+
const picked = await ctx.ui.select("Выбери питомца", opts);
|
|
2209
|
+
if (picked === undefined) break;
|
|
2210
|
+
key = SPECIES_KEYS[opts.indexOf(picked)];
|
|
2211
|
+
}
|
|
2212
|
+
if (!SPECIES[key]) {
|
|
2213
|
+
ctx.ui.notify(`Нет такого вида. Есть: ${SPECIES_KEYS.join(", ")}`, "warning");
|
|
2214
|
+
break;
|
|
2215
|
+
}
|
|
2216
|
+
// меняем облик, прогресс (имя, xp, статы) сохраняется
|
|
2217
|
+
s.species = key;
|
|
2218
|
+
s.rarity = SPECIES[key].rarity;
|
|
2219
|
+
react("happy", 5000);
|
|
2220
|
+
shout("hello", 5000, true);
|
|
2221
|
+
break;
|
|
2222
|
+
}
|
|
2223
|
+
case "mute":
|
|
2224
|
+
s.muted = true;
|
|
2225
|
+
say("молчу, но всё вижу", 3000);
|
|
2226
|
+
break;
|
|
2227
|
+
case "unmute":
|
|
2228
|
+
s.muted = false;
|
|
2229
|
+
shout("hello", 4000, true);
|
|
2230
|
+
break;
|
|
2231
|
+
case "learn":
|
|
2232
|
+
await learn(ctx, rest.join(" "));
|
|
2233
|
+
break;
|
|
2234
|
+
case "phrases": {
|
|
2235
|
+
if ((rest[0] ?? "").toLowerCase() === "clean") {
|
|
2236
|
+
// убрать из своих фраз то, что никогда не прозвучит: длинное или с невозможной позой
|
|
2237
|
+
let file: any = {};
|
|
2238
|
+
try {
|
|
2239
|
+
file = JSON.parse(fs.readFileSync(PHRASES_FILE, "utf8")) ?? {};
|
|
2240
|
+
} catch {
|
|
2241
|
+
ctx.ui.notify("Своих фраз пока нет", "info");
|
|
2242
|
+
break;
|
|
2243
|
+
}
|
|
2244
|
+
const removed: string[] = [];
|
|
2245
|
+
const fix = (obj: any) => {
|
|
2246
|
+
for (const [t, list] of Object.entries(obj ?? {})) {
|
|
2247
|
+
if (!TOPICS.includes(t as Topic) || !Array.isArray(list)) continue;
|
|
2248
|
+
obj[t] = list.filter((x: unknown) => {
|
|
2249
|
+
const why = typeof x === "string" ? phraseProblem(t as Topic, x) : "не строка";
|
|
2250
|
+
if (why) removed.push(`${t}: «${String(x)}» — ${why}`);
|
|
2251
|
+
return !why;
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
};
|
|
2255
|
+
fix(file);
|
|
2256
|
+
for (const k of Object.keys(SPECIES)) if (file[k] && typeof file[k] === "object") fix(file[k]);
|
|
2257
|
+
if (removed.length) {
|
|
2258
|
+
fs.writeFileSync(PHRASES_FILE, JSON.stringify(file, null, 2));
|
|
2259
|
+
custom = file;
|
|
2260
|
+
bags.clear();
|
|
2261
|
+
}
|
|
2262
|
+
ctx.ui.notify(removed.length ? `Убрал ${removed.length}:\n${removed.join("\n")}` : "Все свои фразы в порядке", "info");
|
|
2263
|
+
break;
|
|
2264
|
+
}
|
|
2265
|
+
if (!fs.existsSync(PHRASES_FILE)) {
|
|
2266
|
+
const tpl = {
|
|
2267
|
+
_help: "Свои фразы дополняют встроенные. Темы: " + Object.keys(SAY).join(", ") +
|
|
2268
|
+
". Для конкретного вида: ключ вида (veo, gsd, cat...) с темами внутри. После правки — /reload.",
|
|
2269
|
+
done: ["моя фраза после хода"],
|
|
2270
|
+
veo: { hello: ["гав! моя фраза для ВЕО"] },
|
|
2271
|
+
};
|
|
2272
|
+
fs.writeFileSync(PHRASES_FILE, JSON.stringify(tpl, null, 2));
|
|
2273
|
+
}
|
|
2274
|
+
ctx.ui.notify(`Свои фразы: ${PHRASES_FILE}\nПосле правки — /reload`, "info");
|
|
2275
|
+
break;
|
|
2276
|
+
}
|
|
2277
|
+
case "pxcolor": {
|
|
2278
|
+
const v = (rest[0] ?? "").toLowerCase();
|
|
2279
|
+
if (v === "fg" || v === "bg" || v === "auto") s.pxColor = v;
|
|
2280
|
+
ctx.ui.notify(
|
|
2281
|
+
`Пиксельный пёс: ${s.pxColor ?? "auto"} (сейчас ${pxMode()}). bg — чётче, нужен 24-битный фон; ` +
|
|
2282
|
+
"fg — только цвет символов, для терминала IntelliJ. auto выбирает fg в IntelliJ.",
|
|
2283
|
+
"info",
|
|
2284
|
+
);
|
|
2285
|
+
break;
|
|
2286
|
+
}
|
|
2287
|
+
case "anim": {
|
|
2288
|
+
const v = (rest[0] ?? "").toLowerCase();
|
|
2289
|
+
if (v === "on" || v === "off" || v === "auto") s.anim = v;
|
|
2290
|
+
ctx.ui.notify(
|
|
2291
|
+
`Анимация: ${s.anim ?? "auto"}. auto — анимируется в полноэкранном режиме pi, ` +
|
|
2292
|
+
"а в обычном только пока агент работает (чтобы не мешать прокрутке терминала).",
|
|
2293
|
+
"info",
|
|
2294
|
+
);
|
|
2295
|
+
break;
|
|
2296
|
+
}
|
|
2297
|
+
case "dock":
|
|
2298
|
+
case "big":
|
|
2299
|
+
case "compact":
|
|
2300
|
+
s.size = cmd as Size;
|
|
2301
|
+
if (sideReady) s.placement = "side";
|
|
2302
|
+
break;
|
|
2303
|
+
case "side":
|
|
2304
|
+
case "top":
|
|
2305
|
+
if (cmd === "side" && !sideReady) {
|
|
2306
|
+
ctx.ui.notify("Сбоку не получилось: редактор pi недоступен для встраивания. Остаюсь сверху.", "warning");
|
|
2307
|
+
break;
|
|
2308
|
+
}
|
|
2309
|
+
s.placement = cmd as Placement;
|
|
2310
|
+
break;
|
|
2311
|
+
case "name":
|
|
2312
|
+
if (rest.length) {
|
|
2313
|
+
s.name = rest.join(" ").slice(0, 20);
|
|
2314
|
+
say(`теперь я ${s.name}!`);
|
|
2315
|
+
}
|
|
2316
|
+
break;
|
|
2317
|
+
case "stats":
|
|
2318
|
+
ctx.ui.notify(
|
|
2319
|
+
`${s.name} — ${labelFor(s)} (${s.rarity}${s.shiny ? ", shiny!" : ""}), lv.${level(s.xp)}, xp ${s.xp}\n` +
|
|
2320
|
+
`возраст: ${age(s.born)}, генераций: ${s.generations ?? 0}, токенов: ${s.tokensOut ?? 0}, ` +
|
|
2321
|
+
`tool calls: ${s.toolCalls}, ошибок пережито: ${s.errors}\n` +
|
|
2322
|
+
`еда ${Math.round(s.food)} · радость ${Math.round(s.joy)} · силы ${Math.round(s.energy)}`,
|
|
2323
|
+
"info",
|
|
2324
|
+
);
|
|
2325
|
+
break;
|
|
2326
|
+
case "hatch": {
|
|
2327
|
+
const ok = ctx.ui.confirm
|
|
2328
|
+
? await ctx.ui.confirm("Новое яйцо", `${s.name} уйдёт навсегда. Точно?`)
|
|
2329
|
+
: true;
|
|
2330
|
+
if (ok) {
|
|
2331
|
+
s = hatch(`${os.userInfo().username}-${Date.now()}-${Math.random()}`);
|
|
2332
|
+
say(`вылупился! я ${labelFor(s)}`, 6000);
|
|
2333
|
+
react("happy");
|
|
2334
|
+
}
|
|
2335
|
+
break;
|
|
2336
|
+
}
|
|
2337
|
+
default:
|
|
2338
|
+
ctx.ui.notify("Не понял. /buddy feed | pet | play | nap | choose [вид] | side | top | dock | compact | big | mute | unmute | anim on/off/auto | phrases | name <имя> | stats | hatch", "warning");
|
|
2339
|
+
}
|
|
2340
|
+
s.visible = cmd === "" ? s.visible : true;
|
|
2341
|
+
save(s);
|
|
2342
|
+
render();
|
|
2343
|
+
},
|
|
2344
|
+
});
|
|
2345
|
+
}
|