|
function MapDataResize(mapdata, new_width, new_height) |
|
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("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) |
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DefineClass.DiffEvoAgent = { |
|
__parents = { "InitDone" }, |
|
data = false, |
|
fitness = false, |
|
Evaluate = function() end, |
|
RecombineValue = function(i, a, b, c, F) end, |
|
} |
|
|
|
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 |
|
|
|
|
|
|
|
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() |
|
|
|
|
|
local t = GetPreciseTicks() |
|
WaitNextFrame(5) |
|
local drawcalls, polygons, verts = GetRenderPerformanceStats() |
|
self.fitness = verts |
|
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") |
|
|
|
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) }, |
|
[1] = { guim/2, RGBA(64, 255, 64, 64) }, |
|
[2] = false, |
|
} |
|
|
|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
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 |
|
|