Jump to content

Magicsea Online

Community
  • Content Count

    135
  • Joined

  • Last visited

  • Days Won

    34

Everything posted by Magicsea Online

  1. Время от времени мы публикуем интересные моменты при работе со старым кодом игры, новости разработки и другие интересные фишки в нашем телеграмм канале https://vk.cc/chjcxR Присоединяйтесь, задавайте вопросы и учитывайте в своих проектах!
  2. Всем привет Мы чутка освободились и наконец-то пришли с новостями. Предисловие: Вот уже 2 года и 10 месяцев как мы находимся в разработке нашего продукта. Смотря сейчас на пройденный нами путь, мы понимаем, что разработка собственной игры, а тем более кроссплатформенной это не самое быстрое занятие и легкое хобби. Из-за желания сделать продукт идеальный, мы совершали ошибки, за которые уже поплатились самым ценным ресурсом человечества - временем. Если бы можно было вернуться в начало нашего пути и дать пару советов, то мы бы сэкономили как минимум целый год. Но не всё так плохо, самое важное что мы получили - это опыт, опыт который считается бесценным для нас. Позвонить разработчику на мобильный номер и будить его, потому что он в процессе спринта проспал начало работы? - Да, это про нас. Уснуть на стуле или на столе в ночное время пока вы решаете, как в этом чертовом MindPower'e реализована система эффектов? - Да и это про нас. Сломать, построить, сломать и ещё раз построить чтобы потом опять сломать? - Да, увы и это про нас. Мы предусматриваем даже неразумные вещи, которые могут произойти с продуктом в процессе его эксплуатации. Больше 5 млн рублей в нашей excel таблице сейчас в расходах, больше чем десятки тысяч коммитов в репозиториях, мы разобрали эту чертову игру на кусочки и собрали её заново, мы даже знаем кому звонить когда ошибка в графическом движке при воспроизведении анимации (поищите в исходниках "crash!!!, call"), мы уже не просто команда, а уже большая семья. Всё это время мы искали ту самую, единственную, верную тропу по которой нам необходимо идти, и мы её нашли. Ну и к чему же мы наконец-то пришли? Клиент: На данный момент мы переписываем весь функционал клиента прописанный в C++ на скриптовой язык LUA. Для чего? Скриптовой язык LUA является простым на наш взгляд, как минимум проще чем C++, средняя стоимость оплаты труда на порядок меньше, разграничение скриптовой логики и логики движка предоставляет куча безопасности, избавляет раз и навсегда от утечек памяти, скорости работы и самое важное - ускоряет процесс разработки. Множество важных элементов игрового и графического движка мы переписали с 0, такие как рендер карты, игровые эффекты, рендер в принципе. Наш девиз - Кара должна запускаться даже на кофемолке © alex2772. Мы помешаны на оптимизации и не позволяем себе потратить лишние 10 миллисекунд в UI потоке, поэтому всё что можно мы стараемся вытащить в другие потоки без частого использования мьютексов. И конечно, мы понимаем, что какой-то наш забугорный клиент может иметь телефон на какой-то 4.x версии android’a с ограниченным количество CPU и предусматриваем это. Хочется отметить важный факт, что мы делаем клиент игры, который способен работать на 1.38 версии клиента, а с некоторыми доработками и на 2.0. И не исключаем возможность тестирования нашего клиента совместно с руководством и игроками существующих серверов по их желанию. CI/CD: Мы очень активно пользуемся технологией CI и CD. Каждый раз когда разработчик делает коммит в репозиторий, то клиент игры автоматически собирается под все платформы и обновляется на всех платформах. Чтобы залить обновление для клиента, нам достаточно 1 коммита. Сервер: GateServer, GroupServer и AccountServer переписали с нуля, GameServer пока фулл рефактор скриптов. Все сервера переписали под CMake и адаптировали под работу в Linux, затянули их под технологию Docker и сразу же под технологию Kubernetes. Lua игрового мира: Все вы знаете, как выглядит легаси код сервера, как в нем не понятно что к чему - все навалено в один файл и покрыто китайскими комментариями с битой кодировкой. А если копнуть дальше, то можно заметить в коде очень много проблем с глобальными переменными, а также все это сопровождается алгоритмическими ошибками. Когда к нам в команду пришел опытный луа-разработчик, он сказал: “это все говно нужно переписывать, как оно работало - я хз”. Теперь у нас ООП в Луа, мы переписываем легаси архитектуры, к примеру вот так сейчас выглядит обычный диалог с НПС. Теперь не нужно продумывать сложную логику из взаимосвязи страниц, даже разработчики с низким уровнем знания lua разбираются с этим быстрее, чем это было раньше. “У вас много магических чисел - магические числа — это плохо”, он не исправил это, но теперь весь код наполнен комментариями по всем магическим числам, по которым можно дать комментарии. Большинство функций, поведение которых не тривиально описаны и задокументированы. Пример: Вы знаете самые нагруженные функции в луа на вашем сервере? Вы знаете, как их найти? Вот и мы тоже не знали, а теперь в нашем арсенале 3 профилировщика, которые покажут нам, где мы просаживаемся по производительности при росте онлайна. А где же мы берем онлайн, у нас на сайте все время 4% нагруженности было… Наш луа код на сервере покрыт комментариями на 90%, а все что можно шаблонизировать - шаблонизировано или продолжает шаблонизироваться: Кстати, на скриншоте можно еще увидеть новое логирование, которое вываливает traceback и систему мультиязычности сервера. Вы знаете сколько игрок прошел заданий и сколько денег он потратил на лечение? Вот и мы тоже не знали, но добавили больше 100 различных метрик, которые можем анализировать и выводить в красивые и информативные графики. Перебалансированные квесты по наградам и уровням. Теперь мы можем решать чем заниматься игрокам на сервере: - прокачка через убийство мобов: повышаем рейты на убийство опыта, понижаем рейты на получение опыта с заданий, - прокачка через историческую цепочку: понижаем рейты за убийство мобов, понижаем опыт за обычные сайд задания, повышаем за историческую цепочку. При всех этих изменениях мы продолжаем поддерживать старую, добрую Пиратию и в любой момент все наши изменения баланса/экономики за 10 минут откатываются обратно, кроме квестов, наверное. Это все благодаря одному конфигурационному файлу, который отвечает за константные переменные, а также за активацию тех или иных систем. Каждой системе отдельный файл, каждой системе отдельный флаг отключения - мы разбили весь луа код из 10-15 файлов в 250+ и это позволяет лучше понимать работу систем, что снижает порог вхождения новых разработчиков. Наш репозиторий сервера насчитывает более 2500 коммитов, свою руку к коду приложило около 20 специалистов, различной направленности. А через наши файлы в целом прошло больше 60 человек: они рисовали иконки и картинки, писали новые задания, рефакторили старый код, писали новые системы, писали музыку и звуки, изменяли карты, страдали и наслаждались результатом. С каждым разом описывая наши достижения мы хотим поделиться всем, но пока мы писали данный пост мы понимаем что всё описать невозможно, по этому оставим тут самые интересные и важные на наш взгляд новости.
  3. Распишу на будущее как искать подобные проблемы подсматривая в исходный код. При форже LUA вызывают функцию lua_AddItemFinalAttr inline int lua_AddItemFinalAttr(lua_State* pLS) { ... long lAttrID = static_cast<int>(lua_tonumber(pLS, 2)); short sAttr = static_cast<short>(lua_tonumber(pLS, 3)); if (!pSItem->AddAttr(lAttrID, sAttr)) { lua_pushinteger(pLS, 0); return 1; } ... } Тут нам интересна функция AddAttr inline short SItemGrid::AddAttr(long lAttrID, short sAttr) { if (!CheckAttr()) return 0; return CAttr.AddAttr(short(lAttrID), sAttr); } Тут мы видим, что тип аргумента должен быть short Для того чтоб все работало нужно изменить аргументы функции и этот массив + все места где он используется. class CItemAttr { ... private: short m_sAttr[ITEMATTR_MAX_NUM]; ... }
  4. К сожалению, нет возможности изучить код который там представлен. Могу только обратить внимание на то, что нужно четко понимать какой параметр персонажа начисляется и при наложении эффекта и какой вычитается при снятии эффекта. Думаю, проблема кроется где-то там. Речь про параметры, используемые в этих функциях. function Str(a) local str = GetChaAttr(a, ATTR_STR) return str end function StrSa(a) local strsa = GetChaAttr(a, ATTR_STATEC_STR) / ATTR_RADIX return strsa end function StrSb(a) local strsb = GetChaAttr(a, ATTR_STATEV_STR) return strsb end function StrIa(a) local stria = GetChaAttr(a, ATTR_ITEMC_STR) / ATTR_RADIX return stria end function StrIb(a) local strib = GetChaAttr(a, ATTR_ITEMV_STR) return strib end function Str_final(a) local str_final = (BSStr(a) * StrIa(a) + StrIb(a)) * math.max(0, StrSa(a)) + StrSb(a) return str_final end
  5. Создать карту, при запуске ГСа создавать копий карты столько, сколько всего гильдий разрешено(80 штук по умолчанию). При попытке ТП, смотреть ИД гильдии игрока и телепортировать на копию карты под этим номером - профит.
  6. Советую поставить отладку на функции карты через print и посмотреть как они вызываются. Есть такой баг, когда карта пытается, например, закрыться, когда она уже закрыта. Чистите ли вы карту от существ после открытия/закрытия карты или ее копии? Сколько карт на ГСе, на котором они зависают? Что пишется в логе создания порталов?
  7. Вот и ответ на твой вопрос. Функцию в проверке используешь, но функции нет, она возвращает nil, ContitionsTest возвращает ошибку
  8. Проверь есть ли условие на использование функции Checksailexpmore в ConditionsTest Примерно такой вид ... if conditions[i].func == NoMission then log:debug(string.format("ConditionsTest:NoMission, p1 = %s", conditions[i].p1)) if NoMission(character, conditions[i].p1) ~= LUA_TRUE then log:debug("ConditionsTest:NoMission = false") return LUA_FALSE end ...
  9. Ну про что я и писал выше Должно быть Stall.cfg [ToClient] host = 0.0.0.0 // [string] IP address Game.exe -> StallServer.exe port = 7777 // [integer] Port Game.exe -> StallServer.exe ... [ToGate] host = 127.0.0.1 // [string] IP address StallServer.exe -> GateServer.exe port = 1973 При этом клиент должен подключаться по 7777 порту. Если не хотите менять внешний порт для клиента, то делаем иначе: Stall.cfg [ToClient] host = 0.0.0.0 // [string] IP address Game.exe -> StallServer.exe port = 1973 // [integer] Port Game.exe -> StallServer.exe max_player = 2048 // [integer] The maximum number of clients that can simultaneously connect to the server max_clients_per_ip = 64 // [integer] The maximum number of connections from one IP address (0 = limit is disabled) connection_interval = 1000 // [integer] Time interval between connections from one IP address in milliseconds (0 = limit is disabled) [ToGate] host = 127.0.0.1 // [string] IP address StallServer.exe -> GateServer.exe port = 2715 // [integer] Port StallServer.exe -> GateServer.exe Gate.cfg [ToClient] IP = 0.0.0.0 Port = 2715 CommEncrypt = 1 EnablePing = 60 MaxConnection = 1000 Поясню - для Gate клиентом является не сам клиент, а Stall. Для Stall клиентом является пользователь. Получается пользователь подключается по 1973 порту, а после перенаправляется на 2715 на Gate.
  10. Такое бывает когда они занимают один и тот же порт. Gate должен слушать условно 1973. Stall должен слушать условно 7777 и перенаправлять в 1973. Возможно вы запускаете Gate и Stall и оба они должны слушать 1973 порт. Без конфигов сложно что-либо сказать.
  11. Я об этом и написал, что решение - хранить переводы на стороне клиента самое верное, но реализовать это без дополнительных телодвижений невозможно(без глубокой переработки клиента/серверов)
  12. Тут вопрос в полном мультиязыке, который невозможно реализовать чисто на Lua. То о чем говоришь ты: Да, это делается на Lua без особого труда. И на стороне клиента подсасывать "правильные" ItemInfo/CharacterInfo/SkillInfo/... в зависимости от выбранного языка пользователем при запуске игры или переключения на горячую. Придется повторить еще раз: Если посмотреть на реализацию Notice - она берет строку и бездумно отправляет всем Gate серверам без обработки. Gate сервера ничего не знаю про язык клиента или язык пользователя. Так же как и язык при инициализации NPC или инициализации любых Entity в которую передается ОДНО значение-строка и это должен быть КЛЮЧ, а клиент на своей стороне должен узнать этот ключ и вставить нужную строчку. Логика нормального человека работы с функцией Notice - берешь строку и параметры, кидаешь в клиента, клиент выбирает по ключу строчку нужную ему и сам расставляет параметры и выводит пользователю. Насколько мне известно - вы это не реализовали. Большая часть "нормального" мультиязыка сделана на 2.0 версии и там все строки являются ключами во всех файлах и есть большие словари для работы с языками, но как реализованы функции Notice или MapNotice мне не известно, скорей всего не в полной мере, как и реализация выше. Советую посмотреть нормальные проекты в нормальных движках и изучить как это работает там. Задача реализованная на 99% не реализованная и выглядит примерно вот так: По теме: в каком то бородатом году @insider выкладывал некоторый "мод", о котором ты говоришь и который встречается в файлах сервера на видео примерно в 2017-2018 году. Не по теме: советую посмотреть тему, возьмите на вооружение.
  13. Мы тоже столкнулись с тем что это сложно нормально реализовать. Как итог пришли к тому, что все строчки должны быть на стороне клиента, а сервер должен присылать ключ и значения для строчек(аргументы), а клиент в зависимости от выставленного языка будет выбирать строчку по ключу и подставлять аргументы. Таким образом мы будем экономить трафик и не будем вешать на сервер лишнюю логику по выбору языка. В основном проблема в таких функциях как Notice/GmNotice/MapNotice/ColorNotice(для корсаров)/SetMapEntryMapName/CreateEventEntity/GetItemName. Функции Text/Talk и все что связано с квестами перевести на стороне сервера не составляет трудностей. Но из-за вышеупомянутых функций приходится полностью пересматривать логику на хранение текста на стороне клиента. Минус данного подхода - весь контент с точки зрения лора, квестов, перевода и прочего лежит на стороне клиента и этим могут пользоваться недобросовестные конкуренты. С другой стороны - экономия трафика, экономия времени на принятии решения, если нужно поменять какой-то квест, исправить ошибку в тексте - мы не останавливаем сервер, если файлы не шифрованные - какие-нибудь любители могут сделать свой перевод и тем самым помочь разработчикам. Пока мы не дошли до полномасштабной разработки этой идеи и подводные камни сказать не могу.
  14. Эта игра требует полного пересмотра механик и вообще всеобщего геймплея для адаптации под современного игрока. Не все команды готовы идти на такие шаги и терять аудиторию, которая есть сейчас и которая привыкла к этой игре. Порог входа для нового игрока очень большой. Качество баланса и механик уровня - сделали на коленке, работает и ладно. Эта позиция прослеживается везде в Lua и в C++ коде. Переработка графики, моделей и текстур очень трудозатратное предприятие. При заказе переработок мы услышали ценник в 2кк рублей и примерно пол года ежедневной работы 8 часов 5/2 штата модельеров и художников. Такое не в силах потянуть многие нынешние проекты, которые живут на пожертвования 50-100 игроков и не могут справится с дырами и багами оставленными китайскими разработчиками. А еще есть музыка, которую многие удаляют. Привлечение новых игроков в игру с графикой 2006 года и кривым балансом дело очень финансово невыгодное. При анализе рекламы мы столкнулись с тем, что платим за игрока примерно 146 рублей(агрессивно настроенная рекламная компания) и получаем что 1 из 10 игроков игру не удаляет через 30 секунд после входа в игру. К примеру, на мобильной платформе привлечение нового игрока можно сделать в среднем 0.26-1.97 рубля(условные цифры, так как зависит от вашей рекламы). Да и очень мало проектов которые вообще как либо вкладываются в рекламу на новую аудиторию. А если все таки получается приходят конкуренты в дискорд канал и высасывают твою аудиторию, вкладывая гораздо меньше средств в привлечение новых игроков для себя. Команда тоже острый вопрос. Искать людей в нашем коммьюнити очень тяжело, так как доверия к большей половине ровно 0. Наша команда потратила уйму времени на поиск и найм новой крови. Все разработчики извне привыкли работать с популярными движками и популярными ЯП. Порог вхождения нового разработчика очень большой нужно погрузить человека в игровой процесс, нужно погрузить разработчика в легаси код, на который многие не могут смотреть и работать с ним, в итоге без крови в глазах эти люди не могут работать. Берешь разработчика из нашего коммьюнити и получаешь сливы, бэкдоры, срачи и обидки. А еще большинство не умеет работать в команде и обычно в соло что-то пилят, а когда нужно научиться пользоваться Git`ом то это сложно и не понятно и вообще я привык иначе работать. Итого: Кривой баланс - трогаешь уходит лояльная аудитория. Кривой ЛОР - нет четкого ответа зачем играет игрок и старые игроки устали тыкать одно и тоже несколько лет подряд. Графика 2006 года - пиксели режут глаза, полигоны по пальцам посчитать можно. Музыка - отталкивает примерно сразу же после захода в игру. Реклама - дорогое и неблагодарное удовольствие, причем многим непонятное до сих пор. Команда - дорого, долго, опасно.
  15. @hokage если функции нет, в логах будет информация об этом. @V3ct0r я не имею информации была ли эта функция по умолчанию.
  16. Запретите атаку с суши на море, насколько я помню проблема в этом. Редактируем функцию is_friend ------------------------------------------------ -- Функция определения друг или враг на карте -- ------------------------------------------------ -- cha1 - атакует , cha2 - получает function is_friend(cha1, cha2) local friend_target = 1 local Map_type = GetChaMapType( cha1 ) local check_cha1 = CheckChaRole ( cha1 ) local check_cha2 = CheckChaRole( cha2 ) ... -- Добавим фрагмент if check_cha1 == 1 and check_cha2 == 1 then --Атака игроков с моря if (IsChaInLand(cha1) == 1 and IsChaInLand(cha2) == 0) then return 0 elseif (IsChaInLand(cha1) == 0 and IsChaInLand(cha2) == 1) then return 0 end else --Атака мобов с моря if (IsChaInLand(cha1) == 1 and IsChaInLand(cha2) == 0) then return 1 elseif (IsChaInLand(cha1) == 0 and IsChaInLand(cha2) == 1) then return 1 end end ... P.S. реализация не нравится, требует оптимизации.
  17. Дополню: ввести специальную функцию SetExitTime(map, time) и при инициализации карты в функцию config выставлять время каждой карте сколько на ней выход. Тогда вы сможете регулировать время на каждой карте. Ну и не будете заставлять игроков испытывать негативные эмоции при выходе на мирных картах. Но придется апргейдить не только ГС и клиент, но еще и GateServer или ProxyServer или StallServer в зависимости от того, что вы используете. Скорей всего был холд всех умений на миллисекунду) Не помню какие там подводные камни, но они были. Если память не изменяет то их минимум 5 дефолтно и Вы сами можете изменять ID моба, который будет призван. Только необходимо чтоб у моба в Character.txt были выставлены специальные параметры(AI_TYPE) чтоб моб отображался нормально на мини карте(последнее не точно, нужно смотреть).
  18. Возможно без использования тюрем и различного рода лишений игроков, которые на эмоциях пишут различные плохие вещи, можно реализовать черный список и каждый из игроков будет добавлять туда именно тех игроков, кто по их мнению сквернословит. Тем самым игрок А не будет видеть сообщений от игрока Б, который использует ненормативную лексику. Но игрок А будет видеть сообщения игрока В, хотя игрок В использует ненормативную лексику, но ему с кайфом читать эти сообщения. Игрок Б при этом видит сообщения обоих( А и В ), пока не добавит их в свой черный список. Причем это все касается чатов "ЛС", "Местный", "Торг", "Мир". При этом возможно "Групповой" не нужно игнорировать. Но это на усмотрение разработчика и администраторов. И можно делать это все на стороне клиента и не напрягать GroupServer и фильтр будет использоваться для всех аккаунтов которые запускаются непосредственно с ПК. Эта рекомендация если вы не хотите лишний раз напрягать сервера излишней обработкой. Единственная проблема при удалении игры и установке снова все настройки могут сбросится. Поэтому можно каждый раз выгружать этот черный список из базы если списка нет.
  19. @kras после предоставления CFG советую сменить логин и пароль для доступа к базе. И если у Вас база находится на удаленном сервере, советуем не предоставлять полный ip базы сервера.
  20. Возможно это оно и есть, просто иначе называется. Используется в функции CheckItem_CanJinglian. function CheckItem_CanJinglian ( Item ) local Item_Type = GetItemType ( Item ) local i = 0 for i = 0 , Item_CanForge_Num do if Item_Type == Item_CanForge_ID [i] then return 1 end end return 0 end Так же совет от нас: лучше заменить расположение Item_CanJinglian_Num Item_CanJinglian_ID = { } Item_CanJinglian_ID [0] = 1 --Меч Item_CanJinglian_ID [1] = 2 --Двуручный меч Item_CanJinglian_ID [2] = 3 --Лук Item_CanJinglian_ID [3] = 4 --Мушкет Item_CanJinglian_ID [4] = 7 -- клинок мор Item_CanJinglian_ID [5] = 11 -- Щит Item_CanJinglian_ID [6] = 20 -- Шапка Item_CanJinglian_ID [7] = 22 -- Доспех Item_CanJinglian_ID [8] = 23 -- Перчатки Item_CanJinglian_ID [9] = 24 -- Ботинки Item_CanJinglian_ID [10] = 27 -- тату Item_CanJinglian_ID [11] = 9 -- Посох Item_CanJinglian_ID [12] = 25 -- Ожерелье ( посмотрел по другим сборкам и увидел что данных строк не хватал в них тоесть 12 и 13 строчка как раз -- Item_CanJinglian_ID [13] = 26 -- кольцо ( решили данную проблему по вставке самоцвета в кольца и бижу) Item_CanJinglian_Num = table.getn(Item_CanJinglian_ID) -- или #Item_CanJinglian_ID (получаем количество элементов в таблице Item_CanJinglian_ID Тогда не нужно будет исправлять значение, оно будет переопределяться каждый раз при инициализации GS'a Таких моментов много в коде. На работоспособность не влияет, но удобств добавляет.
  21. Необходимо добавить типы предметов в массив Item_CanForge_ID ------------------------------------------ -- Типы предметов, которые можно ковать -- ------------------------------------------ Item_CanForge_ID = {} Item_CanForge_ID[0] = 1 -- Одноручный меч Item_CanForge_ID[1] = 2 -- Двуручный меч Item_CanForge_ID[2] = 3 -- Лук Item_CanForge_ID[3] = 4 -- Пистолет Item_CanForge_ID[4] = 7 -- Клинок покорителя морей Item_CanForge_ID[5] = 11 -- Щит Item_CanForge_ID[6] = 20 -- Шапка Item_CanForge_ID[7] = 22 -- Доспехи Item_CanForge_ID[8] = 23 -- Перчатки Item_CanForge_ID[9] = 24 -- Ботинки Item_CanForge_ID[10] = 27 -- Тату Item_CanForge_ID[11] = 9 -- Посох Item_CanForge_ID[12] = 25 -- Ожерелье Item_CanForge_ID[13] = 26 -- Кольцо Потом использовать в массиве StoneItemType StoneItemType[29] = { 24, 25--[[Ожерелье]], 26--[[Кольцо]], 0 } -- Самоцвет Ветра (ID 860) Самоцвет взят для примера.
  22. @Graf @V3ct0r на самом деле, администрация сервера сама поставила себя в подобные условия, когда игроки вынуждены использовать CheatEngine для открытия сундуков/свитков. Первое самое важное решение - ввести проверки на стороне LUA, и реализовать открытие сундуков и использование свитков пачками. То есть, при использовании предмета мы должны проверить свободное место в инвентаре и разом открыть Н сундуков, если это возможно. Это костыльная защита, но для решения локальной проблемы здесь и сейчас, без специалиста, который справится с этой проблемы - идеальный вариант исправления ошибки. Второе решение - изменить баланс и избавиться от такого большого количества предметов. Меняем логику получения предметов и не страдаем от игроков, которые пытаются автоматизировать способ добычи тех или иных предметов. Конечно же, это не поможет именно с атаками при использовании стороннего ПО. Но игроки не будут вынуждены использовать это стороннее ПО для решения проблем именно с атаками нужно искать специалистов, которые помогут решить проблемы с валидными пакетами в секунду на стороне GateServer/GameServer.
  23. Так же можно выделять функции в тексте, например: Будет выглядеть вот так: Тут добавлены теги "Полужирный" и выделение цвета. Так легче поставить акценты в тексте и пособие будет более приятно глазу.
×
×
  • Create New...