Руководство по бафам it

Тут тублица не нужна, но я использовал.
Как вы можете видеть, нам нужны все 3 HTML. Мы делаем их, и копируем текст, но мы редактируем их.

Второй шаг: SQL

Так мы здесь
Сначала мы делаем таблицу, в которой будут содержаться ID бафов.
Вот так:

INSERT INTO `npc_buffer` VALUES ('36402', '4356', '3', '0', '0', '4356');INSERT INTO `npc_buffer` VALUES ('36402', '4352', '2', '0', '0', '4352');INSERT INTO `npc_buffer` VALUES ('36402', '4345', '3', '0', '0', '4345');INSERT INTO `npc_buffer` VALUES ('36402', '4359', '3', '0', '0', '4359');INSERT INTO `npc_buffer` VALUES ('36402', '4351', '6', '0', '0', '4351');INSERT INTO `npc_buffer` VALUES ('36402', '4355', '3', '0', '0', '4355');INSERT INTO `npc_buffer` VALUES ('36402', '4357', '2', '0', '0', '4357');INSERT INTO `npc_buffer` VALUES ('36402', '4342', '2', '0', '0', '4342');INSERT INTO `npc_buffer` VALUES ('36402', '4358', '3', '0', '0', '4358');INSERT INTO `npc_buffer` VALUES ('36402', '4360', '3', '0', '0', '4360');INSERT INTO `npc_buffer` VALUES ('50016', '264', '1', '0', '0', '264');INSERT INTO `npc_buffer` VALUES ('50016', '265', '1', '0', '0', '265');INSERT INTO `npc_buffer` VALUES ('50016', '266', '1', '0', '0', '266');INSERT INTO `npc_buffer` VALUES ('50016', '267', '1', '0', '0', '267');INSERT INTO `npc_buffer` VALUES ('50016', '268', '1', '0', '0', '268');INSERT INTO `npc_buffer` VALUES ('50016', '269', '1', '0', '0', '269');INSERT INTO `npc_buffer` VALUES ('50016', '270', '1', '0', '0', '270');INSERT INTO `npc_buffer` VALUES ('50016', '271', '1', '0', '0', '271');INSERT INTO `npc_buffer` VALUES ('50016', '272', '1', '0', '0', '272');INSERT INTO `npc_buffer` VALUES ('50016', '273', '1', '0', '0', '273');INSERT INTO `npc_buffer` VALUES ('50016', '274', '1', '0', '0', '274');INSERT INTO `npc_buffer` VALUES ('50016', '275', '1', '0', '0', '275');INSERT INTO `npc_buffer` VALUES ('50016', '276', '1', '0', '0', '276');INSERT INTO `npc_buffer` VALUES ('50016', '277', '1', '0', '0', '277');INSERT INTO `npc_buffer` VALUES ('50016', '304', '1', '0', '0', '304');INSERT INTO `npc_buffer` VALUES ('50016', '305', '1', '0', '0', '305');INSERT INTO `npc_buffer` VALUES ('50016', '306', '1', '0', '0', '306');INSERT INTO `npc_buffer` VALUES ('50016', '307', '1', '0', '0', '307');INSERT INTO `npc_buffer` VALUES ('50016', '308', '1', '0', '0', '308');INSERT INTO `npc_buffer` VALUES ('50016', '309', '1', '0', '0', '309');INSERT INTO `npc_buffer` VALUES ('50016', '310', '1', '0', '0', '310');INSERT INTO `npc_buffer` VALUES ('50016', '311', '1', '0', '0', '311');INSERT INTO `npc_buffer` VALUES ('50016', '349', '1', '0', '0', '349');INSERT INTO `npc_buffer` VALUES ('50016', '363', '1', '0', '0', '363');INSERT INTO `npc_buffer` VALUES ('50016', '364', '1', '0', '0', '364');INSERT INTO `npc_buffer` VALUES ('50016', '365', '1', '0', '0', '365');INSERT INTO `npc_buffer` VALUES ('50016', '366', '1', '0', '0', '366');INSERT INTO `npc_buffer` VALUES ('50016', '367', '1', '0', '0', '367');INSERT INTO `npc_buffer` VALUES ('50016', '529', '1', '0', '0', '529');INSERT INTO `npc_buffer` VALUES ('50016', '530', '1', '0', '0', '530');INSERT INTO `npc_buffer` VALUES ('50016', '825', '1', '0', '0', '825');INSERT INTO `npc_buffer` VALUES ('50016', '826', '1', '0', '0', '826');INSERT INTO `npc_buffer` VALUES ('50016', '827', '1', '0', '0', '827');INSERT INTO `npc_buffer` VALUES ('50016', '828', '1', '0', '0', '828');INSERT INTO `npc_buffer` VALUES ('50016', '829', '1', '0', '0', '829');INSERT INTO `npc_buffer` VALUES ('50016', '830', '1', '0', '0', '830');INSERT INTO `npc_buffer` VALUES ('50016', '1002', '3', '0', '0', '1002');INSERT INTO `npc_buffer` VALUES ('50016', '1003', '3', '0', '0', '1003');INSERT INTO `npc_buffer` VALUES ('50016', '1004', '3', '0', '0', '1004');INSERT INTO `npc_buffer` VALUES ('50016', '1005', '3', '0', '0', '1005');INSERT INTO `npc_buffer` VALUES ('50016', '1006', '3', '0', '0', '1006');INSERT INTO `npc_buffer` VALUES ('50016', '1007', '3', '0', '0', '1007');INSERT INTO `npc_buffer` VALUES ('50016', '1008', '3', '0', '0', '1008');INSERT INTO `npc_buffer` VALUES ('50016', '1009', '3', '0', '0', '1009');INSERT INTO `npc_buffer` VALUES ('50016', '1010', '3', '0', '0', '1010');INSERT INTO `npc_buffer` VALUES ('50016', '1032', '3', '0', '0', '1032');INSERT INTO `npc_buffer` VALUES ('50016', '1033', '3', '0', '0', '1033');INSERT INTO `npc_buffer` VALUES ('50016', '1035', '4', '0', '0', '1035');INSERT INTO `npc_buffer` VALUES ('50016', '1036', '2', '0', '0', '1036');INSERT INTO `npc_buffer` VALUES ('50016', '1040', '3', '0', '0', '1040');INSERT INTO `npc_buffer` VALUES ('50016', '1043', '1', '0', '0', '1043');INSERT INTO `npc_buffer` VALUES ('50016', '1044', '3', '0', '0', '1044');INSERT INTO `npc_buffer` VALUES ('50016', '1045', '6', '0', '0', '1045');INSERT INTO `npc_buffer` VALUES ('50016', '1048', '6', '0', '0', '1048');INSERT INTO `npc_buffer` VALUES ('50016', '1059', '3', '0', '0', '1059');INSERT INTO `npc_buffer` VALUES ('50016', '1062', '1', '0', '0', '1062');INSERT INTO `npc_buffer` VALUES ('50016', '1068', '3', '0', '0', '1068');INSERT INTO `npc_buffer` VALUES ('50016', '1073', '2', '0', '0', '1073');INSERT INTO `npc_buffer` VALUES ('50016', '1077', '3', '0', '0', '1077');INSERT INTO `npc_buffer` VALUES ('50016', '1078', '6', '0', '0', '1078');INSERT INTO `npc_buffer` VALUES ('50016', '1085', '3', '0', '0', '1085');INSERT INTO `npc_buffer` VALUES ('50016', '1086', '2', '0', '0', '1086');INSERT INTO `npc_buffer` VALUES ('50016', '1087', '3', '0', '0', '1087');INSERT INTO `npc_buffer` VALUES ('50016', '1182', '3', '0', '0', '1182');INSERT INTO `npc_buffer` VALUES ('50016', '1189', '3', '0', '0', '1189');INSERT INTO `npc_buffer` VALUES ('50016', '1191', '3', '0', '0', '1191');INSERT INTO `npc_buffer` VALUES ('50016', '1204', '2', '0', '0', '1204');INSERT INTO `npc_buffer` VALUES ('50016', '1240', '3', '0', '0', '1240');INSERT INTO `npc_buffer` VALUES ('50016', '1242', '3', '0', '0', '1242');INSERT INTO `npc_buffer` VALUES ('50016', '1243', '3', '0', '0', '1243');INSERT INTO `npc_buffer` VALUES ('50016', '1249', '3', '0', '0', '1249');INSERT INTO `npc_buffer` VALUES ('50016', '1250', '3', '0', '0', '1250');INSERT INTO `npc_buffer` VALUES ('50016', '1251', '2', '0', '0', '1251');INSERT INTO `npc_buffer` VALUES ('50016', '1252', '3', '0', '0', '1252');INSERT INTO `npc_buffer` VALUES ('50016', '1253', '3', '0', '0', '1253');INSERT INTO `npc_buffer` VALUES ('50016', '1257', '3', '0', '0', '1257');INSERT INTO `npc_buffer` VALUES ('50016', '1259', '4', '0', '0', '1259');INSERT INTO `npc_buffer` VALUES ('50016', '1260', '3', '0', '0', '1260');INSERT INTO `npc_buffer` VALUES ('50016', '1261', '2', '0', '0', '1261');INSERT INTO `npc_buffer` VALUES ('50016', '1268', '4', '0', '0', '1268');INSERT INTO `npc_buffer` VALUES ('50016', '1282', '2', '0', '0', '1282');INSERT INTO `npc_buffer` VALUES ('50016', '1284', '3', '0', '0', '1284');INSERT INTO `npc_buffer` VALUES ('50016', '1303', '2', '0', '0', '1303');INSERT INTO `npc_buffer` VALUES ('50016', '1304', '3', '0', '0', '1304');INSERT INTO `npc_buffer` VALUES ('50016', '1308', '3', '0', '0', '1308');INSERT INTO `npc_buffer` VALUES ('50016', '1309', '3', '0', '0', '1309');INSERT INTO `npc_buffer` VALUES ('50016', '1310', '4', '0', '0', '1310');INSERT INTO `npc_buffer` VALUES ('50016', '1323', '1', '0', '0', '1323');INSERT INTO `npc_buffer` VALUES ('50016', '1352', '1', '0', '0', '1352');INSERT INTO `npc_buffer` VALUES ('50016', '1353', '1', '0', '0', '1353');INSERT INTO `npc_buffer` VALUES ('50016', '1354', '1', '0', '0', '1354');INSERT INTO `npc_buffer` VALUES ('50016', '1355', '1', '0', '0', '1355');INSERT INTO `npc_buffer` VALUES ('50016', '1356', '1', '0', '0', '1356');INSERT INTO `npc_buffer` VALUES ('50016', '1357', '1', '0', '0', '1357');INSERT INTO `npc_buffer` VALUES ('50016', '1362', '1', '0', '0', '1362');INSERT INTO `npc_buffer` VALUES ('50016', '1363', '1', '0', '0', '1363');INSERT INTO `npc_buffer` VALUES ('50016', '1364', '1', '0', '0', '1364');INSERT INTO `npc_buffer` VALUES ('50016', '1365', '1', '0', '0', '1365');INSERT INTO `npc_buffer` VALUES ('50016', '1388', '3', '0', '0', '1388');INSERT INTO `npc_buffer` VALUES ('50016', '1389', '3', '0', '0', '1389');INSERT INTO `npc_buffer` VALUES ('50016', '1390', '3', '0', '0', '1390');INSERT INTO `npc_buffer` VALUES ('50016', '1391', '3', '0', '0', '1391');INSERT INTO `npc_buffer` VALUES ('50016', '1392', '3', '0', '0', '1392');INSERT INTO `npc_buffer` VALUES ('50016', '1393', '3', '0', '0', '1393');INSERT INTO `npc_buffer` VALUES ('50016', '1397', '3', '0', '0', '1397');INSERT INTO `npc_buffer` VALUES ('50016', '1413', '1', '0', '0', '1413');INSERT INTO `npc_buffer` VALUES ('50016', '1414', '1', '0', '0', '1414');INSERT INTO `npc_buffer` VALUES ('50016', '1415', '1', '0', '0', '1415');INSERT INTO `npc_buffer` VALUES ('50016', '1416', '1', '0', '0', '1416');INSERT INTO `npc_buffer` VALUES ('50016', '1460', '1', '0', '0', '1460');INSERT INTO `npc_buffer` VALUES ('50016', '1461', '1', '0', '0', '1461');INSERT INTO `npc_buffer` VALUES ('50016', '21046', '1', '0', '0', '21046');

