Здравствуйте, гость ( Вход | Регистрация )

> UO Inferno
nullptr
сообщение 24.3.2025, 19:30
Сообщение #1


***

Novice
Сообщений: 68
Регистрация: 13.3.2021
Группа: Пользователи
Наличность: 68
Пользователь №: 19.916



Привет всем фанатам старой школы Ultima Online! Этот сервер - попытка воссоздать атмосферу моего детства в 2000-х. Помните разделение на "Сферу" с магическим PvP и "Полы" (Inferno, Dragon World, Utopia и др.) с классическим? Сейчас это хардкорный T2A без рас и классов, построенный на последнем OSI эмуляторе. Добавлены OSI управление, аукцион и другие фишки последних клиентов.

Сайт https://playuo.ru/

Аукцион
(IMG:https://i124.fastpic.org/big/2025/0324/2e/67c2da2bcec6c59ef61d6316b22db02e.png)

Гильдии
(IMG:https://i124.fastpic.org/big/2025/0324/ae/71fc1085db30d199a36ec443b88153ae.png)


Запись ежедневных замесов
[+]


Последние 4 года я провёл над разработкой сервера, и мне удалось создать рабочее ядро T2A, а также написать несколько автоматических турниров. Однако со временем меня стало сильно удручать отсутствие контекста за пределами стандартной "песочницы" UO: добычи ресурсов, крафта и прочего. Мне захотелось оживить PvP-сервер, добавив квесты в духе старой школы, наподобие DxD и других классических проектов.

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

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

Example диалог.
(IMG:https://i124.fastpic.org/big/2025/0324/9b/3eb64748dcc469054dfd3017048af89b.png)

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

Как только появится немного свободного времени, я планирую написать небольшое обучающее задание, которое познакомит новых игроков с особенностями игры на сервере
Пользователь в офлайнеDelete PostОтправить личное сообщение
Вернуться в начало страницы
+Ответить с цитированием данного сообщения
 
Ответить в эту темуОткрыть новую тему
Ответов
olduo.com
сообщение 2.9.2026, 18:54
Сообщение #2


******

Expert
Сообщений: 520
Регистрация: 31.7.2016
Группа: Пользователи
Наличность: 293
Пользователь №: 18.060



Хаха прикольно ))

С турнирами понятно и в целом видиться не очень сложным.
А на сколько сложные квесты, наверняка это принеси подай?

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

Поэтому вот хотелось бы понять крутость генерируемых квестов.

А в целом естественно жму руку, очень крутая идея и реализация.


--------------------
Пользователь в офлайнеDelete PostОтправить личное сообщение
Вернуться в начало страницы
+Ответить с цитированием данного сообщения
nullptr
сообщение 2.9.2026, 19:02
Сообщение #3


***

Novice
Сообщений: 68
Регистрация: 13.3.2021
Группа: Пользователи
Наличность: 68
Пользователь №: 19.916



Цитата(olduo.com @ 2.9.2026, 20:54) *

Хаха прикольно ))

С турнирами понятно и в целом видиться не очень сложным.
А на сколько сложные квесты, наверняка это принеси подай?

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

Поэтому вот хотелось бы понять крутость генерируемых квестов.

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

Для примера могу скинуть куски детективной цепочки.

Код
Handlers:
  OnTalk: |
    function(player, npcName)
      local questName = "TavernMurder"

      _G.TavernMurder = _G.TavernMurder or {}
      _G.TavernMurderCompleted = _G.TavernMurderCompleted or {}

      if _G.TavernMurderCompleted[player] then
        RunDialog(player, "TavernMurder_Main", "captain_already_completed")
        return
      end

      if not Utility.QHave(player, questName) then
        if _G.TavernMurder.Active and TavernMurder_Main.CheckCluesExist() then
          RunDialog(player, "TavernMurder_Main", "captain_quest_locked")
          return
        end
      end

      if not Utility.QHave(player, questName) then
        RunDialog(player, "TavernMurder_Main", "captain_intro")
        return
      end

      if Utility.QComplete(player, questName, 1) then
        RunDialog(player, "TavernMurder_Main", "captain_accusation")
        return
      end

      RunDialog(player, "TavernMurder_Main", "captain_investigation")
    end

