4[WorkbenchToolAttribute(
5 name:
"Prefab Destruction Setup Tool",
6 description:
"Set building destruction effects",
8 awesomeFontCode: 0xE50C)]
9class SCR_BuildingDestructionSetupTool : WorldEditorTool
15 protected ref SCR_BuildingDestructionSetupToolConfig m_DestructionEffectsConfig;
17 protected static const string PREFAB_EXTENSION =
"et";
18 protected static const string BUILDING_CLASS =
"SCR_DestructibleBuildingEntity";
19 protected static const string RUIN_FILTER =
"_Ruin";
20 protected static const string FILE_NAME_FILTER[] = {
"_Ruin",
"_ruin",
"_Ruins",
"_ruins",
"_Base",
"_base",
"_Dst_",
"_dst_" };
21 protected static const int FILE_NAME_FILTER_COUNT = 8;
23 protected static const ResourceName DEFAULT_RUIN =
"{11B789B5F476A4B5}Prefabs/Structures/Debris/DebrisPile_HouseVillage/DebrisPile_HouseVillage_01_Large.et";
25 protected static const string COMPONENTS_ARRAY =
"components";
26 protected static const string DESTRUCTION_COMPONENT =
"SCR_DestructibleBuildingComponent";
27 protected static const string DESTRUCTION_COMPONENT_EFFECTS_ARRAY =
"m_aEffects";
29 protected static const string TIMED_PARTICLE_CLASSNAME =
"SCR_TimedParticle";
30 protected static const string TIMED_PARTICLE_PARTICLE =
"m_Particle";
31 protected static const string TIMED_PARTICLE_PARTICLE_CLASSNAME =
"SCR_ParticleSpawnable";
33 protected static const string TIMED_SOUND_CLASSNAME =
"SCR_TimedSound";
34 protected static const string TIMED_SOUND_AUDIO_CONFIG =
"m_AudioSourceConfiguration";
35 protected static const string TIMED_SOUND_AUDIO_CONFIG_CLASSNAME =
"SCR_AudioSourceConfiguration";
37 protected static const string TIMED_PREFAB_CLASSNAME =
"SCR_TimedPrefab";
38 protected static const string TIMED_PREFAB_PATH =
"m_sRuinsPrefab";
40 protected static const float LEVENSHTEIN_THRESHOLD = 0.75;
44 protected void BtnFixBuildingPrefabRuins()
46 array<ResourceName> resourceNames = SCR_WorldEditorToolHelper.GetSelectedOrOpenedResources(PREFAB_EXTENSION);
47 if (resourceNames.IsEmpty())
49 Print(
"No resources are selected, exiting",
LogLevel.WARNING);
54 FixBuildingPrefabRuins(resourceNames);
59 protected void BtnSetBuildingDestructionEffects()
61 if (!m_DestructionEffectsConfig)
63 Print(
"Config (object) is not defined, exiting",
LogLevel.WARNING);
67 if (!m_DestructionEffectsConfig.m_aEffects)
73 array<ResourceName> resourceNames = SCR_WorldEditorToolHelper.GetSelectedOrOpenedResources(PREFAB_EXTENSION);
74 if (resourceNames.IsEmpty())
76 Print(
"No resources are selected, exiting",
LogLevel.WARNING);
81 SetBuildingDestructionEffects(resourceNames, m_DestructionEffectsConfig.m_aEffects);
87 protected void FixBuildingPrefabRuins(notnull array<ResourceName> resourceNames)
89 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
96 bool manageEditAction = SCR_WorldEditorToolHelper.BeginEntityAction();
98 array<ResourceName> allRuinPrefabs = SCR_WorkbenchHelper.SearchWorkbenchResources({ PREFAB_EXTENSION }, { RUIN_FILTER });
99 if (allRuinPrefabs.IsEmpty())
105 map<ResourceName, string> ruinPrefabFileNameMap =
new map<ResourceName, string>();
106 foreach (ResourceName ruinPrefab : allRuinPrefabs)
108 ruinPrefabFileNameMap.Insert(ruinPrefab, FilePath.StripPath(ruinPrefab.GetPath()));
111 IEntitySource entitySource;
112 IEntitySource actualPrefab;
114 int layerId = m_API.GetCurrentEntityLayerId();
124 string fileName = FilePath.StripPath(
resourceName.GetPath());
125 for (
int i; i < FILE_NAME_FILTER_COUNT; ++i)
127 if (fileName.Contains(FILE_NAME_FILTER[i]))
138 entitySource = m_API.CreateEntity(
resourceName,
string.Empty, layerId, null, vector.Zero, vector.Zero);
145 if (!entitySource.GetClassName().ToType() || !entitySource.GetClassName().ToType().IsInherited(SCR_DestructibleBuildingEntity))
148 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
152 actualPrefab = entitySource.GetAncestor();
156 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
160 if (!FixPrefabRuin(actualPrefab, ruinPrefabFileNameMap, worldEditorAPI))
163 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
167 if (SCR_PrefabHelper.UpdatePrefabFromEntitySourceAncestor(actualPrefab))
172 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
175 SCR_WorldEditorToolHelper.EndEntityAction(manageEditAction);
183 protected bool FixPrefabRuin(notnull IEntitySource ancestor, notnull map<ResourceName, string> ruinPrefabFileNameMap, notnull WorldEditorAPI worldEditorAPI)
185 int componentIndex = -1;
186 IEntityComponentSource componentSource;
187 for (
int i, count = ancestor.GetComponentCount(); i < count; ++i)
189 componentSource = ancestor.GetComponent(i);
190 if (componentSource.GetClassName().ToType() &&
191 componentSource.GetClassName().ToType().IsInherited(SCR_DestructibleBuildingComponent))
198 if (componentIndex < 0)
212 Print(
"No SCR_DestructibleBuildingComponent found",
LogLevel.WARNING);
217 BaseContainerList effects = componentSource.GetObjectArray(DESTRUCTION_COMPONENT_EFFECTS_ARRAY);
220 Print(DESTRUCTION_COMPONENT_EFFECTS_ARRAY +
" not present",
LogLevel.WARNING);
224 int effectsCount = effects.Count();
226 BaseContainer effect;
231 for (; effectIndex < effectsCount; ++effectIndex)
233 effect = effects.Get(effectIndex);
234 type = effect.GetClassName().ToType();
235 if (!
type || !
type.IsInherited(SCR_TimedPrefab))
238 if (!effect.Get(TIMED_PREFAB_PATH, ruin))
241 if (ruin != DEFAULT_RUIN)
254 effectIndex = effectsCount;
255 if (!worldEditorAPI.CreateObjectArrayVariableMember(ancestor,
path, DESTRUCTION_COMPONENT_EFFECTS_ARRAY, TIMED_PREFAB_CLASSNAME, effectIndex))
261 string fileName = FilePath.StripPath(ancestor.GetResourceName().GetPath());
262 ResourceName bestRuin = GetBestRuinByName(fileName, ruinPrefabFileNameMap);
266 Print(
"no best ruin found - best match = " + GetBestRuinScore(fileName, ruinPrefabFileNameMap),
LogLevel.NORMAL);
270 return worldEditorAPI.SetVariableValue(ancestor,
path, TIMED_PREFAB_PATH, bestRuin);
278 protected ResourceName GetBestRuinByName(
string fileName, notnull map<ResourceName, string> ruinPrefabFileNameMap)
280 float bestScore = -1;
281 ResourceName bestResult;
282 string fileNameWithoutExtension = FilePath.StripExtension(fileName);
283 foreach (ResourceName ruinPrefab,
string ruinFileName : ruinPrefabFileNameMap)
285 if (ruinFileName ==
string.Format(
"%1%2.%3", fileNameWithoutExtension, RUIN_FILTER, PREFAB_EXTENSION))
288 float score = SCR_StringHelper.GetLevenshteinDistanceScore(fileName, ruinFileName,
false);
289 if (score >= LEVENSHTEIN_THRESHOLD && score > bestScore)
292 bestResult = ruinPrefab;
304 protected float GetBestRuinScore(
string fileName, notnull map<ResourceName, string> ruinPrefabFileNameMap)
307 foreach (ResourceName ruinPrefab,
string ruinFileName : ruinPrefabFileNameMap)
309 float score = SCR_StringHelper.GetLevenshteinDistanceScore(fileName, ruinFileName,
false);
310 if (bestScore < score)
321 protected void SetBuildingDestructionEffects(notnull array<ResourceName> resourceNames, notnull array<ref SCR_TimedEffect> effects)
323 bool manageEditAction = SCR_WorldEditorToolHelper.BeginEntityAction();
325 IEntitySource entitySource;
326 BaseContainer actualPrefab;
329 entitySource = m_API.CreateEntity(
resourceName,
string.Empty, m_API.GetCurrentEntityLayerId(), null, vector.Zero, vector.Zero);
336 actualPrefab = entitySource.GetAncestor();
338 if (!SetEffectsValue(actualPrefab, effects))
341 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
345 if (SCR_PrefabHelper.UpdatePrefabFromEntitySourceAncestor(actualPrefab))
350 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
353 SCR_WorldEditorToolHelper.EndEntityAction(manageEditAction);
360 protected bool SetEffectsValue(notnull BaseContainer actualPrefab, notnull array<ref SCR_TimedEffect> timedEffects)
362 array<ref ContainerIdPathEntry> destructionComponentPath = GetDestructionComponentPath(actualPrefab);
363 if (!destructionComponentPath)
365 Print(DESTRUCTION_COMPONENT +
" component is missing",
LogLevel.ERROR);
370 if (!m_API.SetVariableValue(actualPrefab, destructionComponentPath, DESTRUCTION_COMPONENT_EFFECTS_ARRAY,
string.Empty))
376 if (!m_API.ClearVariableValue(actualPrefab, destructionComponentPath, DESTRUCTION_COMPONENT_EFFECTS_ARRAY))
382 if (timedEffects.IsEmpty())
387 foreach (
int effectIndex, SCR_TimedEffect timedEffect : timedEffects)
389 if (SCR_TimedParticle.Cast(timedEffect))
391 if (!InsertTimedParticleValue(actualPrefab, destructionComponentPath, effectIndex, SCR_TimedParticle.Cast(timedEffect)))
397 if (SCR_TimedSound.Cast(timedEffect))
399 if (!InsertTimedSoundValue(actualPrefab, destructionComponentPath, effectIndex, SCR_TimedSound.Cast(timedEffect)))
405 Print(
"Encountered an unsupported type: " + timedEffect.Type(),
LogLevel.ERROR);
419 protected bool InsertTimedParticleValue(
420 notnull BaseContainer actualPrefab,
421 notnull array<ref ContainerIdPathEntry> destructionComponentPath,
423 notnull SCR_TimedParticle effect)
425 if (!m_API.CreateObjectArrayVariableMember(
427 destructionComponentPath,
428 DESTRUCTION_COMPONENT_EFFECTS_ARRAY,
429 TIMED_PARTICLE_CLASSNAME,
432 Print(
"Could not create SCR_TimedParticle array item",
LogLevel.ERROR);
436 array<ref ContainerIdPathEntry>
path = GetTimedEffectPath(destructionComponentPath, effectIndex);
438 if (!InsertTimedEffectValue(actualPrefab,
path, effect))
441 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_fParticlesMultiplier", effect.GetParticlesMultiplier().ToString()))
445 SCR_ParticleSpawnable particle = effect.GetParticle();
448 if (!m_API.CreateObjectVariableMember(actualPrefab,
path, TIMED_PARTICLE_PARTICLE, TIMED_PARTICLE_PARTICLE_CLASSNAME))
453 array<vector> positionAndRotation = {};
454 particle.GetPositionAndRotation(positionAndRotation);
456 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_vOffsetPosition", positionAndRotation[0].ToString(
false)))
459 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_vOffsetRotation", positionAndRotation[1].ToString(
false)))
462 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_Particle", particle.m_Particle))
465 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_bAtCenter", particle.m_bAtCenter.ToString(
true)))
468 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_bDirectional", particle.m_bDirectional.ToString(
true)))
482 protected bool InsertTimedSoundValue(
483 notnull BaseContainer actualPrefab,
484 notnull array<ref ContainerIdPathEntry> destructionComponentPath,
486 notnull SCR_TimedSound effect)
488 if (!m_API.CreateObjectArrayVariableMember(
490 destructionComponentPath,
491 DESTRUCTION_COMPONENT_EFFECTS_ARRAY,
492 TIMED_SOUND_CLASSNAME,
495 Print(
"Could not create SCR_TimedSound array item",
LogLevel.ERROR);
499 array<ref ContainerIdPathEntry>
path = GetTimedEffectPath(destructionComponentPath, effectIndex);
501 if (!InsertTimedEffectValue(actualPrefab,
path, effect))
505 SCR_AudioSourceConfiguration audioSourceConfig = effect.GetAudioSourceConfiguration();
506 if (audioSourceConfig)
508 if (!m_API.CreateObjectVariableMember(actualPrefab,
path, TIMED_SOUND_AUDIO_CONFIG, TIMED_SOUND_AUDIO_CONFIG_CLASSNAME))
513 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_sSoundProject", audioSourceConfig.m_sSoundProject))
516 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_sSoundEventName", audioSourceConfig.m_sSoundEventName))
519 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_eFlags", audioSourceConfig.m_eFlags.ToString()))
534 protected bool InsertTimedEffectValue(
535 notnull BaseContainer actualPrefab,
536 notnull array<ref ContainerIdPathEntry>
path,
537 notnull SCR_TimedEffect effect)
539 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_fSpawnTime", effect.m_fSpawnTime.ToString()))
542 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_bSnapToTerrain", effect.m_bSnapToTerrain.ToString(
true)))
545 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_bAttachToParent", effect.m_bAttachToParent.ToString(
true)))
548 if (!m_API.SetVariableValue(actualPrefab,
path,
"m_bPersistent", effect.m_bPersistent.ToString(
true)))
557 protected static array<ref ContainerIdPathEntry> GetDestructionComponentPath(BaseContainer actualPrefab)
559 int destructionComponentIndex = SCR_BaseContainerTools.FindComponentIndex(actualPrefab, DESTRUCTION_COMPONENT);
560 if (destructionComponentIndex < 0)
570 protected static array<ref ContainerIdPathEntry> GetTimedEffectPath(notnull array<ref ContainerIdPathEntry> destructionComponentPath,
int effectIndex)
572 array<ref ContainerIdPathEntry> result = SCR_ArrayHelperRefT<ContainerIdPathEntry>.GetCopy(destructionComponentPath);
583 return containerIdPathEntry.PropertyName +
"/" + containerIdPathEntry.Index;
590 protected static string ToString(notnull array<ref ContainerIdPathEntry> containerIdPathEntries)
592 array<string> bits = {};
598 return "(array<ContainerIdPathEntry>) " + SCR_StringHelper.Join(
", ", bits);
603class SCR_BuildingDestructionSetupToolConfig
605 [
Attribute(
desc:
"Timed Effects to be applied to provided building(s)")]
void ContainerIdPathEntry(string propertyName, int index=-1)
SCR_AIAnimation_Loitering BaseContainerProps
Commanding menu commanding element class.
ResourceName resourceName
ref array< ref SCR_BaseEntitiesEditorUIEffect > m_aEffects
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
class WorkbenchDialog_AbortRetryIgnore ButtonAttribute("OK", true)
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
proto void PrintFormat(string fmt, void param1=NULL, void param2=NULL, void param3=NULL, void param4=NULL, void param5=NULL, void param6=NULL, void param7=NULL, void param8=NULL, void param9=NULL, LogLevel level=LogLevel.NORMAL)
SCR_FieldOfViewSettings Attribute
proto external string ToString()
Plain C++ pointer, no weak pointers, no memory management.