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

  
Novice
Сообщений: 65
Регистрация: 13.3.2021 Группа: Пользователи Наличность: 25
Пользователь №: 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 |
23.11.2025, 19:33
|

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

|
|
|
|
|
|
|
| nullptr |
24.11.2025, 11:55
|

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

|
Цитата(olduo.com @ 24.11.2025, 10:32)  - есть какие мероприятия в мире для масс пвп? - какой средний онлайн? - ненахожу дискорда ток телеграм. это так задуманно или ошибка какая?
сайт прям оч нравица, ничего лишнего, тёмный всё читается хорошо, шрифт JetBrains, чусвуеца рука программиста, а не дизайнера =)
Discord в рф заблокирован, многие попросту не смогут поставить обход. В выходные ~40, в будни бывает и 10. О массовом PvP пришлось задуматься только сейчас, потому что онлайн больше 20 человек появился лишь этой зимой.
|
|
|
|
|
|
| nullptr |
25.11.2025, 22:55
|

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

|
Цитата(olduo.com @ 24.11.2025, 16:19)  в целом мне на всё ответили, в гильду вроде взяли. между гильдиями какаято движуха говорят часто есть. про ДС впервые слышу чтобы людей было больше в телеге так как везде где бываю большинство используют zapret и продолжают пользоватся ДС
если ИП меняется, тыб DNS запись типа А сделал бы и всё у людей типа uo.playuo.ru а ИП если что там меняеш в DNS записи
Спасибо, полезу сделаю. Цитата(Legnes @ 25.11.2025, 19:05)  Маркетплейс удобно сделан. А Time в маркетплейсе показывает сколько лежит предмет? Или это время сколько он будет продаваться?
Сколько времени осталось продаваться.
|
|
|
|
|
|
| nullptr |
5.3.2026, 21:58
|

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

|
|
|
|
|
|
|
| olduo.com |
2.9.2026, 17:27
|

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

