|
UO Inferno |
|
|
| nullptr |
24.3.2025, 19:30
|

  
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 для выполнения более сложных выборок данных. Поскольку сервер работает в однопоточном режиме, у меня не возникло желания писать дополнительную синхронизацию для квестового потока. Как только появится немного свободного времени, я планирую написать небольшое обучающее задание, которое познакомит новых игроков с особенностями игры на сервере
|
|
|
|
|
|
|
  |
Ответов
| nullptr |
2.9.2026, 19:02
|

  
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: []
|
|
|
|
|
|
Сообщений в этой теме
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
|
|