12[WorkbenchToolAttribute(
13 name:
"Windows Prefabs Generator Tool",
14 description:
"Generate prefabs for destroyable windows.\n\n" +
15 "- select sash/window xob/prefab by:\n" +
16 "-- using the Resource Browser\n" +
17 "-- opening the prefab in Prefab Edit Mode\n" +
18 "- select glass xob from the UI",
19 awesomeFontCode : 0xF2D2)]
20class SCR_GenerateWindowsPrefabsTool : WorldEditorTool
26 [
Attribute(defvalue:
"1", uiwidget: UIWidgets.ComboBox,
desc:
"File system where new prefabs will be created", enums: SCR_ParamEnumArray.FromAddons(),
category:
"General")]
27 protected int m_iAddonToUse;
30 protected bool m_bRemoveExistingChildren;
36 [
Attribute(defvalue:
"{86834A0D5920F32F}Prefabs/Structures/Core/DestructibleGlass_Base.et",
desc:
"Choose a base class of your glass prefabs",
params:
"et",
category:
"Glass")]
37 protected ResourceName m_sGlassBasePrefab;
40 protected ResourceName m_sGlassSavePath;
43 protected ref array<ResourceName> m_aGlassVariants;
45 [
Attribute(defvalue:
"-1", UIWidgets.Slider,
desc:
"Number of damage variants - set to -1 to autodetect",
params:
"-1 100 1",
category:
"Glass")]
46 protected int m_iGlassDmgCount;
48 [
Attribute(
desc:
"Model base path and base name to use for the shards; leave empty to generate a name from the save path",
category:
"Glass")]
49 protected string m_sGlassName;
51 [
Attribute(defvalue:
"1",
desc:
"Use Multi Phase destruction instead of Fractal destruction",
category:
"Glass")]
52 protected bool m_bUseMultiPhaseDestruction;
58 [
Attribute(defvalue:
"{08C59B2AAE05ACFE}Prefabs/Structures/BuildingParts/Windows/WindowSash_base.et",
desc:
"Base class for the sash prefabs",
params:
"et",
category:
"Sash")]
59 protected ResourceName m_sSashBasePrefab;
61 [
Attribute(defvalue:
"{604D1E6E05FB1038}Assets/Structures/BuildingsParts/Windows",
desc:
"Path to save the sash prefabs",
params:
"unregFolders",
category:
"Sash")]
62 protected ResourceName m_sSashSavePath;
68 [
Attribute(defvalue:
"{3FF56BA19C8780DE}Prefabs/Structures/BuildingParts/Windows/Window_Base.et",
desc:
"Base class for window prefabs",
params:
"et",
category:
"Window")]
69 protected ResourceName m_sWindowBasePrefab;
71 [
Attribute(defvalue:
"{604D1E6E05FB1038}Assets/Structures/BuildingsParts/Windows",
desc:
"Path to save the window prefabs",
params:
"unregFolders",
category:
"Window")]
72 protected ResourceName m_sWindowSavePath;
74 protected static const string BASE_PREFAB_SUFFIX =
"_base";
75 protected static const string MESHOBJECT_CLASSNAME =
"MeshObject";
76 protected static const string HIERARCHY_CLASSNAME =
"Hierarchy";
77 protected static const string GENERICENTITY_CLASSNAME =
"GenericEntity";
80 protected static const ref array<string> GLASS_CRITERIA = {
"glass_",
"x" };
81 protected static const ref array<string> SASH_CRITERIA = {
"winsash_",
"x" };
82 protected static const ref array<string> WINDOW_CRITERIA = {
"win_",
"x" };
85 protected static const ref array<string> BONE_CRITERIA = {
"x" };
93 bool m_bUsePureIEntitySource;
103 protected void CreateGlassButton()
110 protected void CreateSashesButton()
112 CreateAndFillPrefabFromXOBs(SASH_CRITERIA, { GLASS_CRITERIA }, m_sSashBasePrefab, m_sSashSavePath);
117 protected void CreateFramesButton()
119 CreateAndFillPrefabFromXOBs(WINDOW_CRITERIA, { SASH_CRITERIA, GLASS_CRITERIA }, m_sWindowBasePrefab, m_sWindowSavePath);
124 protected void UpdateSashPrefabs()
126 RefreshPrefabs(SASH_CRITERIA, { GLASS_CRITERIA });
131 protected void UpdateWindowPrefabs()
133 RefreshPrefabs(WINDOW_CRITERIA, { SASH_CRITERIA, GLASS_CRITERIA });
144 protected void CreateAndFillPrefabFromXOBs(notnull array<string> xobCriteria, notnull array<ref array<string>> fillerCriteria, ResourceName basePrefab, ResourceName saveDirectory)
147 if (saveDirectory.IsEmpty())
149 Print(
"Save Directory is empty. Please select a valid save location",
LogLevel.WARNING);
153 if (basePrefab.IsEmpty())
155 Print(
"Base Class is empty. Please select a valid base prefab",
LogLevel.WARNING);
160 string absoluteSaveDir;
161 if (!SCR_AddonTool.GetAddonAbsolutePath(m_iAddonToUse, saveDirectory, absoluteSaveDir))
163 Print(
"Wrong path: " + SCR_AddonTool.GetAddonID(m_iAddonToUse) + saveDirectory.GetPath(),
LogLevel.WARNING);
168 array<ResourceName> foundXOBs = GetListedSelectedOrOpenedResources(
"xob", xobCriteria);
169 if (foundXOBs.IsEmpty())
171 Print(
"No Sash models are listed nor are they selected via the Resource Browser. Do one of the two",
LogLevel.WARNING);
175 map<string, ResourceName> prefabMap = PrefabSearchMap(fillerCriteria);
177 foreach (ResourceName xobPath : foundXOBs)
180 m_API.BeginEntityAction(
"Processing " + xobPath);
182 if (!CreateAndSaveBaseAndDefaultPrefabFromXOB(prefabMap, xobPath, basePrefab, absoluteSaveDir))
183 Print(
"Could not process " + xobPath.GetPath(),
LogLevel.ERROR);
185 m_API.EndEntityAction();
192 protected bool CreateAndSaveBaseAndDefaultPrefabFromXOB(map<string, ResourceName> prefabMap, ResourceName xobPath, ResourceName parentPrefab,
string absoluteSaveDir)
194 IEntitySource entitySource = m_API.CreateEntity(parentPrefab,
"", m_API.GetCurrentEntityLayerId(), null, vector.Zero, vector.Zero);
197 Print(
"Prefab's entity could not be created",
LogLevel.ERROR);
202 if (!SCR_BaseContainerTools.FindComponentSource(entitySource, MESHOBJECT_CLASSNAME))
204 Print(parentPrefab +
" does not have the " + MESHOBJECT_CLASSNAME +
" component - fix the base prefab " + parentPrefab,
LogLevel.ERROR);
205 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
210 if (!m_API.SetVariableValue(entitySource, { new ContainerIdPathEntry(MESHOBJECT_CLASSNAME) },
"Object", xobPath))
213 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
217 string fileNameWithoutExtension = FilePath.StripExtension(FilePath.StripPath(xobPath));
220 string absoluteFilePath = FilePath.Concat(absoluteSaveDir, fileNameWithoutExtension + BASE_PREFAB_SUFFIX +
".et");
221 if (!SaveEntitySourceAsNewTemplate(entitySource, absoluteFilePath))
223 Print(
"Script was unable to create BASE prefab",
LogLevel.ERROR);
224 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
229 entitySource = m_API.CreateEntity(SCR_WorldEditorToolHelper.GetResourceNameFromFile(absoluteFilePath),
"", m_API.GetCurrentEntityLayerId(), null, vector.Zero, vector.Zero);
233 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
238 if (prefabMap && !prefabMap.IsEmpty())
239 CreateEntitySourcesFromBoneNames(prefabMap, entitySource,
true);
242 absoluteFilePath = FilePath.Concat(absoluteSaveDir, fileNameWithoutExtension +
".et");
243 if (!SaveEntitySourceAsNewTemplate(entitySource, absoluteFilePath))
246 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
250 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
258 protected bool SaveEntitySourceAsNewTemplate(notnull IEntitySource entitySource,
string absoluteFilePath)
260 bool fileCreated = m_API.CreateEntityTemplate(entitySource, absoluteFilePath);
265 Print(
"Failed to create " + absoluteFilePath,
LogLevel.WARNING);
272 protected void CreateGlass()
275 if (m_sGlassSavePath.IsEmpty())
277 Print(
"Path To SaveGlass is empty. Please select a valid save location",
LogLevel.WARNING);
281 if (m_sGlassBasePrefab.IsEmpty())
283 Print(
"Base Class Glass is empty. Please select a valid base prefab for your glass",
LogLevel.WARNING);
287 if (m_aGlassVariants)
289 for (
int i = m_aGlassVariants.Count() - 1; i >= 0; i--)
291 if (m_aGlassVariants[i].
IsEmpty())
292 m_aGlassVariants.RemoveOrdered(i);
295 WorldEditorTool.UpdatePropertyPanel();
298 if (!m_aGlassVariants || m_aGlassVariants.IsEmpty())
300 Print(
"Glass Variants is empty. Please select at least one glass XOB variant",
LogLevel.WARNING);
304 string glassName = m_sGlassName.Trim();
305 if (glassName.IsEmpty())
306 glassName = FilePath.StripPath(m_sGlassSavePath.GetPath());
309 string absoluteSaveDir;
310 if (!SCR_AddonTool.GetAddonAbsolutePath(m_iAddonToUse, m_sGlassSavePath, absoluteSaveDir))
312 Print(
"Wrong path: " + SCR_AddonTool.GetAddonID(m_iAddonToUse) + m_sGlassSavePath.GetPath(),
LogLevel.WARNING);
317 int glassVariants = m_iGlassDmgCount;
318 if (glassVariants < 0)
319 glassVariants = GetGlassCount(m_aGlassVariants[0]);
322 m_API.BeginEntityAction(
"Processing " + glassName);
323 IEntitySource entitySource = m_API.CreateEntity(m_sGlassBasePrefab,
"", m_API.GetCurrentEntityLayerId(), null, vector.Zero, vector.Zero);
324 array<ref ContainerIdPathEntry> containerPath;
328 if (m_bUseMultiPhaseDestruction)
332 m_API.SetVariableValue(entitySource, subContainerPath,
"m_PhaseModel", GetDestroyedGlassModelPath(m_aGlassVariants[0], 0));
338 foreach (
int currentIndex, ResourceName glassVariant : m_aGlassVariants)
340 for (
int i = glassVariants; i > 0; i--)
343 string damageMask = GetDamageMask(glassVariant);
344 if (glassVariants > 1)
346 string tempPath = FilePath.StripFileName(damageMask);
347 string tempFile = FilePath.StripPath(damageMask);
348 tempFile.Replace(
"Glass_01",
"Glass_" + i.ToString(2));
349 damageMask = tempPath + tempFile;
353 m_API.CreateObjectArrayVariableMember(entitySource, containerPath,
"m_FractalVariants",
"SCR_FractalVariation", currentIndex);
355 m_API.SetVariableValue(entitySource, subContainerPath,
"m_ModelNormal", (
string)glassVariant);
356 m_API.SetVariableValue(entitySource, subContainerPath,
"m_ModelDestroyed", GetDestroyedGlassModelPath(glassVariant, i));
357 m_API.SetVariableValue(entitySource, subContainerPath,
"m_aModelFragments", GetDamageVariants(damageMask));
364 m_API.SetVariableValue(entitySource, containerPath,
"Object", m_aGlassVariants[0]);
367 bool fileCreated = m_API.CreateEntityTemplate(entitySource, FilePath.Concat(absoluteSaveDir, glassName +
".et"));
369 Print(
string.Format(
"@\"%1\"", FilePath.Concat(absoluteSaveDir, glassName +
".et")),
LogLevel.NORMAL);
371 Print(
"Script was unable to create new prefab at the designated location",
LogLevel.ERROR);
373 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
374 m_API.EndEntityAction();
379 protected ResourceName GetDestroyedGlassModelPath(ResourceName
resourceName,
int variant)
389 tempString.Replace(
"Glass_01",
"Glass_" + variant.ToString(2));
391 tempString = FilePath.StripFileName(tempString) +
"dst/" + FilePath.StripPath(tempString);
392 tempString.Replace(
".xob",
"_dst.xob");
395 if (!Workbench.GetAbsolutePath(tempString, absPathDst,
true))
397 Print(
"Unable to find destroyed model for " + tempString +
". Base model will be used instead",
LogLevel.WARNING);
401 return SCR_WorldEditorToolHelper.GetResourceNameFromFile(absPathDst);
407 protected string GetDamageMask(ResourceName
resourceName)
410 inputString = FilePath.StripFileName(inputString) +
"Dmg/" + FilePath.StripPath(inputString);
411 inputString.Replace(
".xob",
"_dmg_");
418 protected string GetDamageVariants(ResourceName
resourceName)
420 bool variantExists =
true;
421 int numberOfVariants = 0;
423 array<string> variants = {};
424 while (variantExists)
427 string inputStringTemp =
resourceName + numberOfVariants.ToString(2) +
".xob";
428 variantExists = Workbench.GetAbsolutePath(inputStringTemp, absPath);
431 variants.Insert(Workbench.GetResourceName(inputStringTemp));
436 return SCR_StringHelper.Join(
",", variants,
false);
441 protected int GetGlassCount(ResourceName glassResourceName)
443 if (glassResourceName.IsEmpty())
446 bool variantExists =
true;
447 int numberOfVariants = 1;
448 string relativeFilePath = glassResourceName.GetPath();
449 relativeFilePath.Replace(
"Glass_01",
"Dmg/Glass_01");
451 while (variantExists)
453 string inputStringTemp = relativeFilePath;
454 inputStringTemp.Replace(
".xob",
"_dmg_00.xob");
455 inputStringTemp.Replace(
"Glass_01",
"Glass_" + numberOfVariants.ToString(2));
458 variantExists = Workbench.GetAbsolutePath(inputStringTemp, inputStringTemp,
true);
463 return numberOfVariants;
474 protected void RefreshPrefabs(notnull array<string> prefabCriteria, notnull array<ref array<string>> fillerCriteria)
476 array<ResourceName> prefabsToUpdate = GetListedSelectedOrOpenedResources(
"et", prefabCriteria);
477 if (prefabsToUpdate.IsEmpty())
483 map<string, ResourceName> prefabMap = PrefabSearchMap(fillerCriteria);
484 if (prefabMap.IsEmpty())
490 foreach (ResourceName prefabToUpdate : prefabsToUpdate)
492 if (prefabToUpdate.IsEmpty())
495 m_API.BeginEntityAction(
"Processing " + prefabToUpdate);
497 if (!RefreshPrefab(prefabMap, prefabToUpdate))
498 Print(
"Could not process " + prefabToUpdate,
LogLevel.ERROR);
500 m_API.EndEntityAction();
506 protected bool RefreshPrefab(notnull map<string, ResourceName> boneResourceNames, ResourceName prefabResourceName)
508 IEntitySource entitySource = m_API.CreateEntity(prefabResourceName,
"", m_API.GetCurrentEntityLayerId(), null, vector.Zero, vector.Zero);
512 IEntitySource actualPrefab = IEntitySource.Cast(entitySource.GetAncestor());
515 Print(
"Created entity's source does not have an ancestor",
LogLevel.ERROR);
516 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
521 array<IEntitySource> childEntitySources;
522 if (m_bRemoveExistingChildren && entitySource.GetNumChildren() > 0)
524 childEntitySources = {};
525 for (
int i, count = entitySource.GetNumChildren(); i < count; i++)
527 childEntitySources.Insert(actualPrefab.GetChild(i));
530 if (!childEntitySources.IsEmpty())
531 m_API.RemovePrefabMembers(childEntitySources);
534 array<ref Resource> childrenResources;
535 if (m_bUsePureIEntitySource)
539 childrenResources = CreateResourcesFromBoneNames(boneResourceNames, entitySource);
542 childEntitySources = {};
543 foreach (Resource resource : childrenResources)
545 childEntitySources.Insert(resource.GetResource().ToEntitySource());
547 if (childEntitySources && !childEntitySources.IsEmpty())
548 m_API.AddPrefabMembers(actualPrefab, childEntitySources);
555 childEntitySources = CreateEntitySourcesFromBoneNames(boneResourceNames, entitySource,
false);
556 if (childEntitySources && !childEntitySources.IsEmpty())
557 m_API.MoveEntitiesToPrefab(entitySource, actualPrefab, childEntitySources);
561 bool savedSuccessfully = m_API.SaveEntityTemplate(actualPrefab);
562 if (savedSuccessfully)
565 Print(prefabResourceName.GetPath() +
" failed to save!",
LogLevel.ERROR);
567 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
581 protected map<string, ResourceName> PrefabSearchMap(notnull array<ref array<string>> searchStringGroups)
583 map<string, ResourceName> result =
new map<string, ResourceName>();
584 array<ResourceName> searchResult;
585 foreach (array<string> searchStrings : searchStringGroups)
587 foreach (ResourceName
resourceName : SCR_WorkbenchHelper.SearchWorkbenchResources({
"et" }, searchStrings))
589 string toLowerFileNameWithoutExtension = FilePath.StripExtension(FilePath.StripPath(
resourceName));
590 toLowerFileNameWithoutExtension.ToLower();
591 result.Insert(toLowerFileNameWithoutExtension,
resourceName);
605 protected array<ResourceName> GetListedSelectedOrOpenedResources(
string wantedExtension =
"", array<string> keywords = null, array<ResourceName> resourceNames = null)
607 array<ResourceName> temporaryResults;
608 if (resourceNames && !resourceNames.IsEmpty())
609 temporaryResults = resourceNames;
611 temporaryResults = SCR_WorldEditorToolHelper.GetSelectedOrOpenedResources();
613 array<ResourceName> result = {};
617 string resourceNameLC = FilePath.StripExtension(
resourceName, extension);
619 if (!wantedExtension.IsEmpty() && extension != wantedExtension)
625 resourceNameLC = FilePath.StripPath(resourceNameLC);
626 resourceNameLC.ToLower();
628 if (SCR_StringHelper.ContainsEvery(resourceNameLC, keywords))
637 protected array<IEntitySource> CreateEntitySourcesFromBoneNames(notnull map<string, ResourceName> prefabMap, notnull IEntitySource entitySource,
bool addToParent)
639 if (prefabMap.IsEmpty())
642 IEntity entity = m_API.SourceToEntity(entitySource);
649 array<string> boneNames = {};
651 boneNames.RemoveItem(
"Scene_Root");
653 IEntitySource parent;
655 parent = entitySource;
657 array<IEntitySource> newPrefabSources = {};
658 foreach (
string boneName : boneNames)
660 IEntitySource bonePrefabEntitySource = CreateEntitySourceFromBoneName(prefabMap, boneName, parent);
661 if (bonePrefabEntitySource)
662 newPrefabSources.Insert(bonePrefabEntitySource);
664 Print(
"Bone " + boneName +
" could not see a matching prefab created",
LogLevel.VERBOSE);
667 return newPrefabSources;
674 protected IEntitySource CreateEntitySourceFromBoneName(notnull map<string, ResourceName> prefabMap,
string boneName, IEntitySource parent)
676 if (boneName.IsEmpty())
679 string prefab = FindAdaptedPrefab(prefabMap, boneName);
680 if (prefab.IsEmpty())
682 Print(
"No prefab could be found matching bone " + boneName,
LogLevel.VERBOSE);
686 IEntitySource result = m_API.CreateEntity(prefab,
"", m_API.GetCurrentEntityLayerId(), parent, vector.Zero, vector.Zero);
689 Print(
"Entity " + prefab +
" could not be created for bone " + boneName,
LogLevel.WARNING);
693 IEntityComponentSource hierarchyComponent = SCR_BaseContainerTools.FindComponentSource(result, HIERARCHY_CLASSNAME);
694 if (!hierarchyComponent)
696 Print(
"No Hierarchy component - fix by adding a " + HIERARCHY_CLASSNAME +
" component to " + prefab +
" first",
LogLevel.WARNING);
697 SCR_WorldEditorToolHelper.DeleteEntityFromSource(result);
701 m_API.SetVariableValue(result, {
new ContainerIdPathEntry(HIERARCHY_CLASSNAME) },
"PivotID", boneName);
708 protected array<ref Resource> CreateResourcesFromBoneNames(notnull map<string, ResourceName> prefabMap, notnull IEntitySource entitySource)
710 if (prefabMap.IsEmpty())
713 IEntity entity = m_API.SourceToEntity(entitySource);
722 array<ref Resource> result = {};
723 Resource bonePrefabResource;
724 foreach (
string boneName : boneNames)
726 bonePrefabResource = CreateResourceFromBoneName(prefabMap, boneName);
727 if (bonePrefabResource)
728 result.Insert(bonePrefabResource);
730 Print(
"Bone " + boneName +
" could not see a matching prefab created",
LogLevel.VERBOSE);
738 protected Resource CreateResourceFromBoneName(notnull map<string, ResourceName> prefabMap,
string boneName)
740 if (boneName.IsEmpty())
743 string prefab = FindAdaptedPrefab(prefabMap, boneName);
744 if (prefab.IsEmpty())
746 Print(
"No prefab could be found matching bone " + boneName,
LogLevel.VERBOSE);
750 Resource result = BaseContainerTools.CreateContainer(GENERICENTITY_CLASSNAME);
751 if (!result || !result.IsValid())
753 Print(
"Could not create a new " + GENERICENTITY_CLASSNAME +
" for boneName",
LogLevel.WARNING);
757 IEntitySource resultSource = result.GetResource().ToEntitySource();
758 resultSource.SetAncestor(prefab);
759 IEntityComponentSource hierarchyComponent = SCR_BaseContainerTools.FindComponentSource(resultSource, HIERARCHY_CLASSNAME);
760 if (!hierarchyComponent)
762 Print(
"No Hierarchy component - fix by adding a " + HIERARCHY_CLASSNAME +
" component to " + prefab +
" first",
LogLevel.WARNING);
765 hierarchyComponent.Set(
"PivotID", boneName);
773 array<string> result = {};
774 entity.GetAnimation().GetBoneNames(result);
775 for (
int i = result.Count() - 1; i >= 0; i--)
777 string boneName = result[i];
779 if (boneName ==
"Scene_Root" || boneName.StartsWith(
"COM_") || !SCR_StringHelper.ContainsEvery(boneName, BONE_CRITERIA))
789 protected ResourceName FindAdaptedPrefab(notnull map<string, ResourceName> prefabMap,
string boneName)
791 if (boneName.IsEmpty())
795 foreach (
string key, ResourceName value : prefabMap)
797 if (boneName.Contains(key))
799 Print(
"Found " + key +
" for bone " + boneName,
LogLevel.DEBUG);
804 Print(
"Did not find prefab for bone " + boneName,
LogLevel.DEBUG);
void ContainerIdPathEntry(string propertyName, int index=-1)
ResourceName resourceName
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
enum EVehicleType IEntity
class WorkbenchDialog_AbortRetryIgnore ButtonAttribute("OK", true)
proto external Animation GetAnimation()
proto external int GetBoneNames(int ctrlIdx, array< string > events)
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
SCR_FieldOfViewSettings Attribute
proto native bool IsEmpty()