‘50016’ ID NPC. Вы можете его менять.

И NPC sql:

INSERT INTO `custom_npc` VALUES ('50016', '4', 'L2MaxiBuffer', '1', 'L2maxi.ru', '1', 'Monster2.baby_tiger', '13.00', '21.00', '80', 'male', 'L2NpcBuffer', '80', '3862', '1493', '0.00', '0.00', '10', '10', '10', '10', '10', '10', '0', '0', '500', '500', '500', '500', '282', '0', '2000', '0', '0', '0', '60', '80', 'NULL', '0', '0', '0', 'LAST_HIT', '0', '0', '0', 'balanced', 'false');

Удачи в Ваших достижениях!
С уважением, портал L2Maxi.ru


Вступление

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

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

Ниже расположены настройки выделенного в таблице элемента, а также кнопки для добавления и удаления элементов списка. Если вы вдруг захотите поменять расположение элемента в списке — просто перетащите его с помощью мышки.


Настройки

Рассмотрим доступные настройки:

Тип: в этом выпадающем списке мы выбираем чем бот будет пользоваться для бафа: умением персонажа, предметом, умением пета, предметом пета или игровым бафом. Игровой баф нужен на серверах, где во вкладке Сообщество (Alt+B) есть возможность получить положительные эффекты. Подробней о том, как настроить в интерфейсе такой баф будет рассказано в конце видео. Предметы используются, если нужный вам баф применяется не умением, а предметом, например Зельем ускорения. А с помощью умений пета, вы сможете накладывать на себя бафы суммонов, например Благословение серафима.

Имя: после выбора типа, выбираем из этого выпадающего списка необходимое умение, предмет или шаблон игрового бафа. Для удобства поиска можно начать вводить нужное название. Обратите внимание, что бот имеет собственную базу данных и названия могут не совпадать с теми, что вы видите в игре.

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

Пауза перед исполнением: тут настраивается время, которое бот подождет после выполнения всех условий и до непосредственного использования бафа. Т.е. если с вас спадет какой-то баф, бот не будет моментально накладывать его обратно, а подождет несколько секунд, чтобы не вызывать лишних подозрений. Причем перед использованием бафа бот еще раз проверит все необходимые условия. Данная функция работает только если у вас активирован Интервал!

К примеру, в вашей группе есть бишоп, который подхиливает вас на отметке 60%. Вы можете настроить УД на случай, если бишоп отвлекся и ваше ХП упало ниже 40%, но с задержкой в 5 секунд. Таким образом, когда ваше ХП упадет ниже 40%, бот не сразу заюзает УД, а подождет еще 5 секунд, потом еще раз проверит уровень ХП, и если оно все еще ниже 40%, то заюзает УД. Если же бишоп успеет вас отхилить за эти 5 секунд, то УД не будет использовано.

Комбо: эту опцию нужно использовать в случаях, если вам нужно ребафать сразу несколько бафов одновременно. Все отмеченные этой галочкой бафы выделяются в отдельную группу и будут ребафаться только тогда, когда выполняются условия у всех элементов одновременно. Если хотя бы у одного из «комбо» бафов не будут выполняться условия на использование — бот не будет ребафаться ими вообще. Данная функция актуальна для дэнсов и сонгов. Например, можно настроить, чтобы дэнсы бафались только тогда, когда спадут вообще все дэнсы, но при этом только после каких-то определенных сонгов.

Снимать баф: с помощью данной функции можно настроить снятие не нужных нам положительных эффектов, если те уменьшают нужные нам характеристики, например, Дух Берсерка. Данная функция аналогична Alt-клику по бафу в игре. Используется в сочетании с опцией «Если присутствуют дебафы». По умолчанию в дебаффы будет записано название выбранного эффекта, но вы так же можете добавить в поле ниже несколько бафов — все они будут удаляться.
Обратите внимание, что при использовании этой опции совершенно не важно, какое умение у вас было выбрано изначально. Т.е. если мы хотим снимать баф Дух берсерка, но у нашего персонажа нет такого умения, просто выбираем любой скил, активируем Снятие бафа, и вписываем название Дух берсерка.
С помощью данной опции, например, можно настроить снятие пати УД танком, сразу как только оно появится в бафах персонажа, что бы получаемый сопартийцами урон, не перенаправлялся на нашего персонажа. Настройку бафа пати УД лучше производить во вкладке «Пати хил баф».

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

В бою: если данная опция включена, бот будет использовать выбранный баф в том числе во время боя, т.е. пока нас атакую агрессивные монстры, иначе — только когда нас никто не атакует. Практически всегда требуется отключать эту функцию для Alt + B бафа, потому что использовать его во время боя, как правило нельзя. Из-за этого с Alt+B часто возникают проблемы на спотах, где на бота постоянно агрятся мобы. В таких случаях вам нужно отбегать со спота в безопасное место, и ждать выхода из боя с помощью скрипта, например, так умеет делать плагин Hunter, ссылка на него будет в описании.

Саммон и Пет: данные опции отвечают за то, чтобы выбранный баф работал не для персонажа, а для его самона и / или пета соответственно. Это означает, что все настройки ниже будут относиться к призванным существам, а не персонажу. Обратите внимание, что при активации одной из этих опций, первое условие всё так же контролирует состояние нашего персонажа, а второе и третье — состояние призванного существа. При использовании на самоне или пете таргетного бафа, не забывайте выключать функцию «Без таргета», если она активна.

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

Выбрав вариант Если отсутствуют бафы нам необходимо указать в поле ниже название бафа, наличие которого будет проверяться, и если он отсутствует на персонаже — бот будет использовать этот баф. Для удобства настройки, вы можете двойным щелчком по полю вызвать список эффектов, наложенных на персонажа, а так же его умений. Дополнительно к этому можно добавить проверку условий на оставшееся время бафа или его уровень. Например, можно бафать Ауру Атаки, только если на нас нет ни самой ауры, ни бафов с аналогичным эффектом от ПП или варка. Чтобы ребафф происходил слегка заранее, выставляем условие на оставшееся время < 60 секунд. И еще, выставляем условие на уровень < 2, чтобы бафать ауру поверх чужих бафов, если наш эффект сильнее.

Выбирая вариант Если присутствуют дебафы в поле ниже нам нужно указать уже название дебафа, при наличии которого бот будет использовать выбранное умение. Так, например, можно настроить умение Клинс, а в поле ниже занести список дебафов через точку с запятой. Или, опять таки двойным щелчком открыть список текущих дебафов на нас и выбрать среди них нужные. Обратите внимание, что условия на оставшееся время и уровень в случае с дебафами начинают работать по принципу И, а не ИЛИ, как это было с бафами. Рассмотрим пример: у нас есть умение Очищение, мы хотим, чтобы оно использовалось если на нас висит один из перечисленных дебафов и только если время этого дебафа > 5 секунд.

