-- Read-only RuntimeFloor capture template. Substitute __FLOOR_INPUTS__ from -- the matching Client MapEntrance rows and __CAPTURE_KEY__ with a unique key. -- Work is bounded per heartbeat; level graphs are serialized, never executed. do local key = '__CAPTURE_KEY__' local floors = __FLOOR_INPUTS__ _G.__gacSceneExports = _G.__gacSceneExports or {} local state = _G.__gacSceneExports[key] or {floorIndex = 1} _G.__gacSceneExports[key] = state local function log(kind, text) CS.UnityEngine.Debug.Log('[GAC_SCENE_' .. kind .. '] ' .. tostring(text)) end local function world(group, item) local yaw = group.RotY * math.pi / 180 return group.PosX + item.PosX * math.cos(yaw) + item.PosZ * math.sin(yaw), group.PosY + item.PosY, group.PosZ - item.PosX * math.sin(yaw) + item.PosZ * math.cos(yaw), group.RotY + item.RotY end local function position(kind, prefix, group, item) local x, y, z, rot = world(group, item) log(kind, prefix .. ' pos=' .. tostring(x) .. ',' .. tostring(y) .. ',' .. tostring(z) .. ' rot=' .. tostring(rot)) end if state.floorIndex <= #floors then local pair = floors[state.floorIndex] local ok, err = pcall(function() if state.floor == nil then local path = CS.RPG.Client.MapDef.GetRtFloorInfoPath(pair[1], pair[2]) state.floor = CS.RPG.GameCore.GameCoreConfigLoader.LoadRtLevelFloorInfo(path) if state.floor == nil then error('missing RuntimeFloor ' .. path) end state.floorIndexInGroups = 0 state.failed = false log('META', 'FLOOR ' .. tostring(pair[1]) .. ' ' .. tostring(pair[2]) .. ' startGroup=' .. tostring(state.floor.StartGroupIndex) .. ' anchor=' .. tostring(state.floor.StartAnchorID) .. ' count=' .. tostring(state.floor.GroupInstanceCommonMap.Count)) state.commonEntries = {} local iterator = state.floor.GroupInstanceCommonMap:GetEnumerator() while iterator:MoveNext() do local entry = iterator.Current state.commonEntries[#state.commonEntries + 1] = entry end state.commonIndex = 1 state.groupIndex = 0 end local floor = state.floor local floorID = tostring(pair[2]) if not state.savedCaptured then for index = 0, math.min(floor.DimensionList.Length - 1, 3) do local dimension = floor.DimensionList[index] log('SAVED', 'DIMENSION ' .. floorID .. ' ' .. tostring(index) .. ' ' .. CS.Newtonsoft.Json.JsonConvert.SerializeObject(dimension.SavedValues)) local ids = {} for j = 0, dimension.GroupIndexList.Length - 1 do ids[#ids + 1] = floor.GroupInstanceList[dimension.GroupIndexList[j]].ID end log('DIMENSION', 'GROUPS ' .. floorID .. ' ' .. tostring(index) .. ' ' .. tostring(dimension.ID) .. ' [' .. table.concat(ids, ',') .. ']') end state.savedCaptured = true end local commonEnd = math.min(state.commonIndex + 15, #state.commonEntries) for index = state.commonIndex, commonEnd do local entry = state.commonEntries[index] log('META', 'GROUP ' .. floorID .. ' ' .. tostring(entry.Key) .. ' side=' .. tostring(entry.Value.LoadSide) .. ' hoyo=' .. tostring(entry.Value.IsHoyoGroup) .. ' deleted=' .. tostring(entry.Value.IsDelete)) end state.commonIndex = commonEnd + 1 if state.commonIndex > #state.commonEntries then -- Skip cheap Client-owned instances without spending a whole -- heartbeat on each four indices; still load at most four groups. local groupEnd = math.min(state.groupIndex + 31, floor.GroupInstanceList.Length - 1) local lastIndex = state.groupIndex - 1 local loadedGroups = 0 for index = state.groupIndex, groupEnd do if loadedGroups >= 4 then break end lastIndex = index local group = floor.GroupInstanceList[index] local common = floor.GroupInstanceCommonMap[group.ID] if not group.IsDelete and common ~= nil and not common.IsDelete and (index == floor.StartGroupIndex or tostring(common.LoadSide):find('Server', 1, true)) then loadedGroups = loadedGroups + 1 local loaded, groupErr = pcall(function() local data = CS.RPG.GameCore.GameCoreConfigLoader.LoadRtLevelGroupInfo(group.GroupPath) if data == nil then error('missing RuntimeGroup ' .. tostring(group.GroupPath)) end if index == floor.StartGroupIndex and data.AnchorList ~= nil then for j = 0, data.AnchorList.Length - 1 do local anchor = data.AnchorList[j] if not anchor.IsDelete and anchor.ID == floor.StartAnchorID then position('PLACEMENT', 'ANCHOR ' .. floorID .. ' ' .. tostring(anchor.ID), group, anchor) end end end if data.MonsterList ~= nil then for j = 0, data.MonsterList.Length - 1 do local monster = data.MonsterList[j] if not monster.IsDelete and not monster.IsClientOnly then position('PLACEMENT', 'MONSTER ' .. floorID .. ' ' .. tostring(group.ID) .. ' ' .. tostring(monster.ID), group, monster) end end end if data.PropList ~= nil then for j = 0, data.PropList.Length - 1 do local prop = data.PropList[j] if not prop.IsDelete and not prop.IsClientOnly then local x, y, z, rot = world(group, prop) log('PROPS', 'PROP ' .. floorID .. ' ' .. tostring(group.ID) .. ' ' .. tostring(prop.ID) .. ' ' .. tostring(prop.PropID) .. ' ' .. tostring(CS.System.Convert.ToUInt32(prop.State)) .. ' pos=' .. tostring(x) .. ',' .. tostring(y) .. ',' .. tostring(z) .. ' rot=' .. tostring(rot)) end end end end) if not loaded then state.failed = true log('META', 'ERROR group=' .. tostring(group.ID) .. ' ' .. tostring(groupErr)) end end end state.groupIndex = lastIndex + 1 if state.groupIndex >= floor.GroupInstanceList.Length then log('META', 'END ' .. floorID) if not state.failed then log('PLACEMENT', 'END ' .. floorID) log('PROPS', 'END ' .. floorID) end state.floorIndex = state.floorIndex + 1 state.floor = nil state.savedCaptured = nil state.commonEntries = nil end end end) if not ok then state.failed = true log('META', 'ERROR floor=' .. tostring(pair[2]) .. ' ' .. tostring(err)) state.floorIndex = state.floorIndex + 1 state.floor = nil state.savedCaptured = nil state.commonEntries = nil end elseif not state.done then state.done = true log('META', 'CAPTURE_COMPLETE') end end