Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_GenerateWindowsPrefabsTool.c
Go to the documentation of this file.
1#ifdef WORKBENCH
4
6
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
21{
22 /*
23 Category: General
24 */
25
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;
28
29 [Attribute(defvalue: "1", desc: "Remove updated prefab's existing children", category: "General")]
30 protected bool m_bRemoveExistingChildren;
31
32 /*
33 Category: Glass
34 */
35
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;
38
39 [Attribute(desc: "Path to save the glass prefabs", params: "unregFolders", category: "Glass")]
40 protected ResourceName m_sGlassSavePath;
41
42 [Attribute(desc: "Choose XOB files to process", uiwidget: UIWidgets.ResourcePickerThumbnail, params: "xob", category: "Glass")]
43 protected ref array<ResourceName> m_aGlassVariants;
44
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;
47
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;
50
51 [Attribute(defvalue: "1", desc: "Use Multi Phase destruction instead of Fractal destruction", category: "Glass")]
52 protected bool m_bUseMultiPhaseDestruction;
53
54 /*
55 Category: Sash
56 */
57
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;
60
61 [Attribute(defvalue: "{604D1E6E05FB1038}Assets/Structures/BuildingsParts/Windows", desc: "Path to save the sash prefabs", params: "unregFolders", category: "Sash")]
62 protected ResourceName m_sSashSavePath;
63
64 /*
65 Category: Window
66 */
67
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;
70
71 [Attribute(defvalue: "{604D1E6E05FB1038}Assets/Structures/BuildingsParts/Windows", desc: "Path to save the window prefabs", params: "unregFolders", category: "Window")]
72 protected ResourceName m_sWindowSavePath;
73
74 protected static const string BASE_PREFAB_SUFFIX = "_base"; //< only used to name the Prefab file - case-insensitive
75 protected static const string MESHOBJECT_CLASSNAME = "MeshObject";
76 protected static const string HIERARCHY_CLASSNAME = "Hierarchy";
77 protected static const string GENERICENTITY_CLASSNAME = "GenericEntity";
78
79 // criteria MUST be lowercase as ResourceNames are lowercased when searched
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" };
83
85 protected static const ref array<string> BONE_CRITERIA = { "x" };
86
87 /*
88 Category: Debug
89 */
90
91 // pure IEntitySource mode activated! -not yet-
92 // [Attribute(category: "DEBUG - DO NOT USE FOR PRODUCTION")]
93 bool m_bUsePureIEntitySource;
94
95 /*
96
97 BUTTON SECTION
98
99 */
100
101 //------------------------------------------------------------------------------------------------
102 [ButtonAttribute("IMPORT: Glass .xob")]
103 protected void CreateGlassButton()
104 {
105 CreateGlass();
106 }
107
108 //------------------------------------------------------------------------------------------------
109 [ButtonAttribute("Sash .xob")]
110 protected void CreateSashesButton()
111 {
112 CreateAndFillPrefabFromXOBs(SASH_CRITERIA, { GLASS_CRITERIA }, m_sSashBasePrefab, m_sSashSavePath);
113 }
114
115 //------------------------------------------------------------------------------------------------
116 [ButtonAttribute("Frame .xob")]
117 protected void CreateFramesButton()
118 {
119 CreateAndFillPrefabFromXOBs(WINDOW_CRITERIA, { SASH_CRITERIA, GLASS_CRITERIA }, m_sWindowBasePrefab, m_sWindowSavePath);
120 }
121
122 //------------------------------------------------------------------------------------------------
123 [ButtonAttribute("UPDATE: Sash .et")]
124 protected void UpdateSashPrefabs()
125 {
126 RefreshPrefabs(SASH_CRITERIA, { GLASS_CRITERIA });
127 }
128
129 //------------------------------------------------------------------------------------------------
130 [ButtonAttribute("Window .et")]
131 protected void UpdateWindowPrefabs()
132 {
133 RefreshPrefabs(WINDOW_CRITERIA, { SASH_CRITERIA, GLASS_CRITERIA });
134 }
135
136 /*
137
138 CREATE SECTION
139
140 */
141
142 //------------------------------------------------------------------------------------------------
144 protected void CreateAndFillPrefabFromXOBs(notnull array<string> xobCriteria, notnull array<ref array<string>> fillerCriteria, ResourceName basePrefab, ResourceName saveDirectory)
145 {
146 // check input
147 if (saveDirectory.IsEmpty())
148 {
149 Print("Save Directory is empty. Please select a valid save location", LogLevel.WARNING);
150 return;
151 }
152
153 if (basePrefab.IsEmpty())
154 {
155 Print("Base Class is empty. Please select a valid base prefab", LogLevel.WARNING);
156 return;
157 }
158
159 // check saveDir
160 string absoluteSaveDir;
161 if (!SCR_AddonTool.GetAddonAbsolutePath(m_iAddonToUse, saveDirectory, absoluteSaveDir))
162 {
163 Print("Wrong path: " + SCR_AddonTool.GetAddonID(m_iAddonToUse) + saveDirectory.GetPath(), LogLevel.WARNING);
164 return;
165 }
166
167 // get & check XOBs
168 array<ResourceName> foundXOBs = GetListedSelectedOrOpenedResources("xob", xobCriteria);
169 if (foundXOBs.IsEmpty())
170 {
171 Print("No Sash models are listed nor are they selected via the Resource Browser. Do one of the two", LogLevel.WARNING);
172 return;
173 }
174
175 map<string, ResourceName> prefabMap = PrefabSearchMap(fillerCriteria);
176
177 foreach (ResourceName xobPath : foundXOBs)
178 {
179 // create entity with selected base class
180 m_API.BeginEntityAction("Processing " + xobPath);
181
182 if (!CreateAndSaveBaseAndDefaultPrefabFromXOB(prefabMap, xobPath, basePrefab, absoluteSaveDir))
183 Print("Could not process " + xobPath.GetPath(), LogLevel.ERROR);
184
185 m_API.EndEntityAction();
186 }
187 }
188
189 //------------------------------------------------------------------------------------------------
192 protected bool CreateAndSaveBaseAndDefaultPrefabFromXOB(map<string, ResourceName> prefabMap, ResourceName xobPath, ResourceName parentPrefab, string absoluteSaveDir)
193 {
194 IEntitySource entitySource = m_API.CreateEntity(parentPrefab, "", m_API.GetCurrentEntityLayerId(), null, vector.Zero, vector.Zero);
195 if (!entitySource)
196 {
197 Print("Prefab's entity could not be created", LogLevel.ERROR);
198 return false;
199 }
200
201 // find MeshObject component - if it doesn't exist, SKIP
202 if (!SCR_BaseContainerTools.FindComponentSource(entitySource, MESHOBJECT_CLASSNAME))
203 {
204 Print(parentPrefab + " does not have the " + MESHOBJECT_CLASSNAME + " component - fix the base prefab " + parentPrefab, LogLevel.ERROR);
205 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
206 return false;
207 }
208
209 // apply XOB path to mesh
210 if (!m_API.SetVariableValue(entitySource, { new ContainerIdPathEntry(MESHOBJECT_CLASSNAME) }, "Object", xobPath))
211 {
212 Print("Could not apply XOB model", LogLevel.ERROR);
213 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
214 return false;
215 }
216
217 string fileNameWithoutExtension = FilePath.StripExtension(FilePath.StripPath(xobPath));
218
219 // save as base prefab
220 string absoluteFilePath = FilePath.Concat(absoluteSaveDir, fileNameWithoutExtension + BASE_PREFAB_SUFFIX + ".et");
221 if (!SaveEntitySourceAsNewTemplate(entitySource, absoluteFilePath))
222 {
223 Print("Script was unable to create BASE prefab", LogLevel.ERROR);
224 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
225 return false;
226 }
227
228 // prepare the default prefab
229 entitySource = m_API.CreateEntity(SCR_WorldEditorToolHelper.GetResourceNameFromFile(absoluteFilePath), "", m_API.GetCurrentEntityLayerId(), null, vector.Zero, vector.Zero);
230 if (!entitySource)
231 {
232 Print("Could not create BASE prefab!", LogLevel.ERROR);
233 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
234 return false;
235 }
236
237 // get all bones and create prefab from them - if prefabMap is provided
238 if (prefabMap && !prefabMap.IsEmpty())
239 CreateEntitySourcesFromBoneNames(prefabMap, entitySource, true);
240
241 // save the default prefab
242 absoluteFilePath = FilePath.Concat(absoluteSaveDir, fileNameWithoutExtension + ".et");
243 if (!SaveEntitySourceAsNewTemplate(entitySource, absoluteFilePath))
244 {
245 Print("Could not create DEFAULT prefab", LogLevel.ERROR);
246 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
247 return false;
248 }
249
250 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
251
252 return true;
253 }
254
255 //------------------------------------------------------------------------------------------------
258 protected bool SaveEntitySourceAsNewTemplate(notnull IEntitySource entitySource, string absoluteFilePath)
259 {
260 bool fileCreated = m_API.CreateEntityTemplate(entitySource, absoluteFilePath);
261
262 if (fileCreated)
263 Print("Created " + absoluteFilePath, LogLevel.VERBOSE);
264 else
265 Print("Failed to create " + absoluteFilePath, LogLevel.WARNING);
266
267 return fileCreated;
268 }
269
270 //------------------------------------------------------------------------------------------------
272 protected void CreateGlass()
273 {
274 // Validate input parameters
275 if (m_sGlassSavePath.IsEmpty())
276 {
277 Print("Path To SaveGlass is empty. Please select a valid save location", LogLevel.WARNING);
278 return;
279 }
280
281 if (m_sGlassBasePrefab.IsEmpty())
282 {
283 Print("Base Class Glass is empty. Please select a valid base prefab for your glass", LogLevel.WARNING);
284 return;
285 }
286
287 if (m_aGlassVariants)
288 {
289 for (int i = m_aGlassVariants.Count() - 1; i >= 0; i--)
290 {
291 if (m_aGlassVariants[i].IsEmpty())
292 m_aGlassVariants.RemoveOrdered(i);
293 }
294
295 WorldEditorTool.UpdatePropertyPanel();
296 }
297
298 if (!m_aGlassVariants || m_aGlassVariants.IsEmpty())
299 {
300 Print("Glass Variants is empty. Please select at least one glass XOB variant", LogLevel.WARNING);
301 return;
302 }
303
304 string glassName = m_sGlassName.Trim();
305 if (glassName.IsEmpty())
306 glassName = FilePath.StripPath(m_sGlassSavePath.GetPath());
307
308 // check saveDir
309 string absoluteSaveDir;
310 if (!SCR_AddonTool.GetAddonAbsolutePath(m_iAddonToUse, m_sGlassSavePath, absoluteSaveDir))
311 {
312 Print("Wrong path: " + SCR_AddonTool.GetAddonID(m_iAddonToUse) + m_sGlassSavePath.GetPath(), LogLevel.WARNING);
313 return;
314 }
315
316 // Automatically detect number of glass variants
317 int glassVariants = m_iGlassDmgCount;
318 if (glassVariants < 0)
319 glassVariants = GetGlassCount(m_aGlassVariants[0]);
320
321 // Create new entity
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;
325
326 // Add glass variants to entity with correct parameters
327 // Use Multi Phase destruction
328 if (m_bUseMultiPhaseDestruction)
329 {
330 // Modify SCR_DestructionMultiPhaseComponent first m_DamagePhases and replace m_PhaseModel with ResourceName fetched from SCR_DestructionFractalComponent
331 array<ref ContainerIdPathEntry> subContainerPath = { ContainerIdPathEntry("SCR_DestructionMultiPhaseComponent"), ContainerIdPathEntry("m_DamagePhases", 0) };
332 m_API.SetVariableValue(entitySource, subContainerPath, "m_PhaseModel", GetDestroyedGlassModelPath(m_aGlassVariants[0], 0));
333 }
334 else
335 {
336 // Use Fractal destruction
337 containerPath = { new ContainerIdPathEntry("SCR_DestructionFractalComponent") };
338 foreach (int currentIndex, ResourceName glassVariant : m_aGlassVariants)
339 {
340 for (int i = glassVariants; i > 0; i--)
341 {
342 // Modify damage mask
343 string damageMask = GetDamageMask(glassVariant);
344 if (glassVariants > 1)
345 {
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;
350 }
351
352 // Apply parameters to sub variant
353 m_API.CreateObjectArrayVariableMember(entitySource, containerPath, "m_FractalVariants", "SCR_FractalVariation", currentIndex);
354 array<ref ContainerIdPathEntry> subContainerPath = { ContainerIdPathEntry("SCR_DestructionFractalComponent"), ContainerIdPathEntry("m_FractalVariants", 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));
358 }
359 }
360 }
361
362 // Assign some model to MeshObject
363 containerPath = { new ContainerIdPathEntry(MESHOBJECT_CLASSNAME) };
364 m_API.SetVariableValue(entitySource, containerPath, "Object", m_aGlassVariants[0]);
365
366 // Save our modified entity to prefab
367 bool fileCreated = m_API.CreateEntityTemplate(entitySource, FilePath.Concat(absoluteSaveDir, glassName + ".et"));
368 if (fileCreated)
369 Print(string.Format("@\"%1\"", FilePath.Concat(absoluteSaveDir, glassName + ".et")), LogLevel.NORMAL);
370 else
371 Print("Script was unable to create new prefab at the designated location", LogLevel.ERROR);
372
373 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
374 m_API.EndEntityAction();
375 }
376
377 //------------------------------------------------------------------------------------------------
379 protected ResourceName GetDestroyedGlassModelPath(ResourceName resourceName, int variant)
380 {
381 if (resourceName.IsEmpty())
382 {
383 Print("Provided glass is empty!", LogLevel.WARNING);
384 return resourceName;
385 }
386
387 string tempString = resourceName.GetPath();
388 if (variant > 0)
389 tempString.Replace("Glass_01", "Glass_" + variant.ToString(2));
390
391 tempString = FilePath.StripFileName(tempString) + "dst/" + FilePath.StripPath(tempString);
392 tempString.Replace(".xob", "_dst.xob");
393
394 string absPathDst;
395 if (!Workbench.GetAbsolutePath(tempString, absPathDst, true))
396 {
397 Print("Unable to find destroyed model for " + tempString + ". Base model will be used instead", LogLevel.WARNING);
398 return resourceName;
399 }
400
401 return SCR_WorldEditorToolHelper.GetResourceNameFromFile(absPathDst);
402 }
403
404 //------------------------------------------------------------------------------------------------
407 protected string GetDamageMask(ResourceName resourceName)
408 {
409 string inputString = resourceName.GetPath();
410 inputString = FilePath.StripFileName(inputString) + "Dmg/" + FilePath.StripPath(inputString);
411 inputString.Replace(".xob", "_dmg_");
412 return inputString;
413 }
414
415 //------------------------------------------------------------------------------------------------
418 protected string GetDamageVariants(ResourceName resourceName)
419 {
420 bool variantExists = true;
421 int numberOfVariants = 0;
422
423 array<string> variants = {};
424 while (variantExists)
425 {
426 string absPath;
427 string inputStringTemp = resourceName + numberOfVariants.ToString(2) + ".xob";
428 variantExists = Workbench.GetAbsolutePath(inputStringTemp, absPath);
429 if (variantExists)
430 {
431 variants.Insert(Workbench.GetResourceName(inputStringTemp));
432 numberOfVariants++;
433 }
434 }
435
436 return SCR_StringHelper.Join(",", variants, false);
437 }
438
439 //------------------------------------------------------------------------------------------------
441 protected int GetGlassCount(ResourceName glassResourceName)
442 {
443 if (glassResourceName.IsEmpty())
444 return 0;
445
446 bool variantExists = true;
447 int numberOfVariants = 1;
448 string relativeFilePath = glassResourceName.GetPath();
449 relativeFilePath.Replace("Glass_01", "Dmg/Glass_01");
450
451 while (variantExists)
452 {
453 string inputStringTemp = relativeFilePath;
454 inputStringTemp.Replace(".xob", "_dmg_00.xob");
455 inputStringTemp.Replace("Glass_01", "Glass_" + numberOfVariants.ToString(2));
456 Print(inputStringTemp, LogLevel.DEBUG);
457
458 variantExists = Workbench.GetAbsolutePath(inputStringTemp, inputStringTemp, true);
459 if (variantExists)
460 numberOfVariants++;
461 }
462
463 return numberOfVariants;
464 }
465
466 /*
467
468 REFRESH SECTION
469
470 */
471
472 //------------------------------------------------------------------------------------------------
474 protected void RefreshPrefabs(notnull array<string> prefabCriteria, notnull array<ref array<string>> fillerCriteria)
475 {
476 array<ResourceName> prefabsToUpdate = GetListedSelectedOrOpenedResources("et", prefabCriteria);
477 if (prefabsToUpdate.IsEmpty())
478 {
479 Print("No Prefabs were found to update", LogLevel.WARNING);
480 return;
481 }
482
483 map<string, ResourceName> prefabMap = PrefabSearchMap(fillerCriteria);
484 if (prefabMap.IsEmpty())
485 {
486 Print("No filler Prefabs were found", LogLevel.WARNING);
487 return;
488 }
489
490 foreach (ResourceName prefabToUpdate : prefabsToUpdate)
491 {
492 if (prefabToUpdate.IsEmpty())
493 continue;
494
495 m_API.BeginEntityAction("Processing " + prefabToUpdate);
496
497 if (!RefreshPrefab(prefabMap, prefabToUpdate))
498 Print("Could not process " + prefabToUpdate, LogLevel.ERROR);
499
500 m_API.EndEntityAction();
501 }
502 }
503
504 //------------------------------------------------------------------------------------------------
506 protected bool RefreshPrefab(notnull map<string, ResourceName> boneResourceNames, ResourceName prefabResourceName)
507 {
508 IEntitySource entitySource = m_API.CreateEntity(prefabResourceName, "", m_API.GetCurrentEntityLayerId(), null, vector.Zero, vector.Zero);
509 if (!entitySource)
510 return false;
511
512 IEntitySource actualPrefab = IEntitySource.Cast(entitySource.GetAncestor());
513 if (!actualPrefab)
514 {
515 Print("Created entity's source does not have an ancestor", LogLevel.ERROR);
516 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
517 return false;
518 }
519
520 // remove existing children
521 array<IEntitySource> childEntitySources;
522 if (m_bRemoveExistingChildren && entitySource.GetNumChildren() > 0)
523 {
524 childEntitySources = {};
525 for (int i, count = entitySource.GetNumChildren(); i < count; i++)
526 {
527 childEntitySources.Insert(actualPrefab.GetChild(i));
528 }
529
530 if (!childEntitySources.IsEmpty()) // crashes otherwise
531 m_API.RemovePrefabMembers(childEntitySources);
532 }
533
534 array<ref Resource> childrenResources; // needed for nullpointer reasons
535 if (m_bUsePureIEntitySource)
536 {
537 Print("USE PURE IENTITYSOURCE", LogLevel.DEBUG);
538
539 childrenResources = CreateResourcesFromBoneNames(boneResourceNames, entitySource);
540
541 // reused array var
542 childEntitySources = {};
543 foreach (Resource resource : childrenResources)
544 {
545 childEntitySources.Insert(resource.GetResource().ToEntitySource());
546 }
547 if (childEntitySources && !childEntitySources.IsEmpty())
548 m_API.AddPrefabMembers(actualPrefab, childEntitySources);
549 }
550 else
551 {
552 Print("USE WORLDEDITOR IENTITYSOURCE", LogLevel.DEBUG);
553
554 // reused array var
555 childEntitySources = CreateEntitySourcesFromBoneNames(boneResourceNames, entitySource, false);
556 if (childEntitySources && !childEntitySources.IsEmpty())
557 m_API.MoveEntitiesToPrefab(entitySource, actualPrefab, childEntitySources); // deletes children IEntities in the process
558 }
559
560 // update the prefab's .et
561 bool savedSuccessfully = m_API.SaveEntityTemplate(actualPrefab);
562 if (savedSuccessfully)
563 Print("Saved successfully", LogLevel.VERBOSE);
564 else
565 Print(prefabResourceName.GetPath() + " failed to save!", LogLevel.ERROR);
566
567 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
568 return true;
569 }
570
571 /*
572
573 GLOBAL SECTION
574
575 */
576
577 //------------------------------------------------------------------------------------------------
580
581 protected map<string, ResourceName> PrefabSearchMap(notnull array<ref array<string>> searchStringGroups)
582 {
583 map<string, ResourceName> result = new map<string, ResourceName>();
584 array<ResourceName> searchResult;
585 foreach (array<string> searchStrings : searchStringGroups)
586 {
587 foreach (ResourceName resourceName : SCR_WorkbenchHelper.SearchWorkbenchResources({ "et" }, searchStrings))
588 {
589 string toLowerFileNameWithoutExtension = FilePath.StripExtension(FilePath.StripPath(resourceName));
590 toLowerFileNameWithoutExtension.ToLower();
591 result.Insert(toLowerFileNameWithoutExtension, resourceName);
592 Print("Adding " + toLowerFileNameWithoutExtension + " - " + resourceName, LogLevel.VERBOSE);
593 }
594 }
595
596 return result;
597 }
598
599 //------------------------------------------------------------------------------------------------
605 protected array<ResourceName> GetListedSelectedOrOpenedResources(string wantedExtension = "", array<string> keywords = null, array<ResourceName> resourceNames = null)
606 {
607 array<ResourceName> temporaryResults;
608 if (resourceNames && !resourceNames.IsEmpty())
609 temporaryResults = resourceNames;
610 else // find selected assets
611 temporaryResults = SCR_WorldEditorToolHelper.GetSelectedOrOpenedResources();
612
613 array<ResourceName> result = {};
614 foreach (ResourceName resourceName : temporaryResults)
615 {
616 string extension;
617 string resourceNameLC = FilePath.StripExtension(resourceName, extension);
618
619 if (!wantedExtension.IsEmpty() && extension != wantedExtension)
620 continue;
621
622 if (resourceName.EndsWith(BASE_PREFAB_SUFFIX + ".et")) // avoid updating BASE prefabs
623 continue;
624
625 resourceNameLC = FilePath.StripPath(resourceNameLC);
626 resourceNameLC.ToLower();
627
628 if (SCR_StringHelper.ContainsEvery(resourceNameLC, keywords))
629 result.Insert(resourceName);
630 }
631
632 return result;
633 }
634
635 //------------------------------------------------------------------------------------------------
637 protected array<IEntitySource> CreateEntitySourcesFromBoneNames(notnull map<string, ResourceName> prefabMap, notnull IEntitySource entitySource, bool addToParent)
638 {
639 if (prefabMap.IsEmpty())
640 return {};
641
642 IEntity entity = m_API.SourceToEntity(entitySource);
643 if (!entity)
644 {
645 Print("null entity?!", LogLevel.WARNING);
646 return {};
647 }
648
649 array<string> boneNames = {};
650 entity.GetAnimation().GetBoneNames(boneNames);
651 boneNames.RemoveItem("Scene_Root");
652
653 IEntitySource parent;
654 if (addToParent)
655 parent = entitySource;
656
657 array<IEntitySource> newPrefabSources = {};
658 foreach (string boneName : boneNames)
659 {
660 IEntitySource bonePrefabEntitySource = CreateEntitySourceFromBoneName(prefabMap, boneName, parent);
661 if (bonePrefabEntitySource)
662 newPrefabSources.Insert(bonePrefabEntitySource);
663 else
664 Print("Bone " + boneName + " could not see a matching prefab created", LogLevel.VERBOSE);
665 }
666
667 return newPrefabSources;
668 }
669
670 //------------------------------------------------------------------------------------------------
672 // - if so, the IEntitySource will be created from Resource, no IEntity will be created ← not a Doxygen comment -yet- for a reason
674 protected IEntitySource CreateEntitySourceFromBoneName(notnull map<string, ResourceName> prefabMap, string boneName, IEntitySource parent)
675 {
676 if (boneName.IsEmpty())
677 return null;
678
679 string prefab = FindAdaptedPrefab(prefabMap, boneName);
680 if (prefab.IsEmpty())
681 {
682 Print("No prefab could be found matching bone " + boneName, LogLevel.VERBOSE);
683 return null;
684 }
685
686 IEntitySource result = m_API.CreateEntity(prefab, "", m_API.GetCurrentEntityLayerId(), parent, vector.Zero, vector.Zero);
687 if (!result)
688 {
689 Print("Entity " + prefab + " could not be created for bone " + boneName, LogLevel.WARNING);
690 return null;
691 }
692
693 IEntityComponentSource hierarchyComponent = SCR_BaseContainerTools.FindComponentSource(result, HIERARCHY_CLASSNAME);
694 if (!hierarchyComponent) // do not create the component, directly throw an error
695 {
696 Print("No Hierarchy component - fix by adding a " + HIERARCHY_CLASSNAME + " component to " + prefab + " first", LogLevel.WARNING);
697 SCR_WorldEditorToolHelper.DeleteEntityFromSource(result);
698 return null;
699 }
700
701 m_API.SetVariableValue(result, { new ContainerIdPathEntry(HIERARCHY_CLASSNAME) }, "PivotID", boneName);
702
703 return result;
704 }
705
706 //------------------------------------------------------------------------------------------------
708 protected array<ref Resource> CreateResourcesFromBoneNames(notnull map<string, ResourceName> prefabMap, notnull IEntitySource entitySource)
709 {
710 if (prefabMap.IsEmpty())
711 return {};
712
713 IEntity entity = m_API.SourceToEntity(entitySource);
714 if (!entity)
715 {
716 Print("null entity?!", LogLevel.WARNING);
717 return {};
718 }
719
720 array<string> boneNames = GetBoneNames(entity);
721
722 array<ref Resource> result = {};
723 Resource bonePrefabResource;
724 foreach (string boneName : boneNames)
725 {
726 bonePrefabResource = CreateResourceFromBoneName(prefabMap, boneName);
727 if (bonePrefabResource)
728 result.Insert(bonePrefabResource);
729 else
730 Print("Bone " + boneName + " could not see a matching prefab created", LogLevel.VERBOSE);
731 }
732
733 return result;
734 }
735
736 //------------------------------------------------------------------------------------------------
738 protected Resource CreateResourceFromBoneName(notnull map<string, ResourceName> prefabMap, string boneName)
739 {
740 if (boneName.IsEmpty())
741 return null;
742
743 string prefab = FindAdaptedPrefab(prefabMap, boneName);
744 if (prefab.IsEmpty())
745 {
746 Print("No prefab could be found matching bone " + boneName, LogLevel.VERBOSE);
747 return null;
748 }
749
750 Resource result = BaseContainerTools.CreateContainer(GENERICENTITY_CLASSNAME);
751 if (!result || !result.IsValid())
752 {
753 Print("Could not create a new " + GENERICENTITY_CLASSNAME + " for boneName", LogLevel.WARNING);
754 return null;
755 }
756
757 IEntitySource resultSource = result.GetResource().ToEntitySource();
758 resultSource.SetAncestor(prefab);
759 IEntityComponentSource hierarchyComponent = SCR_BaseContainerTools.FindComponentSource(resultSource, HIERARCHY_CLASSNAME);
760 if (!hierarchyComponent) // do not create the component, directly throw an error
761 {
762 Print("No Hierarchy component - fix by adding a " + HIERARCHY_CLASSNAME + " component to " + prefab + " first", LogLevel.WARNING);
763 return null;
764 }
765 hierarchyComponent.Set("PivotID", boneName);
766 return result;
767 }
768
769 //------------------------------------------------------------------------------------------------
771 protected array<string> GetBoneNames(notnull IEntity entity)
772 {
773 array<string> result = {};
774 entity.GetAnimation().GetBoneNames(result);
775 for (int i = result.Count() - 1; i >= 0; i--)
776 {
777 string boneName = result[i];
778 // ditch Scene_Root, Center Of Mass bones, non-WxH bones
779 if (boneName == "Scene_Root" || boneName.StartsWith("COM_") || !SCR_StringHelper.ContainsEvery(boneName, BONE_CRITERIA))
780 result.Remove(i); // order does not matter here
781 }
782
783 return result;
784 }
785
786 //------------------------------------------------------------------------------------------------
789 protected ResourceName FindAdaptedPrefab(notnull map<string, ResourceName> prefabMap, string boneName)
790 {
791 if (boneName.IsEmpty())
792 return string.Empty;
793
794 boneName.ToLower();
795 foreach (string key, ResourceName value : prefabMap)
796 {
797 if (boneName.Contains(key))
798 {
799 Print("Found " + key + " for bone " + boneName, LogLevel.DEBUG);
800 return value;
801 }
802 }
803
804 Print("Did not find prefab for bone " + boneName, LogLevel.DEBUG);
805 return string.Empty;
806 }
807}
808#endif // WORKBENCH
void ContainerIdPathEntry(string propertyName, int index=-1)
Definition worldEditor.c:30
ResourceName resourceName
Definition SCR_AIGroup.c:66
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.
Definition LogLevel.c:14
SCR_FieldOfViewSettings Attribute
proto native bool IsEmpty()