Еще, с помощью этих опций можно настроить использование аур. Для этого нужно добавить 2 элемента в списке — на включение, если отсутствует баф, и на выключение — если присутствует дебаф. Так же можно указать доп условия, например на МП и количество атакующих монстров. Ауры, настроенные во вкладке «селф бафф» в отличие от вкладки «атака» будут использоваться не только во время боя с монстрами, что сделает поведение чуть более реалистичным.

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

Условия: здесь вы можете настроить до 3 различных условий на использование выбранного умения, предмета или игрового бафа. Слева выбирается тип условия, справа — значение, с которым нужно сравнивать. Для разных типов условий сюда нужно вписывать разные значения.

Например, всевозможные условия на HP / MP / CP — тут указывается либо %, либо абсолютные значения. Еще есть такие условия, где вообще не нужно указывать никаких значений, например проверки на рут, стан, призванность петов и тд.

Если вы настроите 2 или более условий, то для использования бафов они должны будут выполняться одновременно все вместе. Если вам нужно, чтобы баф применялся хотя бы по одному из двух условий, то просто добавьте два бафа в список и укажите каждому по одному из этих условий. Будьте внимательны при настройке, условий они не должны противоречить друг другу, иначе бафы не будут использоваться вовсе!

Использовать заряды: если вам необходимо экономить заряды, с помощью это опции можно гибко настроить условия, когда и какие нужно использовать заряды, а когда не нужно вовсе. Например, можно настроить, чтобы заряды использовались только с теми умениями, которые долго кастуются или только если у нас мало ХП, или на нас напало много монстров. Главное не забудьте отключить опцию «автоматически включать имеющиеся заряды» на вкладке «Разное».

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


Alt+B

Рассмотрим теперь настройку бафа через Alt+B с помощью интерфейса Адреналин бота. Указываем Тип — Игровой баф, а Имя — один из предложенных шаблонов, после чего нажимаем на ставшую активной кнопку Н. Откроется окно записи игрового бафа. Нажимаем кнопку «Записать», после чего в игре открываем вкладку сообщества (Alt+B) и последовательно выбираем все необходимые диалоги, чтобы получить нужный нам баф. После этого возвращаемся к окну записи и останавливаем её. Нажимаем кнопку «Принять», выставляем интервал бафа 10 секунд, а так же активируем условие «если отсутствуют бафы» и добавляем один или несколько бафов, которые мы получили через игровое меню. Не забудьте также отключить функцию «в бою», если она включена, в противном случае, бот не будет выходить из боя при попытке выполнить ребаф. Стоит отметить, что на некоторых серверах стоят защиты от подобного рода настроек, поэтому если у вас бот не хочет бафаться, а каждый раз при записи диалогов вы видите разные строки — вам нужно использовать скрипт для ребафа, например Hunter.

Волшебная палочка действительно существует, по крайней мере, в маникюре. Это бафик, как ласково называют его все, он жизненно необходим, если вам хочется иметь красивые ухоженные руки. Хороший маникюр без бафа просто невозможен, эта универсальная пилочка выполняет множество функций в маникюре для натуральных и нарощенных ногтей. Правильный баф и использование его по всем правилам подготовит ногти для любого шикарного покрытия. Для чего нужен баф для ногтей, чем он так хорош, и почему должен быть в маникюрном наборе любой модницы – узнаем прямо сейчас.

Использование бафа

Использование бафа

Преимущества уникальной пилочки

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

Для чего нужна каждая грань:

  • первая сторона — обычная пилка для создания нужной формы ногтей, их можно укоротить, подровнять;
  • вторая сторона служит для удаления неровностей на пластине – заусенцев, шероховатостей;
  • третья сторона шлифует и удаляет небольшие изъяны;
  • четвёртая сторона полирует пластины, придает им естественный блеск.

Производители предлагают

Ассортимент бафов значительный, они отличаются:

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

Разноцветные бафы

Разноцветные бафы

Рассмотрим основные характеристики бафов

Из каких материалов могут быть бафы

Основу обычно изготавливают из пластмассы, дерева, шёлка и других тканей.

Рабочие поверхности могут быть:

  • пенополиэтиленовые (пластик) – популярные, дешевые, прочные. Их можно стерилизовать;
  • силиконовые также можно мыть и дезинфицировать, прочные, недорогие;
  • из замши бафик может быть с ручками, качество среднее, дезинфекцию провести невозможно;
  • баф из тканей — хорошее качество, подходит для натуральных ногтей, эластичный, но стоимость выше, особенно у бафа с шелковым покрытием, стерилизовать невозможно.

О форме

Самая распространенная форма бафа в виде бруска, у которого все стороны (или 2) абразивные. Более удобный и функциональный четырехсторонний баф. Встречаются и другие формы инструмента, например, в виде бумеранга.

Четырехсторонняя полировка

Четырехсторонняя полировка

Основные функции

Пилка с 4 гранями – универсальная, каждая грань выполняет определенную функцию, а в результате происходит полная подготовка к нанесению гель-лака.

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

Как правильно использовать

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

Использование бафа

Использование бафа

Предоставляем пошаговую инструкцию, как пользоваться бафом для ногтей при подготовке к маникюру:

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

Видео

Всё об абразивности

Разная функциональность граней объясняется разной степенью абразивности, то есть величиной зерен (пор) на поверхности бафа. Абразивность определяет жесткость каждой грани и измеряется в гритах. Рассмотрим классификацию абразивности и функциональные возможности граней:

  • 60-80 гр. Это жесткие бафы для грубой полировки нарощенных и искусственных ногтей, выполнения педикюра;
  • 100-150 гр. Подходит для корректировки нарощенных ногтей перед нанесением гель-лака, можно корректировать ногти на руках и ногах, нарощенные акрилом или гелем пластины;
  • 150-240 гр. Можно спиливать натуральный ноготь и искусственный, подпиливать кончики натуральных ногтей, шлифовать искусственные ногти;
  • 300-450 гр. Это баф для натуральных ногтей, для деликатных работ с ними. Зернистость мелкая, возможна мягкая полировка поверхности натуральных пластин.

Баф для ногтей мини

Бафы для ногтей мини

Важно. Какой баф выбрать — зависит от назначения инструмента. Для тонких и ослабленных ногтей подходит баф с максимальной абразивностью — выше 300 гр. Из материалов лучше всего замша, которая действует аккуратно и щадит ногти.

Уход за инструментом

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

Виды и правила использования бафа для ногтей

Бафы для ногтей

Лучше выбирать инструмент с простым уходом:

  • тканевые изделия пропускают воду, поэтому их просто заменяют на новые, но можно почистить щеточкой, пропитанной дезинфицирующим средством;
  • хорошо поддаются дезинфекции бафы из пластика, их можно мыть в теплом мыльном растворе;
  • идеальный вариант — приобрести несколько брусков с разной абразивностью, из разных материалов и менять по мере потребности, это вариант для тех, кому не приходится экономить.

Использование четырехстороннего бафа

Использование четырехстороннего бафа

Альтернатива

Бафик не продается на каждой полке в любом магазине, тем не менее, лучше его найти и приобрести, так как заменить его просто нечем. Баф и пилка для ногтей отличаются по качеству обработки, у простой пилки оно довольно низкое. Если всё же возникла срочная потребность в маникюре, а бафа нет, выход следующий:

  • любая пилочка с абразивностью более 300 гр. может временно выручить;
  • полировку, устранение дефектов и неровностей можно произвести любым грубым материалом, например кусочком грубой суровой ткани.

Пилочка баф для ногтей

Пилочка баф для ногтей

Итоги. Баф хорошего качества поможет тщательно отполировать ногти для покрытия гель-лаком, он прочно ляжет на ноготь. Срок использования гель-лака значительно продлится, но надо учитывать состояние ногтевых пластин — если они тонкие, баф надо выбирать помягче.

Как реализовать гибкую систему баффов / дебаффов?


Обзор:

Во многих играх с RPG-подобной статистикой предусмотрены «положительные эффекты» для персонажей, начиная от простого «Нанести 25% дополнительного урона» до более сложных вещей, таких как «Наносить 15 урона обратно атакующим при попадании».

Специфика каждого типа баффа на самом деле не актуальна. Я ищу (предположительно объектно-ориентированный) способ обработки произвольных эффектов.

Подробности:

В моем конкретном случае у меня есть несколько персонажей в пошаговом боевом окружении, поэтому я предполагал, что баффы будут привязаны к таким событиям, как «OnTurnStart», «OnReceiveDamage» и т. Д. Возможно, каждый бафф является подклассом основного абстрактного класса Buff, где перегружены только соответствующие события. Тогда у каждого персонажа может быть свой вектор положительных эффектов.

Имеет ли это решение смысл? Я, конечно, вижу, что нужны десятки типов событий, похоже, что создание нового подкласса для каждого баффа является излишним, и, похоже, он не допускает каких-либо «взаимодействий» баффов. То есть, если бы я хотел применить ограничение на усиление урона, чтобы даже если у вас было 10 различных баффов, каждый из которых дает 25% дополнительного урона, вы наносите только 100% дополнительного вместо 250% дополнительного.

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

Как относительно неопытный программист C ++ (я обычно использовал C во встроенных системах), я чувствую, что мое решение упрощено и, вероятно, не в полной мере использует объектно-ориентированный язык.

Мысли? Кто-нибудь здесь разрабатывал довольно надежную систему баффов раньше?

Изменить: Относительно ответа (ов):

Я выбрал ответ, основанный в первую очередь на хорошей детализации и четком ответе на вопрос, который я задал, но чтение ответов дало мне некоторое понимание.

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

