Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_BuildingDestructionSetupTool.c
Go to the documentation of this file.
1#ifdef WORKBENCH
2// TODO: use BaseContainer and remove direct property methods?
3// doing so will force tool's config -file- usage
4[WorkbenchToolAttribute(
5 name: "Prefab Destruction Setup Tool",
6 description: "Set building destruction effects",
7 category: "Building Destruction",
8 awesomeFontCode: 0xE50C)]
9class SCR_BuildingDestructionSetupTool : WorldEditorTool
10{
11// [Attribute(defvalue: "0", desc: "Automatically adds " + DESTRUCTION_COMPONENT + " if missing", category: "Fix Prefab Ruins")]
12// protected bool m_bAddMissingDestructibleBuildingComponent;
13
14 [Attribute(category: "Set Building Destruction Effects")]
15 protected ref SCR_BuildingDestructionSetupToolConfig m_DestructionEffectsConfig;
16
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"; // case-sensitive
20 protected static const string FILE_NAME_FILTER[] = { "_Ruin", "_ruin", "_Ruins", "_ruins", "_Base", "_base", "_Dst_", "_dst_" }; // case-sensitive
21 protected static const int FILE_NAME_FILTER_COUNT = 8;
22
23 protected static const ResourceName DEFAULT_RUIN = "{11B789B5F476A4B5}Prefabs/Structures/Debris/DebrisPile_HouseVillage/DebrisPile_HouseVillage_01_Large.et";
24
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";
28
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";
32
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";
36
37 protected static const string TIMED_PREFAB_CLASSNAME = "SCR_TimedPrefab";
38 protected static const string TIMED_PREFAB_PATH = "m_sRuinsPrefab";
39
40 protected static const float LEVENSHTEIN_THRESHOLD = 0.75; // equal or above to this matching value, the ruin's file name will be accepted
41
42 //------------------------------------------------------------------------------------------------
43 [ButtonAttribute("Fix Prefab Ruins")]
44 protected void BtnFixBuildingPrefabRuins()
45 {
46 array<ResourceName> resourceNames = SCR_WorldEditorToolHelper.GetSelectedOrOpenedResources(PREFAB_EXTENSION);
47 if (resourceNames.IsEmpty())
48 {
49 Print("No resources are selected, exiting", LogLevel.WARNING);
50 return;
51 }
52
53 // process'em all
54 FixBuildingPrefabRuins(resourceNames);
55 }
56
57 //------------------------------------------------------------------------------------------------
58 [ButtonAttribute("Set Building Destruction Effects")]
59 protected void BtnSetBuildingDestructionEffects()
60 {
61 if (!m_DestructionEffectsConfig)
62 {
63 Print("Config (object) is not defined, exiting", LogLevel.WARNING);
64 return;
65 }
66
67 if (!m_DestructionEffectsConfig.m_aEffects)
68 {
69 Print("Config array is null, exiting", LogLevel.WARNING);
70 return;
71 }
72
73 array<ResourceName> resourceNames = SCR_WorldEditorToolHelper.GetSelectedOrOpenedResources(PREFAB_EXTENSION);
74 if (resourceNames.IsEmpty())
75 {
76 Print("No resources are selected, exiting", LogLevel.WARNING);
77 return;
78 }
79
80 // process'em all
81 SetBuildingDestructionEffects(resourceNames, m_DestructionEffectsConfig.m_aEffects);
82 }
83
84 //------------------------------------------------------------------------------------------------
87 protected void FixBuildingPrefabRuins(notnull array<ResourceName> resourceNames)
88 {
89 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
90 if (!worldEditorAPI)
91 {
92 Print("World Editor API is not available", LogLevel.ERROR);
93 return;
94 }
95
96 bool manageEditAction = SCR_WorldEditorToolHelper.BeginEntityAction();
97
98 array<ResourceName> allRuinPrefabs = SCR_WorkbenchHelper.SearchWorkbenchResources({ PREFAB_EXTENSION }, { RUIN_FILTER });
99 if (allRuinPrefabs.IsEmpty())
100 {
101 Print("No Ruins found - leaving", LogLevel.WARNING);
102 return;
103 }
104
105 map<ResourceName, string> ruinPrefabFileNameMap = new map<ResourceName, string>();
106 foreach (ResourceName ruinPrefab : allRuinPrefabs)
107 {
108 ruinPrefabFileNameMap.Insert(ruinPrefab, FilePath.StripPath(ruinPrefab.GetPath()));
109 }
110
111 IEntitySource entitySource;
112 IEntitySource actualPrefab;
113
114 int layerId = m_API.GetCurrentEntityLayerId();
115 foreach (ResourceName resourceName : resourceNames)
116 {
117 if (allRuinPrefabs.Contains(resourceName))
118 {
119 Print("Skipping Ruin Prefab " + resourceName, LogLevel.NORMAL);
120 continue;
121 }
122
123 bool skip;
124 string fileName = FilePath.StripPath(resourceName.GetPath());
125 for (int i; i < FILE_NAME_FILTER_COUNT; ++i)
126 {
127 if (fileName.Contains(FILE_NAME_FILTER[i]))
128 {
129 PrintFormat("Skipping \"%1\" Prefab %2", FILE_NAME_FILTER[i], resourceName, level: LogLevel.NORMAL);
130 skip = true;
131 break;
132 }
133 }
134
135 if (skip)
136 continue;
137
138 entitySource = m_API.CreateEntity(resourceName, string.Empty, layerId, null, vector.Zero, vector.Zero);
139 if (!entitySource)
140 {
141 Print("Invalid Prefab " + resourceName, LogLevel.WARNING);
142 continue;
143 }
144
145 if (!entitySource.GetClassName().ToType() || !entitySource.GetClassName().ToType().IsInherited(SCR_DestructibleBuildingEntity))
146 {
147 Print("Prefab is not SCR_DestructibleBuildingEntity, skipping " + resourceName, LogLevel.NORMAL);
148 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
149 continue;
150 }
151
152 actualPrefab = entitySource.GetAncestor();
153 if (!actualPrefab)
154 {
155 Print("Prefab has no ancestor?? " + resourceName, LogLevel.WARNING);
156 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
157 continue;
158 }
159
160 if (!FixPrefabRuin(actualPrefab, ruinPrefabFileNameMap, worldEditorAPI))
161 {
162 Print("Cannot fix ruin for " + resourceName, LogLevel.ERROR);
163 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
164 continue;
165 }
166
167 if (SCR_PrefabHelper.UpdatePrefabFromEntitySourceAncestor(actualPrefab))
168 Print("Successfully saved " + resourceName.GetPath(), LogLevel.NORMAL);
169 else
170 Print("Writing failure on " + resourceName.GetPath(), LogLevel.ERROR);
171
172 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
173 }
174
175 SCR_WorldEditorToolHelper.EndEntityAction(manageEditAction);
176 }
177
178 //------------------------------------------------------------------------------------------------
183 protected bool FixPrefabRuin(notnull IEntitySource ancestor, notnull map<ResourceName, string> ruinPrefabFileNameMap, notnull WorldEditorAPI worldEditorAPI)
184 {
185 int componentIndex = -1;
186 IEntityComponentSource componentSource;
187 for (int i, count = ancestor.GetComponentCount(); i < count; ++i)
188 {
189 componentSource = ancestor.GetComponent(i);
190 if (componentSource.GetClassName().ToType() &&
191 componentSource.GetClassName().ToType().IsInherited(SCR_DestructibleBuildingComponent))
192 {
193 componentIndex = i;
194 break;
195 }
196 }
197
198 if (componentIndex < 0)
199 {
200// if (m_bAddMissingDestructibleBuildingComponent)
201// {
202// componentIndex = ancestor.GetComponentCount();
203// componentSource = worldEditorAPI.CreateComponent(ancestor, DESTRUCTION_COMPONENT);
204// if (!componentSource)
205// {
206// Print("Failed to create " + DESTRUCTION_COMPONENT, LogLevel.WARNING);
207// return false;
208// }
209// }
210// else
211// {
212 Print("No SCR_DestructibleBuildingComponent found", LogLevel.WARNING);
213 return false;
214// }
215 }
216
217 BaseContainerList effects = componentSource.GetObjectArray(DESTRUCTION_COMPONENT_EFFECTS_ARRAY);
218 if (!effects)
219 {
220 Print(DESTRUCTION_COMPONENT_EFFECTS_ARRAY + " not present", LogLevel.WARNING);
221 return false;
222 }
223
224 int effectsCount = effects.Count();
225
226 BaseContainer effect;
227 ResourceName ruin;
228 typename type;
229 int effectIndex;
230 bool found;
231 for (; effectIndex < effectsCount; ++effectIndex)
232 {
233 effect = effects.Get(effectIndex);
234 type = effect.GetClassName().ToType();
235 if (!type || !type.IsInherited(SCR_TimedPrefab))
236 continue;
237
238 if (!effect.Get(TIMED_PREFAB_PATH, ruin)) // huh
239 continue;
240
241 if (ruin != DEFAULT_RUIN)
242 return true;
243
244// if (effect.IsVariableSetDirectly(TIMED_PREFAB_PATH))
245// return true;
246
247 found = true;
248 break;
249 }
250
251 array<ref ContainerIdPathEntry> path = { new ContainerIdPathEntry(COMPONENTS_ARRAY, componentIndex) };
252 if (!found)
253 {
254 effectIndex = effectsCount;
255 if (!worldEditorAPI.CreateObjectArrayVariableMember(ancestor, path, DESTRUCTION_COMPONENT_EFFECTS_ARRAY, TIMED_PREFAB_CLASSNAME, effectIndex))
256 return false;
257 }
258
259 path.Insert(new ContainerIdPathEntry(DESTRUCTION_COMPONENT_EFFECTS_ARRAY, effectIndex));
260
261 string fileName = FilePath.StripPath(ancestor.GetResourceName().GetPath());
262 ResourceName bestRuin = GetBestRuinByName(fileName, ruinPrefabFileNameMap);
263 if (bestRuin) // !.IsEmpty()
264 Print("RUIN FOUND: " + bestRuin, LogLevel.NORMAL);
265 else
266 Print("no best ruin found - best match = " + GetBestRuinScore(fileName, ruinPrefabFileNameMap), LogLevel.NORMAL);
267
268 return false;
269
270 return worldEditorAPI.SetVariableValue(ancestor, path, TIMED_PREFAB_PATH, bestRuin);
271 }
272
273 //------------------------------------------------------------------------------------------------
278 protected ResourceName GetBestRuinByName(string fileName, notnull map<ResourceName, string> ruinPrefabFileNameMap)
279 {
280 float bestScore = -1;
281 ResourceName bestResult;
282 string fileNameWithoutExtension = FilePath.StripExtension(fileName);
283 foreach (ResourceName ruinPrefab, string ruinFileName : ruinPrefabFileNameMap)
284 {
285 if (ruinFileName == string.Format("%1%2.%3", fileNameWithoutExtension, RUIN_FILTER, PREFAB_EXTENSION))
286 return ruinPrefab;
287
288 float score = SCR_StringHelper.GetLevenshteinDistanceScore(fileName, ruinFileName, false);
289 if (score >= LEVENSHTEIN_THRESHOLD && score > bestScore)
290 {
291 bestScore = score;
292 bestResult = ruinPrefab;
293 }
294
295 if (score >= 1)
296 break;
297 }
298
299 return bestResult;
300 }
301
302 //------------------------------------------------------------------------------------------------
303 // temp
304 protected float GetBestRuinScore(string fileName, notnull map<ResourceName, string> ruinPrefabFileNameMap)
305 {
306 float bestScore;
307 foreach (ResourceName ruinPrefab, string ruinFileName : ruinPrefabFileNameMap)
308 {
309 float score = SCR_StringHelper.GetLevenshteinDistanceScore(fileName, ruinFileName, false);
310 if (bestScore < score)
311 bestScore = score;
312 }
313
314 return bestScore;
315 }
316
317 //------------------------------------------------------------------------------------------------
321 protected void SetBuildingDestructionEffects(notnull array<ResourceName> resourceNames, notnull array<ref SCR_TimedEffect> effects)
322 {
323 bool manageEditAction = SCR_WorldEditorToolHelper.BeginEntityAction();
324
325 IEntitySource entitySource;
326 BaseContainer actualPrefab;
327 foreach (ResourceName resourceName : resourceNames)
328 {
329 entitySource = m_API.CreateEntity(resourceName, string.Empty, m_API.GetCurrentEntityLayerId(), null, vector.Zero, vector.Zero);
330 if (!entitySource)
331 {
332 Print(resourceName + " is not a valid Prefab", LogLevel.WARNING);
333 continue;
334 }
335
336 actualPrefab = entitySource.GetAncestor();
337
338 if (!SetEffectsValue(actualPrefab, effects))
339 {
340 Print("Could not apply Effects", LogLevel.ERROR);
341 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
342 continue;
343 }
344
345 if (SCR_PrefabHelper.UpdatePrefabFromEntitySourceAncestor(actualPrefab))
346 Print("Successfully saved " + resourceName.GetPath(), LogLevel.NORMAL);
347 else
348 Print("Writing failure on " + resourceName.GetPath(), LogLevel.ERROR);
349
350 SCR_WorldEditorToolHelper.DeleteEntityFromSource(entitySource);
351 }
352
353 SCR_WorldEditorToolHelper.EndEntityAction(manageEditAction);
354 }
355
356 //------------------------------------------------------------------------------------------------
360 protected bool SetEffectsValue(notnull BaseContainer actualPrefab, notnull array<ref SCR_TimedEffect> timedEffects)
361 {
362 array<ref ContainerIdPathEntry> destructionComponentPath = GetDestructionComponentPath(actualPrefab);
363 if (!destructionComponentPath)
364 {
365 Print(DESTRUCTION_COMPONENT + " component is missing", LogLevel.ERROR);
366 return false;
367 }
368
369 // TODO: fix that VM Exception if the array exists
370 if (!m_API.SetVariableValue(actualPrefab, destructionComponentPath, DESTRUCTION_COMPONENT_EFFECTS_ARRAY, string.Empty))
371 {
372 Print("Could not clear variable value (1)", LogLevel.ERROR);
373 return false;
374 }
375
376 if (!m_API.ClearVariableValue(actualPrefab, destructionComponentPath, DESTRUCTION_COMPONENT_EFFECTS_ARRAY))
377 {
378 Print("Could not clear variable value (2)", LogLevel.ERROR);
379 return false;
380 }
381
382 if (timedEffects.IsEmpty())
383 return true;
384
385 // no need to create an array, using CreateObjectArrayVariableMember does it
386
387 foreach (int effectIndex, SCR_TimedEffect timedEffect : timedEffects)
388 {
389 if (SCR_TimedParticle.Cast(timedEffect))
390 {
391 if (!InsertTimedParticleValue(actualPrefab, destructionComponentPath, effectIndex, SCR_TimedParticle.Cast(timedEffect)))
392 return false;
393
394 continue;
395 }
396
397 if (SCR_TimedSound.Cast(timedEffect))
398 {
399 if (!InsertTimedSoundValue(actualPrefab, destructionComponentPath, effectIndex, SCR_TimedSound.Cast(timedEffect)))
400 return false;
401
402 continue;
403 }
404
405 Print("Encountered an unsupported type: " + timedEffect.Type(), LogLevel.ERROR);
406 return false;
407 }
408
409 return true;
410 }
411
412 //------------------------------------------------------------------------------------------------
419 protected bool InsertTimedParticleValue(
420 notnull BaseContainer actualPrefab,
421 notnull array<ref ContainerIdPathEntry> destructionComponentPath,
422 int effectIndex,
423 notnull SCR_TimedParticle effect)
424 {
425 if (!m_API.CreateObjectArrayVariableMember(
426 actualPrefab,
427 destructionComponentPath,
428 DESTRUCTION_COMPONENT_EFFECTS_ARRAY,
429 TIMED_PARTICLE_CLASSNAME,
430 effectIndex))
431 {
432 Print("Could not create SCR_TimedParticle array item", LogLevel.ERROR);
433 return false;
434 }
435
436 array<ref ContainerIdPathEntry> path = GetTimedEffectPath(destructionComponentPath, effectIndex);
437
438 if (!InsertTimedEffectValue(actualPrefab, path, effect))
439 return false;
440
441 if (!m_API.SetVariableValue(actualPrefab, path, "m_fParticlesMultiplier", effect.GetParticlesMultiplier().ToString()))
442 return false;
443
444 // object must be the last change as 'path' gets changed
445 SCR_ParticleSpawnable particle = effect.GetParticle();
446 if (particle)
447 {
448 if (!m_API.CreateObjectVariableMember(actualPrefab, path, TIMED_PARTICLE_PARTICLE, TIMED_PARTICLE_PARTICLE_CLASSNAME))
449 return false;
450
451 path.Insert(new ContainerIdPathEntry(TIMED_PARTICLE_PARTICLE));
452
453 array<vector> positionAndRotation = {};
454 particle.GetPositionAndRotation(positionAndRotation);
455
456 if (!m_API.SetVariableValue(actualPrefab, path, "m_vOffsetPosition", positionAndRotation[0].ToString(false)))
457 return false;
458
459 if (!m_API.SetVariableValue(actualPrefab, path, "m_vOffsetRotation", positionAndRotation[1].ToString(false)))
460 return false;
461
462 if (!m_API.SetVariableValue(actualPrefab, path, "m_Particle", particle.m_Particle))
463 return false;
464
465 if (!m_API.SetVariableValue(actualPrefab, path, "m_bAtCenter", particle.m_bAtCenter.ToString(true)))
466 return false;
467
468 if (!m_API.SetVariableValue(actualPrefab, path, "m_bDirectional", particle.m_bDirectional.ToString(true)))
469 return false;
470 }
471
472 return true;
473 }
474
475 //------------------------------------------------------------------------------------------------
482 protected bool InsertTimedSoundValue(
483 notnull BaseContainer actualPrefab,
484 notnull array<ref ContainerIdPathEntry> destructionComponentPath,
485 int effectIndex,
486 notnull SCR_TimedSound effect)
487 {
488 if (!m_API.CreateObjectArrayVariableMember(
489 actualPrefab,
490 destructionComponentPath,
491 DESTRUCTION_COMPONENT_EFFECTS_ARRAY,
492 TIMED_SOUND_CLASSNAME,
493 effectIndex))
494 {
495 Print("Could not create SCR_TimedSound array item", LogLevel.ERROR);
496 return false;
497 }
498
499 array<ref ContainerIdPathEntry> path = GetTimedEffectPath(destructionComponentPath, effectIndex);
500
501 if (!InsertTimedEffectValue(actualPrefab, path, effect))
502 return false;
503
504 // object must be the last change as 'path' gets changed
505 SCR_AudioSourceConfiguration audioSourceConfig = effect.GetAudioSourceConfiguration();
506 if (audioSourceConfig)
507 {
508 if (!m_API.CreateObjectVariableMember(actualPrefab, path, TIMED_SOUND_AUDIO_CONFIG, TIMED_SOUND_AUDIO_CONFIG_CLASSNAME))
509 return false;
510
511 path.Insert(new ContainerIdPathEntry(TIMED_SOUND_AUDIO_CONFIG));
512
513 if (!m_API.SetVariableValue(actualPrefab, path, "m_sSoundProject", audioSourceConfig.m_sSoundProject))
514 return false;
515
516 if (!m_API.SetVariableValue(actualPrefab, path, "m_sSoundEventName", audioSourceConfig.m_sSoundEventName))
517 return false;
518
519 if (!m_API.SetVariableValue(actualPrefab, path, "m_eFlags", audioSourceConfig.m_eFlags.ToString()))
520 return false;
521 }
522
523 return true;
524 }
525
526 //------------------------------------------------------------------------------------------------
534 protected bool InsertTimedEffectValue(
535 notnull BaseContainer actualPrefab,
536 notnull array<ref ContainerIdPathEntry> path,
537 notnull SCR_TimedEffect effect)
538 {
539 if (!m_API.SetVariableValue(actualPrefab, path, "m_fSpawnTime", effect.m_fSpawnTime.ToString()))
540 return false;
541
542 if (!m_API.SetVariableValue(actualPrefab, path, "m_bSnapToTerrain", effect.m_bSnapToTerrain.ToString(true)))
543 return false;
544
545 if (!m_API.SetVariableValue(actualPrefab, path, "m_bAttachToParent", effect.m_bAttachToParent.ToString(true)))
546 return false;
547
548 if (!m_API.SetVariableValue(actualPrefab, path, "m_bPersistent", effect.m_bPersistent.ToString(true)))
549 return false;
550
551 return true;
552 }
553
554 //------------------------------------------------------------------------------------------------
557 protected static array<ref ContainerIdPathEntry> GetDestructionComponentPath(BaseContainer actualPrefab)
558 {
559 int destructionComponentIndex = SCR_BaseContainerTools.FindComponentIndex(actualPrefab, DESTRUCTION_COMPONENT);
560 if (destructionComponentIndex < 0)
561 return null;
562
563 return { new ContainerIdPathEntry(COMPONENTS_ARRAY, destructionComponentIndex) };
564 }
565
566 //------------------------------------------------------------------------------------------------
570 protected static array<ref ContainerIdPathEntry> GetTimedEffectPath(notnull array<ref ContainerIdPathEntry> destructionComponentPath, int effectIndex)
571 {
572 array<ref ContainerIdPathEntry> result = SCR_ArrayHelperRefT<ContainerIdPathEntry>.GetCopy(destructionComponentPath);
573 result.Insert(new ContainerIdPathEntry(DESTRUCTION_COMPONENT_EFFECTS_ARRAY, effectIndex));
574 return result;
575 }
576
577 //------------------------------------------------------------------------------------------------
581 protected static string ToString(notnull ContainerIdPathEntry containerIdPathEntry)
582 {
583 return containerIdPathEntry.PropertyName + "/" + containerIdPathEntry.Index;
584 }
585
586 //------------------------------------------------------------------------------------------------
590 protected static string ToString(notnull array<ref ContainerIdPathEntry> containerIdPathEntries)
591 {
592 array<string> bits = {};
593 foreach (ContainerIdPathEntry pathEntry : containerIdPathEntries)
594 {
595 bits.Insert(ToString(pathEntry));
596 }
597
598 return "(array<ContainerIdPathEntry>) " + SCR_StringHelper.Join(", ", bits);
599 }
600}
601
602[BaseContainerProps(configRoot: true)]
603class SCR_BuildingDestructionSetupToolConfig
604{
605 [Attribute(desc: "Timed Effects to be applied to provided building(s)")]
606 ref array<ref SCR_TimedEffect> m_aEffects;
607}
608#endif
string path
void ContainerIdPathEntry(string propertyName, int index=-1)
Definition worldEditor.c:30
SCR_AIAnimation_Loitering BaseContainerProps
Commanding menu commanding element class.
ResourceName resourceName
Definition SCR_AIGroup.c:66
EDamageType type
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.
Definition LogLevel.c:14
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.