|
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 |
25.11.2025, 22:55
|

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

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

     
Expert
Сообщений: 520
Регистрация: 31.7.2016 Группа: Пользователи Наличность: 293
Пользователь №: 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
Сообщений: 68
Регистрация: 13.3.2021 Группа: Пользователи Наличность: 68
Пользователь №: 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 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 Legnes Маркетплейс удобно сделан.
А Time в маркетплейсе п... 25.11.2025, 17:05 olduo.com Хаха прикольно ))
С турнирами понятно и в целом в... 2.9.2026, 18:54 nullptr
Хаха прикольно ))
С турнирами понятно и в целом ... 2.9.2026, 19:02 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
18 чел. читают эту тему (гостей: 18, скрытых пользователей: 0)
Пользователей: 0
|
|