Для такой игры, как Diablo 3 (упоминается ниже), где почти любой бит экипировки может изменить силу баффа, баффы — это просто статистика персонажей, и система всегда кажется хорошей идеей, когда это возможно.

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

В любом случае, я все еще надеюсь, что кто-то придет с необычной магической пулей «ОО», которая позволит мне применить +2 к расстоянию перемещения за бафф хода , нанести 50% урона обратно баффу атакующего , и автоматически телепортироваться к соседней плитке при атаке из 3 или более плиток прочь любителя в одной системе , не поворачивая +5 силы бафф в свой собственный подкласс.

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






Ответы:


Это сложный вопрос, потому что вы говорите о нескольких разных вещах, которые (в наши дни) объединяются в «баффы»:

  • модификаторы атрибутов игрока
  • спецэффекты, которые происходят на определенных событиях
  • комбинации вышеперечисленного.

Я всегда реализую первый со списком активных эффектов для определенного персонажа. Удаление из списка, будь то на основе продолжительности или явно, довольно тривиально, поэтому я не буду здесь останавливаться на этом. Каждый эффект содержит список модификаторов атрибутов и может применять его к базовому значению путем простого умножения.

Затем я обертываю его функциями для доступа к измененным атрибутам. например.:

def get_current_attribute_value(attribute_id, criteria):
    val = character.raw_attribute_value[attribute_id]
    # Accumulate the modifiers
    for effect in character.all_effects:
        val = effect.apply_attribute_modifier(attribute_id, val, criteria)
    # Make sure it doesn't exceed game design boundaries
    val = apply_capping_to_final_value(val)
    return val

class Effect():
    def apply_attribute_modifier(attribute_id, val, criteria):
        if attribute_id in self.modifier_list:
            modifier = self.modifier_list[attribute_id]
            # Does the modifier apply at this time?
            if modifier.criteria == criteria:
                # Apply multiplicative modifier
                return val * modifier.amount
        else:
            return val

class Modifier():
    amount = 1.0 # default that has no effect
    criteria = None # applies all of the time

Это позволяет достаточно легко применять мультипликативные эффекты. Если вам также нужны аддитивные эффекты, решите, в каком порядке вы собираетесь их применять (возможно, аддитивные в последнюю очередь), и дважды просмотрите список. (Я бы, вероятно, имел отдельные списки модификаторов в Effect, один для мультипликативного, один для аддитивного).

Значение критерия состоит в том, чтобы позволить вам реализовать «+ 20% против нежити» — установите значение UNDEAD для эффекта и передайте значение UNDEAD только get_current_attribute_value()при расчете броска урона против врага-нежити.

Между прочим, у меня не было бы соблазна попробовать написать систему, которая применяет и отменяет применение значений непосредственно к значению базового атрибута — конечный результат состоит в том, что ваши атрибуты с большой вероятностью отклонятся от предполагаемого значения из-за ошибки. (Например, если вы умножаете что-то на 2, но затем ограничиваете это, когда вы снова разделите это на 2, это будет ниже, чем началось.)

Что касается основанных на событиях эффектов, таких как «Нанести 15 урона обратно атакующим при попадании», вы можете добавить методы для класса «Эффект» для этого. Но если вы хотите различного и произвольного поведения (например, некоторые эффекты для вышеуказанного события могут отражать нанесенный ущерб, некоторые могут вылечить вас, это может вас телепортировать случайно, что угодно), вам понадобятся пользовательские функции или классы для их обработки. Вы можете назначить функции обработчикам событий для эффекта, затем вы можете просто вызвать обработчики событий для любых активных эффектов.

# This is a method on a Character, called during combat
def on_receive_damage(damage_info):
    for effect in character.all_effects:
        effect.on_receive_damage(character, damage_info)

class Effect():
    self.on_receive_damage_handler = DoNothing # a default function that does nothing
    def on_receive_damage(character, damage_info):
        self.on_receive_damage_handler(character, damage_info)

def reflect_damage(character, damage_info):
    damage_info.attacker.receive_damage(15)

reflect_damage_effect = new Effect()
reflect_damage_effect.on_receive_damage_handler = reflect_damage
my_character.all_effects.add(reflect_damage_effect)

Очевидно, что ваш класс Effect будет иметь обработчик события для каждого типа события, и вы можете назначить функции обработчика столько, сколько вам нужно в каждом случае. Вам не нужно создавать подкласс Effect, поскольку каждый из них определяется составом модификаторов атрибутов и обработчиков событий, которые он содержит. (Возможно, оно также будет содержать имя, продолжительность и т. Д.)




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

Идея была проста, и хотя мы применили ее в Python, она была довольно эффективной.

В основном, вот как это было:

  • У пользователя был список примененных в настоящее время баффов и дебаффов (обратите внимание, что бафф и дебафф относительно одинаковы, просто эффект имеет другой результат)
  • Баффы имеют различные атрибуты, такие как длительность, имя и текст для отображения информации и время жизни. Важными являются время, продолжительность и ссылка на актера, к которому применяется этот бафф.
  • Для Баффа, когда он присоединяется к игроку через player.apply (buff / debuff), он вызывает метод start (), это применяет к игроку критические изменения, такие как увеличение скорости или замедление.
  • Затем мы перебираем каждый бафф в цикле обновления, и бафы обновляются, что увеличивает их время жизни. Подклассы будут реализовывать такие вещи, как отравление игрока, предоставление игроку HP со временем и т. Д.
  • Когда бафф был сделан для, то есть timeAlive> = duration, логика обновления удалит бафф и вызовет метод finish (), который будет варьироваться от снятия ограничений скорости у игрока до создания небольшого радиуса (например, эффект бомбы) после DoT)

Теперь о том, как на самом деле применять баффы из мира, — другая история. Вот моя пища для размышлений.






Я не уверен, читаете ли вы это до сих пор, но я долго боролся с подобной проблемой.

Я разработал множество различных типов систем воздействия. Я кратко расскажу о них сейчас. Это все основано на моем опыте. Я не утверждаю, что знаю все ответы.


Статические модификаторы

Этот тип системы в основном полагается на простые целые числа для определения любых модификаций. Например, от +100 до Макс. HP, +10 к атаке и так далее. Эта система также может обрабатывать проценты. Вам просто нужно убедиться, что укладка не выходит из-под контроля.

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

(Я работаю на Java, поэтому ниже следует Java, но он должен работать с некоторыми модификациями для других языков). Эту систему можно легко сделать, используя перечисления для типов модификации, а затем целые числа. Конечный результат может быть помещен в какую-то коллекцию, которая имеет пары упорядоченных ключей и значений. Это будет быстрый поиск и расчеты, поэтому производительность очень хорошая.

В целом, это работает очень хорошо с простыми статическими модификаторами Тем не менее, код должен существовать в надлежащих местах для используемых модификаторов: getAttack, getMaxHP, getMeleeDamage и так далее и так далее.

Там, где этот метод не работает (для меня) очень сложное взаимодействие между любителями. Нет реального простого способа взаимодействия, кроме как немного поднять его. У него есть несколько простых возможностей взаимодействия. Чтобы сделать это, вы должны внести изменения в способ хранения статических модификаторов. Вместо использования enum в качестве ключа, вы используете String. Эта строка будет именем Enum + дополнительная переменная. 9 раз из 10 дополнительная переменная не используется, поэтому вы по-прежнему сохраняете имя перечисления в качестве ключа.

Давайте сделаем быстрый пример: если вы хотите иметь возможность изменять урон против нежити, у вас может быть упорядоченная пара, подобная этой: (DAMAGE_Undead, 10) DAMAGE — это Enum, а Undead — дополнительная переменная. Поэтому во время боя вы можете сделать что-то вроде:

dam += attacker.getMod(Mod.DAMAGE + npc.getRaceFamily()); //in this case the race family would be undead

В любом случае, это работает довольно хорошо и быстро. Но он терпит неудачу при сложных взаимодействиях и наличии «специального» кода везде. Например, рассмотрим ситуацию с «25% шансом телепортироваться при смерти». Это «довольно» сложный вопрос. Вышеуказанная система может справиться с этим, но не легко, так как вам необходимо следующее:

  1. Определите, есть ли у игрока этот мод.
  2. Где-нибудь, есть код для выполнения телепортации, если все прошло успешно. Расположение этого кода само по себе является обсуждением!
  3. Получить правильные данные с карты модов. Что означает значение? Это комната, где они тоже телепортируются? Что, если у игрока есть два мода на телепорт? Не сложатся ли суммы вместе ?????? ПРОВАЛ!

Так что это подводит меня к следующему:


Ультимативная комплексная система баффов

Однажды я попытался написать 2D-MMORPG самостоятельно. Это была ужасная ошибка, но я многому научился!

Я переписал систему аффектов 3 раза. Первый использовал менее мощный вариант из вышеперечисленного. Вторым было то, о чем я собираюсь поговорить.

Эта система имела ряд классов для каждой модификации, поэтому такие вещи, как: ChangeHP, ChangeMaxHP, ChangeHPByPercent, ChangeMaxByPercent. У меня был миллион таких парней — даже такие вещи, как TeleportOnDeath.

У моих классов были вещи, которые делали следующее:

  • applyAffect
  • removeAffect
  • checkForInteraction <— важно

Применить и удалить объяснить сами (хотя для таких вещей, как проценты, эффект будет отслеживать, насколько он увеличил HP, чтобы убедиться, что когда эффект исчезает, он только удалит добавленную сумму. Это было с ошибками, lol, и Мне потребовалось много времени, чтобы убедиться, что это правильно. У меня все еще не было хорошего чувства по этому поводу.).

