15 name:
"Terrain Entity Variant Replacement",
16 description:
"Convert a terrain to a variant with conversion config",
17 wbModules: {
"WorldEditor" },
19 awesomeFontCode: 0xF06C)]
20class SCR_TerrainEntityVariantReplacementPlugin : WorkbenchPlugin
26 [
Attribute(
category:
"Debug", defvalue:
"0",
desc:
"Read Only - all calculations are done but no edits happen")]
27 protected bool m_bReadOnly;
34 protected int m_iReplacedEntities;
36 [
Attribute(
category:
"Replacement", defvalue: SET_ANCESTOR_FORCE_RELOAD.ToString(), uiwidget:
UIWidgets.ComboBox,
desc:
"Entity replacement mode - various modes, same (visual) results", enums:
SCR_ParamEnumArray.
FromString(
"Delete/create entity (SLOW - 2x);SetAncestor + visual refresh (slow - 1x);SetAncestor (fast - 0.01x - almost instant but requires world reload);SetAncestor + Save/Load world;SetAncestor + save and force reload"))]
37 protected int m_iReplacementMode;
39 [
Attribute(
category:
"Replacement", defvalue: SCR_ETerrainEntityVariant.AUTUMN.ToString(),
desc:
"Expected terrain season (DEFAULT = reset XOB only)", uiwidget:
UIWidgets.ComboBox, enumType: SCR_ETerrainEntityVariant)]
40 protected SCR_ETerrainEntityVariant m_eWantedVariant;
42 [
Attribute(
category:
"Replacement",
desc:
"Config object - takes precedence over Config File if defined")]
45 [
Attribute(
category:
"Replacement", defvalue:
"{E10A32BEF1F9E157}Configs/Workbench/WorldEditor/TerrainEntityVariantReplacementTool/Autumn.conf",
desc:
"Config file - if Config above is defined, this field is ignored",
params:
"conf class=SCR_TerrainEntityVariantConfig")]
48#ifdef DEBUG_TEVR_DUPLICATION
52 [
Attribute(
category:
"Duplication Debug", defvalue: SCR_ETerrainEntityVariant.AUTUMN.ToString(), uiwidget:
UIWidgets.ComboBox, enumType: SCR_ETerrainEntityVariant)]
53 protected SCR_ETerrainEntityVariant m_eDuplicationVariant;
56 protected bool m_bDuplicationDebug;
62 protected SCR_ETerrainEntityVariant m_eDuplicationVariant;
65 protected bool m_bDuplicationDebug;
70 protected bool m_bIsUI;
73 protected static const int CURRENT_LAYER_MODE = 1;
74 protected static const int SELECTED_ENTITIES_MODE = 2;
76 protected static const int CREATE_AND_DELETE = 0;
77 protected static const int SET_ANCESTOR_REFRESH = 1;
78 protected static const int SET_ANCESTOR = 2;
79 protected static const int SET_ANCESTOR_SAVELOAD = 3;
80 protected static const int SET_ANCESTOR_FORCE_RELOAD = 4;
82 protected static const string PARAM_WORLD =
"tevrWorld";
83 protected static const string PARAM_VARIANT =
"tevrVariant";
84 protected static const string PARAM_CONFIG =
"tevrConfig";
87 protected override void Run()
91 if (Workbench.ScriptDialog(
"Terrain Entity Variant Replacement",
"",
this) == 0)
94 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
102 if (worldEditorAPI.IsGameMode())
108 if (worldEditorAPI.IsPrefabEditMode())
114 if (!m_Config || !LoadConfig(m_Config, m_eWantedVariant))
116 if (!LoadConfig(m_sConfigFile, m_eWantedVariant))
120#ifdef DEBUG_TEVR_DUPLICATION
121 if (m_sTerrainToDuplicate)
128 if (!ConvertCurrentTerrain(m_iReplacedEntities, m_iReplacementMode))
131 if (m_iReplacementMode == SET_ANCESTOR_REFRESH || m_iReplacementMode == SET_ANCESTOR)
134 if (m_iReplacementMode != SET_ANCESTOR_FORCE_RELOAD && Workbench.ScriptDialog(
"Save/Load operation",
"SetAncestor requires a terrain save/load. Proceed?",
new WorkbenchDialog_OKCancel()) == 0)
137 if (!worldEditor.Save())
139 Print(
"World Editor cannot save the current world - please proceed manually",
LogLevel.ERROR);
140 Workbench.Dialog(
"Error saving world",
"World Editor cannot save the current world - please proceed manually");
145 worldEditorAPI.GetWorldPath(worldPath);
147 if (!worldPath || !worldEditor.SetOpenedResource(worldPath))
149 Print(
"World Editor cannot load the world - please proceed manually",
LogLevel.ERROR);
150 Workbench.Dialog(
"Error loading world",
"World Editor cannot load the saved world - please proceed manually");
157#ifdef DEBUG_TEVR_DUPLICATION
159 protected void DebugDuplication()
161 string terrainFilePath;
162 if (!Workbench.GetAbsolutePath(m_sTerrainToDuplicate.GetPath(), terrainFilePath,
true))
164 Print(
"Cannot get world path for " + m_sTerrainToDuplicate,
LogLevel.ERROR);
168 const string newTerrainFilePath = GetNewWorldFilePath(terrainFilePath, m_eDuplicationVariant);
170 if (m_bDuplicationDebug)
173 Print(
"destination = " + newTerrainFilePath,
LogLevel.NORMAL);
177 if (!SCR_WorldFilesHelper.DuplicateWorld(terrainFilePath, newTerrainFilePath))
192 Print(
"[SCR_TerrainEntityVariantReplacementPlugin.RunCommandline] STARTED", level:
LogLevel.NORMAL);
194 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
196 string terrainFilePath;
197 SCR_ETerrainEntityVariant variant;
199 if (!GetCLIParameterValues(worldEditor, terrainFilePath, variant, configResourceName))
202 PrintFormat(
"Converting %1 terrain to %2 variant using config %3", terrainFilePath, variant, configResourceName, level:
LogLevel.NORMAL);
204 if (!LoadConfig(configResourceName, variant))
209 string newTerrainFilePath = GetNewWorldFilePath(terrainFilePath, variant);
211 Print(
"Terrain duplication destination: " + newTerrainFilePath,
LogLevel.NORMAL);
213 if (!SCR_WorldFilesHelper.DuplicateWorld(terrainFilePath, newTerrainFilePath))
215 PrintFormat(
"Cannot duplicate world from %1 to %2", terrainFilePath, newTerrainFilePath, level:
LogLevel.ERROR);
221 if (!worldEditor.SetOpenedResource(newTerrainFilePath))
223 PrintFormat(
"Cannot open duplicated terrain in World Editor (%1)", newTerrainFilePath, level:
LogLevel.ERROR);
229 if (!ConvertCurrentTerrain())
235 if (!worldEditor.Save())
237 PrintFormat(
"Error saving duplicated terrain (%1)", newTerrainFilePath, level:
LogLevel.ERROR);
241 Print(
"Converted terrain successfully saved - " + newTerrainFilePath,
LogLevel.NORMAL);
242 Print(
"[SCR_TerrainEntityVariantReplacementPlugin.RunCommandline] FINISHED", level:
LogLevel.NORMAL);
247 protected bool GetCLIParameterValues(notnull WorldEditor worldEditor, out
string worldPath, out SCR_ETerrainEntityVariant variant, out
ResourceName config)
253 worldPath = GetCLIWorldPath(worldEditor,
error);
257 error =
string.Empty;
261 variant = GetCLIVariant(worldEditor,
error);
265 error =
string.Empty;
269 config = GetCLIConfig(worldEditor, config);
273 error =
string.Empty;
281 protected string GetCLIWorldPath(notnull WorldEditor worldEditor, out
string error)
285 if (!worldEditor.GetCmdLine(
"-" + PARAM_WORLD, result))
287 error =
string.Format(
"-%1 not provided", PARAM_WORLD);
291 if (!result.EndsWith(
".ent") || !
FileIO.FileExists(result))
293 error =
string.Format(
"-%1 value is not a valid file (%2)", PARAM_WORLD, result);
297 error =
string.Empty;
302 protected SCR_ETerrainEntityVariant GetCLIVariant(notnull WorldEditor worldEditor, out
string error)
304 SCR_ETerrainEntityVariant result;
307 if (!worldEditor.GetCmdLine(
"-" + PARAM_VARIANT, variantStr))
309 error =
string.Format(
"-%1 not provided - see SCR_ETerrainEntityVariant for possible values", PARAM_VARIANT);
313 variantStr.ToUpper();
314 result =
typename.StringToEnum(SCR_ETerrainEntityVariant, variantStr);
318 error =
string.Format(
"-%1 value is invalid - see SCR_ETerrainEntityVariant for possible values (provided %2)", PARAM_VARIANT, variantStr);
322 error =
string.Empty;
327 protected ResourceName GetCLIConfig(notnull WorldEditor worldEditor, out
string error)
330 if (!worldEditor.GetCmdLine(
"-" + PARAM_CONFIG, config))
332 error =
string.Format(
"-%1 not provided - config must be of type SCR_TerrainEntityVariantConfig", PARAM_CONFIG);
338 error =
string.Format(
"-%1 is empty - config must be of type SCR_TerrainEntityVariantConfig", PARAM_CONFIG);
343 if (config.StartsWith(
"{"))
349 if (!Workbench.GetAbsolutePath(config, config,
true) || !
FileIO.FileExists(config))
351 error =
string.Format(
"-%1 provided file does not exist (%2)", PARAM_CONFIG, config);
355 ResourceManager resourceManager = Workbench.GetModule(ResourceManager);
356 if (!resourceManager)
358 error =
"Cannot obtain Resource Manager";
362 MetaFile metaFile = resourceManager.GetMetaFile(config);
365 error =
string.Format(
"-%1 provided file is not registered (%2)", PARAM_CONFIG, config);
369 result = metaFile.GetResourceID();
372 if (!
Resource.Load(result).IsValid())
374 error =
string.Format(
"-%1 is not a valid/registered config (%2)", PARAM_CONFIG, config);
378 error =
string.Empty;
383 protected bool LoadConfig(
ResourceName configResourceName, SCR_ETerrainEntityVariant variant)
385 if (!configResourceName)
387 Print(
"Provided config ResourceName is empty",
LogLevel.ERROR);
392 if (!managedInstance)
394 Print(
"Provided config cannot be loaded - " + configResourceName,
LogLevel.ERROR);
405 return LoadConfig(config, variant);
411 if (config.m_aEntries.IsEmpty())
417 m_mReplacementMap.Clear();
419 foreach (SCR_TerrainEntityVariantConfigEntry configEntry : config.m_aEntries)
421 if (m_mReplacementMap.Contains(configEntry.m_sResourceName))
423 Print(
"Config definition duplicate of " + configEntry.m_sResourceName,
LogLevel.WARNING);
429 if (variantValue.m_eVariant == variant)
431 m_mReplacementMap.Insert(configEntry.m_sResourceName, variantValue.m_sResourceName);
437 if (m_mReplacementMap.IsEmpty())
447 protected bool ConvertCurrentTerrain(
int replacedEntities =
ALL_ENTITIES,
int replacementMode = SET_ANCESTOR)
449 Debug.BeginTimeMeasure();
450 bool result = ConvertCurrentTerrainMeasured(replacedEntities, replacementMode);
451 Debug.EndTimeMeasure(
"Total conversion time");
458 protected bool ConvertCurrentTerrainMeasured(
int replacedEntities,
int replacementMode)
460 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
463 Debug.BeginTimeMeasure();
464 array<IEntitySource> entitiesToProcess = GetentitiesToProcess(replacedEntities);
465 Debug.EndTimeMeasure(
"Get entities to process");
467 int entitiesToProcessCount = entitiesToProcess.Count();
468 if (entitiesToProcessCount < 1)
470 Print(
"No terrain entities were found to be replaced",
LogLevel.NORMAL);
472 Workbench.Dialog(
"No entities found to replace",
"No terrain entities were found to be replaced");
477 if (replacementMode == CREATE_AND_DELETE)
479 Debug.BeginTimeMeasure();
480 array<ref SCR_TEVR_Data> creationDataList = GetCreationData(entitiesToProcess);
481 Debug.EndTimeMeasure(entitiesToProcessCount.ToString() +
" entities replacement data creation");
485 Print(
"Read Only mode - no terrain entities were modified",
LogLevel.NORMAL);
487 Workbench.Dialog(
"Read Only mode",
"No terrain entities were modified");
492 worldEditorAPI.BeginEntityAction();
494 Debug.BeginTimeMeasure();
495 DeleteEntities(worldEditorAPI, entitiesToProcess);
496 Debug.EndTimeMeasure(entitiesToProcessCount.ToString() +
" entities deletion");
498 Debug.BeginTimeMeasure();
499 array<IEntitySource> createdEntitySources = CreateEntities(creationDataList);
500 Debug.EndTimeMeasure(
string.Format(
"%1/%2 entities (re-)creation", createdEntitySources.Count(), entitiesToProcessCount));
502 worldEditorAPI.EndEntityAction();
508 Print(
"Read Only mode - no terrain entities were modified",
LogLevel.NORMAL);
510 Workbench.Dialog(
"Read Only mode",
"No terrain entities were modified");
515 worldEditorAPI.BeginEntityAction();
517 Debug.BeginTimeMeasure();
518 SetAncestor(entitiesToProcess, replacementMode == 1);
519 Debug.EndTimeMeasure(
string.Format(
"%1/%1 entities re-ancestored", entitiesToProcessCount));
521 worldEditorAPI.EndEntityAction();
528 protected array<IEntitySource> GetentitiesToProcess(
int replacedEntities)
530 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
533 array<IEntitySource> result = {};
536 if (replacedEntities == CURRENT_LAYER_MODE)
537 currentLayerID = worldEditorAPI.GetCurrentEntityLayerId();
541 WBProgressDialog progress;
542 int count = worldEditorAPI.GetEditorEntityCount();
543 float prevProgress, currProgress;
545 progress =
new WBProgressDialog(
"Finding entities to replace...", worldEditor);
547 for (
int i; i < count; ++i)
551 currProgress = i / count;
552 if (currProgress - prevProgress >= 0.01)
554 progress.SetProgress(currProgress);
555 prevProgress = currProgress;
559 entitySource = worldEditorAPI.GetEditorEntity(i);
561 if (replacedEntities == SELECTED_ENTITIES_MODE && !worldEditorAPI.IsEntitySelected(entitySource))
567 int entitySourceLayerID = entitySource.GetLayerID();
568 if (replacedEntities == CURRENT_LAYER_MODE && entitySourceLayerID != currentLayerID)
571 ancestor = entitySource.GetAncestor();
575 if (!ancestor.GetResourceName())
578 if (!m_mReplacementMap.Contains(ancestor.GetResourceName()))
581 result.Insert(entitySource);
588 protected array<ref SCR_TEVR_Data> GetCreationData(notnull array<IEntitySource> entitySources)
590 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
593 array<ref SCR_TEVR_Data> result = {};
595 SCR_TEVR_Data creationData;
599 if (!entitySource.Get(
"coords",
coords))
606 if (!entitySource.Get(
"angles",
angles))
608 Print(
"Cannot get angleX/Y/Z from entity",
LogLevel.WARNING);
613 if (!entitySource.Get(
"scale",
scale))
620 if (!entitySource.Get(
"Flags",
flags))
626 creationData =
new SCR_TEVR_Data();
628 creationData.m_sResourceName = m_mReplacementMap[entitySource.GetAncestor().GetResourceName()];
629 if (!creationData.m_sResourceName)
632 creationData.m_Parent = entitySource.GetParent();
633 creationData.m_iLayerID = entitySource.GetLayerID();
634 creationData.m_vCoords =
coords;
635 creationData.m_vAngles =
angles;
636 creationData.m_fScale =
scale;
637 creationData.m_eFlags =
flags;
639 creationData.m_bSelected = worldEditorAPI.IsEntitySelected(entitySource);
641 result.Insert(creationData);
649 protected void DeleteEntities(notnull
WorldEditorAPI worldEditorAPI, notnull array<IEntitySource> entitySources)
651 worldEditorAPI.DeleteEntities(entitySources);
655 protected array<IEntitySource> CreateEntities(notnull array<ref SCR_TEVR_Data> creationDataList)
657 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
660 array<IEntitySource> result = {};
663 foreach (SCR_TEVR_Data creationData : creationDataList)
665 createdSource = worldEditorAPI.CreateEntity(
666 creationData.m_sResourceName,
668 creationData.m_iLayerID,
669 creationData.m_Parent,
670 creationData.m_vCoords,
671 creationData.m_vAngles);
674 Print(
"cannot create Prefab " + creationData.m_sResourceName,
LogLevel.ERROR);
679 if (createdSource.Get(
"scale",
scale) &&
scale != creationData.m_fScale)
681 if (!worldEditorAPI.SetVariableValue(createdSource, null,
"scale", creationData.m_fScale.ToString()))
682 Print(
"Cannot set scale to newly created entity",
LogLevel.WARNING);
686 if (createdSource.Get(
"Flags",
flags) &&
flags != creationData.m_eFlags)
688 if (!worldEditorAPI.SetVariableValue(createdSource, null,
"Flags", creationData.m_eFlags.ToString()))
689 Print(
"Cannot set Flags to newly created entity",
LogLevel.WARNING);
692 if (creationData.m_bSelected)
693 worldEditorAPI.AddToEntitySelection(createdSource);
695 result.Insert(createdSource);
702 protected void SetAncestor(notnull array<IEntitySource> entitySources,
bool forceRefresh)
704 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
707 set<int> updatedLayers;
709 updatedLayers =
new set<int>();
715 WBProgressDialog progress;
717 float prevProgress, currProgress;
721 progress =
new WBProgressDialog(
"Setting entity ancestors with visual refresh", worldEditor);
723 progress =
new WBProgressDialog(
"Setting entity ancestors w/o visual refresh", worldEditor);
725 count = entitySources.Count();
732 entitySource.SetAncestor(m_mReplacementMap[entitySource.GetAncestor().GetResourceName()]);
734 entityLayerID = entitySource.GetLayerID();
735 if (forceRefresh || !updatedLayers.Contains(entityLayerID))
739 !entitySource.Get(
"coords",
coords)
740 || !worldEditorAPI.SetVariableValue(entitySource, null,
"coords",
string.Format(
"%1 %2 %3",
coords[0],
coords[1],
coords[2])))
742 Print(
"Cannot refresh layer using coords!",
LogLevel.WARNING);
747 updatedLayers.Insert(entityLayerID);
753 currProgress = i / count;
754 if (currProgress - prevProgress >= 0.01)
756 progress.SetProgress(currProgress);
757 prevProgress = currProgress;
767 static string GetNewWorldFilePath(
string terrainFilePath, SCR_ETerrainEntityVariant variant)
769 string variantStr =
typename.EnumToString(SCR_ETerrainEntityVariant, variant);
770 variantStr.ToLower();
773 string newTerrainFilePath =
FilePath.StripFileName(terrainFilePath);
774 if (newTerrainFilePath)
776 newTerrainFilePath = newTerrainFilePath.Substring(0, newTerrainFilePath.Length() - 1);
777 if (newTerrainFilePath.EndsWith(
":"))
780 newTerrainFilePath =
string.Format(
"%1_%2/", newTerrainFilePath, variantStr);
783 return string.Format(
"%1%2_%3.ent", newTerrainFilePath,
FilePath.StripExtension(
FilePath.StripPath(terrainFilePath)), variantStr);
805 IEntitySource m_Parent;
818 [
Attribute(
desc:
"If multiple entries are of the same variant, only the first one is used")]
819 ref array<ref SCR_TerrainEntityVariantConfigEntry> m_aEntries;