File size: 18,169 Bytes
b6a38d7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 |
---
--- Runs a real-time thread to change the map for the "ArtTest" context.
---
--- This function is likely used for debugging or testing purposes, to quickly
--- switch between different map configurations in the "ArtTest" context.
---
function bat() -- debug function
CreateRealTimeThread(ChangeMap, "ArtTest")
end
---
--- Gets the lowercase version of the project name.
---
--- @return string The lowercase project name.
---
local project_name = string.lower(const.ProjectName)
---
--- Defines paths to various asset directories and files used in the ArtTest context.
---
--- @class path
--- @field assets table A table containing paths to asset directories.
--- @field assets.root string The root directory for assets.
--- @field assets.entities string The directory for entity assets.
--- @field assets.art_producer_lua string The path to the CurrentArtProducer.lua file.
--- @field assets.exporter string The directory for the HGExporter.
--- @field assets.entity_producers_lua string The path to the EntityProducers.lua file.
--- @field max table A table containing paths to 3DS Max related files and directories.
--- @field max.root string The root directory for 3DS Max scripts.
--- @field max.startup string The path to the HGExporterUtility startup script.
--- @field max.exporter string The directory for the HGExporter scripts.
--- @field max.exporter_startup string The path to the HGExporterUtility startup script in the exporter directory.
--- @field max.art_producer_ms string The path to the CurrentArtProducer.ms file in the exporter directory.
--- @field max.grannyexp_ini string The path to the grannyexp.ini file in the exporter directory.
---
local path = {}
path.assets = {}
path.assets.root = GetExecDirectory() .. "Assets/"
path.assets.entities = path.assets.root .. "Bin/Common/Entities/"
path.assets.art_producer_lua = path.assets.root .. "CurrentArtProducer.lua"
path.assets.exporter = path.assets.root .. "HGExporter/"
path.assets.entity_producers_lua = path.assets.root .. "Spec/EntityProducers.lua"
path.max = {}
path.max.root = "AppData/../../Local/Autodesk/3dsmax/2019 - 64bit/ENU/scripts/"
path.max.startup = path.max.root .. "startup/HGExporterUtility_" .. project_name .. ".ms"
path.max.exporter = path.max.root .. "HGExporter_" .. project_name .. "/"
path.max.exporter_startup = path.max.exporter .. "Startup/HGExporterUtility.ms"
path.max.art_producer_ms = path.max.exporter .. "CurrentArtProducer.ms"
path.max.grannyexp_ini = path.max.exporter .. "grannyexp.ini"
local atprint = CreatePrint({
"ArtPreview",
format = "printf",
})
ArtTest = { }
---
--- Opens a dialog to allow the user to choose a new art producer, then sets the new producer and updates the corresponding Lua and Max Script files.
---
--- @function ArtTest.OpenChangeProducerDialog
function ArtTest.OpenChangeProducerDialog()
local producers = table.icopy(ArtSpecConfig.EntityProducers)
table.insert(producers, 1, "Any")
local new_producer = WaitListChoice(terminal.desktop, producers, "Choose art producer:", 1)
ArtTest.SetProducer(new_producer or "Any")
CreateRealTimeThread(ChangeMap, "ArtTest")
end
---
--- Sets the new art producer and updates the corresponding Lua and Max Script files.
---
--- @param new_producer string The new art producer to set.
---
function ArtTest.SetProducer(new_producer)
if not new_producer then
return
end
atprint("Setting new art producer %s", new_producer)
-- set producer
rawset(_G, "g_ArtTestProducer", new_producer)
AsyncCreatePath(path.assets.root)
-- write to Lua file for the game
local lua_content = string.format("return \"%s\"", new_producer)
AsyncStringToFile(path.assets.art_producer_lua, lua_content)
-- write to Max Script file for the exporter
local ms_content = string.format("global g_ArtTestProducer = \"%s\"", new_producer)
AsyncStringToFile(path.max.art_producer_ms, ms_content)
local os_path_assets = ConvertToOSPath(path.assets.root)
if string.ends_with(os_path_assets, "\\") then
os_path_assets = string.sub(os_path_assets, 1, #os_path_assets - 1)
end
ArtTest.InstallMaxExporter()
end
---
--- Sets the new art producer and updates the corresponding Autodesk 3DS Max exporter configuration.
---
--- This function is responsible for configuring the Autodesk 3DS Max exporter by updating the `grannyexp.ini` file with the correct assets path. It first checks if the game is run with the `-globalappdirs` command line parameter, which is required for the exporter to function properly. If the parameter is not present, it prints a warning message and returns.
---
--- The function then retrieves the OS-specific path for the assets root directory and checks if it ends with a backslash. If so, it removes the trailing backslash.
---
--- Next, the function checks if the `grannyexp.ini` file exists. If it does, it reads the file and updates the `assetsPath` setting with the correct assets path. If the file does not exist, it creates a new `grannyexp.ini` file with the assets path.
---
--- @param new_producer string The new art producer to set.
---
function ArtTest.SetProducer_3DSMax(new_producer)
if not new_producer then
return
end
local globalappdirs = string.match(GetAppCmdLine() or "", "-globalappdirs")
if not globalappdirs then
atprint(
"Please run the game with the -globalappdirs command line parameter to install/update the Autodesk 3DS Max exporter")
return
end
local os_path_assets = ConvertToOSPath(path.assets.root)
if string.ends_with(os_path_assets, "\\") then
os_path_assets = string.sub(os_path_assets, 1, #os_path_assets - 1)
end
if io.exists(path.max.grannyexp_ini) then -- TODO proper ini handling
local err, ini = AsyncFileToString(path.max.grannyexp_ini)
local first, last = string.find(ini, "assetsPath=.*\n")
if first and last and first <= last then
ini = string.format("%sassetsPath=%s%s", string.sub(ini, 1, first), os_path_assets,
string.sub(ini, last - 1))
else
ini = string.format("%s\n[Directories]\nassetsPath=%s", ini, os_path_assets)
end
else
local ini = string.format("[Directories]\nassetsPath=%s", os_path_assets)
AsyncStringToFile(path.max.grannyexp_ini, ini)
end
end
---
--- Installs the Autodesk 3DS Max exporter by creating the necessary folder structure and copying the exporter files.
---
--- This function first checks if the game is run with the `-globalappdirs` command line parameter, which is required for the exporter to function properly. If the parameter is not present, it prints a warning message and returns.
---
--- The function then creates the folder structure where the exported entities will be stored, including the `Bin/`, `Bin/Common/`, and other subfolders.
---
--- Next, the function copies the exporter folder structure from the `path.assets.exporter` directory to the `path.max.exporter` directory. It skips any folders or files that contain `.svn`.
---
--- Finally, the function copies the exporter startup file from `path.max.exporter_startup` to `path.max.startup`, and calls `ArtTest.SetProducer_3DSMax()` with the current art producer.
---
--- @return nil
function ArtTest.InstallMaxExporter()
local globalappdirs = string.match(GetAppCmdLine() or "", "-globalappdirs")
if not globalappdirs then
atprint(
"Please run the game with the -globalappdirs command line parameter to install/update the Autodesk 3DS Max exporter")
return
end
-- crate assets folder structure (where entities will be exported)
local structure = {"Bin/", "Bin/Common/", "Bin/Common/Animations", "Bin/Common/Entities", "Bin/Common/Mapping",
"Bin/Common/Materials", "Bin/Common/Meshes", "Bin/Common/TexturesMeta", "Bin/win32/", "Bin/win32/Textures",
"Bin/win32/Fallbacks", "Bin/win32/Fallbacks/Textures"}
for i, subpath in ipairs(structure) do
local full_path = path.assets.root .. subpath
local os_path = ConvertToOSPath(full_path)
local err = AsyncCreatePath(os_path)
if err then
atprint("Failed creating exporter target folder structure - %s", err)
return
end
end
-- copy exporter folder structure
local err, folders = AsyncListFiles(path.assets.exporter, "*", "recursive,relative,folders")
if err then
atprint("Failed listing Autodesk 3DS Max exporter folder structure - %s", err)
return err
end
local os_path = ConvertToOSPath(path.max.exporter)
local err = AsyncCreatePath(os_path)
if err then
atprint("Failed copying Autodesk 3DS Max exporter folder structure - %s", err)
return err
end
for _, folder in ipairs(folders) do
if not string.find(folder, ".svn") then
local os_path = ConvertToOSPath(path.max.exporter .. folder)
local err = AsyncCreatePath(os_path)
if err then
atprint("Failed copying Autodesk 3DS Max exporter folder structure - %s", err)
return err
end
end
end
-- copy exporter files
local err, files = AsyncListFiles(path.assets.exporter, "*", "recursive,relative")
if err then
atprint("Failed listing Autodesk 3DS Max exporter files - %s", err)
return err
end
for _, file in ipairs(files) do
if not string.find(file, ".svn") then
local os_dest_path = ConvertToOSPath(path.max.exporter .. file)
local err = AsyncCopyFile(path.assets.exporter .. file, os_dest_path, "raw")
if err then
atprint("Failed copying Autodesk 3DS Max exporter files - %s", err)
return err
end
end
end
-- copy exporter startup file
local err = AsyncCopyFile(path.max.exporter_startup, path.max.startup)
if err then
atprint("Failed copying Autodesk 3DS Max exporter startup file - %s", err)
return err
end
ArtTest.SetProducer_3DSMax(rawget(_G, "g_ArtTestProducer"))
atprint("Installed Autodesk 3DS Max exporter. Restart Autodesk 3DS Max.")
end
---
--- Starts the art preview mode.
---
--- This function is responsible for initializing the art preview mode. It performs the following steps:
--- 1. Checks if an art producer script exists and loads it.
--- 2. If no art producer is found, it opens a dialog to allow the user to select an art producer.
--- 3. Sets the selected art producer.
--- 4. Loads external entities.
--- 5. Sets up the map for the art preview.
---
--- @function ArtTest.Start
--- @return nil
function ArtTest.Start()
atprint("Starting art preview mode")
if io.exists(path.assets.art_producer_lua) then
local producer = dofile(path.assets.art_producer_lua)
if type(producer) == "string" then
rawset(_G, "g_ArtTestProducer", producer)
end
end
local art_producer = rawget(_G, "g_ArtTestProducer")
local no_art_producer = (art_producer == nil)
if no_art_producer then
ArtTest.OpenChangeProducerDialog()
return
else
-- updates all files
ArtTest.SetProducer(art_producer)
atprint("Selected art producer %s", art_producer)
end
ArtTest.LoadExternalEntities()
ArtTest.SetUpMap()
end
local mounted
---
--- Loads all external entities required for the art preview mode.
---
--- This function is responsible for loading all the necessary entities, meshes, animations, materials, and textures for the art preview mode. It performs the following steps:
--- 1. Mounts the required folders to make the assets accessible.
--- 2. Enumerates all the entity files in the `path.assets.entities` directory.
--- 3. Loads each entity file using `DelayedLoadEntity`.
--- 4. Opens a loading screen and forces a reload of the bin assets and DLC assets.
--- 5. Waits for the bin assets to finish loading and then closes the loading screen.
--- 6. Waits for any delayed entity loads to complete.
--- 7. Reloads the Lua script.
---
--- @function ArtTest.LoadExternalEntities
--- @return nil
function ArtTest.LoadExternalEntities()
if not mounted then
mounted = true
MountFolder(path.assets.root .. "Bin/Common/Entities/Meshes/", path.assets.root .. "Bin/Common/Meshes/")
MountFolder(path.assets.root .. "Bin/Common/Entities/Animations/", path.assets.root .. "Bin/Common/Animations/")
MountFolder(path.assets.root .. "Bin/Common/Entities/Materials/", path.assets.root .. "Bin/Common/Materials/")
MountFolder(path.assets.root .. "Bin/Common/Entities/Mapping/", path.assets.root .. "Bin/Common/Mapping/")
MountFolder(path.assets.root .. "Bin/Common/Entities/Textures/", path.assets.root .. "Bin/win32/Textures/")
atprint("Mounted all entity folders")
end
local err, all_entities = AsyncListFiles(path.assets.entities, "*.ent")
if err then
atprint("Failed to enumerate entities - %s", err)
return
end
if not all_entities or #all_entities == 0 then
atprint("No entities to load")
return
end
for i, ent_file in ipairs(all_entities) do
DelayedLoadEntity(false, false, ent_file)
end
atprint("Will load %d entities", #all_entities)
LoadingScreenOpen("idArtTestLoadEntities", "ArtTestLoadEntities")
local old_render_mode = GetRenderMode()
WaitRenderMode("ui")
ForceReloadBinAssets()
DlcReloadAssets(DlcDefinitions)
-- actually reload the assets
LoadBinAssets(CurrentMapFolder)
-- wait & unmount
WaitNextFrame(2)
while AreBinAssetsLoading() do
Sleep(1)
end
WaitRenderMode(old_render_mode)
LoadingScreenClose("idArtTestLoadEntities", "ArtTestLoadEntities")
WaitDelayedLoadEntities()
-- ReloadClassEntities()
ReloadLua()
atprint("Reloaded all entities")
end
--- Sets up the map for the ArtTest module.
---
--- This function performs the following tasks:
--- - Activates the cameraMax camera
--- - Prints a message to the console indicating the camera has been set up
--- - Calls the ArtTest.PlacePreviewObjects() function to place preview objects on the map
--- - If any preview objects were placed, it moves the camera to view the first preview object
function ArtTest.SetUpMap()
cameraMax.Activate(1)
atprint("Camera set up")
local preview_objs = ArtTest.PlacePreviewObjects()
if preview_objs and next(preview_objs) then
ViewPos(preview_objs[1]:GetVisualPos())
atprint("Showing first preview object")
end
end
--- Returns a list of object classes to preview in the ArtTest module.
---
--- The list of object classes is determined by the current producer set in the
--- `g_ArtTestProducer` global variable. If `g_ArtTestProducer` is set to "Any",
--- then all object classes that have an entry in the `entity_producers_lua` file
--- will be included in the list.
---
--- @return table A list of object class names to preview.
function ArtTest.GetObjectClassesToPreview()
local current_producer = rawget(_G, "g_ArtTestProducer") or "Any"
local result = {}
if io.exists(path.assets.entity_producers_lua) then
local entity_producers = dofile(path.assets.entity_producers_lua)
for entity_id, produced_by in pairs(entity_producers) do
if (current_producer == "Any" or produced_by == current_producer) and g_Classes[entity_id] then
table.insert(result, entity_id)
end
end
end
return result
end
local spacing = 10 * guim
--- Places preview objects on the map for the ArtTest module.
---
--- This function places preview objects on the map for each object class that is
--- eligible to be previewed in the ArtTest module. The list of eligible object
--- classes is determined by the current producer set in the `g_ArtTestProducer`
--- global variable.
---
--- For each eligible object class, the function places one preview object for
--- each valid state of the object. The preview objects are placed in a grid
--- pattern, with a spacing of `spacing` units between each object.
---
--- The function returns a list of all the preview objects that were placed.
---
--- @param classes (optional) A list of object class names to preview. If not
--- provided, the function will use the list returned by
--- `ArtTest.GetObjectClassesToPreview()`.
--- @return table A list of preview objects that were placed.
function ArtTest.PlacePreviewObjects(classes)
local current_producer = rawget(_G, "g_ArtTestProducer") or "Any"
local y = 0
local result = {}
local classes = classes or ArtTest.GetObjectClassesToPreview()
if not classes or #classes == 0 then
atprint("No preview objects to place")
return
end
for i, classname in ipairs(classes) do
local class = g_Classes[classname]
local entity = class:GetEntity()
local entity_bbox = GetEntityBBox(entity)
local _, radius = entity_bbox:GetBSphere()
local x = 0
local half_spacing = radius + spacing
for i, state in pairs(EnumValidStates(entity)) do
x, y = x + half_spacing, y + half_spacing
local pos = point(x, y)
local preview_pos = point(x, y, terrain.GetHeight(x, y))
x, y = x + half_spacing, y + half_spacing
local preview_obj = PlaceObject(classname)
preview_obj:SetPos(preview_pos)
preview_obj:SetState(state)
table.insert(result, preview_obj)
local text_obj = PlaceObject("Text")
text_obj:SetDepthTest(false)
text_obj:SetText(entity .. "\n" .. GetStateName(state))
text_obj:SetPos(pos + point(radius, radius))
end
end
atprint("Placed %d preview objects", #result)
return result
end
----
function OnMsg.ChangeMapDone()
if CurrentMap == "ArtTest" then
CreateRealTimeThread(ArtTest.Start)
end
end
if FirstLoad and config.ArtTest then
CreateRealTimeThread(ChangeMap, "ArtTest")
end
|