Метод checkForInteraction был ужасно сложным фрагментом кода. В каждом из классов аффектов (т. Е. ChangeHP) он будет иметь код, чтобы определить, должен ли он быть изменен входным аффектом. Так, например, если у вас было что-то вроде ….

  • Buff 1: Наносит 10 ед. Урона от огня при атаке
  • Buff 2: Увеличивает весь урон от огня на 25%.
  • Buff 3: Увеличивает весь урон от огня на 15.

Метод checkForInteraction будет обрабатывать все эти эффекты. Для этого нужно было проверить каждое влияние на ВСЕХ игроков поблизости! Это потому, что тип аффектов, с которыми я сталкивался у нескольких игроков на протяжении области. Это означает, что в коде НИКОГДА не было каких-либо специальных утверждений, подобных приведенным выше — «если мы только что умерли, мы должны проверить телепорт при смерти». Эта система будет автоматически обрабатывать его правильно в нужное время.

Попытка написать эту систему заняла у меня около 2 месяцев и несколько раз заставила голову взорваться. ОДНАКО, он был ДЕЙСТВИТЕЛЬНО мощным и мог делать безумное количество вещей — особенно если учесть следующие два факта для способностей в моей игре: 1. У них были целевые диапазоны (то есть: одиночный, сам, только группа, PB AE self , PB AE target, целевой AE и т. Д.). 2. Способности могут иметь более 1 влияния на них.

Как я уже упоминал выше, это была 2-ая из 3-х аффектных систем для этой игры. Почему я отошел от этого?

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

Итак, мы подошли к моей третьей версии (и другому типу баффов):


Комплексный аффект-класс с обработчиками

Так что это в значительной степени комбинация первых двух: у нас могут быть статические переменные в классе Affect, который содержит множество функций и дополнительных данных. Затем просто вызовите обработчики (для меня, скорее, некоторые статические служебные методы вместо подклассов для конкретных действий. Но я уверен, что вы можете использовать подклассы для действий, если вы тоже этого хотите), когда мы хотим что-то сделать.

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

Мы все равно должны были бы добавить специальные коды для обработки ситуаций, например, телепортации после смерти. Нам все равно придется проверять это в боевом коде вручную, а затем, если он существует, мы получим список эффектов. Этот список аффектов содержит все применяемые в настоящее время аффекты на игрока, который имел дело с телепортацией после смерти. Затем мы просто посмотрели бы на каждый из них и проверили, выполнялся ли он и был ли он успешным (остановимся на первом успешном). Если все прошло успешно, мы бы просто позвонили обработчику, чтобы позаботиться об этом.

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

