function MapDataResize(mapdata, new_width, new_height) -- Call in RealTimeThread local old_width, old_height = mapdata.Width, mapdata.Height if old_width ~= new_width or old_height ~= new_height then OpenMapLoadingScreen("") WaitRenderMode("ui") print("Resizing map...") if CurrentMap ~= mapdata.id then ChangeMap(mapdata.id) end mapdata.Width = new_width mapdata.Height = new_height local folder = GetMap() local function ResizeMapGridFile(filename, original, is_heightmap) local path = folder .. filename local old_grid = GridDest(original, 0, 0) GridLoadRaw(path, old_grid) local w, h = new_width, new_height if not is_heightmap then w,h = (w - 1), (h - 1) end local new_grid = GridDest(old_grid, w, h, true) GridExtend(old_grid, new_grid, true) GridSaveRaw(path, new_grid) end ResizeMapGridFile("biome.grid", terrain.GetBiomeGrid()) ResizeMapGridFile("grass.grid", terrain.GetGrassGrid(0)) ResizeMapGridFile("height.grid", terrain.GetHeightGrid(), true) --ResizeMapGridFile("impassable.grid", terrain.GetForcedImpassGrid()) --ResizeMapGridFile("passable.grid", terrain.GetForcedPassGrid()) ResizeMapGridFile("type.grid", terrain.GetTypeGrid()) local new_size = point(new_width, new_height, 0) local old_size = point(old_width, old_height, 0) local offset = MulDivRound(new_size - old_size, const.SlabSizeX, 4) --local offset = (offset/const.SlabSizeX)*const.SlabSizeX MapForEach("map", "CObject", function(obj) if not obj:IsValidPos() then return end local old_pos = obj:GetVisualPos() local new_pos = old_pos + offset obj:SetPos(new_pos) end) SaveObjects(folder .. "objects.lua") mapdata:SaveMapData(folder) hr.TR_ForceReloadNoTextures = 1 WaitRenderMode("scene") CloseMapLoadingScreen("") ChangeMap(mapdata.id) print("Map resized!") end end function SaveDecalsPositions() local decals = {} MapForEach("map", "TerrainDecal", function(o) decals[#decals+1] = { o.class, o:GetPos(), o:GetAngle() } end) AsyncStringToFile("AppData/" .. GetMapName() .. ".decals.lua", TableToLuaCode(decals)) end function RestoreDecalsPositions() local err, decals = FileToLuaValue("AppData/" .. GetMapName() .. ".decals.lua") local by_x = {} for k,v in ipairs(decals) do local x, y, z = v[2]:xyz() by_x[x] = by_x[x] or {} by_x[x][#by_x[x]+1] = v end MapForEach("map", "TerrainDecal", function(o) local x, y, z = o:GetPosXYZ() for _, decal in ipairs(by_x[x] or empty_table) do if decal[1] == o.class and decal[2] == o:GetPos() then if decal[3] ~= o:GetAngle() then print("Fixing ", o.class, "at", o:GetPos(), o:GetAngle(), decal[3]) o:SetAngle(decal[3]) end end end end) end function CreateCheckpointTimer(name) return { name = name, current_time = GetPreciseTicks(), Checkpoint = function(self, name) local time = GetPreciseTicks() print(self.name, name, time - self.current_time) self.current_time = time end } end --[[ function OnMsg.Autorun() if FirstLoad then PerfInstrument("EngineChangeMap") PerfInstrument("WaitLoadBinAssets") end PerfInstrument("RebuildSlabTunnels") PerfInstrument("ChangeMap") PerfInstrument("LoadObjects") PerfInstrument("LoadMap") PerfInstrument("ResumePassEdits", 100, 100) PerfInstrument("ComputeSlabVisibility") end ]] -- Blackbox optimization based on the rather pompously called "differential evolution" -- https://en.wikipedia.org/wiki/Differential_evolution -- given points in a possibility space ("agents") which can be evaluated and recombined via a certain procedure -- try to find a point which maximizes the evaluation function ("fitness") DefineClass.DiffEvoAgent = { __parents = { "InitDone" }, data = false, -- an array of values which represent the agent fitness = false, Evaluate = function() end, -- set fitness member to integer, higher is better RecombineValue = function(i, a, b, c, F) end, -- compute a new value of the i-th parameter based on the i-th paramethers of three other agents } function DiffEvoAgent:Init() self.data = self.data or {} end function DiffEvoAgent:Recombine(a, b, c, rand, CR, F) local changed = {} for i=1,#self.data do changed[i] = self:RecombineValue(i, a.data[i], b.data[i], c.data[i], F) end local recombined_data = {} local R = 1 + rand(#self.data) for i=1,#self.data do if i == R or rand(1000) < CR then recombined_data[i] = changed[i] else recombined_data[i] = self.data[i] end end return g_Classes[self.class]:new{ data = recombined_data } end function DiffEvoAgent:RecombineValue(idx, a, b, c, F) return a + MulDivRound(b - c, F, 1000) end function DiffEvoOptimize(pop, timeout, seed, CR, F) local rand = BraidRandomCreate(seed or 0) local start_time = GetPreciseTicks() local best_fitness, avg_fitness = 0, 0 for i=1,#pop do if not pop[i].fitness then pop[i]:Evaluate() end if pop[i].fitness > best_fitness then best_fitness = pop[i].fitness end avg_fitness = avg_fitness + pop[i].fitness end print("initial", best_fitness) local iteration = 0 while GetPreciseTicks() < start_time + timeout and avg_fitness <= MulDivRound(97*#pop, best_fitness, 100) do for i=1,#pop do local a, b, c = i, i, i while a == i do a = 1 + rand(#pop) end while b == i or b == a do b = 1 + rand(#pop) end while c == i or c == a or c == b do c = 1 + rand(#pop) end local new_agent = pop[i]:Recombine(pop[a], pop[b], pop[c], rand, CR, F) new_agent:Evaluate() if new_agent.fitness > pop[i].fitness then avg_fitness = avg_fitness - pop[i].fitness pop[i] = new_agent avg_fitness = avg_fitness + pop[i].fitness if new_agent.fitness > best_fitness then best_fitness = new_agent.fitness print("improvement", best_fitness) end end iteration = iteration + 1 if GetPreciseTicks() > start_time + timeout or avg_fitness > MulDivRound(97*#pop, best_fitness, 100) then break end end end local best_i, best_fitness = 0, 0 for i=1,#pop do if pop[i].fitness > best_fitness then best_i, best_fitness = i, pop[i].fitness end end pop[best_i]:View() return pop[best_i] end -- class representing possible gameplay (not action) cameras for Zulu DefineClass.DiffEvoTacCamera = { __parents = { "DiffEvoAgent" }, } function DiffEvoTacCamera.GeneratePopulation(n, seed) seed = seed or 0 local rand = BraidRandomCreate(seed) local sizex, sizey = terrain.GetMapSize() local pass_slabs = {} ForEachPassSlab("map", function(x, y, z) pass_slabs[#pass_slabs+1] = point(x, y) end ) table.shuffle(pass_slabs, rand()) local pop = {} if next(pass_slabs) then for i=1,n do pop[#pop+1] = DiffEvoTacCamera:new{ data = { pass_slabs[i]:x(), pass_slabs[i]:y(), rand(360*60) } } end for i=#pop+1,n do pop[#pop+1] = DiffEvoTacCamera:new{ data = { rand(sizex), rand(sizey), rand(360*60) } } end end return pop end function DiffEvoTacCamera:GetCameraPosLookatFloor() local x, y, angle = table.unpack(self.data) local lookat = point(x, y, terrain.GetHeight(x, y) + const.SlabSizeZ*16) local pos = lookat + Rotate(point(13*guim, 0, 0), angle) + point(0, 0, 11*guim) return pos, lookat, 5 end function DiffEvoTacCamera:View() local pos, lookat, floor = self:GetCameraPosLookatFloor() cameraTac.SetCamera(pos, lookat, 0) cameraTac.Normalize() cameraTac.SetFloor(floor) end function DiffEvoTacCamera:Evaluate() self:View() local p, l = cameraTac.GetPosLookAt() self.data[1], self.data[2] = l:xy() --self.data[4] = cameraTac.GetFloor()*1000 + 499 local t = GetPreciseTicks() WaitNextFrame(5) local drawcalls, polygons, verts = GetRenderPerformanceStats() self.fitness = verts -- GetPreciseTicks() - t end function DiffEvoTacCamera:RecombineValue(idx, a, b, c, F) if idx == 3 then return (a + MulDivRound(AngleDiff(b, c), F, 1000)) % (360*60) else return DiffEvoAgent.RecombineValue(self, idx, a, b, c, F) end end function CurrentMapSlowestCamera(timeout, rand) CMT_SetPause(true, "CurrentMapSlowestCamera") table.change(hr, "CurrentMapSlowestCamera", { ShadowSDSMEnable = 0, StreamingForceFallbacks = 1, Shadowmap = 1 }) WaitNextFrame(10) local pop = DiffEvoTacCamera.GeneratePopulation(20, rand and rand() or AsyncRand()) if not next(pop) then return { map = GetMapName(), err = "no valid camera positions found", fitness = 0 } end local best = DiffEvoOptimize(pop, timeout, rand and rand() or AsyncRand(), 700, 700) local t = GetPreciseTicks() WaitNextFrame(100) local frame_time = (GetPreciseTicks() - t)/100 local str = string.format("DbgLoadLocation(\"%s\", %s, false)", GetMapName(), TableToLuaCode({GetCamera()}, ' ')) CMT_SetPause(false, "CurrentMapSlowestCamera") table.restore(hr, "CurrentMapSlowestCamera") return { map = GetMapName(), frame_time = frame_time, fps = 1000/Max(frame_time, 1), str = str, data = best.data, fitness = best.fitness, lightmodel = CurrentLightmodel and CurrentLightmodel[1].id, } end function BruteForceSlowestCamera(duration) CMT_SetPause(true, "CurrentMapSlowestCamera") table.change(hr, "CurrentMapSlowestCamera", { ShadowSDSMEnable = 0, StreamingForceFallbacks = 1, Shadowmap = 1 }) WaitNextFrame(10) local pass_slabs = {} ForEachPassSlab("map", function(x, y, z) pass_slabs[#pass_slabs+1] = point(x, y) end) local clusters = KMeans2D(pass_slabs, Min(200, #pass_slabs/16), 100) local cameras = {} local angles = 12 for i=1,#clusters do for i=1,angles do cameras[#cameras+1] = DiffEvoTacCamera:new{ data = { clusters[i]:x(), clusters[i]:y(), MulDivRound(i, 360*60, angles) } } end end local t = GetPreciseTicks() local best = { fitness = 0 } for i, p in ipairs(cameras) do p:Evaluate() if p.fitness > best.fitness then best = p print(p.fitness) end if GetPreciseTicks() - t > duration then print("iterations", i) break end end best:Evaluate() local t = GetPreciseTicks() WaitNextFrame(100) local frame_time = (GetPreciseTicks() - t)/100 local str = string.format("DbgLoadLocation(\"%s\", %s, false)", GetMapName(), TableToLuaCode({GetCamera()}, ' ')) CMT_SetPause(false, "CurrentMapSlowestCamera") table.restore(hr, "CurrentMapSlowestCamera") return { map = GetMapName(), frame_time = frame_time, fps = 1000/Max(frame_time, 1), str = str, data = best.data, fitness = best.fitness, lightmodel = CurrentLightmodel and CurrentLightmodel[1].id, } end function AllMapsSlowestCameras(total_time, seed) total_time = total_time or 0 local rand = BraidRandomCreate(seed or AsyncRand()) IgnoreDebugErrors(true) ChangeMap("__Empty") -- to restart CMT thread after CMT_Time is changed local maps = {} for _, map in pairs(MapData) do if map.Status ~= "Not started" then maps[#maps+1] = map.id end end table.sort(maps) local results = {} for _, map in ipairs(maps) do ChangeMap(map) WaitLoadingScreenClose() WaitNextFrame(10) local ok, this_map = sprocall(BruteForceSlowestCamera, total_time/#maps, rand) if ok then table.insert(results, this_map) print(results[#results].str, results[#results].fitness/1000) else print("Failed on map", map) end end table.sortby_field(results, "fitness") results.lua_rev = LuaRevision results.assets_rev = AssetsRevision results.hardware = GetHardwareInfo(GetMainWindowDisplayIndex()) results.resolution = UIL:GetScreenSize() AsyncStringToFile("svnAssets/Tests/BruteForceSlowCameras.lua", TableToLuaCode(results)) end function CPUTasksBenchmark() CreateRealTimeThread( function() DbgLoadLocation("G-8 - Colonial Mansion", {point(168360, 174403, 26350),point(165120, 161748, 15350),"Tac",1000,{floor = 3},4200}, false) camera.Lock() local hardware_info = GetHardwareInfo(GetMainWindowDisplayIndex()) print("------------", hardware_info.cpuName) table.change(hr, "CPUTasksBenchmark", { PrimitiveCountModifier=-1, EnablePostprocess=0, EnableScreenSpaceReflections=0, EnableScreenSpaceAmbientObscurance=0 }) WaitNextFrame() for i=1, hardware_info.cpuThreads do config.CPUTaskThreads=i Sleep(2000) print(i, hr.RenderStatsFrameTimeCPU/100) end table.restore(hr, "CPUTasksBenchmark") print("------------") print("done.") camera.Unlock() end ) end MapVar("LOSVoxelsMesh", false) LOSVoxelConfig = { [0] = { guim*2, RGBA(0, 64, 255, 64) }, -- fully hidden from sight [1] = { guim/2, RGBA(64, 255, 64, 64) }, -- only a Standing or Crouched unit would be visible here [2] = false, -- fully visible, don't draw anything } function DbgShowLOSVoxelsWireframe(center, radius) DbgClearVectors() if LOSVoxelsMesh then LOSVoxelsMesh:delete() end center = center or SelectedObj radius = radius or SelectedObj:GetSightRadius() local t = GetLOSVoxelsInRadius(center, radius) local colors = { const.clrRed, const.clrBlue, const.clrGreen } for _, voxel in ipairs(t) do local x, y, z, stance_idx = stance_pos_unpack(voxel) z = z or terrain.GetHeight(point(x,y)) local vd = LOSVoxelConfig[stance_idx] if vd then DbgAddVoxel(point(x,y,z), vd[2]) end end end local function AppendQuad(pstr, corner, x, y, color) pstr:AppendVertex(corner, color) pstr:AppendVertex(corner + x, color) pstr:AppendVertex(corner + x + y, color) pstr:AppendVertex(corner, color) pstr:AppendVertex(corner + x + y, color) pstr:AppendVertex(corner + y, color) end function DbgShowLOSVoxels(center, radius) DbgClearVectors() if LOSVoxelsMesh then LOSVoxelsMesh:delete() end center = center or SelectedObj radius = radius or SelectedObj:GetSightRadius() local t = GetLOSVoxelsInRadius(center, radius) local hs = const.SlabSizeX/2 local mesh_str = pstr("", 24*20*#t) for _, voxel in ipairs(t) do local x, y, z, stance_idx = stance_pos_unpack(voxel) local vd = LOSVoxelConfig[stance_idx] if vd then z = z or terrain.GetHeight(point(x,y)) local vert = vd[1] local color = vd[2] AppendQuad(mesh_str, point(x-hs, y-hs, z), point(2*hs, 0, 0), point(0, 2*hs, 0), color) AppendQuad(mesh_str, point(x-hs, y-hs, z + vert), point(2*hs, 0, 0), point(0, 2*hs, 0), color) AppendQuad(mesh_str, point(x-hs, y-hs, z), point(2*hs, 0, 0), point(0, 0, vert), color) AppendQuad(mesh_str, point(x-hs, y+hs, z), point(2*hs, 0, 0), point(0, 0, vert), color) AppendQuad(mesh_str, point(x-hs, y-hs, z), point(0, 2*hs, 0), point(0, 0, vert), color) AppendQuad(mesh_str, point(x+hs, y-hs, z), point(0, 2*hs, 0), point(0, 0, vert), color) end end LOSVoxelsMesh = Mesh:new() LOSVoxelsMesh:SetMesh(mesh_str) LOSVoxelsMesh:SetMeshFlags(const.mfWorldSpace) LOSVoxelsMesh:SetDepthTest(true) LOSVoxelsMesh:SetPos(center:GetVisualPos()) end function DbgIsTreeWind(obj) local entity = obj:GetEntity() if not entity or entity == "" then return end local mat = GetStateMaterial(entity, obj:GetStateText()) if not mat then return end local num_sub_mtls = GetNumSubMaterials(mat) for i=1, num_sub_mtls do local sub_mat = GetMaterialProperties(mat, i-1) if sub_mat.VertexNoise == "Tree" then return true end end end function ForceLODMinOutsideBorder() local border = GetBorderAreaLimits() MapForEach("map", "AutoAttachObject", function(obj) if not obj:GetPos():InBox2D(border) and border:Intersect2D(obj:GetObjectBBox()) == const.irOutside then obj:SetForcedLODMin(true) obj:SetAutoAttachMode(obj:GetAutoAttachMode()) end end) MapForEach("map", function(obj) if not obj:GetPos():InBox2D(border) and border:Intersect2D(obj:GetObjectBBox()) == const.irOutside then obj:SetForcedLODMin(true) end end) RecreateRenderObjects() end -- Compatibility method used for reading the old LowerLOD value in the maps function CObject:SetLowerLOD(value) if value then self:SetForcedLODState("Minimum") else self:SetForcedLODState("Automatic") end end function CountEntitiesInAllMaps() local stats = {} for entity in pairs(GetAllEntities()) do stats[entity] = 0 end local maps = {} for _, map in pairs(MapData) do if map.Status ~= "Not started" then maps[#maps+1] = map.id end end -- maps = { maps[1] } ForEachMap(maps, function() local count = MapForEach("map", function(obj) local entity = obj:GetEntity() if entity then stats[entity] = (stats[entity] or 0) + 1 end end) print(count .. " objects in " .. GetMap()) end) local csv = {} for entity, count in pairs(stats) do if entity ~= "" and GetStateMeshFile(entity, 0) then local mesh_props = GetMeshProperties(GetStateMeshFile(entity, 0)) if mesh_props then local last_lod = 0 while GetStateMeshFile(entity, 0, last_lod+1) do last_lod = last_lod + 1 end local mesh_props_lod = GetMeshProperties( GetStateMeshFile(entity, 0, last_lod) ) csv[#csv+1] = { entity, mesh_props.NumVerts, mesh_props.NumTriangles, mesh_props.NumVerts*count, mesh_props.NumTriangles*count, (mesh_props_lod or mesh_props).NumVerts*count, (mesh_props_lod or mesh_props).NumTriangles*count, count, } end end end SaveCSV("mesh_stats.csv", csv, nil, {"entity", "vertexes", "triangles", "vertexes_times_count", "triangles_times_count", "lod_vertexes_times_count", "lod_triangles_times_count", "count",}) end function OnMsg.BeforeUpsampledScreenshot(store) local gridMarkers = {} MapForEachMarker("GridMarker", nil, function(marker) if marker:IsAreaShown() then marker:HideArea() table.insert(gridMarkers,marker) end end) store.gridMarkers = gridMarkers end function OnMsg.AfterUpsampledScreenshot(store) for _, marker in ipairs(store.gridMarkers) do marker:ShowArea() end end function ReplaceSoundSourcesWithBeachMarkers() local to_be_replaced = { "waves_cliffs", "waves_shore", "waves_wharf", "waves_beach", } if not IsEditorActive() then print("Please run this in editor") return end local replaced = 0 local wss = MapGet("map", "SoundSource") for _, source in ipairs(wss) do local count = 0 for _, sound in ipairs(source.Sounds) do for _, pattern in ipairs(to_be_replaced) do if sound.Sound:find(pattern) then count = count + 1 end end end if count == #source.Sounds then local bm = PlaceObject("BeachMarker") bm:SetGameFlags(const.gofPermanent) bm:SetPos(source:GetPos()) DoneObject(source) replaced = replaced + 1 elseif count > 0 then StoreErrorSource(source, "Sound source contains both waves and other sound banks, not replacing - please review") end end print("Replaced sound sources with beach markers:", replaced) end if FirstLoad then s_BlacklistEntity = false end local function GetBlacklist(filename) local err, data = AsyncFileToString(filename, nil, nil, "lines") if err then printf("Error loading blacklist entities: %s", err) else return data end end function ToggleBlacklistEntitiesVisualization() if s_BlacklistEntity then s_BlacklistEntity = false hr.UseSatGammaModifier = 0 RecreateRenderObjects() return end local entities = GetBlacklist(EngineBinAssetsBlacklistEntitiesFilename) s_BlacklistEntity = {} for _, entity in ipairs(entities) do s_BlacklistEntity[entity] = true end hr.UseSatGammaModifier = 1 RecreateRenderObjects() MapForEach(true, "CObject", function(obj) if IsBlacklistEntity(obj) then obj:SetGamma(const.clrWhite) end end) end function IsBlacklistEntity(obj_or_ent) if not s_BlacklistEntity then return false end return s_BlacklistEntity[IsValid(obj_or_ent) and obj_or_ent:GetEntity() or obj_or_ent] end local old_CObject_new = CObject.new function CObject.new(self, ...) local obj = old_CObject_new(self, ...) if IsBlacklistEntity(obj) then obj:SetGamma(const.clrWhite) end return obj end local function GetBlacklistFootprint(filename, folder, ext) folder = folder or "" local footprint = 0 local files = GetBlacklist(filename) for _, file in ipairs(files) do local path, filename, org_ext = SplitPath(file) local size = io.getsize(folder .. path .. filename .. (ext or org_ext)) footprint = footprint + size end return footprint end function GetBlacklistTexturesFootprint() local size = GetBlacklistFootprint(EngineBinAssetsBlacklistTexturesFilename) printf("Textures DDS: %.2fGB", size / (1024.0 * 1024 * 1024)) end function GetBlacklistMusicFootprint() local wav = GetBlacklistFootprint(EngineBinAssetsBlacklistMusicFilename, nil, ".wav") local opus = GetBlacklistFootprint(EngineBinAssetsBlacklistMusicFilename, "svnAssets/Bin/win32/", ".opus") printf("Music WAV: %.2fGB", wav / (1024.0 * 1024 * 1024)) printf("Music Opus: %.2fGB", opus / (1024.0 * 1024 * 1024)) printf("Music Compression Ratio: %.2f", 1.0 * wav / opus) end function GetBlacklistSoundsFootprint() local wav = GetBlacklistFootprint(EngineBinAssetsBlacklistSoundsFilename, nil, ".wav") local opus = wav / 14.19 printf("Sounds WAV: %.2fGB", wav / (1024.0 * 1024 * 1024)) printf("Sounds Opus: %.2fGB", opus / (1024.0 * 1024 * 1024)) printf("Sounds Compression Ratio: %.2f", 1.0 * wav / opus) end function GetBlacklistVoicesFootprint() local wav = GetBlacklistFootprint(EngineBinAssetsBlacklistVoicesFilename, "svnAssets/Source/", ".wav") local opus = GetBlacklistFootprint(EngineBinAssetsBlacklistVoicesFilename, "svnAssets/Bin/win32/", ".opus") printf("Voices WAV: %.2fGB", wav / (1024.0 * 1024 * 1024)) printf("Voices Opus: %.2fGB", opus / (1024.0 * 1024 * 1024)) printf("Voices Compression Ratio: %.2f", 1.0 * wav / opus) end EngineBinAssetsBlacklistEntitiesFilename = "BinAssets/EngineBinAssetBlacklistEntities.txt" EngineBinAssetsBlacklistTexturesFilename = "BinAssets/EngineBinAssetBlacklistTextures.txt" EngineBinAssetsBlacklistMusicFilename = "BinAssets/EngineBinAssetBlacklistMusic.txt" EngineBinAssetsBlacklistSoundsFilename = "BinAssets/EngineBinAssetBlacklistSounds.txt" EngineBinAssetsBlacklistVoicesFilename = "BinAssets/EngineBinAssetBlacklistVoices.txt" function GetGameMapEntities() local err, folders = AsyncListFiles("svnAssets/Source/Maps", "*", "folders") if err then EngineBinAssetsPrint("Error generating black list entities: %s", err) return end local used_entity, unit_markers_bantes_groups = {}, {} for _, folder in ipairs(folders) do local _, sector = SplitPath(folder) if IsDemoSector(sector) then local filename = folder .. "/entlist.txt" if io.exists(filename) then local err, lines = AsyncFileToString(filename, nil, nil, "lines") if err then EngineBinAssetsPrint("Error paris '%s': %s", filename, err) else for _, entity in ipairs(lines) do used_entity[entity] = true end end end local filename = folder .. "/markers.debug.lua" if io.exists(filename) then local err, str = AsyncFileToString(filename) if str then local _, markers_data = LuaCodeToTuple(str) local map_name = markers_data and markers_data[1].map if map_name then for _, marker in ipairs(markers_data) do if marker.type == "UnitMarker" then if #(marker.ApproachedBanters or empty_table) > 0 or #(marker.BanterGroups or empty_table) > 0 or #(marker.ApproachBanterGroup or empty_table) > 0 then for _, group in ipairs(marker.Groups or empty_table) do unit_markers_bantes_groups[group] = true end end end end end end end end end return used_entity, unit_markers_bantes_groups end function GetBlacklistEntities(entity_textures, used_textures, textures_data) local used_entity, used_voices = GetGameMapEntities() local additional_blacklist_textures = {} Msg("GatherGameEntities", used_entity, additional_blacklist_textures, used_voices) local all_entities = GetAllEntities() local blacklist_entities = {} for entity in pairs(all_entities) do if not used_entity[entity] and not entity:starts_with("Terrain") then table.insert(blacklist_entities, entity) end end table.sort(blacklist_entities) local is_blacklisted = {} for _, entity in ipairs(blacklist_entities) do is_blacklisted[entity] = true end local all_textures, ref_by_non_blacklisted = {}, {} local err, list = AsyncListFiles("Textures/", "*.dds", "size,relative") local texture_sizes = {} for k,v in ipairs(list) do local file_data = textures_data[v] if not file_data.alias then texture_sizes["Textures/" .. v] = list.size[k] else texture_sizes["Textures/" .. v] = 0 end end local entity_sizes = {} local texture_counted = {} for entity, textures in pairs(entity_textures) do for texture in pairs(textures) do all_textures[texture] = true if not is_blacklisted[entity] then ref_by_non_blacklisted[texture] = true entity_sizes[entity] = entity_sizes[entity] or 0 if not texture_counted[texture] then entity_sizes[entity] = entity_sizes[entity] + texture_sizes[texture] texture_counted[texture] = true end end end end AsyncStringToFile("svnAssets/tmp/entity_sizes.lua", ValueToLuaCode(entity_sizes)) AsyncStringToFile("svnAssets/tmp/entity_textures.lua", ValueToLuaCode(entity_textures)) local blacklist_textures = {} local all_textures = table.keys(all_textures) for _, texture in ipairs(all_textures) do if not ref_by_non_blacklisted[texture] then blacklist_textures[texture] = true end end -- collapsing means out of multiple identical texture only one is shipped in the game -- if a texture is shipped, blacklisted, but has identical textures which are not blacklisted, it should be removed from the blacklist local siblings = {} for textureId, textureData in pairs(entity_textures) do local texture_siblings = siblings[textureId] or {} texture_siblings[#texture_siblings+1] = "Textures/" .. textureId siblings[textureId] = texture_siblings if textureData.alias then siblings[textureData.alias] = texture_siblings end end local remove_from_blacklist = {} for texture in pairs(blacklist_textures) do texture = texture:match("Textures/(.*)$") if texture and used_textures[texture] then for _, sibling in ipairs(siblings[texture]) do if not blacklist_textures[sibling] then remove_from_blacklist[texture] = true break end end end end for texture in pairs(remove_from_blacklist) do blacklist_textures["Textures/" .. texture] = nil end blacklist_textures = table.keys(blacklist_textures, "sorted") table.iappend(blacklist_textures, additional_blacklist_textures) local blacklist_banter_voices = {} Msg("GatherVoiceBanters", blacklist_banter_voices) local blacklist_voices = {} local err, voices = AsyncListFiles("svnAssets/bin/win32/Voices/English", "*") if err then printf("Error listing voices: %s", err) else local loc = LoadCSV("svnProject/LocalizationOut/English/CurrentLanguage/Game.csv") for i = 2, #loc do local entry = loc[i] local voice_id = tonumber(entry[1]) local voice_blacklisted = blacklist_banter_voices[voice_id] local used_actor = used_voices[entry[15]] or used_voices[entry[16]] or used_voices[entry[17]] if used_actor then voice_blacklisted = false end if voice_blacklisted then local voice = string.format("Voices/English/%s.opus", voice_id) blacklist_voices[voice] = true end end end blacklist_voices = table.keys(blacklist_voices, "sorted") return blacklist_entities, blacklist_textures, blacklist_voices end local function GetBlacklistFiles(folder, used_files, ext) ext = ext or "" local err, files = AsyncListFiles(folder, "*", "recursive") if err then printf("Error listing '%s' folder: %s", folder, err) return end local blacklist_files = {} for _, filename in ipairs(files) do local path, file, ext = SplitPath(filename) local track = path .. file if not used_files[track] then table.insert(blacklist_files, track .. ext) end end table.sort(blacklist_files) return blacklist_files end local function GetBlacklistMusic() local used_music = {} Msg("GatherMusic", used_music) return GetBlacklistFiles("Music", used_music, ".wav") end local function GetBlacklistSounds() local used_sounds = {} Msg("GatherSounds", used_sounds) return GetBlacklistFiles("Sounds/environment-stereo", used_sounds, ".wav") end function OnMsg.BuildEngineBinAssets(entity_textures, used_tex, textures_data) local blacklist_entities, blacklist_textures, blacklist_voices = GetBlacklistEntities(entity_textures, used_tex, textures_data) AsyncStringToFile(EngineBinAssetsBlacklistEntitiesFilename, table.concat(blacklist_entities, "\n")) SVNAddFile(EngineBinAssetsBlacklistEntitiesFilename) AsyncStringToFile(EngineBinAssetsBlacklistTexturesFilename, table.concat(blacklist_textures, "\n")) SVNAddFile(EngineBinAssetsBlacklistTexturesFilename) local blacklist_music = GetBlacklistMusic() AsyncStringToFile(EngineBinAssetsBlacklistMusicFilename, table.concat(blacklist_music, "\n")) SVNAddFile(EngineBinAssetsBlacklistMusicFilename) local blacklist_sounds = GetBlacklistSounds() AsyncStringToFile(EngineBinAssetsBlacklistSoundsFilename, table.concat(blacklist_sounds, "\n")) SVNAddFile(EngineBinAssetsBlacklistSoundsFilename) AsyncStringToFile(EngineBinAssetsBlacklistVoicesFilename, table.concat(blacklist_voices, "\n")) SVNAddFile(EngineBinAssetsBlacklistVoicesFilename) end if Platform.developer then function CheckWaterObjPos(water, data, no_vme) local pos = water:GetPos() local z = pos:z() or terrain.GetHeight(pos) local bbox = water:GetObjectBBox():SetInvalidZ() local minx, miny = bbox:min():xy() local maxx, maxy = bbox:max():xy() local sizex, sizey = terrain.GetMapSize() if 0 <= minx and minx < sizex and 0 <= miny and miny <= sizey and 0 <= maxx and maxx < sizex and 0 <= maxy and maxy <= sizey then local min_z, max_z = terrain.GetMinMaxHeight(bbox) if z < min_z then if not no_vme then StoreErrorSource(water, string.format("Water plane Z=%d but range[%d-%d] under terrain!", z, min_z, max_z)) end if data == "delete" then DoneObject(water) elseif type(data) == "table" then table.insert(data, water) end end end end function CheckWaterObjectsUnderTerrain(delete) MapForEach("map", "WaterObj", CheckWaterObjPos, delete) end function GetMapsWithWaterUnderTerrain() CreateRealTimeThread(function() local maps = {} ForEachMap(nil, function() local objs = {} MapForEach("map", "WaterObj", CheckWaterObjPos, objs, "no VME") if #objs > 0 then local msg = string.format("%s: %d", GetMapName(), #objs) table.insert(maps, msg) print(msg) end end) print(string.format("%d Maps with WaterObj under terrain\n%s", #maps, table.concat(maps, "\n"))) end) end function OnMsg.SaveMap() CheckWaterObjectsUnderTerrain() end end function editor.EyeCandyOutsideMap() if #editor.GetSel() < 1 then print("Please select an object to force EyeCandy to instances of that object outside of the map") return end local bx = GetBorderAreaLimits() local threshold = (terminal.IsKeyPressed(const.vkControl) and 50 or 20) * const.SlabSizeX local salt = "EyeCandyOutsideMap" local objs = MapGet("map", table.keys2(table.invert(table.map(editor.GetSel(), "class"))), function(x) local dist = bx:Dist2D(x) if dist == 0 then return false end return dist > threshold or ((xxhash(x:GetPos(), salt) % 1024) < MulDivRound(dist, 1024, threshold)) end) XEditorUndo:BeginOp{ objects = objs, name = "Eye Candy outside map" } SuspendPassEditsForEditOp(objs) for _, x in ipairs(objs) do x:ClearEnumFlags(const.efCollision + const.efApplyToGrids) x:SetDetailClass("Eye Candy") end ResumePassEditsForEditOp(objs) XEditorUndo:EndOp(objs) end