|
Цитата(nullptr @ 2.9.2026, 15:28)  Очень сильно надоело пилить контент на стороне сервере. Встроил в ядро Lua с горячей перезагрузкой. Теперь можно буквально генерировать через GPT практически любые активности и квесты. (IMG: https://i128.fastpic.org/big/2026/0902/6b/d4a62619c9ff373dc3827a5093ac336b.png) А можно попадробнее? Рисуется какаято фантасмагория. Чатжпт сам кодит на луа чтоле и вываливает баги в прадакшен прямо на лайф? Картинка какогото рейтинга турнира только увеличивает вопросы. И что за движок такой без горячей перезагрузки скрпатов?
--------------------
|
|
|
|
|
|
| nullptr |
2.9.2026, 18:39
|

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

|
Цитата(olduo.com @ 2.9.2026, 19:27)  А можно попадробнее? Рисуется какаято фантасмагория. Чатжпт сам кодит на луа чтоле и вываливает баги в прадакшен прямо на лайф? Картинка какогото рейтинга турнира только увеличивает вопросы.
И что за движок такой без горячей перезагрузки скрпатов?
Человек через GPT буквально пишет квесты, турниры и другие активности. Из последнего. Систама PvP-рейтинга, голосование за турниры, около восьми массовых турниров и какой-то огромный детективный квест, где нужно искать преступников. ИИ начинает лажать, когда ему дают слишком большой контекст. Поэтому решил ограничить его YAML-описанием с набором событий и Lua-вызовов — по сути, YAML задаёт рамки и не даёт GPT слишком сильно косячить. Внутри YAML можно делать вставки Lua-кода, которые затем проверяются через серверный gump. Оттуда же можно отдельно вызвать нужный метод или событие и посмотреть результат. Алгоритм простой: человек кидает в GPT пример квеста и описание API, получает готовый код, загружает его через FTP, проверяет через серверный gump скриптов — и так поэтапно собирает всю логику. И как минимум скриптер, не имеет доступа к исходникам сервереа. Вот пример как это выглядит. Код Handlers: OnStart: | function() BR_Serial = 0x400AF1ED
BR_TZOffsetMin = 300 BR_Weekday = 7 BR_Hour = 22 BR_Minute = 45 BR_GatherSeconds = 1200 BR_CatchUpMinutes = 5 BR_MinPlayers = 6
BR_Map = "Felucca"
BR_EntryTeleporter = { 0x4007A755, 0x40072289 } BR_ExitTeleporter = { 0x4007FDEC } BR_ExitGate = { 0x4007FDEE } BR_ExitHue = 1150 BR_ExitName = "Выход" BR_ExitGhost = false BR_GateItems = { 0x4007A3B3, 0x400AE045 } BR_GateHue = 1300 BR_GateName = "Королевская битва" BR_Doors = {}
BR_LobbyRect = { x1 = 6086, y1 = 1131, x2 = 6103, y2 = 1137, z = 0 } BR_ArenaRect = { x1 = 6085, y1 = 1107, x2 = 6103, y2 = 1125, z = 0 }
BR_OutsideX = 6083 BR_OutsideY = 1129 BR_OutsideZ = 0
BR_HeraldX = 6094; BR_HeraldY = 1130; BR_HeraldZ = 10 BR_RefX = 6094; BR_RefY = 1116; BR_RefZ = 0
BR_BriefingSeconds = 25 BR_CountdownSeconds = 60 BR_CountdownTail = 10 BR_ArmDelaySeconds = 5 BR_LootSeconds = 300 BR_AwardSeconds = 15 BR_CloseSeconds = 60
BR_AwolGraceSeconds = 45
BR_MonsterSwapSeconds = 20 BR_MonsterAnnounce = false BR_MonsterSpawnGap = 2
BR_SpawnerSerial = 0x400CA182
BR_Monsters = { { Type = "OrcishLord", Props = "Pack CrypthonOrc AI AI_HighPredator Name \"a Crypthon Orc\" Hue 1184" }, { Type = "BloodElemental", Props = "Pack CrypthonElemental AI AI_HighPredator Name \"a Crypthon Elemental\" Hue 1184" }, { Type = "Daemon", Props = "Pack CrypthonDaemon AI AI_HighPredator Hue 1184 Name \"a Crypthon Daemon\"" }, { Type = "LichLord", Props = "Pack CrypthonLich AI AI_HighPredator Name \"a Crypthon Lich\" Hue 1184" }, { Type = "DreadSpider", Props = "Pack CrypthonBeetle Body 0xF4 BaseSoundID 1255 AI AI_HighPredator Hue 1184 Name \"a Crypthon Beetle\"" }, { Type = "Dragon", Props = "Pack CrypthonDragon AI AI_HighPredator Name \"a Crypthon Dragon\" Hue 1184" }, { Type = "Ogre", Props = "Pack CrypthonOgre AI AI_HighPredator Name \"a Crypthon Ogre\" Hue 1184" }, { Type = "Balron", Props = "Pack Balron AI AI_HighPredator" }, { Type = "AncientWyrm", Props = "Pack InfernoWyrm AI AI_LowBoss Name \"a Inferno Wyrm\" Hue 2180" }, { Type = "Moloch", Props = "Pack InfernoMoloch Hue 2180 Name \"a Inferno Moloch\" AI AI_LowBoss" }, }
BR_MonsterProps = "FightMode Closest RangePerception 24 SeeksHome True Tamable False"
BR_CombatantProp = "Combatant" BR_CombatantHex = true
BR_MonsterAggroProps = "ReacquireOnMovement True"
BR_ScatterAttempts = 40 BR_ScatterZSearch = 0 BR_MinSpawnGap = 6
BR_FreezeOn = "Frozen True" BR_FreezeOff = "Frozen False" BR_HideOn = "Hidden True" BR_HideOff = "Hidden False" BR_SquelchOn = "Squelched True" BR_SquelchOff = "Squelched False"
BR_BcHue = 0x0035 BR_PreAnnounceMin = 30 BR_GatherAnnounceMin = 5
BR_RegionSerial = 0x4003BC07 BR_RegionOn = "Active True" BR_RegionOff = "Active False" BR_RegionRules = { "AttackAllowed True", "MurdersAllowed False", "Guarded False", "DisableLoot False", "DisableDrop False", "DisableTrade True", "MountsAllowed False", "TravelFrom False", "TravelTo False", "CanMark False", "CastAllowed True", "CorpseDecay False", "ResurrectionAllowed False", "LogoutAllowed False", "HouseAllowed False", "ItemsBreak False", "PoisonLevelAllowed 4", "Priority 75", "ShowRegion False", "Name \"Battle Royale\"", "ExcludeSpell \"Mark, Recall, Gate Travel, Sacred Journey\"", "EnterMessage \"Арена не выпускает живых. Приготовьтесь к сражению!\"", }
BR_RulesLines = { { Text = "Приветствую на Королевской битве!", Pause = 2500 }, { Text = "Сейчас всех вас разбросает по арене - врозь, скрытыми и недвижимыми.", Pause = 4000 }, { Text = "Как только отсчёт кончится, чары спадут и на арену выйдет зверь.", Pause = 4000 }, { Text = "Зверь меняется каждые двадцать секунд и не щадит никого.", Pause = 3500 }, { Text = "Бьёте друг друга, уворачиваетесь от зверя - выжить должен один.", Pause = 3500 }, { Text = "Последний выживший - чемпион, ему пять минут на трупы и редкая роба.", Pause = 4000 }, { Text = "Роба привязана к нему и служит неделю. Удачи, бойцы!", Pause = 3000 }, }
BR_Robe = { Class = "Robe", ItemId = 0, Hue = 1300 } BR_RobeDays = 7 BR_RobeTitle = "Королевская битва" BR_RobeTemplate = "{Winner} - {Title} {Date}" BR_ChampReturn = { x = 0, y = 0, z = 0 } BR_RobeDropDX = 0 BR_RobeDropDY = 1
BR_ConsolationItems = { { Class = "SilverCrystal", Props = "" }, { Class = "BloodCrystal", Props = "" }, { Class = "ArmorCrystal", Props = "" }, }
BR_IgnoredSerials = { 0x00002298, 0x00001899, 0x000067BD, 0x00002D7F, 0x000002C7, 0x00000001, }
BR_DefGatherSeconds = BR_GatherSeconds BR_DefCountdownSeconds = BR_CountdownSeconds BR_DefLootSeconds = BR_LootSeconds BR_DefMinPlayers = BR_MinPlayers BR_DefMonsterSwapSeconds = BR_MonsterSwapSeconds
BR_DescEn = "" BR_Phase = "IDLE" BR_Deadline = "" BR_ArmAt = "" BR_LastRunDate = "" BR_Roster = {} BR_Elim = {} BR_Champion = 0 BR_Monster = 0 BR_MonsterIdx = 0 BR_MonsterSwapAt = "" BR_MonsterTarget = 0 BR_CountdownSpoken = false BR_AwolSince = {} BR_PreAnnDate = "" BR_GatherAnnLast = -1 BR_TickCount = 0 BR_LastError = ""
BattleRoyale_Main.ComputeZones() BattleRoyale_Main.LoadState() end
OnTimer: | function() BR_TickCount = (BR_TickCount or 0) + 1 local ok, err = pcall(BattleRoyale_Main.Tick) if not ok then BR_LastError = tostring(err) end end
Methods:
RulesTail: | function() if not BR_DescEn or BR_DescEn == "" then return "" end return " Rules: " .. BR_DescEn .. "." end
Go: | function(player) BattleRoyale_Main.ResetState() BR_GatherSeconds = 60 BR_BriefingSeconds = 10 BR_CountdownSeconds = 20 BR_LootSeconds = 60 BR_MinPlayers = 2 BR_MonsterSwapSeconds = 20 local now = Tournament_Utils.ParseDT(CreateDT(0, BR_TZOffsetMin)) local today = string.format("%02d.%02d.%04d", now.day, now.month, now.year) BattleRoyale_Main.OpenGather(today) Say(player, "BattleRoyale: fast mode, gather open 60s. Stand in the LOBBY.") end
Stat: | function(player) Say(player, "BattleRoyale: phase=" .. BR_Phase .. " roster=" .. #BR_Roster .. " elim=" .. #BR_Elim .. " ticks=" .. tostring(BR_TickCount)) Say(player, " champion=" .. tostring(BR_Champion) .. " monster=" .. tostring(BR_Monster) .. " (" .. tostring(BR_MonsterIdx > 0 and BR_Monsters[BR_MonsterIdx].Type or "-") .. ")" .. " swapIn=" .. tostring(BattleRoyale_Main.LeftSec(BR_MonsterSwapAt)) .. " target=" .. tostring(BR_MonsterTarget)) Say(player, " " .. BattleRoyale_Main.SchedText()) if BR_LastError ~= "" then Say(player, "BattleRoyale: LAST TICK ERROR: " .. BR_LastError) end end
SchedText: | function() local raw = CreateDT(0, BR_TZOffsetMin) local now = Tournament_Utils.ParseDT(raw) if not now then return "now=" .. tostring(raw) .. " PARSE FAILED (check CreateDT/date format)" end local last = tostring(BR_LastRunDate) if last == "" then last = "(none)" end return "now=" .. tostring(raw) .. " weekday=" .. Tournament_Utils.Weekday(now.year, now.month, now.day) .. " | need weekday=" .. BR_Weekday .. " at " .. string.format("%02d:%02d", BR_Hour, BR_Minute) .. " | today=" .. string.format("%02d.%02d.%04d", now.day, now.month, now.year) .. " lastRun=" .. last end
Arm: | function(player) Say(player, "BattleRoyale: was phase=" .. BR_Phase .. " | " .. BattleRoyale_Main.SchedText()) BattleRoyale_Main.ResetState() Say(player, "BattleRoyale: ARMED - phase IDLE, lastRun cleared, waiting for schedule.") end
Skip: | function(player) BR_Deadline = 1 BR_ArmAt = 1 Say(player, "BattleRoyale: phase " .. BR_Phase .. " forced to end.") end
Beast: | function(player) BattleRoyale_Main.SwapMonster() Say(player, "BattleRoyale: monster=" .. tostring(BR_Monster) .. " type=" .. tostring(BR_MonsterIdx > 0 and BR_Monsters[BR_MonsterIdx].Type or "-")) end
ComputeZones: | function() BR_Lobby = Tournament_Utils.ZoneFromRect(BR_LobbyRect) BR_Arena = Tournament_Utils.ZoneFromRect(BR_ArenaRect) if BR_RefX == 0 and BR_RefY == 0 then BR_RefX = BR_Arena.cx; BR_RefY = BR_Arena.cy; BR_RefZ = BR_Arena.z end end
SpeechSeconds: | function(lines) local ms = 0 for i, l in ipairs(lines) do ms = ms + (l.Pause or 0) end return math.ceil(ms / 1000) end
LeftSec: | function(deadline) local d = tonumber(deadline) if not d then return "-" end local left = d - Tournament_Utils.NowSec(BR_TZOffsetMin) if left < 0 then left = 0 end return left end
SerialOf: | function(x) if type(x) == "table" then return tonumber(x.Serial) end return tonumber(x) end
IsIgnored: | function(serial) for i, s in ipairs(BR_IgnoredSerials) do if s == serial then return true end end return false end
InRoster: | function(serial) for i, s in ipairs(BR_Roster) do if s == serial then return true end end return false end
RemoveFromRoster: | function(serial) for i = #BR_Roster, 1, -1 do if BR_Roster[i] == serial then table.remove(BR_Roster, i) end end end
PlayerName: | function(serial) if not serial or serial == 0 then return "Unknown" end local res, val = GetProps(serial, "Name") local n = tostring(val or "") n = n:gsub('^"', ''):gsub('"$', '') if n == "" then n = "Unknown" end return n end
TodayStr: | function() local now = Tournament_Utils.ParseDT(CreateDT(0, BR_TZOffsetMin)) if not now then return "??.??.????" end return string.format("%02d.%02d.%04d", now.day, now.month, now.year) end
Freeze: | function(serial) if serial and serial ~= 0 and BR_FreezeOn ~= "" then SetProps(serial, BR_FreezeOn) end end
Unfreeze: | function(serial) if serial and serial ~= 0 and BR_FreezeOff ~= "" then SetProps(serial, BR_FreezeOff) end end
Hide: | function(serial) if serial and serial ~= 0 and BR_HideOn ~= "" then SetProps(serial, BR_HideOn) end end
Unhide: | function(serial) if serial and serial ~= 0 and BR_HideOff ~= "" then SetProps(serial, BR_HideOff) end end
Squelch: | function(serial) if serial and serial ~= 0 and BR_SquelchOn ~= "" then SetProps(serial, BR_SquelchOn) end end
Unsquelch: | function(serial) if serial and serial ~= 0 and BR_SquelchOff ~= "" then SetProps(serial, BR_SquelchOff) end end
Spellbind: | function(serial) BattleRoyale_Main.Hide(serial) BattleRoyale_Main.Squelch(serial) BattleRoyale_Main.Freeze(serial) end
Release: | function(serial) BattleRoyale_Main.Unfreeze(serial) BattleRoyale_Main.Unsquelch(serial) BattleRoyale_Main.Unhide(serial) end
SaveState: | function() local head = table.concat({ BR_Phase, Tournament_Utils.Pack(BR_Deadline), Tournament_Utils.Pack(BR_ArmAt), Tournament_Utils.Pack(BR_LastRunDate), tostring(BR_Champion), tostring(BR_Monster), tostring(BR_MonsterIdx), Tournament_Utils.Pack(BR_MonsterSwapAt), }, "^") local body = table.concat({ Tournament_Utils.JoinSerials(BR_Roster), Tournament_Utils.JoinSerials(BR_Elim), }, "~~~") if BR_Serial and BR_Serial ~= 0 then local data = "|" .. head .. "~~~" .. body .. "~~~B=" .. tostring(BR_TickCount or 0) .. "~~~T=" .. tostring(BR_MinPlayers or 0) .. "|" SetProps(BR_Serial, "Data " .. data) BR_DataSeen = data end end
LoadState: | function() if not BR_Serial or BR_Serial == 0 then return end local res, val = GetProps(BR_Serial, "Data") if not val or tostring(val) == "" then return end local raw = tostring(val):match("^|(.*)|$") or tostring(val) local parts = {} for part in (raw .. "~~~"):gmatch("(.-)~~~") do table.insert(parts, part) end BR_DataSeen = tostring(val) for i, pt in ipairs(parts) do if string.sub(pt, 1, 2) == "T=" then local mn = tonumber(string.match(pt, "T=(%d+)")) if mn and mn > 0 then BR_MinPlayers = mn end end end if #parts < 3 then return end local h = {} for f in (parts[1] .. "^"):gmatch("(.-)%^") do table.insert(h, f) end if #h < 8 then return end BR_Phase = h[1] BR_Deadline = h[2] BR_ArmAt = h[3] BR_LastRunDate = h[4] BR_Champion = tonumber(h[5]) or 0 BR_Monster = tonumber(h[6]) or 0 BR_MonsterIdx = tonumber(h[7]) or 0 BR_MonsterSwapAt = h[8] BR_Roster = Tournament_Utils.SplitSerials(parts[2]) BR_Elim = Tournament_Utils.SplitSerials(parts[3]) end
ExitOpen: | function(on) if on then pcall(function() Tournament_Utils.GateSet(BR_ExitTeleporter, BR_OutsideX, BR_OutsideY, BR_OutsideZ, BR_ExitHue, BR_ExitName, BR_ExitGate) end) end pcall(function() Tournament_Utils.GhostMode(BR_ExitTeleporter, on and BR_ExitGhost) end) pcall(function() Tournament_Utils.SetTeleporter(BR_ExitTeleporter, on and true or false) end) end
ResetState: | function() BattleRoyale_Main.ExitOpen(false) pcall(function() Tournament_Utils.LadderMode(BR_EntryTeleporter, false) end) Tournament_Utils.SetTeleporter(BR_EntryTeleporter, false) Tournament_Utils.SetDoors(BR_Doors, false) BattleRoyale_Main.SetRegion(false) BattleRoyale_Main.RemoveMonster() for i, s in ipairs(BR_Roster) do BattleRoyale_Main.Release(s) end BR_Phase = "IDLE" BR_Deadline = ""; BR_ArmAt = ""; BR_LastRunDate = "" BR_Roster = {}; BR_Elim = {} BR_Champion = 0 BR_MonsterIdx = 0; BR_MonsterSwapAt = "" BR_CountdownSpoken = false BR_AwolSince = {} BR_PreAnnDate = ""; BR_GatherAnnLast = -1 BR_GatherSeconds = BR_DefGatherSeconds BR_CountdownSeconds = BR_DefCountdownSeconds BR_LootSeconds = BR_DefLootSeconds BR_MinPlayers = BR_DefMinPlayers BR_MonsterSwapSeconds = BR_DefMonsterSwapSeconds BattleRoyale_Main.SaveState() end
Tick: | function() if BR_Phase == "IDLE" then BattleRoyale_Main.TickIdle() elseif BR_Phase == "GATHER" then BattleRoyale_Main.TickGather() elseif BR_Phase == "BRIEFING" then BattleRoyale_Main.TickBriefing() elseif BR_Phase == "COUNTDOWN" then BattleRoyale_Main.TickCountdown() elseif BR_Phase == "RUNNING" then BattleRoyale_Main.TickRunning() elseif BR_Phase == "LOOT" then BattleRoyale_Main.TickLoot() elseif BR_Phase == "AWARD" then BattleRoyale_Main.TickAward() elseif BR_Phase == "CLOSING" then BattleRoyale_Main.TickClosing() end end
TickIdle: | function() local now = Tournament_Utils.ParseDT(CreateDT(0, BR_TZOffsetMin)) if not now then return end if Tournament_Utils.Weekday(now.year, now.month, now.day) ~= BR_Weekday then return end local today = string.format("%02d.%02d.%04d", now.day, now.month, now.year) local mod = now.hour * 60 + now.min local target = BR_Hour * 60 + (BR_Minute or 0)
if mod >= target - BR_PreAnnounceMin and mod < target then if BR_PreAnnDate ~= today and BR_LastRunDate ~= today then BR_PreAnnDate = today BroadcastMessage("Battle Royale starts in " .. (target - mod) .. " minutes!" .. BattleRoyale_Main.RulesTail(), BR_BcHue) end return end
if mod < target or mod >= target + (BR_CatchUpMinutes or 5) then return end if BR_LastRunDate == today then return end BattleRoyale_Main.OpenGather(today) end
OpenGather: | function(today) BattleRoyale_Main.ExitOpen(false) pcall(function() Tournament_Utils.LadderMode(BR_EntryTeleporter, false) end) pcall(function() Tournament_Utils.SetVendors(true, BR_Map) end) pcall(function() Tournament_Utils.ArenaFor("ffa") end) pcall(function() Tournament_Utils.GateSet(BR_EntryTeleporter, 6093, 1133, 0, BR_GateHue, BR_GateName, BR_GateItems) end) BR_LastRunDate = today BR_Phase = "GATHER" BR_Deadline = Tournament_Utils.FutureSec(BR_TZOffsetMin, BR_GatherSeconds) BR_Roster = {}; BR_Elim = {} BR_Champion = 0 BR_ArmAt = "" BR_CountdownSpoken = false BR_AwolSince = {} BR_GatherAnnLast = math.floor(BR_GatherSeconds / (BR_GatherAnnounceMin * 60)) Tournament_Utils.SetDoors(BR_Doors, true) Tournament_Utils.SetTeleporter(BR_EntryTeleporter, true) BroadcastMessage("Battle Royale is OPEN! " .. math.ceil(BR_GatherSeconds / 60) .. " min to join through the gate." .. BattleRoyale_Main.RulesTail(), BR_BcHue) BattleRoyale_Main.SaveState() end
TickGather: | function() BattleRoyale_Main.CollectWaiting()
local left = tonumber(BR_Deadline) - Tournament_Utils.NowSec(BR_TZOffsetMin) if left > 0 then local bucket = math.floor(left / (BR_GatherAnnounceMin * 60)) if bucket < BR_GatherAnnLast then BR_GatherAnnLast = bucket BroadcastMessage("Battle Royale: " .. math.ceil(left / 60) .. " min left to join!", BR_BcHue) end end
if not Tournament_Utils.Expired(BR_TZOffsetMin, BR_Deadline) then return end
Tournament_Utils.SetTeleporter(BR_EntryTeleporter, false) Tournament_Utils.SetDoors(BR_Doors, false)
local present = {} local ppl = Tournament_Utils.PlayersIn(BR_Map, BR_Lobby) for i, p in ipairs(ppl) do if p.Serial and BattleRoyale_Main.InRoster(p.Serial) then table.insert(present, p.Serial) end end BR_Roster = present
if #BR_Roster < BR_MinPlayers then BroadcastMessage("Battle Royale cancelled: only " .. #BR_Roster .. " fighters, need " .. BR_MinPlayers .. ".", BR_BcHue) BattleRoyale_Main.StartClosing() return end
BattleRoyale_Main.GrantVoteRights()
Tournament_Utils.MasterSay(BR_HeraldX, BR_HeraldY, BR_HeraldZ, BR_Map, BR_RulesLines) BR_Phase = "BRIEFING" local briefSec = math.max(BR_BriefingSeconds, BattleRoyale_Main.SpeechSeconds(BR_RulesLines) + 4) BR_Deadline = Tournament_Utils.FutureSec(BR_TZOffsetMin, briefSec) BroadcastMessage("Battle Royale: gate closed, " .. #BR_Roster .. " fighters, rules briefing...", BR_BcHue) BattleRoyale_Main.SaveState() end
CollectWaiting: | function() local ppl = Tournament_Utils.PlayersIn(BR_Map, BR_Lobby) for i, p in ipairs(ppl) do if p.Serial and not BattleRoyale_Main.IsIgnored(p.Serial) then if not Tournament_Utils.IsAlive(p) then Resurrect(p.Serial) end if not BattleRoyale_Main.InRoster(p.Serial) then table.insert(BR_Roster, p.Serial) end end end end
GrantVoteRights: | function() if not TournamentVote_Main then return end local ok, err = pcall(TournamentVote_Main.GrantVoteRights, BR_Roster, true) if not ok then BR_LastError = "GrantVoteRights: " .. tostring(err) end end
TickBriefing: | function() if not Tournament_Utils.Expired(BR_TZOffsetMin, BR_Deadline) then return end
BattleRoyale_Main.SetRegion(true) BattleRoyale_Main.ScatterRoster()
BR_Phase = "COUNTDOWN" BR_Deadline = Tournament_Utils.FutureSec(BR_TZOffsetMin, BR_CountdownSeconds) BR_CountdownSpoken = false
Tournament_Utils.MasterSay(BR_RefX, BR_RefY, BR_RefZ, BR_Map, { { Text = "Все на арене. Чары держат вас до начала боя.", Pause = 3500 }, { Text = "До схватки " .. Tournament_Utils.SpanText(BR_CountdownSeconds) .. " - готовьте макросы и зелья.", Pause = 4000 }, })
BroadcastMessage("Battle Royale: fighters are on the arena, " .. Tournament_Utils.SpanTextEn(BR_CountdownSeconds) .. " to the start!", BR_BcHue) BattleRoyale_Main.SaveState() end
ScatterRoster: | function() local now = Tournament_Utils.ParseDT(CreateDT(0, BR_TZOffsetMin)) local seed = 1 if now then seed = (now.year * 1000000 + now.month * 10000 + now.day * 100 + now.hour) + now.min * 60 + now.sec end math.randomseed(seed % 2147483647) math.random(); math.random(); math.random()
local used = {} for i, s in ipairs(BR_Roster) do local x, y, z = BattleRoyale_Main.ScatterPoint(used) Resurrect(s) MoveToWorld(s, x, y, z, BR_Map) BattleRoyale_Main.Spellbind(s) table.insert(used, { x = x, y = y }) end end
SpellbindAll: | function() for i, s in ipairs(BR_Roster) do BattleRoyale_Main.Spellbind(s) end end
ScatterPoint: | function(used) local half = math.floor(BR_ScatterAttempts / 2) for attempt = 1, BR_ScatterAttempts do local x = math.random(BR_Arena.x1, BR_Arena.x2) local y = math.random(BR_Arena.y1, BR_Arena.y2) local z = BattleRoyale_Main.FindValidZ(x, y, BR_Arena.z) if z then if attempt > half or BattleRoyale_Main.FarEnough(used, x, y) then return x, y, z end end end return BR_Arena.cx, BR_Arena.cy, BR_Arena.z end
FarEnough: | function(used, x, y) for i, p in ipairs(used) do local dx = p.x - x local dy = p.y - y if (dx * dx + dy * dy) < (BR_MinSpawnGap * BR_MinSpawnGap) then return false end end return true end
FindValidZ: | function(x, y, baseZ) if CanSpawnMobile(BR_Map, x, y, baseZ) then return baseZ end for delta = 1, BR_ScatterZSearch do if CanSpawnMobile(BR_Map, x, y, baseZ + delta) then return baseZ + delta end if CanSpawnMobile(BR_Map, x, y, baseZ - delta) then return baseZ - delta end end return nil end
TickCountdown: | function() local left = tonumber(BR_Deadline) - Tournament_Utils.NowSec(BR_TZOffsetMin)
if left <= 0 then BattleRoyale_Main.BeginFight() return end
BattleRoyale_Main.SpellbindAll()
if left <= BR_CountdownTail and not BR_CountdownSpoken then BR_CountdownSpoken = true local lines = {} for nsec = math.min(left, BR_CountdownTail), 1, -1 do table.insert(lines, { Text = "Старт через: " .. nsec, Pause = 1000 }) end table.insert(lines, { Text = "БОЙ! Зверь идёт!", Pause = 1500 }) Tournament_Utils.MasterSay(BR_RefX, BR_RefY, BR_RefZ, BR_Map, lines) end end
BeginFight: | function() for i, s in ipairs(BR_Roster) do BattleRoyale_Main.Release(s) end
BR_Phase = "RUNNING" BR_ArmAt = Tournament_Utils.FutureSec(BR_TZOffsetMin, BR_ArmDelaySeconds) BR_Deadline = "" BR_AwolSince = {}
BattleRoyale_Main.SwapMonster()
BroadcastMessage("Battle Royale has begun! " .. #BR_Roster .. " fighters and the beast are on the arena.", BR_BcHue) BattleRoyale_Main.SaveState() end
RemoveMonster: | function() if BR_Monster and BR_Monster ~= 0 then DeleteTimer(BR_Monster, 0) BR_Monster = 0 end if BR_SpawnerSerial ~= 0 then BattleRoyale_Main.WipeArenaMonsters() end BR_MonsterTarget = 0 end
FindArenaMonsters: | function() local out = {} local objs = GetObjectsInRange(BR_Map, BR_Arena.cx, BR_Arena.cy, BR_Arena.z, BR_Arena.r, 1) if not objs then return out end for i, o in ipairs(objs) do local cls = string.lower(tostring(o.Class or "")) if o.Serial and o.Serial ~= BR_SpawnerSerial and cls ~= "refereenpc" and Tournament_Utils.InRect(BR_Arena, o.x, o.y) then table.insert(out, o.Serial) end end return out end
WipeArenaMonsters: | function() for i, s in ipairs(BattleRoyale_Main.FindArenaMonsters()) do DeleteTimer(s, 0) end end
AlivePlayersOnArena: | function() local out = {} local ppl = Tournament_Utils.PlayersIn(BR_Map, BR_Arena) for i, p in ipairs(ppl) do if p.Serial and Tournament_Utils.IsAlive(p) and BattleRoyale_Main.InRoster(p.Serial) then table.insert(out, p) end end return out end
AliveSerialsOnArena: | function() local out = {} for i, p in ipairs(BattleRoyale_Main.AlivePlayersOnArena()) do table.insert(out, p.Serial) end return out end
MonsterSpawnPoint: | function() local cand = BattleRoyale_Main.AlivePlayersOnArena() if #cand == 0 then return BattleRoyale_Main.ScatterPoint({}) end
local p = cand[math.random(1, #cand)] local px = tonumber(p.x) or BR_Arena.cx local py = tonumber(p.y) or BR_Arena.cy local pz = tonumber(p.z) or BR_Arena.z
for r = BR_MonsterSpawnGap, BR_MonsterSpawnGap + 3 do for attempt = 1, 8 do local x = px + math.random(-r, r) local y = py + math.random(-r, r) if Tournament_Utils.InRect(BR_Arena, x, y) and CanSpawnMobile(BR_Map, x, y, pz) then return x, y, pz end end end
return px, py, pz end
ArmMonster: | function() if BR_Monster == 0 or BR_MonsterAggroProps == "" then return end pcall(SetProps, BR_Monster, BR_MonsterAggroProps) end
AimMonster: | function(alive) if BR_Monster == 0 or BR_CombatantProp == "" then return end if not alive then alive = BattleRoyale_Main.AliveSerialsOnArena() end if #alive == 0 then BR_MonsterTarget = 0 return end
local keep = false for i, s in ipairs(alive) do if s == BR_MonsterTarget then keep = true end end if not keep then BR_MonsterTarget = alive[math.random(1, #alive)] end
pcall(SetProps, BR_Monster, BR_CombatantProp .. " " .. BattleRoyale_Main.SerialText(BR_MonsterTarget)) end
SerialText: | function(serial) if BR_CombatantHex then return string.format("0x%X", serial) end return string.format("%d", serial) end
SpawnFromSpawner: | function() Spawn(BR_SpawnerSerial) local found = BattleRoyale_Main.FindArenaMonsters() BR_Monster = found[1] or 0 end
SpawnByScript: | function() if not BR_Monsters or #BR_Monsters == 0 then return end
local idx = math.random(1, #BR_Monsters) if #BR_Monsters > 1 then local guard = 0 while idx == BR_MonsterIdx and guard < 10 do idx = math.random(1, #BR_Monsters) guard = guard + 1 end end BR_MonsterIdx = idx local m = BR_Monsters[idx]
local x, y, z = BattleRoyale_Main.MonsterSpawnPoint() local props = BR_MonsterProps .. " RangeHome " .. BR_Arena.r .. " Home (" .. x .. "," .. y .. "," .. z .. ")" if m.Props and m.Props ~= "" then props = props .. " " .. m.Props end
BR_Monster = tonumber(CreateNPCAtLocation(m.Type, x, y, z, props)) or 0 end
SwapMonster: | function() BattleRoyale_Main.RemoveMonster()
if BR_SpawnerSeri
|
|
|
|
|
|
| nullptr |
2.9.2026, 19:02
|

  
Novice
Сообщений: 65
Регистрация: 13.3.2021 Группа: Пользователи Наличность: 25
Пользователь №: 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: []
|
|
|
|
|
|
| RL_ka |
4.9.2026, 14:27
|
    
Разработчик Injection
Сообщений: 481
Регистрация: 13.7.2013 Группа: Модераторы Наличность: 15352 Из: Екатеринбург
Пользователь №: 16.211

|
Цитата(olduo.com @ 4.9.2026, 11:59)  Когда состоится релиз на гитхабе? Я бы портировал на сферу хаха ))
так попроси тот же ИИ сделать тебе такое, он сделает. В текущих реалиях я бы вообще для УОшки ничего больше руками не делал учитывая сколько ей лет, сколько инфы в интернете и наработок. В 90% случаев ИИ сделает лучше чем человек-самоучка без должных навыков (IMG: style_emoticons/default/smile.gif) То на что мы раньше тратили месяцы при разработке инжекта разбирая клиент на инструкции, ИИ делает за минуту Сообщение отредактировал RL_ka - 4.9.2026, 14:34
|
|
|
|
|
|
| nullptr |
4.9.2026, 20:08
|

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

|
Цитата(olduo.com @ 2.9.2026, 21:21)  Обалдеть, я редко встречаю в любительской среде разработку уровня коммерческого геймдева.
Нет слов, круто! Маркус злодей ))
Главное не забывай, что возможно, мы ответим за то что игроки пялились на пиксели, а не жили с миром и природой.
Я не силён в сфере, теоретически можно портнуть, но это точно надо делать на плюсах в ядре. Это ещё один скриптовый движок поверх ядра, у многих свои версии сферы, тут ещё геморнее. Цитата(RL_ka @ 4.9.2026, 16:27)  так попроси тот же ИИ сделать тебе такое, он сделает.
В плюсах без шансов, считай надо точно знать кто держит ссылки, use after free краш сервера. Должны кидаться чистые серийники, никаких объектов, каждый метод должен заново получать объект если он есть и делать операции. Тут надо строго задавать границу и хорошо знать архитектуру сферы. Теже таймеры, когда отдаёшь управление в луа.
|
|
|
|
|
|
5 чел. читают эту тему (гостей: 5, скрытых пользователей: 0)
Пользователей: 0
|
|