Таким образом, она имеет высокую производительность первой системы и все еще очень сложна, как вторая (но не так много). В Java, по крайней мере, вы можете сделать несколько хитрых вещей, чтобы получить производительность почти первой в большинстве случаев (например, наличие карты enum ( http://docs.oracle.com/javase/6/docs/api/java) /util/EnumMap.html ) с Enums в качестве ключей и ArrayList воздействий в качестве значений. Это позволяет увидеть, есть ли у вас быстрые эффекты [поскольку список будет равен 0, или на карте не будет перечисления], и не иметь постоянно перебирать списки аффектов игрока без всякой причины. Я не возражаю перебирать аффекты, если они нам нужны в данный момент. Я оптимизирую позже, если это станет проблемой).

В настоящее время я заново открываю (переписываю игру на Java вместо базы кода FastROM, в которой она была изначально), мой MUD, который закончился в 2005 году, и недавно я столкнулся с тем, как я хочу реализовать свою систему баффов? Я собираюсь использовать эту систему, потому что она хорошо работала в моей предыдущей неудачной игре.

Что ж, надеюсь, кто-нибудь где-нибудь найдет некоторые из этих идей полезными.


Различный класс (или адресуемая функция) для каждого баффа не является избыточным, если поведение этих баффов отличается друг от друга. Одно было бы иметь баффы + 10% или + 20% (что, конечно, было бы лучше представить в виде двух объектов одного класса), другое — реализовывать совершенно разные эффекты, которые в любом случае требовали бы пользовательского кода. Тем не менее, я считаю, что лучше иметь стандартные способы настройки игровой логики, а не позволять каждому баффу делать то, что ему нравится (и, возможно, мешать друг другу непредвиденными способами, нарушая баланс игры).

Я бы предложил разделить каждый «цикл атаки» на этапы, где каждый шаг имеет базовое значение, упорядоченный список модификаций, которые могут быть применены к этому значению (возможно, ограничен), и окончательный предел. Каждая модификация имеет преобразование идентичности по умолчанию и может зависеть от нуля или более баффов / дебаффов. Специфика каждой модификации будет зависеть от примененного шага. Как реализовать этот цикл, зависит от вас (включая опцию архитектуры, управляемой событиями, как вы уже обсуждали).

Одним из примеров цикла атаки может быть:

  • рассчитать атаку игрока (база + моды);
  • рассчитать защиту противника (база + моды);
  • сделать разницу (и применить моды) и определить базовый урон;
  • рассчитывать любые эффекты парирования / брони (моды на базовый урон) и наносить урон;
  • рассчитать любой эффект отдачи (моды на базовый урон) и применить к атакующему.

Важно отметить, что чем раньше в цикле применяется бафф, тем больший эффект он будет иметь в результате . Поэтому, если вы хотите более «тактический» бой (где умение игрока важнее уровня персонажа), создайте много баффов / дебаффов на базовых характеристиках. Если вы хотите более «сбалансированный» бой (где уровень важнее — важно в MMOG-играх ограничивать скорость прогресса), используйте баффы / дебаффы только в конце цикла.

Различие между «Модификациями» и «Баффами», о которых я упоминал ранее, имеет цель: решения о правилах и балансе могут быть применены к первым, поэтому любые изменения в них не должны отражаться в изменениях в каждом классе последних. OTOH, количество и виды положительных эффектов ограничены только вашим воображением, поскольку каждый из них может выражать желаемое поведение, не принимая во внимание любое возможное взаимодействие между ними и другими (или даже вообще существование других).

Итак, отвечая на вопрос: не создавайте класс для каждого Баффа, а по одному для каждой (типа) Модификации, и привязывайте Модификацию к циклу атаки, а не к персонажу. Баффы могут быть просто списком (модификация, ключ, значение) кортежей, и вы можете применить бафф к персонажу, просто добавив / удалив его в набор баффов персонажа. Это также уменьшает окно для ошибки, так как статистика персонажа вообще не должна изменяться при применении баффов (так что меньше риск восстановить статистику до неправильного значения после истечения баффа).



Я не знаю, читаете ли вы это по-прежнему, но вот как я это делаю сейчас (код основан на UE4 и C ++). Обдумав проблему в течение более двух недель (!!), я наконец нашел это:

http://gamedevelopment.tutsplus.com/tutorials/using-the-composite-design-pattern-for-an-rpg-attributes-system—gamedev-243

И я подумал, что инкапсуляция одного атрибута в классе / структуре не так уж плоха. Имейте в виду, однако, что я пользуюсь действительно большим преимуществом встроенной системы отражения кода UE4, поэтому без некоторых переделок это может не подойти везде.

В любом случае, я начал с упаковки атрибута в одну структуру:

USTRUCT(BlueprintType)
struct GAMEATTRIBUTES_API FGAAttributeBase
{
    GENERATED_USTRUCT_BODY()
public:
    UPROPERTY()
        FName AttributeName;
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Value")
        float BaseValue;
    /*
        This is maxmum value of this attribute.
    */
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Value")
        float ClampValue;
protected:
    float BonusValue;
    //float OldCurrentValue;
    float CurrentValue;
    float ChangedValue;

    //map of modifiers.
    //It could be TArray, but map seems easier to use in this case
    //we need to keep track of added/removed effects, and see 
    //if this effect affected this attribute.
    TMap<FGAEffectHandle, FGAModifier> Modifiers;

public:

    inline float GetFinalValue(){ return BaseValue + BonusValue; };
    inline float GetCurrentValue(){ return CurrentValue; };
    void UpdateAttribute();

    void Add(float ValueIn);
    void Subtract(float ValueIn);

    //inline float GetCurrentValue()
    //{
    //  return FMath::Clamp<float>(BaseValue + BonusValue + AccumulatedBonus, 0, GetFinalValue());;
    //}

    void AddBonus(const FGAModifier& ModifiersIn, const FGAEffectHandle& Handle);
    void RemoveBonus(const FGAEffectHandle& Handle);

    void InitializeAttribute();

    void CalculateBonus();

    inline bool operator== (const FGAAttributeBase& OtherAttribute) const
    {
        return (OtherAttribute.AttributeName == AttributeName);
    }

    inline bool operator!= (const FGAAttributeBase& OtherAttribute) const
    {
        return (OtherAttribute.AttributeName != AttributeName);
    }

    inline bool IsValid() const
    {
        return !AttributeName.IsNone();
    }
    friend uint32 GetTypeHash(const FGAAttributeBase& AttributeIn)
    {
        return AttributeIn.AttributeName.GetComparisonIndex();
    }
};

Это еще не закончено, но основная идея состоит в том, что эта структура отслеживает свое внутреннее состояние. Атрибуты могут быть изменены только с помощью эффектов. Попытки изменить их напрямую небезопасны и не доступны для дизайнеров. Я предполагаю, что все, что может взаимодействовать с атрибутами — это Effect. В том числе плоские бонусы от предметов. Когда новый предмет экипирован, создается новый эффект (вместе с дескриптором), который добавляется на выделенную карту, которая обрабатывает бонусы бесконечной продолжительности (те, которые игрок должен удалить вручную). Когда применяется новый эффект, создается новый дескриптор для него (дескриптор просто int, обернутый структурой), а затем этот дескриптор передается как средство взаимодействия с этим эффектом, а также отслеживается, если эффект все еще активен. Когда эффект удален, его дескриптор транслируется на все заинтересованные объекты,

Действительно важной частью этого является TMap (TMap — хешированная карта). FGAModifier — это очень простая структура:

struct FGAModifier
{
    EGAAttributeOp AttributeMod;
    float Value;
};

Содержит тип модификации:

UENUM()
enum class EGAAttributeOp : uint8
{
    Add,
    Subtract,
    Multiply,
    Divide,
    Set,
    Precentage,

    Invalid
};

И Значение, которое является окончательным рассчитанным значением, которое мы собираемся применить к атрибуту.

Мы добавляем новый эффект, используя простую функцию, а затем вызываем:

void FGAAttributeBase::CalculateBonus()
{
    float AdditiveBonus = 0;
    auto ModIt = Modifiers.CreateConstIterator();
    for (ModIt; ModIt; ++ModIt)
    {
        switch (ModIt->Value.AttributeMod)
        {
        case EGAAttributeOp::Add:
            AdditiveBonus += ModIt->Value.Value;
                break;
            default:
                break;
        }
    }
    float OldBonus = BonusValue;
    //calculate final bonus from modifiers values.
    //we don't handle stacking here. It's checked and handled before effect is added.
    BonusValue = AdditiveBonus; 
    //this is absolute maximum (not clamped right now).
    float addValue = BonusValue - OldBonus;
    //reset to max = 200
    CurrentValue = CurrentValue + addValue;
}

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

Моя самая большая проблема сейчас связана с обработкой атрибута Damaging / Healing (без перерасчета всего стека), я думаю, что это несколько решено, но все равно требуется больше тестов, чтобы быть на 100%.

В любом случае атрибуты определяются следующим образом (+ нереальные макросы, здесь опущены):

FGAAttributeBase Health;
FGAAttributeBase Energy;

и т.п.

Также я не уверен на 100% в обработке CurrentValue атрибута, но он должен работать. Они так и есть сейчас.

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





Я работал над небольшой MMO, и все предметы, способности, баффы и т. Д. Имели «эффекты». Эффектом был класс, в котором были переменные для AddDefense, InstantDamage, HealHP и т. Д. Силы, предметы и т. Д. Будут обрабатывать длительность этого эффекта.

Когда вы накладываете силу или надеваете предмет, он будет применять эффект к персонажу в течение указанного времени. Тогда основная атака и т. Д. Расчеты будут учитывать примененные эффекты.

Например, у вас есть бафф, который добавляет защиту. Для этого баффа будут как минимум EffectID и Duration. При приведении его он будет применять EffectID к персонажу в течение указанной продолжительности.

Другой пример для элемента, будет иметь те же поля. Но длительность будет бесконечной или до тех пор, пока эффект не будет снят, снимая предмет с персонажа.

Этот метод позволяет перебирать список эффектов, которые в настоящее время применяются.

Надеюсь, я объяснил этот метод достаточно четко.



  1. Если вы являетесь пользователем Unity, вот с чего начать:
    http://www.stevegargolinski.com/armory-a-free-and-unfinished-stat-inventory-and-buffdebuff-framework-for-unity/

Я использую ScriptableOjects как баффы / заклинания / таланты

public class Spell : ScriptableObject 
{
    public SpellType SpellType = SpellType.Ability;
    public SpellTargetType SpellTargetType = SpellTargetType.SingleTarget;
    public SpellCategory SpellCategory = SpellCategory.Ability;
    public MagicSchools MagicSchool = MagicSchools.Physical;
    public CharacterClass CharacterClass = CharacterClass.None;
    public string Description = "no description available";
    public SpellDragType DragType = SpellDragType.Active; 
    public bool Active = false;
    public int TargetCount = 1;
    public float CastTime = 0;
    public uint EffectRange = 3;
    public int RequiredLevel = 1;
    public virtual void OnGUI()
    {
    }
}

используя UnityEngine; using System.Collections.Generic;

public enum BuffType {Buff, Debuff} [System.Serializable] открытый класс BuffStat {public Stat Stat = Stat.Strength; public float ModValueInPercent = 0.1f; }

public class Buff : Spell
{
    public BuffType BuffType = BuffType.Buff;
    public BuffStat[] ModStats;
    public bool PersistsThroughDeath = false;
    public int AmountPerTick = 3;
    public bool UseTickTimer = false;
    public float TickTime = 1.5f;
    [HideInInspector]
    public float Ticktimer = 0;
    public float Duration = 360; // in seconds
    public float ModifierPerStack = 1.1f;
    [HideInInspector]
    public float Timer = 0;
    public int Stack = 1;
    public int MaxStack = 1;
}

BuffModul:

using System;
using RPGCore;
using UnityEngine;

public class Buff_Modul : MonoBehaviour
{
    private Unit _unit;

    // Use this for initialization
    private void Awake()
    {
        _unit = GetComponent<Unit>();
    }

    #region BUFF MODUL

    public virtual void RUN_BUFF_MODUL()
    {
        try
        {
            foreach (var buff in _unit.Attr.Buffs)
            {
                CeckBuff(buff);
            }
        }
        catch(Exception e) {throw new Exception(e.ToString());}
    }

    #endregion BUFF MODUL

    public void ClearBuffs()
    {
        _unit.Attr.Buffs.Clear();
    }

    public void AddBuff(string buffName)
    {
        var buff = Instantiate(Resources.Load("Scriptable/Buff/" + buffName, typeof(Buff))) as Buff;
        if (buff == null) return;
        buff.name = buffName;
        buff.Timer = buff.Duration;
        _unit.Attr.Buffs.Add(buff);
        foreach (var buffStat in buff.ModStats)
        {
            switch (buff.BuffType)
            {
                case BuffType.Buff:
                    _unit.Attr.AddBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat] + _unit.Attr.StatsItem[buffStat.Stat]) * buffStat.ModValueInPercent));
                    break;
                case BuffType.Debuff:
                    _unit.Attr.RemoveBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat] /*+ unit.character.StatsItem[_stat.stat]*/) * buffStat.ModValueInPercent));
                    break;
            }
            Core.StatController(_unit.Attr, buffStat.Stat);
        }
    }

    public void RemoveBuff(Buff buff)
    {
        foreach (var buffStat in buff.ModStats)
        {
            switch (buff.BuffType)
            {
                case BuffType.Buff:
                    _unit.Attr.RemoveBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat] + _unit.Attr.StatsItem[buffStat.Stat]) * buffStat.ModValueInPercent));
                    break;
                case BuffType.Debuff:
                    _unit.Attr.AddBuffStatValue(buffStat.Stat, Mathf.RoundToInt((_unit.Attr.StatsBase[buffStat.Stat]  /*+ unit.character.StatsItem[_stat.stat]*/) * buffStat.ModValueInPercent));
                    break;
            }
            Core.StatController(_unit.Attr, buffStat.Stat);
        }
        _unit.Attr.Buffs.Remove(buff);
    }

    void CeckBuff(Buff buff)
    {
        buff.Timer -= Time.deltaTime;
        if (!_unit.IsAlive && !buff.PersistsThroughDeath)
        {
            if (buff.ModStats != null)
                foreach (var stat in buff.ModStats)
                {
                    _unit.Attr.StatsBuff[stat.Stat] = 0;
                }

            RemoveBuff(buff);
        }
        if (_unit.IsAlive && buff.Timer <= 0)
        {
            RemoveBuff(buff);
        }
    }
}


Это был актуальный вопрос для меня. У меня есть одна идея об этом.

  1. Как было сказано ранее, нам нужно реализовать Buffсписок и логику обновления для баффов.
  2. Затем нам нужно изменить все настройки конкретного игрока в каждом кадре в подклассах Buffкласса.
  3. Затем мы получаем текущие настройки игрока из изменяемого поля настроек.

class Player {
  settings: AllPlayerStats;

  private buffs: Array<Buff> = [];
  private baseSettings: AllPlayerStats;

  constructor(settings: AllPlayerStats) {
    this.baseSettings = settings;
    this.resetSettings();
  }

  addBuff(buff: Buff): void {
    this.buffs.push(buff);
    buff.start(this);
  }

  findBuff(predcate(buff: Buff) => boolean): Buff {...}

  removeBuff(buff: Buff): void {...}

  update(dt: number): void {
    this.resetSettings();
    this.buffs.forEach((item) => item.update(dt));
  }

  private resetSettings(): void {
    //some way to copy base to settings
    this.settings = this.baseSettings.copy();
  }
}

class Buff {
    private owner: Player;        

    start(owner: Player) { this.owner = owner; }

    update(dt: number): void {
      //here we change anything we want in subclasses like
      this.owner.settings.hp += 15;
      //if we need base value, just make owner.baseSettings public but don't change it! only read

      //also here logic for removal buff by time or something
    }
}

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


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

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

Что я имею в виду? Неважно, является ли что-то баффом или дебаффом, они оба являются модификаторами, которые просто влияют на что- то положительное или отрицательное. Коду не важно, что есть что. В этом отношении в конечном счете не имеет значения, добавляет ли что-то статистику или умножает ее, это просто разные операторы, и опять-таки, в коде все равно, какой именно.

Так куда я иду с этим? Создание хорошего (читай: простого, изящного) класса баффа / дебаффа не так уж и сложно, сложно разработать системы, которые рассчитывают и поддерживают состояние игры.