Methods:
  CheckCluesExist: |
    function()
      local objects = GetObjectsInRange("Felucca", 1494, 1681, 20, 10, 0)

      if not objects then
        Say(1, "CheckClues: объектов не найдено")
        return false
      end

      Say(1, string.format("CheckClues: найдено объектов: %d", #objects))

      for _, obj in ipairs(objects) do
        if obj and obj.Name then
          local name = tostring(obj.Name)

          if name == "BloodStain" or name == "BloodyKnife" or
             name == "BrokenChair" or name == "TornLetter" then
            Say(1, "НАЙДЕНА УЛИКА: " .. name)
            return true
          end
        end
      end

      Say(1, "CheckClues: улик среди объектов нет")
      return false
    end

  CheckAndResetIfExpired: |
    function()
      _G.TavernMurder = _G.TavernMurder or {}

      if not _G.TavernMurder.Active then
        Say(1, "Задание не активно - проверять нечего")
        return
      end

      Say(1, "Проверяю, не исчезли ли улики...")

      if not TavernMurder_Main.CheckCluesExist() then
        _G.TavernMurder.Active = false
        Say(1, "Улики исчезли - флаг сброшен - задание доступно")
      else
        Say(1, "Улики на месте - задание пока закрыто")
      end
    end

  AddQuest: |
    function(player)
      local questName = "TavernMurder"

      _G.TavernMurder = _G.TavernMurder or {}
      _G.TavernMurderCompleted = _G.TavernMurderCompleted or {}

      if _G.TavernMurder.Active then
        if TavernMurder_Main.CheckCluesExist() then
          Say(player, "Расследование уже ведёт другой.")
          return
        else
          _G.TavernMurder.Active = false
          Say(player, "Прошлое расследование закрыто. Начинаем новое.")
        end
      end

      AddQuest(player, questName)

      _G.TavernMurder.Active = true

      Utility.SetQuestVar(player, questName, "Clue_BloodyKnife", "0")
      Utility.SetQuestVar(player, questName, "Clue_TornLetter", "0")
      Utility.SetQuestVar(player, questName, "Clue_BloodStain", "0")
      Utility.SetQuestVar(player, questName, "Clue_BrokenMug", "0")
      Utility.SetQuestVar(player, questName, "Witness_Innkeeper", "0")
      Utility.SetQuestVar(player, questName, "Witness_Maid", "0")
      Utility.SetQuestVar(player, questName, "Witness_Drunk", "0")

      Say(player, "Задание принято.")

      local deleteTime = CreateDT(0, 30)

      CreateNPCAtLocation("WallBlood", 1494, 1680, 20,
        "Name BloodStain Movable False OnUseScript Clue_BloodStain.OnUse Expired " .. deleteTime)

      CreateNPCAtLocation("BloodBlade", 1495, 1681, 20,
        "Name BloodyKnife Movable False OnUseScript Clue_BloodyKnife.OnUse Expired " .. deleteTime)

      CreateNPCAtLocation("BrokenChair", 1494, 1681, 20,
        "Name BrokenChair Movable False OnUseScript Clue_BrokenMug.OnUse Expired " .. deleteTime)

      CreateNPCAtLocation("BlankMap", 1494, 1682, 20,
        "Name TornLetter Movable False OnUseScript Clue_TornLetter.OnUse Expired " .. deleteTime)

      Say(player, "Место преступления готово.")
      Say(player, "Улики исчезнут через 30 минут.")
    end

  TryAdvanceStage: |
    function(player)
      local questName = "TavernMurder"

      if not Utility.QHave(player, questName) then
        return
      end

      if Utility.QComplete(player, questName, 1) then
        return
      end

      local clues = 0
      if Utility.GetQuestVar(player, questName, "Clue_BloodyKnife") == "1" then clues = clues + 1 end
      if Utility.GetQuestVar(player, questName, "Clue_TornLetter") == "1" then clues = clues + 1 end
      if Utility.GetQuestVar(player, questName, "Clue_BloodStain") == "1" then clues = clues + 1 end
      if Utility.GetQuestVar(player, questName, "Clue_BrokenMug") == "1" then clues = clues + 1 end

      local witnesses = 0
      if Utility.GetQuestVar(player, questName, "Witness_Innkeeper") == "1" then witnesses = witnesses + 1 end
      if Utility.GetQuestVar(player, questName, "Witness_Maid") == "1" then witnesses = witnesses + 1 end
      if Utility.GetQuestVar(player, questName, "Witness_Drunk") == "1" then witnesses = witnesses + 1 end

      Say(player, string.format("Собрано: улик %d/4, свидетелей %d/3", clues, witnesses))

      if clues >= 4 and witnesses >= 3 then
        CompleteObjective(player, questName)
        Say(player, "Все доказательства собраны.")
      end
    end

  UnlockQuest: |
    function()
      _G.TavernMurder = _G.TavernMurder or {}
      _G.TavernMurder.Active = false
      Say(1, "Задание снова доступно.")
    end

  ResetCompletedFlag: |
    function(player)
      _G.TavernMurderCompleted = _G.TavernMurderCompleted or {}
      _G.TavernMurderCompleted[player] = nil
      Say(player, "Задание можно взять снова.")
    end

  DoAccuse: |
    function(player, accusedKey)
      local questName = "TavernMurder"

      if not Utility.QHave(player, questName) then
        Say(player, "У вас нет этого задания.")
        return
      end

      if not Utility.QComplete(player, questName, 1) then
        Say(player, "Сначала соберите все доказательства.")
        return
      end

      if accusedKey == "JealousHusband" then
        Say(player, "В точку! Маркус виновен.")
        CreateItemInBackpack(player, "Gold", "Amount 3000")

        _G.TavernMurderCompleted = _G.TavernMurderCompleted or {}
        _G.TavernMurderCompleted[player] = true

        RunDialog(player, "TavernMurder_Main", "captain_accusation_correct")

      else
        Say(player, "Мимо. Настоящий убийца ушёл.")
        CreateItemInBackpack(player, "Gold", "Amount 300")

        RunDialog(player, "TavernMurder_Main", "captain_accusation_wrong")
      end

      CompleteQuest(player, questName)
      DeleteCompleteQuest(player, questName)

      Utility.ClearQuestVar(player, questName, "Clue_BloodyKnife")
      Utility.ClearQuestVar(player, questName, "Clue_TornLetter")
      Utility.ClearQuestVar(player, questName, "Clue_BloodStain")
      Utility.ClearQuestVar(player, questName, "Clue_BrokenMug")
      Utility.ClearQuestVar(player, questName, "Witness_Innkeeper")
      Utility.ClearQuestVar(player, questName, "Witness_Maid")
      Utility.ClearQuestVar(player, questName, "Witness_Drunk")
    end

  AccuseJealousHusband: |
    function(player)
      TavernMurder_Main.DoAccuse(player, "JealousHusband")
    end

  AccuseBusinessRival: |
    function(player)
      TavernMurder_Main.DoAccuse(player, "BusinessRival")
    end

  AccuseDebtor: |
    function(player)
      TavernMurder_Main.DoAccuse(player, "Debtor")
    end

Quests:
  TavernMurder:
    Name: "Убийство в таверне"
    Reject: true
    Reward:
      - {Type: "Item", ItemId: 0x0EED, Hue: 0x0, ToolTip: "Gold"}
    Objectives:
      - Description: |
          В таверне «Пьяный гном» убит богатый торговец. Капитан стражи поручает вам
          расследование. Осмотрите место преступления, соберите улики и опросите свидетелей.
          ЗАДАЧИ:
          1. Собрать 4 улики на месте преступления
          2. Опросить 3 свидетелей
          ВНИМАНИЕ: Улики исчезнут через 30 минут!
        Actions:
          - Progress: "Собрать улики"
          - Progress: "Опросить свидетелей"

      - Description: |
          Расследование завершено! У вас есть все улики и показания свидетелей.
          Вернитесь к капитану стражи и назовите виновного.

          ПОДОЗРЕВАЕМЫЕ:
          1. Ревнивый муж жертвы (Marcus)
          2. Конкурент по бизнесу (Victor)
          3. Должник (Thomas)
        Actions:
          - Progress: "Назвать убийцу капитану"

nodes:
  - Id: captain_intro
    Text: |
      Капитан стражи хмуро смотрит на вас.
      «Торговца нашли мёртвым в таверне "Пьяный гном". Ножевое ранение.
      Нужен посторонний следователь. Возьмёте дело?»
    Options:
      - Text: "Я займусь расследованием."
        To: captain_accept
      - Text: "Не сейчас."
        To: captain_refuse

  - Id: captain_accept
    Text: |
      *кивает*
      «Хорошо. Я отправлю людей подготовить место преступления.
      Поговорите с теми, кто был в таверне той ночью. Ищите улики.
      Когда соберёте доказательства — возвращайтесь.»
    Options:
      - Text: "[Принять задание]"
        To: captain_after_accept
        Action: 'AddQuest'

  - Id: captain_after_accept
    Text: "Жду вашего доклада. Удачи, следователь."
    Options: []

  - Id: captain_refuse
    Text: "Жаль. Придётся обойтись своими силами."
    Options: []

  - Id: captain_investigation
    Text: |
      «Как продвигается расследование? Чем больше улик — тем крепче дело.»
    Options:
      - Text: "Я продолжаю собирать улики."
        To: captain_investigation_continue

  - Id: captain_investigation_continue
    Text: "Место преступления оцеплено. Можете продолжать осмотр."
    Options: []

  - Id: captain_accusation
    Text: |
      «Итак, вы собрали все улики и опросили свидетелей.
      Кого обвиняете в убийстве?
      ПОДОЗРЕВАЕМЫЕ:
      1. MARCUS — ревнивый муж жертвы
      2. VICTOR — конкурент по бизнесу
      3. THOMAS — должник
      Ваше решение?»
    Options:
      - Text: "Обвиняю Marcus — ревнивого мужа."
        To: captain_accusation_confirm_marcus
      - Text: "Обвиняю Victor — конкурента."
        To: captain_accusation_confirm_victor
      - Text: "Обвиняю Thomas — должника."
        To: captain_accusation_confirm_thomas

  - Id: captain_accusation_confirm_marcus
    Text: |
      «Marcus, ревнивый муж? Вы уверены?
      После обвинения дело будет закрыто.»
    Options:
      - Text: "Да, уверен. Marcus виновен!"
        Action: 'AccuseJealousHusband'
      - Text: "Нет, позвольте подумать ещё."
        To: captain_accusation

  - Id: captain_accusation_confirm_victor
    Text: |
      «Victor, конкурент? Вы уверены?
      После обвинения дело будет закрыто.»
    Options:
      - Text: "Да, уверен. Victor виновен!"
        Action: 'AccuseBusinessRival'
      - Text: "Нет, позвольте подумать ещё."
        To: captain_accusation

  - Id: captain_accusation_confirm_thomas
    Text: |
      «Thomas, должник? Вы уверены?
      После обвинения дело будет закрыто.»
    Options:
      - Text: "Да, уверен. Thomas виновен!"
        Action: 'AccuseDebtor'
      - Text: "Нет, позвольте подумать ещё."
        To: captain_accusation

  - Id: captain_accusation_correct
    Text: |
      *капитан зовёт стражников*
      «ОТЛИЧНАЯ РАБОТА! Вы правы — это Marcus!
      Улики указывают на ревнивого мужа:
      - Письмо с угрозами подписано его именем
      - Нож от кузнеца — Marcus работает кузнецом
      - Свидетели видели его ссору с жертвой
      Стражники арестовали Marcus. Правосудие свершилось!
      Вот ваша награда — 3000 золотых монет.
      Вы доказали себя как талантливый следователь!»
    Options: []

  - Id: captain_accusation_wrong
    Text: |
      *капитан качает головой*
      «Хм... Я проверил ваше обвинение.
      Улики не подтверждают вашу версию.
      Ваши доказательства слишком слабы.
      Пока мы проверяли этого подозреваемого,
      настоящий убийца успел скрыться.
      Правосудие не свершилось.
      Вот ваша оплата за работу — 300 золотых.
      Вы можете попробовать снова, если найдёте новые улики.»
    Options: []

  - Id: captain_quest_locked
    Text: |
      *качает головой*
      «Другой следователь уже работает над этим делом.
      Место преступления оцеплено, улики под охраной.
      Приходите позже — через 30 минут место освободится.»
    Options: []

  - Id: captain_already_completed
    Text: |
      «Вы уже расследовали это убийство и назвали виновного.
      Дело закрыто, повторное расследование не требуется.
      Спасибо за вашу работу.»
    Options: []
Пользователь в офлайнеDelete PostОтправить личное сообщение
Вернуться в начало страницы
+Ответить с цитированием данного сообщения

Сообщений в этой теме
nullptr   UO Inferno   24.3.2025, 19:30
nullptr   Видео замесов. https://rutube.ru/video/9b17c729c93...   23.11.2025, 19:33
olduo.com   - есть какие мероприятия в мире для масс пвп? - ка...   24.11.2025, 8:32
nullptr   - есть какие мероприятия в мире для масс пвп? - к...   24.11.2025, 11:55
olduo.com   в целом мне на всё ответили, в гильду вроде взяли....   24.11.2025, 14:19
nullptr   в целом мне на всё ответили, в гильду вроде взяли...   25.11.2025, 22:55
nullptr   https://i127.fastpic.org/big/2026/0305/07/2a97f848...   5.3.2026, 21:58
nullptr   Очень сильно надоело пилить контент на стороне сер...   2.9.2026, 15:28
olduo.com   Очень сильно надоело пилить контент на стороне се...   2.9.2026, 17:27
nullptr   А можно попадробнее? Рисуется какаято фантасмагор...   2.9.2026, 18:39
Legnes   Маркетплейс удобно сделан. А Time в маркетплейсе п...   25.11.2025, 17:05
olduo.com   Обалдеть, я редко встречаю в любительской среде ра...   2.9.2026, 19:21
nullptr   Обалдеть, я редко встречаю в любительской среде р...   4.9.2026, 20:08
RL_ka   В плюсах без шансов, считай надо точно знать кто ...   5.9.2026, 14:00
olduo.com   не совсем понимаю с чем из этого списка не справи...   5.9.2026, 14:03
RL_ka   Я вообще не большой умелец использовать ИИ для пр...   5.9.2026, 14:10
nullptr   я думаю claude способен написать эмулятор сервера...   5.9.2026, 14:41
olduo.com   Я вообще не большой умелец использовать ИИ для пр...   5.9.2026, 17:37
nullptr   Нулевой поинтер, а ответб плз на это. Ато там стр...   5.9.2026, 18:15
TeS   Я вообще не большой умелец использовать ИИ для пр...   7.9.2026, 18:13
olduo.com   посмотри ютуб там люди только одними запросами де...   7.9.2026, 18:25
TeS   Я кодил года с 93го прошлого века и лет 5 назад м...   7.9.2026, 18:39
nullptr   сначала заполонили книжками от ии, сейчас заполон...   8.9.2026, 11:30
TeS   Это вопрос восприятия, ии в моем понимании это ст...   8.9.2026, 13:37
olduo.com   для чпу надо снять гараж купить это устройство, з...   8.9.2026, 13:54
TeS   А мне понравилась Suno. Вот мои шедевры [url=htt...   8.9.2026, 15:12
olduo.com   Странно что у людей такая вялая реакция на этот ше...   4.9.2026, 12:59
RL_ka   Когда состоится релиз на гитхабе? Я бы портировал...   4.9.2026, 14:27
olduo.com   Правильно ли я понял, что ты написал API с которым...   5.9.2026, 13:51
olduo.com   Правильно ли я понял, что ты написал API с которы...   5.9.2026, 18:24
nullptr   Сорян, не то процитировал. Мне ии для програмиров...   5.9.2026, 22:01


Ответить в эту темуОткрыть новую тему
14 чел. читают эту тему (гостей: 14, скрытых пользователей: 0)
Пользователей: 0

 

- Текстовая версия | Версия для КПК Сейчас: 21.9.2026, 1:36
Designed by Nickostyle