|  | local unit_weight = 4096 | 
					
						
						|  | local type_tile = const.TypeTileSize | 
					
						
						|  | local height_max = const.MaxTerrainHeight | 
					
						
						|  | local height_scale = const.TerrainHeightScale | 
					
						
						|  | local empty_table = empty_table | 
					
						
						|  | local gofPermanent = const.gofPermanent | 
					
						
						|  | local gofGenerated = const.gofGenerated | 
					
						
						|  | local maxh = height_max - 10*guim | 
					
						
						|  | local minh = 10*guim | 
					
						
						|  | local gmodes = {"Height", "Type", "Grass", "Objects", "POI"} | 
					
						
						|  | local smodes = {"Prefab", "POI"} | 
					
						
						|  | local pmodes = {"Marks", "Overlap", "Cover"} | 
					
						
						|  | local omodes = {"Marks", "Overlap", "Cover", "Types"} | 
					
						
						|  | local imodes = {"Rollover", "Pos", "POI"} | 
					
						
						|  | local def_gm = set(table.unpack(gmodes)) | 
					
						
						|  | local def_pn = set("Marks") | 
					
						
						|  | local b_dprint = CreatePrint{ "RM", format = print_format, output = DebugPrint } | 
					
						
						|  | local b_print = Platform.developer and CreatePrint{ "RM", format = print_format, color = yellow} or b_dprint | 
					
						
						|  | local pct_mul = 100 | 
					
						
						|  | local pct_100 = 100*pct_mul | 
					
						
						|  | local function to_pct(mul, div) | 
					
						
						|  | return div == 0 and 0 or MulDivRound(100 * pct_mul, mul, div) | 
					
						
						|  | end | 
					
						
						|  | local def_bd = "BiomeDistort" | 
					
						
						|  | DbgShowPrefab = empty_func | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | DefineClass.BiomeFiller = { | 
					
						
						|  | __parents = { "GridOpInput", "DebugOverlayControl" }, | 
					
						
						|  | properties = { | 
					
						
						|  | { category = "General",    id = "SlopeGrid",     name = "Slope Grid",           editor = "choice",      default = "",     items = function(self) return GridOpOutputNames(self) end, grid_input = true, optional = true, help = "Required by POI logic." }, | 
					
						
						|  | { category = "General",    id = "MinHeight",     name = "Min Height (m)",       editor = "number",      default = minh,   min = 0, max = height_max, scale = guim, help = "Prefabs below that limit will be smart clamped." }, | 
					
						
						|  | { category = "General",    id = "MaxHeight",     name = "Max Height (m)",       editor = "number",      default = maxh,   min = 0, max = height_max, scale = guim, help = "Prefabs above that limit will be smart clamped." }, | 
					
						
						|  | { category = "General",    id = "LoadPrefabLoc", name = "Load Prefab Loc",      editor = "bool",        default = false,  help = "Load any previously saved prefab locations found in the map." }, | 
					
						
						|  | { category = "General",    id = "SavePrefabLoc", name = "Save Prefab Loc",      editor = "bool",        default = false,  help = "Save any prefab locations with persistable tags." }, | 
					
						
						|  | { category = "General",    id = "UseMeshOverlap",name = "Use Mesh Overlap",     editor = "bool",        default = true,   log = true, help = "Detect prefab overlapping objects by analysing their collision mesh instead only their origin"}, | 
					
						
						|  | { category = "General",    id = "OptionalChance",name = "Optional Chance (%)",  editor = "number",      default = 50,     slider = true, min = 0, max = 100, log = true, help = "Chance for optional objects to be placed" }, | 
					
						
						|  | { category = "General",    id = "SteepSlope",    name = "Steep Slope",          editor = "number",      default = 30 * 60,slider = true, min = 0, max = 90*60, log = true, scale = "deg", help = "Slope threshold to start deleting objects marked to be removed on steep slopes" }, | 
					
						
						|  |  | 
					
						
						|  | { category = "Debug",      id = "GenMode",       name = "Gen Mode",             editor = "set",         default = def_gm, items = gmodes, }, | 
					
						
						|  | { category = "Debug",      id = "RemFadedObjs",  name = "Rem Faded Objs",       editor = "bool",        default = true,   log = true, help = "Remove border objects that wont be seen from the playable zone as they are always faded away"}, | 
					
						
						|  | { category = "Debug",      id = "StepMode",      name = "Step Mode",            editor = "set",         default = set(),  items = smodes }, | 
					
						
						|  | { category = "Debug",      id = "StepTime",      name = "Step Time (ms)",       editor = "number",      default = 300,    help = "Delay in each step during Step debug mode. Set to -1 to trigger a pause.", buttons = {{name = "Toggle Pause", func = "ActionTogglePause"}, {name = "Interrupt", func = "ActionInterrupt"}} }, | 
					
						
						|  | { category = "Debug",      id = "Overlay",       name = "Overlay Mode",         editor = "set",         default = set(),  update_dbg = true, items = omodes, max_items_in_set = 1}, | 
					
						
						|  | { category = "Debug",      id = "OverlayAlpha",  name = "Overlay Alpha (%)",    editor = "number",      default = 30,     slider = true, min = 0, max = 100, dont_save = true }, | 
					
						
						|  | { category = "Debug",      id = "OverlayEdges",  name = "Overlay Edges",        editor = "bool",        default = true,   dont_save = true, update_dbg = true, }, | 
					
						
						|  | { category = "Debug",      id = "InspectMode",   name = "Inspect Mode",         editor = "set",         default = set(),  dont_save = true, update_dbg = true, items = imodes, max_items_in_set = 1}, | 
					
						
						|  | { category = "Debug",      id = "InspectFilter", name = "Inspect Filter",       editor = "set",         default = set(),  dont_save = true, update_dbg = true, items = function() return PrefabTagsCombo() end, help = "Show only prefabs with the selected tags" }, | 
					
						
						|  | { category = "Debug",      id = "InspectPattern",name = "Inspect Prefab",       editor = "text",        default = "",     dont_save = true, update_dbg = true, buttons = {{name = "View", func = "ViewInspectedPrefab"}}, help = "Show only prefabs with names matching this pattern" }, | 
					
						
						|  |  | 
					
						
						|  | { category = "Debug",      id = "SelectedPrefab",name = "Selected Prefab",      editor = "text",        default = "",     dont_save = true, buttons = {{name = "Goto", func = "GotoPrefabAction"}} }, | 
					
						
						|  | { category = "Debug",      id = "SelectedPoi",   name = "Selected Info",        editor = "text",        default = "",     dont_save = true}, | 
					
						
						|  | { category = "Debug",      id = "SelectedTags",  name = "Selected Tags",        editor = "text",        default = "",     dont_save = true}, | 
					
						
						|  | { category = "Debug",      id = "SelectedMark",  name = "Selected Mark",        editor = "number",      default = 0,      dont_save = true}, | 
					
						
						|  | { category = "Debug",      id = "SelectedBreak", name = "Selected Break",       editor = "bool",        default = false,  dont_save = true}, | 
					
						
						|  |  | 
					
						
						|  | { category = "Results",    id = "PreviewSet",    name = "Preview Name",         editor = "set",         default = def_pn, items = pmodes, object_update = true, max_items_in_set = 1 }, | 
					
						
						|  | { category = "Results",    id = "GridPreview",   name = "Preview Grid",         editor = "grid",        default = false,  min = 128, max = 512, frame = 1, color = true, invalid_value = 0 }, | 
					
						
						|  | { category = "Results",    id = "MarkGrid",      name = "Prefab Marks",         editor = "grid",        default = false,  no_edit = true }, | 
					
						
						|  | { category = "Results",    id = "OverlapGrid",   name = "Prefab Overlap",       editor = "grid",        default = false,  no_edit = true }, | 
					
						
						|  | { category = "Results",    id = "CoverGrid",     name = "Area Cover",           editor = "grid",        default = false,  no_edit = true }, | 
					
						
						|  | { category = "Results",    id = "PTypeGrid",     name = "PType Marks",          editor = "grid",        default = false,  no_edit = true }, | 
					
						
						|  | { category = "Results",    id = "PrefabCount",   name = "Prefab Count",         editor = "number",      default = 0,      log = true}, | 
					
						
						|  | { category = "Results",    id = "PrefabVisible", name = "Prefab Visible (%)",   editor = "number",      default = 0,      scale = pct_mul, log = true }, | 
					
						
						|  | { category = "Results",    id = "OverlapMax",    name = "Max Overlap Prefabs",  editor = "number",      default = 0,      log = true }, | 
					
						
						|  | { category = "Results",    id = "OverlapPct",    name = "Area Overlap (%)",     editor = "number",      default = 0,      scale = pct_mul, log = true }, | 
					
						
						|  | { category = "Results",    id = "AreaUncovered", name = "Area Uncovered (%)",   editor = "number",      default = 0,      scale = pct_mul, log = true }, | 
					
						
						|  | { category = "Results",    id = "AreaSpill",     name = "Area Spill (%)",       editor = "number",      default = 0,      scale = pct_mul, log = true }, | 
					
						
						|  | { category = "Results",    id = "ObjectCount",   name = "Object Count",         editor = "number",      default = 0,      log = true }, | 
					
						
						|  | { category = "Results",    id = "RemObjects",    name = "Removed Objects",      editor = "number",      default = 0,      log = true }, | 
					
						
						|  | { category = "Results",    id = "RemColls",      name = "Removed Collections",  editor = "number",      default = 0,      log = true }, | 
					
						
						|  | { category = "Results",    id = "PlacedColls",   name = "Placed Collections",   editor = "number",      default = 0,      log = true }, | 
					
						
						|  | { category = "Results",    id = "RasterMem",     name = "Raster Mem (MB)",      editor = "number",      default = 0,      scale = 1024*1024 }, | 
					
						
						|  | { category = "Results",    id = "MixHash",       name = "Prefab Types Hash",    editor = "number",      default = 0,      log = true }, | 
					
						
						|  | { category = "Results",    id = "PrefabHash",    name = "Prefab Placed Hash",   editor = "number",      default = 0,      log = true }, | 
					
						
						|  | { category = "Results",    id = "MarkHash",      name = "Marks Hash",           editor = "number",      default = 0,      log = true }, | 
					
						
						|  | { category = "Results",    id = "FirstRand",     name = "First Rand",           editor = "number",      default = 0,      log = true }, | 
					
						
						|  | { category = "Results",    id = "LastRand",      name = "Last Rand",            editor = "number",      default = 0,      log = true }, | 
					
						
						|  | { category = "Results",    id = "LocateTime",    name = "Locate Time",          editor = "number",      default = 0,      scale = "sec" }, | 
					
						
						|  | { category = "Results",    id = "PoiTime",       name = "Locate POI Time",      editor = "number",      default = 0,      scale = "sec" }, | 
					
						
						|  | { category = "Results",    id = "RasterizeTime", name = "Rasterize Time",       editor = "number",      default = 0,      scale = "sec" }, | 
					
						
						|  | { category = "Results",    id = "ObjectTime",    name = "Object Time",          editor = "number",      default = 0,      scale = "sec" }, | 
					
						
						|  | { category = "Results",    id = "GenStep",       name = "Generate Step",        editor = "number",      default = 0,      scale = "m" }, | 
					
						
						|  |  | 
					
						
						|  | { category = "Results",    id = "PlacedPrefabs", name = "Placed Prefabs",       editor = "text",        default = false,  lines = 10, text_style = "GedConsole",             buttons = {{name = "Sort", func = "ActionSortPrefabs"}} }, | 
					
						
						|  | { category = "Results",    id = "PrefabTypes",   name = "Prefab Types",         editor = "text",        default = false,  lines = 10, text_style = "GedConsole", log = true, buttons = {{name = "Sort", func = "ActionSortPrefabTypes"}} }, | 
					
						
						|  | { category = "Results",    id = "VisiblePrefabs",name = "Prefab Visibility",    editor = "text",        default = false,  lines = 10, text_style = "GedConsole", log = true, buttons = {{name = "Sort", func = "ActionSortVisible"}} }, | 
					
						
						|  | { category = "Results",    id = "PlacedObjects", name = "Placed Objects",       editor = "text",        default = false,  lines = 10, text_style = "GedConsole", log = true, buttons = {{name = "Sort", func = "ActionSortObjects"}} }, | 
					
						
						|  | { category = "Results",    id = "PlacedPOI",     name = "Placed POI",           editor = "text",        default = false,  lines = 10, text_style = "GedConsole", log = true, buttons = {{name = "Sort", func = "ActionSortPoi"}} }, | 
					
						
						|  | { category = "Results",    id = "PrefabList",                                   editor = "prop_table",  default = false,  no_edit = true }, | 
					
						
						|  | }, | 
					
						
						|  | gen_thread = false, | 
					
						
						|  | gen_handles = false, | 
					
						
						|  |  | 
					
						
						|  | DbgInit = empty_func, | 
					
						
						|  | DbgDone = empty_func, | 
					
						
						|  | DbgUpdate = empty_func, | 
					
						
						|  | DbgOnModified = empty_func, | 
					
						
						|  |  | 
					
						
						|  | GridOpType = "Map Biome Fill", | 
					
						
						|  | recalc_on_change = false, | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | do | 
					
						
						|  | for i, prop in ipairs(BiomeFiller.properties) do | 
					
						
						|  | if prop.category == "Results" and not prop.items then | 
					
						
						|  | prop.dont_save = true | 
					
						
						|  | prop.read_only = true | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | function BiomeFiller:GetGridModes() | 
					
						
						|  | return { | 
					
						
						|  | Marks = self.MarkGrid, | 
					
						
						|  | Overlap = self.OverlapGrid, | 
					
						
						|  | Cover = self.CoverGrid, | 
					
						
						|  | Types = self.PTypeGrid, | 
					
						
						|  | } | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | function BiomeFiller:CollectTags(tags) | 
					
						
						|  | tags.Terrain = true | 
					
						
						|  | tags.Objects = true | 
					
						
						|  | tags.Pause = true | 
					
						
						|  | return GridOp.CollectTags(self, tags) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | function BiomeFiller:SetGridInput(state, grid) | 
					
						
						|  | if not next(GetPrefabTypeList()) then | 
					
						
						|  | state.proc:AddLog(self:GetFullName() .. ": No prefab types found!", state) | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | PauseInfiniteLoopDetection("BiomeFiller.Generate") | 
					
						
						|  | SuspendPassEdits("BiomeFiller.Generate") | 
					
						
						|  | SuspendObjModified("BiomeFiller.Generate") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | local map_thread = CurrentThread() | 
					
						
						|  | local gen_thread = CreateRealTimeThread(function() | 
					
						
						|  | self:Generate(state, grid) | 
					
						
						|  | Wakeup(map_thread) | 
					
						
						|  | end) | 
					
						
						|  | self.gen_thread = gen_thread | 
					
						
						|  | while self.gen_thread == gen_thread and IsValidThread(gen_thread) and not WaitWakeup(100) do | 
					
						
						|  |  | 
					
						
						|  | end | 
					
						
						|  | if self.gen_thread ~= gen_thread then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | ResumeObjModified("BiomeFiller.Generate") | 
					
						
						|  | ResumePassEdits("BiomeFiller.Generate") | 
					
						
						|  | ResumeInfiniteLoopDetection("BiomeFiller.Generate") | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | function BiomeFiller:Generate(state, ptype_grid) | 
					
						
						|  | local gen_thread = CurrentThread() | 
					
						
						|  | if gen_thread ~= self.gen_thread then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | self:DbgInit() | 
					
						
						|  |  | 
					
						
						|  | state = state or empty_table | 
					
						
						|  | local debug = state.run_mode ~= "GM" and (Platform.editor or Platform.developer) | 
					
						
						|  | local dump = state.dump | 
					
						
						|  | local gen_mode = self.GenMode | 
					
						
						|  | local step | 
					
						
						|  | local step_prefab, step_poi | 
					
						
						|  | local prefab_stats, prefab_stat_count | 
					
						
						|  | local AddPrefabStat = empty_func | 
					
						
						|  | local Min, Max = Min, Max | 
					
						
						|  | local irOutside = const.irOutside | 
					
						
						|  | local group_dist_pct = const.RandomMap.PrefabGroupSimilarDistPct | 
					
						
						|  | local map_divs = const.RandomMap.PrefabRasterParallelDiv | 
					
						
						|  | local group_attract = const.RandomMap.PrefabGroupSimilarWeight | 
					
						
						|  | local max_map_size = const.RandomMap.PrefabMaxMapSize | 
					
						
						|  | local raster_cache_memory = const.RandomMap.PrefabRasterCacheMemory | 
					
						
						|  | local table_append = table.append | 
					
						
						|  | local table_get = table.get | 
					
						
						|  | local ipairs, pairs = ipairs, pairs | 
					
						
						|  | local BraidRandom = BraidRandom | 
					
						
						|  | local LerpRandRange = LerpRandRange | 
					
						
						|  | local unpack = table.unpack | 
					
						
						|  | local table_keys = table.keys | 
					
						
						|  | local MulDivRound = MulDivRound | 
					
						
						|  | local GetHeight = terrain.GetHeight | 
					
						
						|  | local GetSlopeOrientation = terrain.GetSlopeOrientation | 
					
						
						|  | local IsPointInBounds = terrain.IsPointInBounds | 
					
						
						|  |  | 
					
						
						|  | local start_seed = state.rand or AsyncRand() | 
					
						
						|  | local rand_seed = BraidRandom(start_seed) | 
					
						
						|  |  | 
					
						
						|  | local g_print = b_print | 
					
						
						|  | if dump then | 
					
						
						|  | g_print = function(...) | 
					
						
						|  | dump(print_format("\n--", ...)) | 
					
						
						|  | return b_print(...) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | if debug then | 
					
						
						|  | if next(self.StepMode or empty_table) then | 
					
						
						|  | if self.StepMode.Prefab then | 
					
						
						|  | step_prefab = true | 
					
						
						|  | map_divs = 1 | 
					
						
						|  | end | 
					
						
						|  | if self.StepMode.POI then | 
					
						
						|  | step_poi = true | 
					
						
						|  | end | 
					
						
						|  | step = function(fmt, ...) | 
					
						
						|  | if self.dbg_interrupt then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | if fmt then | 
					
						
						|  | printf(fmt, ...) | 
					
						
						|  | end | 
					
						
						|  | self:DbgOnModified() | 
					
						
						|  | if self.StepTime < 0 then | 
					
						
						|  | self.dbg_paused = true | 
					
						
						|  | else | 
					
						
						|  | Sleep(self.StepTime) | 
					
						
						|  | end | 
					
						
						|  | if self.dbg_paused then | 
					
						
						|  | print("Pause") | 
					
						
						|  | while self.dbg_paused do | 
					
						
						|  | WaitMsg(self) | 
					
						
						|  | end | 
					
						
						|  | print("Resume") | 
					
						
						|  | end | 
					
						
						|  | if gen_thread ~= self.gen_thread then | 
					
						
						|  | Halt() | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | prefab_stats, prefab_stat_count = {}, {} | 
					
						
						|  | AddPrefabStat = function(prefab, name, value) | 
					
						
						|  | local stat = prefab_stats[prefab] or {} | 
					
						
						|  | local count = prefab_stat_count[prefab] or {} | 
					
						
						|  | stat[name] = (stat[name] or 0) + (value or 1) | 
					
						
						|  | count[name] = (count[name] or 0) + 1 | 
					
						
						|  | prefab_stats[prefab] = stat | 
					
						
						|  | prefab_stat_count[prefab] = count | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local mw, mh = terrain.GetMapSize() | 
					
						
						|  | assert(mw == mh) | 
					
						
						|  | if mw > max_map_size then | 
					
						
						|  | g_print("map larger than", max_map_size) | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | local gw, gh = ptype_grid:size() | 
					
						
						|  | if gw ~= gh or mw / gw == 0 or mw % gw ~= 0 or (mw / gw) % type_tile ~= 0 then | 
					
						
						|  | return "Invalid mix grid size!" | 
					
						
						|  | end | 
					
						
						|  | local work_step = mw / gw | 
					
						
						|  | local work_ratio = work_step / type_tile | 
					
						
						|  | self.GenStep = work_step | 
					
						
						|  |  | 
					
						
						|  | local function new_grid(packing) | 
					
						
						|  | return NewComputeGrid(gw, gh, "u", packing or 16) | 
					
						
						|  | end | 
					
						
						|  | local function free_grid(grid) | 
					
						
						|  | if grid then grid:free() end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local function rand_init(name, ...) | 
					
						
						|  | rand_seed = xxhash(start_seed, name, ...) | 
					
						
						|  | if dump then | 
					
						
						|  | dump("\n*** INITRAND %d %s", rand_seed, name) | 
					
						
						|  | end | 
					
						
						|  | return rand_seed | 
					
						
						|  | end | 
					
						
						|  | local function trand(tbl, calc_weight) | 
					
						
						|  | rand_seed = BraidRandom(rand_seed) | 
					
						
						|  | return table.weighted_rand(tbl, calc_weight, rand_seed) | 
					
						
						|  | end | 
					
						
						|  | local function crand(chance, max_chance) | 
					
						
						|  | rand_seed = BraidRandom(rand_seed) | 
					
						
						|  | return LerpRandRange(rand_seed, max_chance or 100) < chance | 
					
						
						|  | end | 
					
						
						|  | local function rand(min, max) | 
					
						
						|  | rand_seed = BraidRandom(rand_seed) | 
					
						
						|  | return min and LerpRandRange(rand_seed, min, max) or rand_seed | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local prefab_markers = PrefabMarkers | 
					
						
						|  | local exported_prefabs = ExportedPrefabs | 
					
						
						|  | local ptype_to_preset = PrefabTypeToPreset | 
					
						
						|  | local prefab_list = {} | 
					
						
						|  |  | 
					
						
						|  | local add_idx = 0 | 
					
						
						|  | local prefabs_count = {} | 
					
						
						|  | local bx_changes, levels_count, placed_marks | 
					
						
						|  | local height_out_of_lims | 
					
						
						|  | local mark_grid, ptype_grid_res, overlap_grid | 
					
						
						|  | local locate_time, poi_time, raster_time = 0, 0, 0 | 
					
						
						|  | local prefab_tag_loc, prefabs_to_persist = {}, {} | 
					
						
						|  | local point_pack, point_unpack = point_pack, point_unpack | 
					
						
						|  |  | 
					
						
						|  | local function FindAndRasterPrefabs() | 
					
						
						|  | rand_init("FindAndRasterPrefabs") | 
					
						
						|  | local ptype_to_prefabs = PrefabTypeToPrefabs | 
					
						
						|  | local poi_type_to_preset = PrefabPoiToPreset | 
					
						
						|  | local idx_to_ptype = GetPrefabTypeList() | 
					
						
						|  | local ptypes_found, ptype_to_tags, ptype_to_area, ptype_to_idx = {}, {}, {}, {} | 
					
						
						|  | for idx, area in pairs(GridLevels(ptype_grid)) do | 
					
						
						|  | local ptype = idx_to_ptype[idx] | 
					
						
						|  | assert(ptype) | 
					
						
						|  | if ptype then | 
					
						
						|  | ptypes_found[#ptypes_found + 1] = ptype | 
					
						
						|  | ptype_to_area[ptype] = area | 
					
						
						|  | ptype_to_idx[ptype] = idx | 
					
						
						|  | local tags = ptype_to_preset[ptype].Tags | 
					
						
						|  | if next(tags) then | 
					
						
						|  | ptype_to_tags[ptype] = tags | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | local ptype_cmp = PrefabType.Compare | 
					
						
						|  | table.sort(ptypes_found, function(a, b) | 
					
						
						|  | local pa, pb = ptype_to_preset[a], ptype_to_preset[b] | 
					
						
						|  | return ptype_cmp(pa, pb) | 
					
						
						|  | end) | 
					
						
						|  |  | 
					
						
						|  | mark_grid = new_grid() | 
					
						
						|  | ptype_grid_res = new_grid() | 
					
						
						|  | local cover_grid | 
					
						
						|  | if debug then | 
					
						
						|  | overlap_grid = new_grid() | 
					
						
						|  | cover_grid = new_grid() | 
					
						
						|  | self.MarkGrid = mark_grid | 
					
						
						|  | self.OverlapGrid = overlap_grid | 
					
						
						|  | self.CoverGrid = cover_grid | 
					
						
						|  | self.PTypeGrid = ptype_grid_res | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local prefab_tags, prefab_to_persist_tags = {}, {} | 
					
						
						|  | local persistable_tags = GetPrefabTagsPersistable() | 
					
						
						|  | for _, prefab in ipairs(prefab_markers) do | 
					
						
						|  | local poi_tags = table_get(poi_type_to_preset, prefab.poi_type, "Tags") | 
					
						
						|  | local ptype_tags = ptype_to_tags[prefab.type] | 
					
						
						|  | local marker_tags = prefab.tags | 
					
						
						|  | if next(ptype_tags) or next(marker_tags) or next(poi_tags) then | 
					
						
						|  | local tags = {} | 
					
						
						|  | table_append(tags, ptype_tags) | 
					
						
						|  | table_append(tags, marker_tags) | 
					
						
						|  | table_append(tags, poi_tags) | 
					
						
						|  | tags = table_keys(tags, true) | 
					
						
						|  | local persist_tags | 
					
						
						|  | for _, tag in ipairs(tags) do | 
					
						
						|  | if persistable_tags[tag] then | 
					
						
						|  | persist_tags = table.create_add(persist_tags, tag) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | prefab_to_persist_tags[prefab] = persist_tags | 
					
						
						|  | prefab_tags[prefab] = tags | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | local persisted_prefabs, persisted_tag_count | 
					
						
						|  | if self.LoadPrefabLoc then | 
					
						
						|  | for _, entry in ipairs(mapdata.PersistedPrefabs) do | 
					
						
						|  | local name = entry[1] | 
					
						
						|  | local prefab = prefab_markers[name] | 
					
						
						|  | local persist_tags = prefab and prefab_to_persist_tags[prefab] | 
					
						
						|  | if not persist_tags then | 
					
						
						|  | g_print("Non persistable prefab loaded:", name) | 
					
						
						|  | else | 
					
						
						|  | persisted_prefabs = table.create_add(persisted_prefabs, entry) | 
					
						
						|  | persisted_tag_count = persisted_tag_count or {} | 
					
						
						|  | for _, tag in pairs(persist_tags) do | 
					
						
						|  | persisted_tag_count[tag] = (persisted_tag_count[tag] or 0) + 1 | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | local function IsPrefabAllowed(prefab) | 
					
						
						|  | if (prefab.max_count or -1) == (prefabs_count[prefab] or 0) then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | if persisted_tag_count then | 
					
						
						|  | local tags = prefab_tags[prefab] | 
					
						
						|  | for _, tag in ipairs(tags) do | 
					
						
						|  | local tag_count = persisted_tag_count[tag] | 
					
						
						|  | if tag_count and tag_count <= 0 then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | return true | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local map_angle = rand(360*60) | 
					
						
						|  | local similar_grids = {} | 
					
						
						|  |  | 
					
						
						|  | local function MulDivWeight(weight, mul, div, pow) | 
					
						
						|  | for i=1,(pow or 0) do | 
					
						
						|  | weight = weight * mul / div | 
					
						
						|  | end | 
					
						
						|  | return weight | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local _overlap_reduct, _fit_effort | 
					
						
						|  | local _place_x, _place_y, _radius_target, _radius_range | 
					
						
						|  |  | 
					
						
						|  | local radius_getters = PrefabRadiusEstimators() | 
					
						
						|  | local get_radius | 
					
						
						|  |  | 
					
						
						|  | local repeat_weights = {} | 
					
						
						|  | local function prefab_weight(prefab) | 
					
						
						|  | local weight = MulDivRound(unit_weight, prefab.weight, 100) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | weight = MulDivWeight(weight, prefab.min_radius, prefab.max_radius, _overlap_reduct) | 
					
						
						|  | local radius = get_radius(prefab) | 
					
						
						|  | local radius_err = abs(_radius_target - radius) | 
					
						
						|  | weight = MulDivWeight(weight, _radius_range - radius_err, _radius_range, _fit_effort) | 
					
						
						|  | local repeat_weight = repeat_weights[prefab] | 
					
						
						|  | if repeat_weight then | 
					
						
						|  | weight = MulDivRound(weight, repeat_weight, unit_weight) | 
					
						
						|  | end | 
					
						
						|  | return 1 + weight | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local function prefab_add(prefab_list, prefab, x, y, radius, mix_grid_idx, ptype, try_persist, skip_raster, angle) | 
					
						
						|  |  | 
					
						
						|  | assert((prefab.max_count or -1) ~= (prefabs_count[prefab] or 0)) | 
					
						
						|  | local count = (prefabs_count[prefab] or 0) + 1 | 
					
						
						|  | prefabs_count[prefab] = count | 
					
						
						|  | local reduct = prefab.repeat_reduct or 0 | 
					
						
						|  | if reduct > 0 then | 
					
						
						|  | local rstep = reduct / 10 | 
					
						
						|  | local weight = unit_weight | 
					
						
						|  | for i=1,count do | 
					
						
						|  | local new_weight = weight * (100 - reduct) / 100 | 
					
						
						|  | if new_weight == weight then | 
					
						
						|  | break | 
					
						
						|  | end | 
					
						
						|  | weight = new_weight | 
					
						
						|  | reduct = reduct - rstep | 
					
						
						|  | if reduct <= 0 then | 
					
						
						|  | break | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | repeat_weights[prefab] = weight | 
					
						
						|  | end | 
					
						
						|  | local mx, my = x * work_step, y * work_step | 
					
						
						|  | local mz = GetHeight(mx, my) | 
					
						
						|  | local prefab_pos = point(mx, my, mz) | 
					
						
						|  | assert(IsPointInBounds(prefab_pos)) | 
					
						
						|  | local bbox | 
					
						
						|  | if not skip_raster then | 
					
						
						|  | local excircle_m = prefab.max_radius * type_tile | 
					
						
						|  | bbox = box(mx - excircle_m, my - excircle_m, mx + excircle_m, my + excircle_m) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | if not angle then | 
					
						
						|  | local angle_variation = prefab.angle_variation or 180*60 | 
					
						
						|  | angle = rand(-angle_variation, angle_variation) - (prefab.angle or 0) | 
					
						
						|  | local rotation_mode = prefab.rotation_mode | 
					
						
						|  | if rotation_mode == "slope" then | 
					
						
						|  | angle = angle + GetSlopeOrientation(prefab_pos, work_step * prefab.min_radius / 2) | 
					
						
						|  | elseif rotation_mode == "map" then | 
					
						
						|  | angle = angle + map_angle | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local raster = { | 
					
						
						|  | pos = prefab_pos, | 
					
						
						|  | angle = angle, | 
					
						
						|  | place_idx = 0, | 
					
						
						|  | place_mask_idx = mix_grid_idx, | 
					
						
						|  | } | 
					
						
						|  | add_idx = add_idx + 1 | 
					
						
						|  | prefab_list[#prefab_list + 1] = {prefab, raster, add_idx, ptype, bbox} | 
					
						
						|  |  | 
					
						
						|  | local remaining = max_int | 
					
						
						|  | local tags = prefab_tags[prefab] | 
					
						
						|  | if tags then | 
					
						
						|  | local loc = point_pack(x, y, radius) | 
					
						
						|  | for _, tag in ipairs(tags) do | 
					
						
						|  | local loc_list = prefab_tag_loc[tag] | 
					
						
						|  | if not loc_list then | 
					
						
						|  | prefab_tag_loc[tag] = { loc } | 
					
						
						|  | else | 
					
						
						|  | loc_list[#loc_list + 1] = loc | 
					
						
						|  | end | 
					
						
						|  | local tag_count = persisted_tag_count and persisted_tag_count[tag] | 
					
						
						|  | if tag_count then | 
					
						
						|  | assert(tag_count > 0) | 
					
						
						|  | tag_count = tag_count - 1 | 
					
						
						|  | persisted_tag_count[tag] = tag_count | 
					
						
						|  | remaining = Min(remaining, tag_count) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if try_persist and prefab_to_persist_tags[prefab] then | 
					
						
						|  | local name = prefab_markers[prefab] | 
					
						
						|  | prefabs_to_persist[#prefabs_to_persist + 1] = { name, ptype, x, y, angle } | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | return count, remaining | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local skip = debug and { | 
					
						
						|  | Height = not gen_mode.Height, | 
					
						
						|  | Type = not gen_mode.Type, | 
					
						
						|  | Grass = not gen_mode.Grass, | 
					
						
						|  | } | 
					
						
						|  | local raster_params = { | 
					
						
						|  | place_grid = mark_grid, | 
					
						
						|  | place_mask = ptype_grid, | 
					
						
						|  | place_mask_res = ptype_grid_res, | 
					
						
						|  | overlap_grid = overlap_grid, | 
					
						
						|  | dither_seed = rand(), | 
					
						
						|  | height_min = self.MinHeight, | 
					
						
						|  | height_max = self.MaxHeight, | 
					
						
						|  | } | 
					
						
						|  | local raster_meta = {__index = raster_params} | 
					
						
						|  | local prefab_cache = {} | 
					
						
						|  | local cache_info = {} | 
					
						
						|  | local current_memory = 0 | 
					
						
						|  | local peak_memory = 0 | 
					
						
						|  | local tasks_count = map_divs * map_divs | 
					
						
						|  | local PREFAB_META, PREFAB_RASTER, PREFAB_IDX, PREFAB_TYPE, PREFAB_BOX = 1, 2, 3, 4, 5 | 
					
						
						|  | local function FreeCache(prefab) | 
					
						
						|  | local cache = prefab_cache[prefab] | 
					
						
						|  | if not cache then | 
					
						
						|  | assert(false, "Missing cache") | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | local data = cache.__index | 
					
						
						|  | free_grid(data.height_grid) | 
					
						
						|  | free_grid(data.type_grid) | 
					
						
						|  | free_grid(data.grass_grid) | 
					
						
						|  | free_grid(data.mask_grid) | 
					
						
						|  | prefab_cache[prefab] = nil | 
					
						
						|  | current_memory = current_memory - (prefab.required_memory or 0) | 
					
						
						|  | assert(current_memory >= 0, "Wrong memory estimation") | 
					
						
						|  | end | 
					
						
						|  | local function LoadCache(prefab) | 
					
						
						|  | local cache, ignore_memory_limits | 
					
						
						|  | while true do | 
					
						
						|  | cache = prefab_cache[prefab] | 
					
						
						|  | if cache then | 
					
						
						|  | break | 
					
						
						|  | elseif cache == false then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | local required_memory = prefab.required_memory or 0 | 
					
						
						|  | if ignore_memory_limits or current_memory + required_memory <= raster_cache_memory then | 
					
						
						|  | local preload_start_time = GetPreciseTicks() | 
					
						
						|  | local data = PrefabPreload(prefab, raster_meta, skip) | 
					
						
						|  | if debug then | 
					
						
						|  | AddPrefabStat(prefab, "grid_load_time", GetPreciseTicks() - preload_start_time) | 
					
						
						|  | end | 
					
						
						|  | cache = data and {__index = data} or false | 
					
						
						|  | if cache then | 
					
						
						|  | current_memory = current_memory + required_memory | 
					
						
						|  | peak_memory = Max(peak_memory, current_memory) | 
					
						
						|  | end | 
					
						
						|  | prefab_cache[prefab] = cache | 
					
						
						|  | break | 
					
						
						|  | end | 
					
						
						|  | local min_prefab | 
					
						
						|  | local min_required_memory = max_int | 
					
						
						|  | local locked = 0 | 
					
						
						|  | for i=1,#cache_info do | 
					
						
						|  | local prefab_i = cache_info[i] | 
					
						
						|  | if prefab_cache[prefab_i] then | 
					
						
						|  | local required_memory_i = prefab_i.required_memory or 0 | 
					
						
						|  | if min_required_memory > required_memory_i then | 
					
						
						|  | if cache_info[prefab_i].locks == 0 then | 
					
						
						|  | min_prefab = prefab_i | 
					
						
						|  | min_required_memory = required_memory_i | 
					
						
						|  | else | 
					
						
						|  | locked = locked + 1 | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if min_prefab then | 
					
						
						|  | FreeCache(min_prefab) | 
					
						
						|  | elseif locked > 0 then | 
					
						
						|  | WaitMsg("PrefabCacheUnloaded") | 
					
						
						|  | else | 
					
						
						|  | g_print("Unable to free enough memory for rasterization!") | 
					
						
						|  | ignore_memory_limits = true | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if not cache then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | local info = cache_info[prefab] | 
					
						
						|  | assert(info, "Load cache error") | 
					
						
						|  | if info then | 
					
						
						|  | info.locks = info.locks + 1 | 
					
						
						|  | end | 
					
						
						|  | return cache | 
					
						
						|  | end | 
					
						
						|  | local function UnloadCache(prefab) | 
					
						
						|  | local info = cache_info[prefab] | 
					
						
						|  | if not info or info.locks <= 0 or info.count <= 0 then | 
					
						
						|  | assert(false, "Unload cache error") | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | info.locks = info.locks - 1 | 
					
						
						|  | info.count = info.count - 1 | 
					
						
						|  | Msg("PrefabCacheUnloaded") | 
					
						
						|  | if info.count ~= 0 then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | assert(info.locks == 0, "Invalid lock count") | 
					
						
						|  | FreeCache(prefab) | 
					
						
						|  | end | 
					
						
						|  | local function WaitRaster(prefabs_to_raster) | 
					
						
						|  | if #prefabs_to_raster == 0 then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | local start_time_raster = GetPreciseTicks() | 
					
						
						|  |  | 
					
						
						|  | local function prefab_list_sort(a, b) | 
					
						
						|  | local p1, p2 = a[PREFAB_META], b[PREFAB_META] | 
					
						
						|  | if p1 ~= p2 then | 
					
						
						|  | local ptype1, ptype2 = a[PREFAB_TYPE], b[PREFAB_TYPE] | 
					
						
						|  | if ptype1 ~= ptype2 then | 
					
						
						|  | local preset1, preset2 = ptype_to_preset[ptype1], ptype_to_preset[ptype2] | 
					
						
						|  | if preset1 and preset2 then | 
					
						
						|  | return ptype_cmp(preset1, preset2) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | local mul1 = (p1.obj_count or 1) * (p2.total_area or 1) | 
					
						
						|  | local mul2 = (p2.obj_count or 1) * (p1.total_area or 1) | 
					
						
						|  | if mul1 ~= mul2 then | 
					
						
						|  | return mul1 < mul2 | 
					
						
						|  | end | 
					
						
						|  | local mul1 = p1.max_radius * p2.min_radius | 
					
						
						|  | local mul2 = p2.max_radius * p1.min_radius | 
					
						
						|  | if mul1 ~= mul2 then | 
					
						
						|  | return mul1 < mul2 | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | return a[PREFAB_IDX] < b[PREFAB_IDX] | 
					
						
						|  | end | 
					
						
						|  | table.sort(prefabs_to_raster, prefab_list_sort) | 
					
						
						|  |  | 
					
						
						|  | local place_idx = #prefab_list | 
					
						
						|  | for _, info in ipairs(prefabs_to_raster) do | 
					
						
						|  | place_idx = place_idx + 1 | 
					
						
						|  | local raster = info[PREFAB_RASTER] | 
					
						
						|  | raster.place_idx = place_idx | 
					
						
						|  | prefab_list[place_idx] = info | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local waiting = {} | 
					
						
						|  | local thread_idx = 0 | 
					
						
						|  | local y0 = 0 | 
					
						
						|  | for y=1,map_divs do | 
					
						
						|  | local y1 = MulDivRound(mh, y, map_divs) | 
					
						
						|  | assert(y1 % type_tile == 0) | 
					
						
						|  | local x0 = 0 | 
					
						
						|  | for x=1,map_divs do | 
					
						
						|  | local x1 = MulDivRound(mw, x, map_divs) | 
					
						
						|  | assert(x1 % type_tile == 0) | 
					
						
						|  | local mbox = box(x0, y0, x1, y1) | 
					
						
						|  | for i=1,#prefabs_to_raster do | 
					
						
						|  | local prefab, raster, add_idx, ptype, bbox = unpack(prefabs_to_raster[i]) | 
					
						
						|  | if bbox and bbox:Intersect2D(mbox) ~= irOutside then | 
					
						
						|  | local info = cache_info[prefab] | 
					
						
						|  | if not info then | 
					
						
						|  | info = { | 
					
						
						|  | count = 1, | 
					
						
						|  | locks = 0, | 
					
						
						|  | } | 
					
						
						|  | cache_info[prefab] = info | 
					
						
						|  | cache_info[#cache_info + 1] = prefab | 
					
						
						|  | else | 
					
						
						|  | info.count = info.count + 1 | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | local thread = CreateRealTimeThread(function() | 
					
						
						|  | for i=1,#prefabs_to_raster do | 
					
						
						|  | local prefab, raster, add_idx, ptype, bbox = unpack(prefabs_to_raster[i]) | 
					
						
						|  | local cache = bbox and bbox:Intersect2D(mbox) ~= irOutside and LoadCache(prefab) | 
					
						
						|  | if cache then | 
					
						
						|  | setmetatable(raster, cache) | 
					
						
						|  | local raster_start_time = GetPreciseTicks() | 
					
						
						|  | local err, ibox, out_of_lims = AsyncGridSetTerrain(raster, mbox) | 
					
						
						|  | if debug then | 
					
						
						|  | AddPrefabStat(prefab, "grid_place_time", GetPreciseTicks() - raster_start_time) | 
					
						
						|  | end | 
					
						
						|  | if err then | 
					
						
						|  | g_print("Failed to rasterize prefab", prefab_markers[prefab], err) | 
					
						
						|  | elseif ibox then | 
					
						
						|  | bx_changes = bx_changes or box() | 
					
						
						|  | bx_changes = Extend(bx_changes, ibox) | 
					
						
						|  | end | 
					
						
						|  | if out_of_lims then | 
					
						
						|  | height_out_of_lims = true | 
					
						
						|  | end | 
					
						
						|  | setmetatable(raster, nil) | 
					
						
						|  | UnloadCache(prefab) | 
					
						
						|  | if step_prefab then | 
					
						
						|  | terrain.InvalidateHeight(ibox) | 
					
						
						|  | terrain.InvalidateType(ibox) | 
					
						
						|  | DbgClear() | 
					
						
						|  | local name = prefab_markers[prefab] | 
					
						
						|  | DbgShowPrefab(raster.pos, name, white, raster.place_idx, prefab.min_radius, prefab.max_radius) | 
					
						
						|  | step("Terrain %d %s", add_idx, name) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | waiting[CurrentThread()] = nil | 
					
						
						|  | Wakeup(gen_thread) | 
					
						
						|  | end) | 
					
						
						|  | thread_idx = thread_idx + 1 | 
					
						
						|  | waiting[thread] = thread_idx | 
					
						
						|  | x0 = x1 | 
					
						
						|  | end | 
					
						
						|  | y0 = y1 | 
					
						
						|  | end | 
					
						
						|  | while next(waiting) do | 
					
						
						|  | WaitWakeup(1000) | 
					
						
						|  | for thread in pairs(waiting) do | 
					
						
						|  | if not IsValidThread(thread) then | 
					
						
						|  | waiting[thread] = nil | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | local inv_bbox = bx_changes:grow(type_tile) | 
					
						
						|  | terrain.FixHeightBorder(inv_bbox) | 
					
						
						|  | if not step_prefab then | 
					
						
						|  | terrain.InvalidateHeight(inv_bbox) | 
					
						
						|  | terrain.InvalidateType(inv_bbox) | 
					
						
						|  | end | 
					
						
						|  | raster_time = raster_time + (GetPreciseTicks() - start_time_raster) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local dist_mask | 
					
						
						|  | local dist_grid = new_grid() | 
					
						
						|  | local dist_prec = 8 | 
					
						
						|  | local dist_tile = work_ratio * dist_prec | 
					
						
						|  |  | 
					
						
						|  | local function WaitFitAndRaster(ptype) | 
					
						
						|  | local ptype_preset = ptype_to_preset[ptype] | 
					
						
						|  | if dump then | 
					
						
						|  | dump("\n----\nPTYPE '%s' %s", ptype, TableToLuaCode(ptype_preset)) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | get_radius = radius_getters[ptype_preset.RadiusEstim] | 
					
						
						|  | _overlap_reduct = ptype_preset.OverlapReduct > 0 and 2 ^ (ptype_preset.OverlapReduct - 1) or 0 | 
					
						
						|  | _fit_effort = ptype_preset.FitEffort > 0 and 2 ^ (ptype_preset.FitEffort - 1) or 0 | 
					
						
						|  |  | 
					
						
						|  | local prefabs = {} | 
					
						
						|  | local respect_bounds = ptype_preset.RespectBounds | 
					
						
						|  | local place_radius = ptype_preset.PlaceRadius / type_tile | 
					
						
						|  | local radius_min, radius_max = max_int, 0 | 
					
						
						|  | for _, prefab in ipairs(ptype_to_prefabs[ptype] or empty_table) do | 
					
						
						|  | local poi_type = prefab.poi_type or "" | 
					
						
						|  | if poi_type == "" and IsPrefabAllowed(prefab) then | 
					
						
						|  | local radius = get_radius(prefab) | 
					
						
						|  | if radius >= place_radius then | 
					
						
						|  | radius_max = Max(radius_max, radius) | 
					
						
						|  | radius_min = Min(radius_min, radius) | 
					
						
						|  | prefabs[#prefabs + 1] = prefab | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if #prefabs == 0 then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | _radius_range = radius_max - radius_min + 1 | 
					
						
						|  |  | 
					
						
						|  | local ptype_idx = ptype_to_idx[ptype] | 
					
						
						|  | local mix_grid_idx = respect_bounds and ptype_idx | 
					
						
						|  | local min_fill_ratio = ptype_preset.MinFillRatio | 
					
						
						|  | local max_fill_error = ptype_preset.MaxFillError | 
					
						
						|  | local max_pass = ptype_preset.FitPasses | 
					
						
						|  | local placed_count = 0 | 
					
						
						|  | for pass = 1, max_pass do | 
					
						
						|  | local start_time_fit = GetPreciseTicks() | 
					
						
						|  | local prefab_list_pass = {} | 
					
						
						|  | local area_remaining, all_area = GridMaskMark(ptype_grid, dist_grid, ptype_idx, ptype_grid_res) | 
					
						
						|  | if not area_remaining then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | local min_area_remaining = all_area - all_area * min_fill_ratio / 100 | 
					
						
						|  | if area_remaining <= min_area_remaining + all_area * max_fill_error / 1000 then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | GridDistance(dist_grid, dist_tile, radius_max * dist_prec) | 
					
						
						|  | if dump then | 
					
						
						|  | local pct_x100 = all_area and MulDivRound(10000, all_area - area_remaining, all_area) or 0 | 
					
						
						|  | dump("\nPASS %d/%d START %s - %2d prefab(s) | form '%s' | min fill %2d%% | filled area %2d.%02d%%\n", pass, max_pass, ptype, #prefabs, ptype_preset.RadiusEstim, min_fill_ratio, pct_x100/100, pct_x100%100) | 
					
						
						|  | end | 
					
						
						|  | rand_init("FindAndRasterPrefabs", pass, ptype) | 
					
						
						|  | local pass_placed_count = 0 | 
					
						
						|  | GridRandomEnumMarkDist(dist_grid, rand(), dist_tile, function(x, y, dist, area) | 
					
						
						|  | if area <= min_area_remaining then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | _radius_target = Max(dist / dist_prec, radius_min) | 
					
						
						|  | _place_x, _place_y = x, y | 
					
						
						|  | local prefab, idx = trand(prefabs, prefab_weight) | 
					
						
						|  | if not prefab then | 
					
						
						|  | g_print("Failed to find prefab for type", ptype) | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | local radius = get_radius(prefab) | 
					
						
						|  | local count, remaining = prefab_add(prefab_list_pass, prefab, x, y, radius, mix_grid_idx, ptype) | 
					
						
						|  | if (prefab.max_count or -1) == count or remaining <= 0 then | 
					
						
						|  | table.remove(prefabs, idx) | 
					
						
						|  | end | 
					
						
						|  | if dump then | 
					
						
						|  | pass_placed_count = pass_placed_count + 1 | 
					
						
						|  | local weight = prefab_weight(prefab) | 
					
						
						|  | local total_weight, min_weight, max_weight = 0 | 
					
						
						|  | for i=1,#prefabs do | 
					
						
						|  | local weight_i = prefab_weight(prefabs[i]) | 
					
						
						|  | total_weight = total_weight + weight_i | 
					
						
						|  | min_weight, max_weight = MinMax(weight_i, min_weight, max_weight) | 
					
						
						|  | end | 
					
						
						|  | local avg_weight = total_weight / #prefabs | 
					
						
						|  | dump("PREFAB %4d | rand 0x%016X | weight %5d (%5d -%5d -%5d) | dist %3d (rad %3d - %3d) | pos (%4d, %4d) | '%s'", | 
					
						
						|  | pass_placed_count, rand_seed, weight, min_weight, avg_weight, max_weight, | 
					
						
						|  | _radius_target, prefab.min_radius, prefab.max_radius, x, y, prefab_markers[prefab]) | 
					
						
						|  | end | 
					
						
						|  | return radius * dist_prec | 
					
						
						|  | end) | 
					
						
						|  | if dump then | 
					
						
						|  | placed_count = placed_count + pass_placed_count | 
					
						
						|  | dump("\nPASS %d/%d END %s PLACED %d TOTAL %d\n", pass, max_pass, ptype, pass_placed_count, placed_count) | 
					
						
						|  | end | 
					
						
						|  | locate_time = locate_time + (GetPreciseTicks() - start_time_fit) | 
					
						
						|  | if #prefab_list_pass == 0 then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | WaitRaster(prefab_list_pass) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | for _, ptype in ipairs(ptypes_found) do | 
					
						
						|  | WaitFitAndRaster(ptype) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local function WaitPlaceAndRasterPois() | 
					
						
						|  | if not gen_mode.POI then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local start_time_poi = GetPreciseTicks() | 
					
						
						|  |  | 
					
						
						|  | local ptype_to_poi_prefabs = {} | 
					
						
						|  | local poi_type_to_ptypes = {} | 
					
						
						|  | local prefab_to_ptype_map = {} | 
					
						
						|  | local prefab_list_poi = {} | 
					
						
						|  |  | 
					
						
						|  | local dbg_poi_count | 
					
						
						|  | local poi_count, poi_marks, poi_types = {}, {}, {} | 
					
						
						|  | local function poi_weight(prefab) | 
					
						
						|  | local weight = MulDivRound(unit_weight, prefab.weight, 100) | 
					
						
						|  | local count = prefabs_count[prefab] or 0 | 
					
						
						|  | if count > 0 then | 
					
						
						|  | local max_count = prefab.max_count or -1 | 
					
						
						|  | weight = max_count > 0 and MulDivRound(weight, max_count - count, max_count) or weight | 
					
						
						|  | end | 
					
						
						|  | return weight | 
					
						
						|  | end | 
					
						
						|  | local tag_to_tag_limits = GetPrefabTagsLimits() | 
					
						
						|  | local slope_grid = self:GetGridInput(self.SlopeGrid) | 
					
						
						|  | local slope_mask | 
					
						
						|  | local function PlacePois(poi_type, poi_area, ptypes, partial_count, max_count) | 
					
						
						|  | local total_count = poi_count[poi_type] or 0 | 
					
						
						|  | if partial_count == 0 or total_count == max_count then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | local prefabs | 
					
						
						|  | local min_radius, max_radius = max_int, 0 | 
					
						
						|  | for _, ptype in ipairs(ptypes) do | 
					
						
						|  | for _, prefab in ipairs(ptype_to_poi_prefabs[ptype]) do | 
					
						
						|  | if poi_type == prefab.poi_type | 
					
						
						|  | and (not poi_area or poi_area == prefab.poi_area) | 
					
						
						|  | and prefab_to_ptype_map[prefab][ptype] | 
					
						
						|  | and IsPrefabAllowed(prefab) then | 
					
						
						|  | prefabs = prefabs or {} | 
					
						
						|  | if not prefabs[prefab] then | 
					
						
						|  | prefabs[prefab] = ptype | 
					
						
						|  | prefabs[#prefabs + 1] = prefab | 
					
						
						|  | local radius = get_radius(prefab) | 
					
						
						|  | max_radius = Max(max_radius, radius) | 
					
						
						|  | min_radius = Min(min_radius, radius) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if not prefabs then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | if dump then | 
					
						
						|  | dump("\nPOI '%s' START {%s} - %2d prefab(s), count %d/%d\n", poi_type, table.concat(ptypes, ","), #prefabs, partial_count, max_count) | 
					
						
						|  | end | 
					
						
						|  | rand_init("PlacePois", poi_type, unpack(ptypes)) | 
					
						
						|  | local ptype_single | 
					
						
						|  | if #ptypes == 1 then | 
					
						
						|  | ptype_single = ptypes[1] | 
					
						
						|  | local ptype_idx = ptype_to_idx[ptype_single] | 
					
						
						|  | if ptype_idx then | 
					
						
						|  | GridMask(ptype_grid, dist_grid, ptype_idx) | 
					
						
						|  | end | 
					
						
						|  | else | 
					
						
						|  | local grid_idx_remap = {} | 
					
						
						|  | for _, ptype in ipairs(ptypes) do | 
					
						
						|  | local ptype_idx = ptype_to_idx[ptype] | 
					
						
						|  | if ptype_idx then | 
					
						
						|  | grid_idx_remap[ptype_idx] = 1 | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | GridReplace(ptype_grid, dist_grid, grid_idx_remap, 0) | 
					
						
						|  | end | 
					
						
						|  | local ptypes_map = table.invert(ptypes) | 
					
						
						|  | local poi_preset = poi_types[poi_type] | 
					
						
						|  | local fill_radius = poi_preset.FillRadius * dist_prec / type_tile | 
					
						
						|  | if fill_radius > 0 then | 
					
						
						|  | GridNot(dist_grid) | 
					
						
						|  | GridDistance(dist_grid, dist_tile, fill_radius) | 
					
						
						|  | GridMask(dist_grid, 0, fill_radius - 1) | 
					
						
						|  | GridDistance(dist_grid, dist_tile, fill_radius) | 
					
						
						|  | GridMask(dist_grid, fill_radius) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local to_mark = {} | 
					
						
						|  | local function MarkDist(x, y, radius, min_dist, max_dist) | 
					
						
						|  | local pos = point_pack(x, y) | 
					
						
						|  | local limits = to_mark[pos] | 
					
						
						|  | if not limits then | 
					
						
						|  | limits = {} | 
					
						
						|  | to_mark[pos] = limits | 
					
						
						|  | end | 
					
						
						|  | if min_dist and min_dist >= 0 then | 
					
						
						|  | limits[1] = Max(limits[1] or min_int, radius + min_dist / type_tile) | 
					
						
						|  | end | 
					
						
						|  | if max_dist and max_dist < max_int then | 
					
						
						|  | limits[2] = Min(limits[2] or max_int, radius + max_dist / type_tile) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local dist_to_same_m = poi_preset.DistToSame | 
					
						
						|  | for _, poi_info in pairs(poi_marks) do | 
					
						
						|  | local poi_type_i, x, y, radius = unpack(poi_info) | 
					
						
						|  | MarkDist(x, y, radius, poi_type_i == poi_type and dist_to_same_m or 0) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | for poi_tag in pairs(poi_preset.Tags) do | 
					
						
						|  | for tag, limits in pairs(tag_to_tag_limits[poi_tag]) do | 
					
						
						|  | local min_dist, max_dist = limits[1], limits[2] | 
					
						
						|  | for _, loc in ipairs(prefab_tag_loc[tag]) do | 
					
						
						|  | local x, y, radius = point_unpack(loc) | 
					
						
						|  | MarkDist(x, y, radius, min_dist, max_dist) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local GridCircleSet = GridCircleSet | 
					
						
						|  | local limit_dist | 
					
						
						|  | for pos, limits in pairs(to_mark) do | 
					
						
						|  | local x, y = point_unpack(pos) | 
					
						
						|  | local min_dist, max_dist = limits[1], limits[2] | 
					
						
						|  | if min_dist and min_dist >= 0 then | 
					
						
						|  | GridCircleSet(dist_grid, 0, x, y, min_dist, 0, work_ratio) | 
					
						
						|  | end | 
					
						
						|  | if max_dist and max_dist < max_int then | 
					
						
						|  | if not limit_dist then | 
					
						
						|  | dist_mask = dist_mask or GridDest(dist_grid) | 
					
						
						|  | dist_mask:clear() | 
					
						
						|  | limit_dist = true | 
					
						
						|  | end | 
					
						
						|  | GridCircleSet(dist_mask, 1, x, y, max_dist, 0, work_ratio) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if limit_dist then | 
					
						
						|  | GridAnd(dist_grid, dist_mask) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local frame = (mapdata.PassBorder - poi_preset.DistToPlayable) / type_tile | 
					
						
						|  | if frame > 0 then | 
					
						
						|  | if frame >= gw / 2 or frame >= gh / 2 then | 
					
						
						|  | g_print("Dist To Playable Area for", poi_type, "leaves no available space for placement!") | 
					
						
						|  | end | 
					
						
						|  | GridFrame(dist_grid, frame, 0) | 
					
						
						|  | end | 
					
						
						|  | local slope_min, slope_max = poi_preset.TerrainSlopeMin, poi_preset.TerrainSlopeMax | 
					
						
						|  | if slope_grid and (slope_min > 0 or slope_max < 90*60) then | 
					
						
						|  | if not slope_mask then | 
					
						
						|  | slope_mask = GridDest(dist_grid) | 
					
						
						|  | slope_grid = GridMakeSame(slope_grid, slope_mask) | 
					
						
						|  | end | 
					
						
						|  | GridMask(slope_grid, slope_mask, slope_min, slope_max) | 
					
						
						|  | GridAnd(dist_grid, slope_mask) | 
					
						
						|  | end | 
					
						
						|  | GridDistance(dist_grid, dist_tile, max_radius * dist_prec) | 
					
						
						|  |  | 
					
						
						|  | if step_poi then | 
					
						
						|  | DbgClear() | 
					
						
						|  | local show_grid, poi_grid = GridDest(dist_grid), GridDest(dist_grid) | 
					
						
						|  | GridMask(dist_grid, show_grid, 1, max_int) | 
					
						
						|  | local colors = { yellow, green, cyan, purple, orange, white, black } | 
					
						
						|  | local palette = { [0] = 0, red } | 
					
						
						|  | for i, prefab in ipairs(prefabs) do | 
					
						
						|  | local radius = get_radius(prefab) | 
					
						
						|  | GridMask(dist_grid, poi_grid, radius * dist_prec, max_int) | 
					
						
						|  | GridAdd(show_grid, poi_grid) | 
					
						
						|  | palette[i + 1] = palette[i + 1] or colors[1 + (i - 1) % #colors] | 
					
						
						|  | end | 
					
						
						|  | DbgShowTerrainGrid(show_grid, palette) | 
					
						
						|  | step("POI %s START {%s}: %2d prefab(s) available", poi_type, table.concat(ptypes, ","), #prefabs) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | local placed_count = 0 | 
					
						
						|  | local place_model = poi_preset.PlaceModel | 
					
						
						|  | local skip_raster = place_model ~= "terrain" | 
					
						
						|  | local place_mark = place_model ~= "point" | 
					
						
						|  | local dist_to_same = dist_to_same_m / type_tile | 
					
						
						|  | local prefab, idx, radius, max_radius, poi_radius | 
					
						
						|  | GridRandomFetchMarkDist(dist_grid, rand(), dist_tile, function(x, y, dist) | 
					
						
						|  | if idx then | 
					
						
						|  | if x < 0 then | 
					
						
						|  | table.remove(prefabs, idx) | 
					
						
						|  | else | 
					
						
						|  | local ptype = ptype_single | 
					
						
						|  | if not ptype then | 
					
						
						|  | local ptype_idx = ptype_grid:get(x, y) | 
					
						
						|  | ptype = idx_to_ptype[ptype_idx] | 
					
						
						|  | if not ptype or not ptypes_map[ptype] then | 
					
						
						|  | ptype = prefabs[prefab] | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | assert(prefab_to_ptype_map[prefab][ptype]) | 
					
						
						|  | local count, remaining = prefab_add(prefab_list_poi, prefab, x, y, radius, false, ptype, true, skip_raster) | 
					
						
						|  | if (prefab.max_count or -1) == count or remaining <= 0 then | 
					
						
						|  | table.remove(prefabs, idx) | 
					
						
						|  | end | 
					
						
						|  | if place_mark then | 
					
						
						|  | poi_marks[#poi_marks + 1] = { poi_type, x, y, radius } | 
					
						
						|  | end | 
					
						
						|  | placed_count = placed_count + 1 | 
					
						
						|  | total_count = total_count + 1 | 
					
						
						|  | if debug then | 
					
						
						|  | if dump then | 
					
						
						|  | dump("POI %4d | rand 0x%016X | pos (%4d, %4d) | '%s'", placed_count, rand_seed, x, y, prefab_markers[prefab]) | 
					
						
						|  | end | 
					
						
						|  | if step_poi then | 
					
						
						|  | local mx, my = x * work_step, y * work_step | 
					
						
						|  | DbgShowPrefab(point(mx, my), prefab_markers[prefab], cyan, total_count, radius, poi_radius) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if placed_count == partial_count or total_count == max_count then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | prefab, idx = trand(prefabs, poi_weight) | 
					
						
						|  | if not prefab then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | radius = get_radius(prefab) | 
					
						
						|  | poi_radius = radius + dist_to_same | 
					
						
						|  | return radius * dist_prec, poi_radius * dist_prec | 
					
						
						|  | end) | 
					
						
						|  | poi_count[poi_type] = total_count | 
					
						
						|  | if debug then | 
					
						
						|  | local poi_name = poi_area and (poi_type .. "." .. poi_area) or poi_type | 
					
						
						|  | dbg_poi_count = dbg_poi_count or {} | 
					
						
						|  | dbg_poi_count[poi_name] = (dbg_poi_count[poi_name] or 0) + placed_count | 
					
						
						|  | local types_str = table.concat(ptypes, ",") | 
					
						
						|  | if step_poi then | 
					
						
						|  | step("POI %s END {%s}: %2d prefab(s) placed", poi_type, types_str, total_count) | 
					
						
						|  | end | 
					
						
						|  | if dump then | 
					
						
						|  | dump("\nPOI '%s' END {%s} PLACED %d TOTAL\n", poi_type, types_str, placed_count, total_count) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | return placed_count | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | for _, prefab in ipairs(prefab_markers) do | 
					
						
						|  | local poi_type = prefab.poi_type or "" | 
					
						
						|  | if poi_type ~= "" then | 
					
						
						|  | local poi_preset = poi_type_to_preset[poi_type] | 
					
						
						|  | if not poi_preset then | 
					
						
						|  | g_print("No such POI type", poi_type, "selected in", prefab_markers[prefab]) | 
					
						
						|  | else | 
					
						
						|  | local ptype_map | 
					
						
						|  | if #poi_preset.CustomTypes > 0 then | 
					
						
						|  | ptype_map = table.invert(poi_preset.CustomTypes) | 
					
						
						|  | elseif #poi_preset.PrefabTypeGroups > 0 then | 
					
						
						|  | local group = table.find_value(poi_preset.PrefabTypeGroups, "id", prefab.poi_area) or empty_table | 
					
						
						|  | ptype_map = table.invert(group.types) | 
					
						
						|  | else | 
					
						
						|  | ptype_map = {[prefab.type] = true} | 
					
						
						|  | end | 
					
						
						|  | assert(next(ptype_map)) | 
					
						
						|  | prefab_to_ptype_map[prefab] = ptype_map | 
					
						
						|  | local all_ptypes = poi_type_to_ptypes[poi_type] | 
					
						
						|  | if not all_ptypes then | 
					
						
						|  | poi_type_to_ptypes[poi_type] = ptype_map | 
					
						
						|  | else | 
					
						
						|  | table.append(all_ptypes, ptype_map) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | for ptype in pairs(ptype_map) do | 
					
						
						|  | if ptype_to_idx[ptype] then | 
					
						
						|  | ptype_to_poi_prefabs[ptype] = table.create_add_unique(ptype_to_poi_prefabs[ptype], prefab) | 
					
						
						|  | if not poi_types[poi_type] then | 
					
						
						|  | poi_types[poi_type] = poi_preset | 
					
						
						|  | poi_types[#poi_types + 1] = poi_type | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local PoiCmp = PrefabPOI.Compare | 
					
						
						|  | table.sort(poi_types, function (poi1, poi2) | 
					
						
						|  | local preset1, preset2 = poi_types[poi1], poi_types[poi2] | 
					
						
						|  | return PoiCmp(preset1, preset2) | 
					
						
						|  | end) | 
					
						
						|  |  | 
					
						
						|  | if dump then | 
					
						
						|  | dump("\n----\nPersisted prefabs loaded: %d", #(persisted_prefabs or "")) | 
					
						
						|  | dump("Persistable tag counters: %s\n", TableToLuaCode(persisted_tag_count)) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | DbgClear() | 
					
						
						|  | local persisted_placed = 0 | 
					
						
						|  | for i, entry in ipairs(persisted_prefabs) do | 
					
						
						|  | local name, ptype, x, y, angle = unpack(entry) | 
					
						
						|  | local prefab = prefab_markers[name] | 
					
						
						|  | local poi_type = prefab.poi_type or "" | 
					
						
						|  | local poi_preset = poi_types[poi_type] | 
					
						
						|  | if not poi_preset then | 
					
						
						|  | g_print("Persisted prefab", name, "has invalid POI type", poi_type) | 
					
						
						|  | else | 
					
						
						|  | if not prefab_to_ptype_map[prefab][ptype] then | 
					
						
						|  | g_print("Persisted prefab", name, "has invalid prefab type", ptype) | 
					
						
						|  | end | 
					
						
						|  | get_radius = radius_getters[poi_preset.RadiusEstim] | 
					
						
						|  | local radius = get_radius(prefab) | 
					
						
						|  | local place_model = poi_preset.PlaceModel | 
					
						
						|  | local skip_raster = place_model ~= "terrain" | 
					
						
						|  | local place_mark = place_model ~= "point" | 
					
						
						|  | if place_mark then | 
					
						
						|  | poi_marks[#poi_marks + 1] = { poi_type, x, y, radius } | 
					
						
						|  | end | 
					
						
						|  | local show | 
					
						
						|  | for tag in pairs(poi_preset.Tags) do | 
					
						
						|  | for other_tag, limits in pairs(tag_to_tag_limits[tag]) do | 
					
						
						|  | local min_dist, max_dist = limits[1], limits[2] | 
					
						
						|  | for _, loc in ipairs(prefab_tag_loc[other_tag]) do | 
					
						
						|  | local xi, yi, radiusi = point_unpack(loc) | 
					
						
						|  | local dx, dy = x - xi, y - yi | 
					
						
						|  | local d = (sqrt(dx*dx + dy*dy) - radius - radiusi) * type_tile | 
					
						
						|  | local is_err | 
					
						
						|  | if min_dist and min_dist >= 0 and d < min_dist then | 
					
						
						|  | g_print("Persisted prefab", i, name, "is too close to tag", other_tag, "(", d, "/", min_dist, ")") | 
					
						
						|  | is_err = true | 
					
						
						|  | end | 
					
						
						|  | if max_dist and max_dist >= 0 and d > max_dist then | 
					
						
						|  | g_print("Persisted prefab", i, name, "is too far from tag", other_tag, "(", d, "/", max_dist, ")") | 
					
						
						|  | is_err = true | 
					
						
						|  | end | 
					
						
						|  | if is_err then | 
					
						
						|  | show = true | 
					
						
						|  | local pos = point(x, y) * type_tile | 
					
						
						|  | local posi = point(xi, yi) * type_tile | 
					
						
						|  | local r = radius * type_tile | 
					
						
						|  | local ri = radiusi * type_tile | 
					
						
						|  | DbgAddCircle(posi, ri, red) | 
					
						
						|  | DbgAddSegment(pos, posi, yellow) | 
					
						
						|  | if dump then | 
					
						
						|  | dump("DbgClear(); DbgAddCircle(%s, %d); DbgAddCircle(%s, %d, red); DbgAddSegment(%s, %s, yellow); ViewPos(%s, %d)\n", | 
					
						
						|  | ValueToLuaCode(pos), r, | 
					
						
						|  | ValueToLuaCode(posi), ri, | 
					
						
						|  | ValueToLuaCode(pos), ValueToLuaCode(posi), | 
					
						
						|  | ValueToLuaCode((pos + posi) / 2), pos:Dist2D(posi) + ri + r) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if show then | 
					
						
						|  | local pos = point(x, y) * type_tile | 
					
						
						|  | DbgAddText(string.format("[%d] %s", i, name), ValidateZ(pos):AddZ(101*guim)) | 
					
						
						|  | DbgAddVector(pos, 100*guim) | 
					
						
						|  | DbgAddCircle(pos, radius * type_tile) | 
					
						
						|  | end | 
					
						
						|  | local total_count = poi_count[poi_type] or 0 | 
					
						
						|  | total_count = total_count + 1 | 
					
						
						|  | poi_count[poi_type] = total_count | 
					
						
						|  | prefab_add(prefab_list_poi, prefab, x, y, radius, false, ptype, true, skip_raster, angle) | 
					
						
						|  | persisted_placed = persisted_placed + 1 | 
					
						
						|  | if debug then | 
					
						
						|  | if dump then | 
					
						
						|  | dump("PERSIST %3d | pos (%4d, %4d) | angle %6d | '%s'", persisted_placed, x, y, angle, name) | 
					
						
						|  | end | 
					
						
						|  | if step_poi then | 
					
						
						|  | local mx, my = x * work_step, y * work_step | 
					
						
						|  | DbgShowPrefab(point(mx, my), name, red, total_count, radius, poi_radius) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | for _, poi_type in ipairs(poi_types) do | 
					
						
						|  | local poi_preset = poi_types[poi_type] | 
					
						
						|  | local poi_max_count = -1 | 
					
						
						|  | if poi_preset.MaxCount ~= -1 then | 
					
						
						|  | poi_max_count = rand(Max(poi_preset.MinCount, 0), poi_preset.MaxCount) | 
					
						
						|  | end | 
					
						
						|  | local orig_max_count = poi_max_count | 
					
						
						|  | get_radius = radius_getters[poi_preset.RadiusEstim] | 
					
						
						|  | local custom_types = poi_preset.CustomTypes | 
					
						
						|  | local type_groups = poi_preset.PrefabTypeGroups | 
					
						
						|  |  | 
					
						
						|  | if dump then | 
					
						
						|  | dump("\n----\nPOITYPE '%s' %s", poi_type, TableToLuaCode(poi_preset)) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | if #custom_types > 0 then | 
					
						
						|  | PlacePois(poi_type, false, custom_types, poi_max_count, orig_max_count) | 
					
						
						|  | elseif #type_groups > 0 then | 
					
						
						|  | local total_area = 0 | 
					
						
						|  | if poi_max_count ~= -1 then | 
					
						
						|  | for _, group in ipairs(type_groups) do | 
					
						
						|  | for _, ptype in ipairs(group.types) do | 
					
						
						|  | total_area = total_area + (ptype_to_area[ptype] or 0) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | for i, group in ipairs(type_groups) do | 
					
						
						|  | local count = poi_max_count | 
					
						
						|  | if total_area > 0 and count ~= -1 and i ~= #type_groups then | 
					
						
						|  | local group_area = 0 | 
					
						
						|  | for _, ptype in ipairs(group.types) do | 
					
						
						|  | group_area = group_area + (ptype_to_area[ptype] or 0) | 
					
						
						|  | end | 
					
						
						|  | count = MulDivRound(poi_max_count, group_area, total_area) | 
					
						
						|  | total_area = total_area - group_area | 
					
						
						|  | end | 
					
						
						|  | count = PlacePois(poi_type, group.id, group.types, count, orig_max_count) | 
					
						
						|  | if count and poi_max_count ~= -1 then | 
					
						
						|  | poi_max_count = poi_max_count - count | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | else | 
					
						
						|  | local ptype_map = poi_type_to_ptypes[poi_type] | 
					
						
						|  | local ptypes = table_keys(ptype_map, true) | 
					
						
						|  | local total_area = 0 | 
					
						
						|  | if poi_max_count ~= -1 then | 
					
						
						|  | for _, ptype in ipairs(ptypes) do | 
					
						
						|  | total_area = total_area + (ptype_to_area[ptype] or 0) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | for i, ptype in ipairs(ptypes) do | 
					
						
						|  | local count = poi_max_count | 
					
						
						|  | if total_area > 0 and count ~= -1 and i ~= #ptypes then | 
					
						
						|  | local ptype_area = (ptype_to_area[ptype] or 0) | 
					
						
						|  | count = MulDivRound(poi_max_count, ptype_area, total_area) | 
					
						
						|  | poi_max_count = poi_max_count - count | 
					
						
						|  | total_area = total_area - ptype_area | 
					
						
						|  | end | 
					
						
						|  | count = PlacePois(poi_type, false, { ptype }, count, orig_max_count) | 
					
						
						|  | if count and poi_max_count ~= -1 then | 
					
						
						|  | poi_max_count = poi_max_count - count | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local placed_count = poi_count[poi_type] or 0 | 
					
						
						|  | if placed_count < poi_preset.MinCount then | 
					
						
						|  | g_print("Not all", poi_type, "prefabs are placed:", placed_count, "/", poi_preset.MinCount) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | poi_time = GetPreciseTicks() - start_time_poi | 
					
						
						|  | WaitRaster(prefab_list_poi) | 
					
						
						|  | if debug then | 
					
						
						|  | if step_poi then | 
					
						
						|  | DbgShowTerrainGrid(false) | 
					
						
						|  | end | 
					
						
						|  | local tmp = {} | 
					
						
						|  | for name, count in pairs(dbg_poi_count) do | 
					
						
						|  | tmp[#tmp + 1] = {name = name, count = count} | 
					
						
						|  | end | 
					
						
						|  | self.dbg_placed_poi = tmp | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | WaitPlaceAndRasterPois() | 
					
						
						|  |  | 
					
						
						|  | free_grid(dist_grid) | 
					
						
						|  | free_grid(dist_mask) | 
					
						
						|  | placed_marks = GridLevels(mark_grid) | 
					
						
						|  | if debug then | 
					
						
						|  | self.LocateTime = locate_time | 
					
						
						|  | self.RasterizeTime = raster_time | 
					
						
						|  | self.PoiTime = poi_time | 
					
						
						|  | self.PrefabCount = #prefab_list | 
					
						
						|  | assert(add_idx == #prefab_list) | 
					
						
						|  | local list = {} | 
					
						
						|  | local prefab_hash | 
					
						
						|  | for i, info in ipairs(prefab_list) do | 
					
						
						|  | local prefab, raster = unpack(info) | 
					
						
						|  | list[i] = { prefab_markers[prefab], raster.pos } | 
					
						
						|  | if debug then | 
					
						
						|  | prefab_hash = xxhash(prefab.hash, prefab_hash) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | self.PrefabHash = prefab_hash | 
					
						
						|  | self.MixHash = xxhash(ptype_grid) | 
					
						
						|  | self.MarkHash = xxhash(mark_grid) | 
					
						
						|  | self.PrefabList = list | 
					
						
						|  | self.RasterMem = peak_memory | 
					
						
						|  | local min, max = GridMinMax(overlap_grid) | 
					
						
						|  | local all = GridCount(overlap_grid, 0, max_int) | 
					
						
						|  | local overlap = GridCount(overlap_grid, 1, max_int) | 
					
						
						|  | self.OverlapMax = max | 
					
						
						|  | self.OverlapPct = to_pct(overlap, all) | 
					
						
						|  |  | 
					
						
						|  | local prefabs_to_raster = 0 | 
					
						
						|  | local visible_prefabs = {} | 
					
						
						|  | local tmp = {} | 
					
						
						|  | for idx, info in ipairs(prefab_list) do | 
					
						
						|  | local prefab, raster = unpack(info) | 
					
						
						|  | if raster.place_mask_idx then | 
					
						
						|  | prefabs_to_raster = prefabs_to_raster + 1 | 
					
						
						|  | local visible_area = Min(placed_marks[idx] or 0, prefab.total_area) | 
					
						
						|  | assert(visible_area <= prefab.total_area) | 
					
						
						|  | local completely_hidden = visible_area == 0 and 1 or 0 | 
					
						
						|  | local stat = tmp[prefab] or {} | 
					
						
						|  | stat.area = (stat.area or 0) + visible_area | 
					
						
						|  | stat.hidden = (stat.hidden or 0) + completely_hidden | 
					
						
						|  | stat.count = (stat.count or 0) + 1 | 
					
						
						|  | tmp[prefab] = stat | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | for prefab, stat in pairs(tmp) do | 
					
						
						|  | local max_area = prefab.total_area * stat.count | 
					
						
						|  | assert(max_area > 0 and stat.area * work_ratio <= max_area) | 
					
						
						|  | visible_prefabs[#visible_prefabs + 1] = { | 
					
						
						|  | name = prefab_markers[prefab], | 
					
						
						|  | visible_area = max_area > 0 and MulDivRound(pct_100, stat.area * work_ratio, max_area) or 0, | 
					
						
						|  | fully_hidden = MulDivRound(pct_100, stat.hidden, stat.count), | 
					
						
						|  | } | 
					
						
						|  | end | 
					
						
						|  | self.dbg_visible_prefabs = visible_prefabs | 
					
						
						|  |  | 
					
						
						|  | self.PrefabVisible = prefabs_to_raster > 0 and to_pct(table.count(placed_marks), prefabs_to_raster) or 0 | 
					
						
						|  | local mix_area = gw * gh | 
					
						
						|  | local area_spill, area_uncovered = GridGetCover(mark_grid, ptype_grid, cover_grid) | 
					
						
						|  | self.AreaUncovered = to_pct(area_uncovered, mix_area) | 
					
						
						|  | self.AreaSpill = to_pct(area_spill, mix_area) | 
					
						
						|  |  | 
					
						
						|  | local tmp = {} | 
					
						
						|  | local w, h = ptype_grid:size() | 
					
						
						|  | for ptype, area in pairs(ptype_to_area) do | 
					
						
						|  | local stats = { | 
					
						
						|  | name = ptype, | 
					
						
						|  | area = pct_mul * 100 * area / (w * h), | 
					
						
						|  | prefabs = table.count(prefab_list, PREFAB_TYPE, ptype) | 
					
						
						|  | } | 
					
						
						|  | tmp[#tmp + 1] = stats | 
					
						
						|  | end | 
					
						
						|  | self.dbg_prefab_types = tmp | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local function PlacePrefabObjects() | 
					
						
						|  | if not gen_mode.Objects then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | local start_time_po = GetPreciseTicks() | 
					
						
						|  | rand_init("PlacePrefabObjects") | 
					
						
						|  |  | 
					
						
						|  | local IsValidPos = CObject.IsValidPos | 
					
						
						|  | local GetClassFlags = CObject.GetClassFlags | 
					
						
						|  | local SetGameFlags = CObject.SetGameFlags | 
					
						
						|  | local GetGameFlags = CObject.GetGameFlags | 
					
						
						|  | local SetCollectionIndex = CObject.SetCollectionIndex | 
					
						
						|  | local ClearCachedZ = CObject.ClearCachedZ | 
					
						
						|  | local GridGetMark = GridGetMark | 
					
						
						|  | local unpack = table.unpack | 
					
						
						|  | local g_Classes = g_Classes | 
					
						
						|  | local IsValid = IsValid | 
					
						
						|  | local handle_provider = empty_func | 
					
						
						|  |  | 
					
						
						|  | if self.gen_handles then | 
					
						
						|  | local first_handle, handle_size = GetHandlesAutoLimits() | 
					
						
						|  | local first_handle_pool, handle_pool_size, handle_pool = GetHandlesAutoPoolLimits() | 
					
						
						|  | local last_handle = first_handle + handle_size - 1 | 
					
						
						|  | local last_handle_pool = first_handle_pool + handle_pool_size - handle_pool | 
					
						
						|  | local system_handles = 1000 | 
					
						
						|  | local start_marker_handle = first_handle | 
					
						
						|  | local next_handle = first_handle + system_handles + 1 | 
					
						
						|  | local next_handle_pool = first_handle_pool | 
					
						
						|  | local handle_collisions = 0 | 
					
						
						|  | local handle_to_prefab = {} | 
					
						
						|  | local handle_to_object = HandleToObject | 
					
						
						|  | local IsKindOf = IsKindOf | 
					
						
						|  | handle_provider = function(current_prefab, classname, reserved_handles, backwards) | 
					
						
						|  | if not reserved_handles then | 
					
						
						|  | local classdef = classname and g_Classes[classname] | 
					
						
						|  | if not classdef or not IsKindOf(classdef, "Object") then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | reserved_handles = classdef.reserved_handles | 
					
						
						|  | end | 
					
						
						|  | local handle | 
					
						
						|  | if not reserved_handles or reserved_handles == 0 then | 
					
						
						|  | if last_handle <= next_handle then | 
					
						
						|  | assert(false, "No more handles!") | 
					
						
						|  | return false, "handles" | 
					
						
						|  | elseif backwards then | 
					
						
						|  | handle = last_handle | 
					
						
						|  | last_handle = handle - 1 | 
					
						
						|  | else | 
					
						
						|  | handle = next_handle | 
					
						
						|  | next_handle = handle + 1 | 
					
						
						|  | end | 
					
						
						|  | else | 
					
						
						|  | if last_handle_pool <= next_handle_pool then | 
					
						
						|  | assert(false, "No more handle pools!") | 
					
						
						|  | return false, "handles" | 
					
						
						|  | elseif backwards then | 
					
						
						|  | handle = last_handle_pool | 
					
						
						|  | last_handle_pool = handle - handle_pool | 
					
						
						|  | else | 
					
						
						|  | handle = next_handle_pool | 
					
						
						|  | next_handle_pool = handle + handle_pool | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | assert(handle > 0) | 
					
						
						|  | local existing_obj = handle_to_object[handle] | 
					
						
						|  | if IsValid(existing_obj) then | 
					
						
						|  | handle_collisions = handle_collisions + 1 | 
					
						
						|  | if handle_collisions == 100 then | 
					
						
						|  | assert(false, "Blank map used for generation isn't empty!") | 
					
						
						|  | return false, "map" | 
					
						
						|  | end | 
					
						
						|  | if backwards then | 
					
						
						|  | return false, "collision" | 
					
						
						|  | end | 
					
						
						|  | if handle_collisions < 100 and classname then | 
					
						
						|  | local prev_prefab = handle_to_prefab[handle] | 
					
						
						|  | g_print("Duplicated handle", handle, "\nNew object is", classname, "from", prefab_markers[current_prefab], "\nExisting object is", existing_obj.class, "from", prefab_markers[prev_prefab] or "map", "\n") | 
					
						
						|  | end | 
					
						
						|  | local new_handle, handle_err | 
					
						
						|  | if handle - start_marker_handle > system_handles then | 
					
						
						|  | while true do | 
					
						
						|  | new_handle, handle_err = handle_provider(current_prefab, existing_obj.class, existing_obj.reserved_handles, true) | 
					
						
						|  | if handle_err ~= "collision" then | 
					
						
						|  | break | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if new_handle then | 
					
						
						|  | existing_obj.handle = new_handle | 
					
						
						|  | handle_to_object[handle] = nil | 
					
						
						|  | handle_to_object[new_handle] = existing_obj | 
					
						
						|  | else | 
					
						
						|  | g_print("Replacing existing object", existing_obj.class, existing_obj.handle, "by", classname) | 
					
						
						|  | DoneObject(existing_obj) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | handle_to_prefab[handle] = current_prefab | 
					
						
						|  | return handle | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local placed_objects, object_source = {}, {} | 
					
						
						|  | local obj_count = 0 | 
					
						
						|  | local game_flags = gofPermanent | gofGenerated | 
					
						
						|  | local function PlacedObject(obj, mark, prefab) | 
					
						
						|  | mark = mark or 0 | 
					
						
						|  | local prev_mark = placed_objects[obj] | 
					
						
						|  | assert(not prev_mark or prev_mark == mark) | 
					
						
						|  | if prev_mark == mark then return end | 
					
						
						|  | placed_objects[obj] = mark or 0 | 
					
						
						|  | object_source[obj] = prefab | 
					
						
						|  | obj_count = obj_count + 1 | 
					
						
						|  | SetGameFlags(obj, game_flags) | 
					
						
						|  |  | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | local max_chance = 100 * 1000 | 
					
						
						|  | local optional_chance = self.OptionalChance * 1000 | 
					
						
						|  | local steep_slope_cos = cos(self.SteepSlope) | 
					
						
						|  | local rem_faded_objs = self.RemFadedObjs | 
					
						
						|  | local use_mesh_overlap = self.UseMeshOverlap | 
					
						
						|  | local removed_count = 0 | 
					
						
						|  | local RemoveObject = DoneObject | 
					
						
						|  | local SkippedObject = empty_func | 
					
						
						|  |  | 
					
						
						|  | if debug then | 
					
						
						|  | RemoveObject = function(obj) | 
					
						
						|  | DoneObject(obj) | 
					
						
						|  | removed_count = removed_count + 1 | 
					
						
						|  | end | 
					
						
						|  | SkippedObject = function() | 
					
						
						|  | removed_count = removed_count + 1 | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local OVRLP_DEL_NONE, OVRLP_DEL_ALL, OVRLP_DEL_IGNORE, OVRLP_DEL_PARTIAL, OVRLP_DEL_SINGLE = false, 0, 1, 2, 3 | 
					
						
						|  |  | 
					
						
						|  | local rem_coll_count = 0 | 
					
						
						|  | local obj_to_coll, coll_to_objs, coll_indice, coll_is_partial, removed_coll = {}, {}, {}, {}, {} | 
					
						
						|  | local function RemoveColl(idx, nested_colls) | 
					
						
						|  | if removed_coll[idx] then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | removed_coll[idx] = true | 
					
						
						|  | if not nested_colls then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | for _, sub_idx in ipairs(nested_colls[idx]) do | 
					
						
						|  | RemoveColl(sub_idx, nested_colls) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local prefab_defs = {} | 
					
						
						|  | local class_to_defaults = {} | 
					
						
						|  | local last_col_idx, placed_collections = 0, 0 | 
					
						
						|  | local collections = Collections | 
					
						
						|  |  | 
					
						
						|  | local rmfOptionalPlacement = const.rmfOptionalPlacement | 
					
						
						|  | local rmfMeshOverlapCheck = const.rmfMeshOverlapCheck | 
					
						
						|  | local rmfDeleteOnSteepSlope = const.rmfDeleteOnSteepSlope | 
					
						
						|  | local cofComponentRandomMap = const.cofComponentRandomMap | 
					
						
						|  | local max_collection_idx = const.GameObjectMaxCollectionIndex | 
					
						
						|  | local base_prop_count = const.RandomMap.PrefabBasePropCount | 
					
						
						|  |  | 
					
						
						|  | local GetPrefabFileObjs = GetPrefabFileObjs | 
					
						
						|  | local AsyncFileToString = async.AsyncFileToString | 
					
						
						|  | local Unserialize = Unserialize | 
					
						
						|  | local GetDefRandomMapFlags = GetDefRandomMapFlags | 
					
						
						|  | local GetPrefabObjPos = GetPrefabObjPos | 
					
						
						|  | local SetPrefabObjPos = SetPrefabObjPos | 
					
						
						|  | local PropObjSetProperty = PropObjSetProperty | 
					
						
						|  | local SetRandomMapFlags = CObject.SetRandomMapFlags | 
					
						
						|  | local selected_break = self.SelectedBreak and self.SelectedMark or 0 | 
					
						
						|  | local abort_prefab_placement | 
					
						
						|  |  | 
					
						
						|  | local function PlacePrefab(prefab, prefab_pos, prefab_angle, mark, ptype, bbox) | 
					
						
						|  | if abort_prefab_placement then | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | assert(prefab_pos:IsValidZ()) | 
					
						
						|  |  | 
					
						
						|  | if dump then | 
					
						
						|  | local x, y = prefab_pos:xy() | 
					
						
						|  | dump("PlacePrefab %4d | pos (%7d, %7d) | angle %7d | '%s' ----------------------", mark, x, y, prefab_angle, prefab_markers[prefab]) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | mark = mark_grid and mark or 0 | 
					
						
						|  |  | 
					
						
						|  | if selected_break > 0 then | 
					
						
						|  | bp(selected_break == mark) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local ptype_preset = ptype_to_preset[ptype] or empty_table | 
					
						
						|  | local on_obj_overlap = ptype_preset.OnObjOverlap | 
					
						
						|  | local ignore_colls = on_obj_overlap == OVRLP_DEL_IGNORE | 
					
						
						|  | local ignore_partial_colls = on_obj_overlap == OVRLP_DEL_PARTIAL | 
					
						
						|  | local delete_no_colls = on_obj_overlap == OVRLP_DEL_SINGLE | 
					
						
						|  |  | 
					
						
						|  | local objs | 
					
						
						|  | local nested_colls = prefab.nested_colls | 
					
						
						|  | local nested_opt_objs = prefab.nested_opt_objs | 
					
						
						|  | local save_collections = prefab.save_collections | 
					
						
						|  | local defs = prefab_defs[prefab] | 
					
						
						|  | if defs == nil then | 
					
						
						|  | local load_time_start = GetPreciseTicks() | 
					
						
						|  | local name = prefab_markers[prefab] | 
					
						
						|  | if not exported_prefabs[name] then | 
					
						
						|  | g_print("no such exported prefab", name) | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | local filename = GetPrefabFileObjs(name) | 
					
						
						|  | local err, bin = AsyncFileToString(nil, filename, nil, nil, "pstr") | 
					
						
						|  | if err then | 
					
						
						|  | g_print("failed to load prefab", name, ":", err) | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | defs = Unserialize(bin) | 
					
						
						|  | if not defs then | 
					
						
						|  | g_print("failed to unserialize objects from prefab", name) | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  | prefab_defs[prefab] = (prefabs_count[prefab] or max_int) > 1 and defs or false | 
					
						
						|  | if debug then | 
					
						
						|  | AddPrefabStat(prefab, "obj_load_time", GetPreciseTicks() - load_time_start) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if not defs then | 
					
						
						|  | g_print("Uncached prefab", prefab_markers[prefab]) | 
					
						
						|  | return | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local place_time_start = GetPreciseTicks() | 
					
						
						|  | objs = {} | 
					
						
						|  | for _, def in ipairs(defs) do | 
					
						
						|  | do | 
					
						
						|  | local class, dpos, angle, daxis, | 
					
						
						|  | scale, rmf_flags, fade_dist, | 
					
						
						|  | ground_offset, normal_offset, | 
					
						
						|  | coll_idx, color, mirror = unpack(def, 1, base_prop_count) | 
					
						
						|  |  | 
					
						
						|  | assert(class ~= "Collection") | 
					
						
						|  | assert(not coll_idx or coll_idx ~= 0) | 
					
						
						|  |  | 
					
						
						|  | local classdef, entity, default_rmf_flags | 
					
						
						|  | local defaults = class_to_defaults[class] | 
					
						
						|  | if defaults then | 
					
						
						|  | classdef, entity, default_rmf_flags = unpack(defaults) | 
					
						
						|  | else | 
					
						
						|  | classdef = g_Classes[class] | 
					
						
						|  | assert(classdef) | 
					
						
						|  | if classdef then | 
					
						
						|  | default_rmf_flags = GetDefRandomMapFlags(classdef) | 
					
						
						|  | entity = classdef.entity or class | 
					
						
						|  | end | 
					
						
						|  | class_to_defaults[class] = {classdef, entity, default_rmf_flags} | 
					
						
						|  | end | 
					
						
						|  | if not classdef then | 
					
						
						|  | goto continue | 
					
						
						|  | end | 
					
						
						|  | rmf_flags = rmf_flags or default_rmf_flags | 
					
						
						|  |  | 
					
						
						|  | if optional_chance > 0 and (rmf_flags & rmfOptionalPlacement) ~= 0 then | 
					
						
						|  | local reduction = coll_idx ~= 0 and nested_opt_objs and nested_opt_objs[coll_idx] or 1 | 
					
						
						|  | local chance = reduction > 1 and Max(1, optional_chance / reduction) or optional_chance | 
					
						
						|  | if crand(chance, max_chance) then | 
					
						
						|  | if coll_idx then | 
					
						
						|  | RemoveColl(coll_idx, nested_colls) | 
					
						
						|  | end | 
					
						
						|  | SkippedObject() | 
					
						
						|  | goto continue | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | local check_mark | 
					
						
						|  | if on_obj_overlap then | 
					
						
						|  | check_mark = bbox and mark | 
					
						
						|  | if coll_idx then | 
					
						
						|  | if removed_coll[coll_idx] then | 
					
						
						|  | SkippedObject() | 
					
						
						|  | goto continue | 
					
						
						|  | end | 
					
						
						|  | if ignore_partial_colls or delete_no_colls then | 
					
						
						|  | check_mark = nil | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local mesh_overlap = use_mesh_overlap and (rmf_flags & rmfMeshOverlapCheck) ~= 0 | 
					
						
						|  | local max_slope = (rmf_flags & rmfDeleteOnSteepSlope) ~= 0 and steep_slope_cos | 
					
						
						|  |  | 
					
						
						|  | local new_pos, new_angle, new_axis, mark_found = GetPrefabObjPos( | 
					
						
						|  | dpos, angle, daxis, | 
					
						
						|  | rem_faded_objs and fade_dist, | 
					
						
						|  | prefab_pos, prefab_angle, | 
					
						
						|  | ground_offset, normal_offset, | 
					
						
						|  | mark_grid, check_mark, | 
					
						
						|  | mesh_overlap, entity, scale, mirror, | 
					
						
						|  | max_slope) | 
					
						
						|  |  | 
					
						
						|  | if not new_pos then | 
					
						
						|  | if not ignore_colls and coll_idx then | 
					
						
						|  | RemoveColl(coll_idx, nested_colls) | 
					
						
						|  | end | 
					
						
						|  | SkippedObject() | 
					
						
						|  | goto continue | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | local handle, err = handle_provider(prefab, class, false, true) | 
					
						
						|  | local components = 0 | 
					
						
						|  | if rmf_flags ~= default_rmf_flags then | 
					
						
						|  | components = components | cofComponentRandomMap | 
					
						
						|  | end | 
					
						
						|  | local obj = classdef:new{handle = handle} | 
					
						
						|  | if not IsValid(obj) then | 
					
						
						|  | g_print("Aborting object placement after a failure to create an object of type", class) | 
					
						
						|  | abort_prefab_placement = true | 
					
						
						|  | break | 
					
						
						|  | end | 
					
						
						|  | SetPrefabObjPos(obj, new_pos, new_angle, new_axis, scale, color, mirror) | 
					
						
						|  | for i=base_prop_count+1,#def,2 do | 
					
						
						|  | PropObjSetProperty(obj, def[i], def[i + 1]) | 
					
						
						|  | end | 
					
						
						|  | if rmf_flags ~= default_rmf_flags then | 
					
						
						|  | SetRandomMapFlags(obj, rmf_flags) | 
					
						
						|  | end | 
					
						
						|  | objs[#objs + 1] = obj | 
					
						
						|  |  | 
					
						
						|  | if coll_idx then | 
					
						
						|  | obj_to_coll[obj] = coll_idx | 
					
						
						|  | local coll_objs = coll_to_objs[coll_idx] | 
					
						
						|  | if coll_objs then | 
					
						
						|  | coll_objs[#coll_objs + 1] = obj | 
					
						
						|  | else | 
					
						
						|  | coll_indice[#coll_indice + 1] = coll_idx | 
					
						
						|  | coll_to_objs[coll_idx] = { obj } | 
					
						
						|  | end | 
					
						
						|  | if ignore_partial_colls and not coll_is_partial[coll_idx] then | 
					
						
						|  | if bbox and mark_found ~= mark then | 
					
						
						|  |  | 
					
						
						|  | goto continue | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | coll_is_partial[coll_idx] = true | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | PlacedObject(obj, mark, prefab) | 
					
						
						|  | end | 
					
						
						|  | ::continue:: | 
					
						
						|  | end | 
					
						
						|  | if debug then | 
					
						
						|  | AddPrefabStat(prefab, "obj_place_time", GetPreciseTicks() - place_time_start) | 
					
						
						|  | end | 
					
						
						|  | if ignore_partial_colls then | 
					
						
						|  | if #coll_indice > 0 then | 
					
						
						|  | for _, coll_idx in ipairs(coll_indice) do | 
					
						
						|  | local coll_objs = coll_to_objs[coll_idx] | 
					
						
						|  | if coll_is_partial[coll_idx] then | 
					
						
						|  |  | 
					
						
						|  | for _, obj in ipairs(coll_objs) do | 
					
						
						|  | PlacedObject(obj, mark, prefab) | 
					
						
						|  | end | 
					
						
						|  | else | 
					
						
						|  |  | 
					
						
						|  | RemoveColl(coll_idx, nested_colls) | 
					
						
						|  | for _, obj in ipairs(coll_objs) do | 
					
						
						|  | RemoveObject(obj) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if save_collections then | 
					
						
						|  | for _, coll_idx in ipairs(coll_indice) do | 
					
						
						|  | local coll_objs = not removed_coll[coll_idx] and coll_to_objs[coll_idx] or "" | 
					
						
						|  | local count = #coll_objs | 
					
						
						|  | for i = count, 1, -1 do | 
					
						
						|  | if not IsValid(coll_objs[i]) then | 
					
						
						|  | coll_objs[i] = coll_objs[count] | 
					
						
						|  | coll_objs[count] = nil | 
					
						
						|  | count = count - 1 | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if count > 0 then | 
					
						
						|  | local col_idx = last_col_idx + 1 | 
					
						
						|  | while collections[col_idx] do | 
					
						
						|  | col_idx = col_idx + 1 | 
					
						
						|  | end | 
					
						
						|  | last_col_idx = col_idx | 
					
						
						|  | assert(col_idx <= max_collection_idx) | 
					
						
						|  | if col_idx > max_collection_idx then | 
					
						
						|  | g_print("max collections reached!", max_map_size) | 
					
						
						|  | else | 
					
						
						|  | local col = Collection:new{ Index = col_idx } | 
					
						
						|  | col:SetName(string.format("MapGen_%d", col_idx)) | 
					
						
						|  | collections[col_idx] = col | 
					
						
						|  | PlacedObject(col) | 
					
						
						|  | for i=1,count do | 
					
						
						|  | SetCollectionIndex(coll_objs[i], col_idx) | 
					
						
						|  | end | 
					
						
						|  | placed_collections = placed_collections + 1 | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | local has_removed_colls = next(removed_coll) | 
					
						
						|  | for _, obj in ipairs(objs) do | 
					
						
						|  | if IsValid(obj) then | 
					
						
						|  | local coll_idx = has_removed_colls and obj_to_coll[obj] | 
					
						
						|  | if coll_idx and removed_coll[coll_idx] then | 
					
						
						|  |  | 
					
						
						|  | RemoveObject(obj) | 
					
						
						|  | elseif obj.__ancestors.Object then | 
					
						
						|  | obj:PostLoad() | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | if has_removed_colls then | 
					
						
						|  | rem_coll_count = rem_coll_count + table.count(removed_coll) | 
					
						
						|  | removed_coll = {} | 
					
						
						|  | end | 
					
						
						|  | if #coll_indice > 0 then | 
					
						
						|  | coll_indice, coll_to_objs = {}, {} | 
					
						
						|  | end | 
					
						
						|  | if next(obj_to_coll) then obj_to_coll = {} end | 
					
						
						|  | if next(coll_is_partial) then coll_is_partial = {} end | 
					
						
						|  | return objs | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | for mark, info in ipairs(prefab_list) do | 
					
						
						|  | local prefab, raster, add_idx, ptype, bbox = unpack(info) | 
					
						
						|  | assert(raster.place_idx == mark) | 
					
						
						|  | if not bbox or not placed_marks or placed_marks[mark] then | 
					
						
						|  | PlacePrefab(prefab, raster.pos, raster.angle, mark, ptype, bbox) | 
					
						
						|  | if step_prefab then | 
					
						
						|  | DbgClear() | 
					
						
						|  | local name = prefab_markers[prefab] | 
					
						
						|  | DbgShowPrefab(raster.pos, name, white, mark, prefab.min_radius, prefab.max_radius) | 
					
						
						|  | step("Objects %d %s", add_idx, name) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | if mark_grid then | 
					
						
						|  | MapForEach(bx_changes or "map", "attached", false, nil, nil, gofPermanent, function(obj) | 
					
						
						|  |  | 
					
						
						|  | local current_mark = GridGetMark(mark_grid, obj) | 
					
						
						|  | if current_mark == 0 then | 
					
						
						|  |  | 
					
						
						|  | ClearCachedZ(obj) | 
					
						
						|  | elseif not placed_objects[obj] then | 
					
						
						|  |  | 
					
						
						|  | DoneObject(obj) | 
					
						
						|  | end | 
					
						
						|  | end) | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | MapForEach(bx_changes or "map", "attached", false, "EditorCallbackObject", nil, nil, gofPermanent | gofGenerated, function(obj, ...) | 
					
						
						|  | obj:EditorCallbackGenerate(...) | 
					
						
						|  | end, self, object_source, placed_objects, prefab_list) | 
					
						
						|  |  | 
					
						
						|  | if debug then | 
					
						
						|  | self.ObjectTime = GetPreciseTicks() - start_time_po | 
					
						
						|  | self.ObjectCount = obj_count | 
					
						
						|  | self.RemObjects = removed_count | 
					
						
						|  | self.RemColls = rem_coll_count | 
					
						
						|  | self.PlacedColls = placed_collections | 
					
						
						|  | local class_to_count = {} | 
					
						
						|  | for obj in pairs(placed_objects) do | 
					
						
						|  | if IsValid(obj) then | 
					
						
						|  | class_to_count[obj.class] = (class_to_count[obj.class] or 0) + 1 | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | local tmp = {} | 
					
						
						|  | local total_count = 0 | 
					
						
						|  | for class, count in pairs(class_to_count) do | 
					
						
						|  | total_count = total_count + count | 
					
						
						|  | end | 
					
						
						|  | for class, count in pairs(class_to_count) do | 
					
						
						|  | local pct = total_count > 0 and MulDivRound(pct_100, count, total_count) or 0 | 
					
						
						|  | tmp[#tmp + 1] = {count = count, class = class, pct = pct} | 
					
						
						|  | end | 
					
						
						|  | self.dbg_placed_objects = tmp | 
					
						
						|  | self.dbg_obj_to_prefab_mark = placed_objects | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | FindAndRasterPrefabs() | 
					
						
						|  |  | 
					
						
						|  | local orig_rand = HandleRand | 
					
						
						|  | local handle_seed = rand_init("HandleRand") | 
					
						
						|  | HandleRand = function(range) | 
					
						
						|  | range, handle_seed = BraidRandom(handle_seed, range) | 
					
						
						|  | return range | 
					
						
						|  | end | 
					
						
						|  | PlacePrefabObjects() | 
					
						
						|  | HandleRand = orig_rand | 
					
						
						|  |  | 
					
						
						|  | if self.SavePrefabLoc then | 
					
						
						|  | mapdata.PersistedPrefabs = prefabs_to_persist | 
					
						
						|  | if dump then | 
					
						
						|  | dump("\n----\nPersisted prefabs saved: %d", #(prefabs_to_persist or "")) | 
					
						
						|  | for i, entry in ipairs(prefabs_to_persist) do | 
					
						
						|  | local name, ptype, x, y, angle = unpack(entry) | 
					
						
						|  | dump("%3d | pos (%4d, %4d) | angle %6d | '%s'", i, x, y, angle, name) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | if height_out_of_lims then | 
					
						
						|  | g_print("The resulting map height is outside the allowed range!") | 
					
						
						|  | end | 
					
						
						|  |  | 
					
						
						|  | if debug then | 
					
						
						|  | self.FirstRand = start_seed | 
					
						
						|  | self.LastRand = rand_seed | 
					
						
						|  |  | 
					
						
						|  | local unpack = table.unpack | 
					
						
						|  | local tmp = {} | 
					
						
						|  | for prefab, stat in pairs(prefab_stats) do | 
					
						
						|  | local counters = prefab_stat_count[prefab] | 
					
						
						|  | local avg_stats = { | 
					
						
						|  | name = prefab_markers[prefab], | 
					
						
						|  | count = prefabs_count[prefab], | 
					
						
						|  | objs = prefab.obj_count, | 
					
						
						|  | grid = sqrt(prefab.total_area), | 
					
						
						|  | } | 
					
						
						|  | local sum = 0 | 
					
						
						|  | for name, value in pairs(stat) do | 
					
						
						|  | sum = sum + value | 
					
						
						|  | avg_stats[name] = value / counters[name] | 
					
						
						|  | end | 
					
						
						|  | avg_stats.impact = sum | 
					
						
						|  | tmp[#tmp + 1] = avg_stats | 
					
						
						|  | end | 
					
						
						|  | self.dbg_placed_prefabs = tmp | 
					
						
						|  |  | 
					
						
						|  | if dump then | 
					
						
						|  | dump("\n\n\nUsed Prefabs Meta:\n") | 
					
						
						|  | local names = {} | 
					
						
						|  | for prefab, count in pairs(prefabs_count) do | 
					
						
						|  | local name = prefab_markers[prefab] | 
					
						
						|  | names[#names + 1] = name | 
					
						
						|  | names[name] = prefab | 
					
						
						|  | end | 
					
						
						|  | table.sort(names) | 
					
						
						|  | for _, name in ipairs(names) do | 
					
						
						|  | local prefab = table.copy(names[name]) | 
					
						
						|  | prefab.marker = nil | 
					
						
						|  | dump("%20s: %s\n", name, TableToLuaCode(prefab)) | 
					
						
						|  | end | 
					
						
						|  | dump("\n\n\nResults:\n") | 
					
						
						|  | for i, prop in ipairs(self:GetProperties()) do | 
					
						
						|  | if prop.log then | 
					
						
						|  | local name = prop.name or prop.id | 
					
						
						|  | local value = tostring(self:GetProperty(prop.id)) | 
					
						
						|  | if prop.editor == "text" then | 
					
						
						|  | dump("\n%s:", name) | 
					
						
						|  | dump(value) | 
					
						
						|  | else | 
					
						
						|  | dump("%20s: %s", name, value) | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | end | 
					
						
						|  | self:DbgUpdate() | 
					
						
						|  | self:DbgOnModified() | 
					
						
						|  | end |