Если бы я проектировал систему баффов / дебаффов, вот несколько вещей, которые я бы рассмотрел:

  • Класс баффа / дебаффа для представления самого эффекта.
  • Класс типа бафф / дебафф, содержащий информацию о том, на что влияет бафф и как.
  • Персонажи, Предметы и, возможно, Местоположения должны иметь свойства list или collection, содержащие баффы и дебаффы.

Некоторые особенности того, что типы баффов / дебаффов должны содержать:

  • К кому / к чему это может быть применено, IE: игрок, монстр, локация, предмет и т. Д.
  • Какой это тип эффекта (положительный, отрицательный), мультипликативный или аддитивный, и на какой тип статистики он влияет, IE: атака, защита, движение и т. Д.
  • Когда это следует проверить (бой, время суток и т. Д.).
  • Может ли это быть удалено, и если так, как это может быть удалено.

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

Пока я поместил правильные типы на место, просто создать запись баффа, которая говорит:

  • Тип: Проклятие
  • ObjectType: Item
  • StatCategory: Утилита
  • StatActed: MovementSpeed
  • Продолжительность: Бесконечно
  • Триггер: OnEquip

И так далее, и когда я создаю бафф, я просто назначаю ему BuffType of Curse, а все остальное зависит от движка …

Пошаговое руководство по созданию торгового бота на любом языке программирования

Время на прочтение
14 мин

Количество просмотров 90K

У меня нет квалификации, позволяющей давать советы, касающиеся инвестиций, законов, или чего-то подобного. Я и не пытаюсь этого делать. Цель руководства заключается в том, чтобы, в учебных целях, рассказать о программировании торговых ботов. Вы должны понимать, что, создав бота, вы несёте ответственность за принимаемые им решения, за выполняемые им инвестиционные операции, за те риски, которые сопряжены с торговой деятельностью. Я не могу отвечать за те решения, которые вы примете после прочтения данного материала. Помните, что боты способны потерять большие деньги, поэтому используйте их с осторожностью.

Часто можно встретить статьи, которые называются примерно так: «10 проектов, которые нужно реализовать программисту». Часто в списки этих статей входят торговые боты. Я считаю, что разработка торгового бота — это достойное вложение сил. Поэтому я решил уделить некоторое время тому, чтобы написать учебное руководство об этом.

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

Поэтом я расскажу о том, что полезно знать для разработки торгового бота, и о том, что для этого нужно (от работы с биржами до реализации простой торговой стратегии). Здесь же я коснусь вопросов, связанных с архитектурой и внутренним устройством простых торговых ботов, с идеями, которые лежат в их основе.

Я буду демонстрировать примеры, написанные на псевдокоде. Поэтому вы сможете читать это руководство и тут же писать собственного бота на выбранном вами языке программирования.

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

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

Шаг 1. Выбираем оружие

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

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

Шаг 2. Ищем поле битвы

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

Итак, вашим первым шагом будет принятие решения о том, чем именно вы собираетесь торговать (акциями, валютами, криптовалютами), и решения о том, где именно вы будете торговать.

Если говорить о биржевых активах, то я посоветовал бы обратить внимание на криптовалюты. Причина такой рекомендации заключается не в том, что я являюсь сторонником блокчейн-технологий и криптовалют (я полностью открыт в этом вопросе), а всего лишь в том, что криптовалютные рынки работают круглые сутки 7 дней в неделю.

Более «традиционными» активами можно торговать только в определённые временные промежутки, и часто — только по будним дням. Рынки акций, например, обычно открыты с 9 утра до 4 вечера и по выходным они не работают. Рынки FOREX, хотя и могут работать круглосуточно, обычно закрыты в выходные.

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

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

  1. У вас должна быть законная возможность торговать на выбранной бирже и работать с предлагаемыми ей торговыми инструментами. Если говорить о криптовалютах, то в некоторых странах торговля ими запрещена. Учитывайте это, выбирая инструменты и биржу.
  2. Биржа должна обладать API, который доступен её клиентам. Нельзя создать торгового бота, который не отправляет запросы к бирже и не получает от неё ответов.

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

И ещё, что так же важно, как и всё остальное, я порекомендовал бы оценить объём торгов биржи. Биржи с низкими объёмами имеют свойство «отставать» от ценовых движений. На них, кроме того, сложнее бывает выполнять лимитные заявки (подробнее об этом мы поговорим ниже).

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

Шаг 3. Строим лагерь

Если биржа — это поле битвы, то теперь мы поговорим о том месте, из которого будем отправлять на поле битвы свои войска. Кстати, мне, пожалуй, пора завязывать с такими аналогиями.

Здесь я говорю о сервере. Код бота должен выполняться на некоем сервере, что позволит боту отправлять запросы к API биржи.

Во время тестирования бота, естественно, в роли сервера может выступать ваш компьютер. Но если вам нужно, чтобы бот работал бы постоянно, обычный компьютер — это, определённо, не лучший выбор.

Тут у меня есть два предложения:

  1. Роль сервера может выполнять Raspberry Pi (этот подход интереснее).
  2. Сервером может быть некая облачная служба (а этот подход лучше).

Я так думаю, что организация деятельности бота на базе собственного Raspberry Pi-сервера — это интересная и современная идея, поэтому, если и вам эта идея нравится, вы можете претворить её в жизнь.

Но большинство создателей ботов, вероятно, остановят свой выбор на каком-нибудь провайдере облачных услуг вроде AWS, Azure, GCS или Digital Ocean.

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

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

Шаг 4. Создаём бота

А вот теперь начинается самое интересное. Но, прежде чем приступать к этой части нашего проекта, проверьте, выполнено ли следующее:

  1. Вы зарегистрировались на бирже и получили необходимые разрешения на работу с ней.
  2. У вас есть возможность работать с API биржи, у вас имеется ключ API.
  3. Вы выбрали хостинг для бота.

Если эти вопросы решены, это значит, что мы можем двигаться дальше.

▍Простейший бот

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

У нашего бота будут некоторые ограничения:

  1. Бот сможет пребывать лишь в одном из двух состояний: BUY (покупка) или SELL (продажа). Он не будет постоянно размещать заявки на покупку или на продажу по разным ценам. Если последней операцией была продажа, то следующей операцией, которую попытается выполнить бот, будет покупка.
  2. Бот будет использовать фиксированные пороговые значения для принятия решений о покупке и продаже. Более интеллектуальный бот может быть способен самостоятельно настраивать подобные значения, основываясь на различных индикаторах, но стратегия и ограничения нашего бота будут задаваться вручную.
  3. Он будет торговать только одной валютной парой. Например — BTC/USD.

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

▍Механизм принятия решений

Вот простая диаграмма, дающая общий обзор функционирования нашего бота.

А теперь можно приступать к планированию архитектуры бота.

Нам, для начала, понадобится переменная, в которой будут храниться сведения о том, в каком именно состоянии находится бот в текущий момент. Это либо BUY, либо — SELL. Для хранения подобных сведений хорошо подойдёт логическая переменная или перечисление.

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

Например, если я купил что-то по цене в $100, а в настоящий момент цена составляет $102, то мы имеем дело с увеличением цены на 2%. Если порог для операции SELL установлен на однопроцентное увеличение цены, то бот, увидев эти 2%, продаст актив, так как он уже получил прибыль, превышающую заданное нами пороговое значение.

В нашем случае подобные значения будут константами. Нам понадобится 4 таких значения — по 2 на каждое состояние бота.

▍Пороговые значения для выполнения операции BUY (если бот находится в состоянии SELL)

  • DIP_THRESHOLD: бот выполняет операцию покупки в том случае, если цена уменьшилась на значение, большее, чем задано DIP_THRESHOLD. Смысл этого заключается в реализации стратегии «покупай дёшево, продавай дорого». То есть, бот будет пытаться купить актив по заниженной цене, ожидая роста цены и возможности выгодной продажи актива.
  • UPWARD_TREND_THRESHOLD: бот покупает актив в том случае, если цена выросла на значение, превышающее то, что задано этой константой. Этот ход противоречит философии «покупай дёшево, продавай дорого». Его цель заключается в том, чтобы выявить восходящий тренд и не пропустить возможность покупки до ещё большего роста цены.

Вот иллюстрация, которая может помочь в понимании смысла этих констант.

Если мы выполнили операцию SELL в момент, отмеченный на рисунке красным маркером SELL, то после этого бот, принимая решение о выполнении операции BUY, будет руководствоваться пороговыми значениями DIP_THRESHOLD и UPWARD_TREND_THRESHOLD.

Если цена уйдёт ниже нижней зелёной линии или выше верхней зелёной линии, мы выполним операцию BUY. В ситуации, показанной на рисунке, цена ушла выше верхнего предела. Поэтому мы, руководствуясь значением UPWARD_TREND_THRESHOLD, выполнили операцию BUY.

▍Пороговые значения для выполнения операции SELL (если бот находится в состоянии BUY)

  • PROFIT_THRESHOLD: бот продаёт актив в том случае, если цена стала выше цены, вычисленной на основе этого значения, так как ранее актив был куплен по более низкой цене. Именно так мы получаем прибыль. Мы продаём актив по цене, которая выше той, что была в момент его покупки.
  • STOP_LOSS_THRESHOLD: в идеальной ситуации мы хотели бы, чтобы бот продавал бы активы только тогда, когда продажа приносит нам прибыль. Но, возможно, произошло сильное движение рынка вниз. В такой ситуации мы решим выйти из сделки до того, как понесём слишком большие убытки, и позже купить актив по более низкой цене. Это пороговое значение используется для закрытия позиции с убытком. Цель этой операции — предотвращение более сильных потерь.

Вот иллюстрация.

Тут показана ситуация, когда там, где стоит маркер BUY, была сделана покупка. После этого цена достигает предела, заданного PROFIT_THRESHOLD, и мы продаём актив с прибылью. Именно так боты зарабатывают.

Теперь, после того, как у нас сформировалось общее понимание того, как функционирует бот, пришло время рассмотреть псевдокод.

▍Вспомогательные функции для работы с API

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

FUNCTION getBalances():
    DO: Выполнить GET-запрос к API биржи для получения 
    сведений о балансах
    RETURN: Сведения о балансах

FUNCTION getMarketPrices():
    DO: Выполнить GET-запрос к API биржи для получение 
    текущей цены актива
    RETURN: Текущая цена актива

FUNCTION placeSellOrder():
    DO:
        1. Вычислить количество актива для продажи (на основе
        некоего заданного порогового значения, например, 
        50% общего баланса)
        2. Отправить POST-запрос к API биржи для выполнения
        операции SELL
    RETURN: Цена совершения сделки

FUNCTION placeBuyOrder():
    DO:
        1. Вычислить количество актива для покупки (на основе
        некоего заданного порогового значения, например, 
        50% общего баланса)
        2. Отправить POST-запрос к API биржи для выполнения
        операции BUY
    RETURN: Цена совершения сделки

// Необязательная функция, которая предназначена для 
// получения подтверждения выполнения операций
FUNCTION getOperationDetails():
    DO: Выполнить GET-запрос к API биржи для получения
    сведений об операции
    RETURN: Сведения об операции

Вышеприведённый код должен быть понятен без особых объяснений. Но, реализуя этот функционал, вы должны точно знать о том, какие именно данные нужно передать API при выполнении POST-запросов, инициирующих покупку или продажу чего-либо.

Часто, например, когда торгуют парой XAU/USD (золото и доллары США), при выполнении запроса можно указать или то, сколько золота нужно купить, или то, сколько долларов нужно продать. При выполнении подобных запросов очень важно чётко понимать смысл производимых действий.

▍Главный цикл бота

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

FUNCTION startBot():
    INFINITE LOOP:
        attemptToMakeTrade()
        sleep(30 seconds)

Далее — настроим переменные и константы, о которых мы говорили выше, и напишем логику бота, позволяющую ему принимать решения. В результате, помимо вспомогательных функций и главного цикла, основной код бота будет выглядеть так:

bool isNextOperationBuy = True

const UPWARD_TREND_THRESHOLD = 1.50
const DIP_THRESHOLD = -2.25

const PROFIT_THRESHOLD = 1.25
const STOP_LOSS_THRESHOLD = -2.00

float lastOpPrice = 100.00

FUNCTION attemptToMakeTrade():
    float currentPrice = getMarketPrice()
    float percentageDiff = (currentPrice - lastOpPrice)/lastOpPrice*100
    IF isNextOperationBuy:
        tryToBuy(percentageDiff)
    ELSE:
        tryToSell(percentageDiff)

FUNCTION tryToBuy(float percentageDiff):
    IF percentageDiff >= UPWARD_TREND_THRESHOLD OR percentageDiff <= DIP_THRESHOLD:
        lastOpPrice = placeBuyOrder()
        isNextOperationBuy = False

FUNCTION tryToSell(float percentageDiff):
    IF percentageDiff >= PROFIT_THRESHOLD OR percentageDiff <= STOP_LOSS_THRESHOLD:
        lastOpPrice = placeSellOrder()
        isNextOperationBuy = True

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

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

На каждой итерации цикла бот будет проверять своё текущее состояние (BUY или SELL) и будет пытаться выполнить торговую операцию, используя при анализе текущей ситуации жёстко заданные в его коде пороговые значения. Затем, если операция будет выполнена, бот обновит сведения о своём текущем состоянии и данные о цене, по которой была совершена последняя операция.

Шаг 5. Улучшаем бота

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

▍Журналы

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

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

[BALANCE] USD Balance = 22.15$
[BUY] Bought 0.002 BTC for 22.15 USD
[PRICE] Last Operation Price updated to 11,171.40 (BTC/USD)
[ERROR] Could not perform SELL operation - Insufficient balance

То, что попадает в файл журнала, снабжается отметками времени. В результате, если я подключаюсь к серверу раз в день, и вижу, например, сообщение об ошибке, я могу узнать о том, когда именно произошла ошибка, и о том, чем бот занимался всё это время.

Оснастить бота подобными возможностями — значит написать функцию, которую можно назвать createLog. Эта функция должна вызываться на каждом шаге главного цикла бота. Вот как может выглядеть эта функция:

FUNCTION createLog(string msg):
    DO:
        1. Вывести msg в терминал
        2. Записать msg в файл журнала, добавив отметку времени

▍Идентификация трендов

Главная цель нашего бота заключается в том, чтобы дёшево покупать активы и продавать их, получая прибыль. Но в его коде есть две константы, символизирующие два пороговых значения, которые отчасти этой цели противоречат. Это UPWARD_TREND_THRESHOLD и STOP_LOSS_THRESHOLD.

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

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

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

Нам нужно лишь организовать наблюдение за большим количеством ценовых значений, чем раньше. А раньше мы хранили сведения лишь об одном ценовом показателе — о стоимости актива на момент последней операции (lastOpPrice). Можно, например, хранить сведения о ценах за 10 или 20 последних итераций цикла бота и сравнивать с текущей ценой их, а не только lastOpPrice. Это, вероятно, позволит лучше идентифицировать тренды, так как при таком подходе мы можем уловить краткосрочные колебания цены, а не колебания, происходящие за долгое время.

▍База данных?

Простому боту, на самом деле, база данных не нужна. Ведь он оперирует весьма небольшими объёмами данных и хранит всю необходимую ему информацию в памяти.

Но что произойдёт в том случае, если, например, бот будет аварийно остановлен? Как ему узнать, без вмешательства человека, о том, каким было значение lastOpPrice?

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

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

В зависимости от того, насколько простой, по вашему мнению, должна быть эта «база данных», вы можете даже решить использовать в таком качестве обычные .txt- или .json-файлы, так как, в любом случае, речь идёт о хранении весьма ограниченного набора данных.

▍Панель управления

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

Это потребует наличия у бота собственного серверного API, предназначенного для управления его функционалом.

Подобный подход, например, позволит легко менять пороговые значения.

Существует множество шаблонов панелей управления, а значит вам, если вы решите сделать что-то подобное, даже не придётся создавать такую панель с нуля. Взгляните, например, на Start Bootstrap и Creative Tim.

▍Тестирование стратегий на исторических данных

Многие биржи дают клиентам доступ к историческим ценовым данным. Кроме того, если вам нужны такие данные, их обычно несложно достать.

Их использование весьма полезно для тестирования торговых стратегий перед их реальным применением. Это позволяет запустить симуляцию, используя исторические данные и «ненастоящие» деньги. Благодаря этому можно узнать о том, насколько удачно показали бы себя пороговые значения, и, если нужно, поменять эти значения.

▍Дополнительные сведения о пороговых значениях и заявках

При размещении заявок нужно учитывать несколько моментов.

Во-первых, нужно знать о том, что существуют два типа заявок: лимитные и рыночные. Если вы совсем ничего об этом не знаете — вам, определённо, стоит почитать специальную литературу. Я тут объясню эти идеи буквально в двух словах.

Рыночные заявки — это заявки, которые исполняются по текущей рыночной цене. В большинстве случаев это означает их немедленное исполнение.

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

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

Кроме того, к таким заявкам обычно применяются более низкие комиссии, чем к рыночным. Это так из-за того, что к рыночным заявкам обычно применимо то, что называется «taker fee» («комиссия получателя»), а к лимитным заявкам — то, что обычно называется «maker fee» («комиссия создателя»).

Причины, по которым эти комиссии называются именно так, заключаются в том, что тот, кто размещает рыночную заявку, просто принимает («taking») текущую рыночную цену. А лимитные заявки находятся за пределами рыночных цен, они добавляют рынку ликвидности и, в результате, «создают рынок», за что их создатели вознаграждаются более низкими комиссиями.

Обратите внимание на то, что бот, который мы тут рассматриваем, лучше всего подходит для работы с рыночными заявками.

И, завершая разговор о комиссиях, хочу отметить, что задавая значение PROFIT_THRESHOLD нужно учитывать и комиссии.

Для того чтобы получить прибыль, бот должен сначала выполнить операцию BUY, а потом — операцию SELL. А это значит, что комиссия будет взята два раза.

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

Поразмыслим об этом, исходя из предположения о применении комиссий, не зависящих от суммы заявки и от вида операции. Итак, комиссия за покупку актива на $100,00 составляет $0,50. Если этот актив будет продан за $100,75 и при этом будет взята такая же комиссия, то окажется, что валовая прибыль составляет 0,75%. Но, на самом деле, тут мы имеем дело с чистым убытком в 0,25%.

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

Итоги

Моей основной целью было раскрытие концепций, о которых нужно знать при разработке торговых ботов. Я старался описать всё так, чтобы это было понятно даже тем, кто раньше никогда не торговал на бирже. При этом я не привязывал повествование к какому-то конкретному языку программирования.

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

Я рассказал вам о разработке торговых ботов всё, что хотел. Надеюсь, теперь вы сможете создать собственного бота.

А вы пользуетесь торговыми ботами?

Понравилась статья? Поделить с друзьями:
  • Национальное руководство по глаукоме для практикующих врачей егоров
  • Клобазам инструкция по применению цена отзывы аналоги
  • Жалоба окей руководству
  • Yamaha psr 550 инструкция по эксплуатации
  • Офлоксацин инструкция по применению цена уколы внутримышечно взрослым