Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
GameModeSetupConfig.c
Go to the documentation of this file.
1#ifdef WORKBENCH
2// TODO: SCR_
3[BaseContainerProps(configRoot: true)]
4class GameModeSetupConfig
5{
6 [Attribute(desc: "Prefabs that will be created automatically by the plugin.\nTheir classes, and classes of their children, will be used as required classes for the game mode.")]
7 protected ref array<ref GameModeSetupConfigEntry> m_Prefabs;
8
9 [Attribute(desc: "Template from which mission header will be created.")]
10 protected ref MissionHeader m_MissionHeader;
11
12 protected ref array<ref GameModeSetupConfigEntry> m_aRequiredTypes = {};
13 protected typename m_GameModeType;
14 protected int m_iLongestTypeLength;
15 protected string m_sWorldPath;
16 protected string m_sFileSystem;
17
18 protected const string DESCRIPTION_ENTITY_PRESENT = " ✅ %1\n";
19 protected const string DESCRIPTION_ENTITY_MISSING = " ❌ %1\n";
20
21 protected const string DESCRIPTION_VALIDATION_SUCCESS = "\n✅ The world is configured correctly.";
22 protected const string DESCRIPTION_VALIDATION_NO_SUBSCENE = "\n⛔ Missing Subscene\nYou are attempting to add the game mode to the root world.\nPlease create a subscene for it instead, while leaving the root world intact.\nThat will allow others to create their own subscenes with different game modes.\n";
23 protected const string DESCRIPTION_VALIDATION_WRONG_GAME_MODE = "\n⛔ Incompatible Game Mode\nThe scan found existing game mode of type %1.\nThat's incompatible with the expected type %2.\nThere can be only one game mode in the world, so if you wish to continue,\nyou have to manually remove the existing one. The plugin cannot automate this process.\n";
24 protected const string DESCRIPTION_VALIDATION_PRESENT_ENTITIES = "\nPresent entity types:\n (NOT USED)";
25 protected const string DESCRIPTION_VALIDATION_MISSING_ENTITIES = "\n⛔ Missing Entities\nOne or more required entity types are missing, listed below.\nThese can be generated automatically in the next step.\n";
26
27 protected const string DESCRIPTION_GENERATION_ENTITY = " ❗ %1: %2\n";
28 protected const string DESCRIPTION_GENERATION_MORE_STEPS_1 = "\nSome entities need to be manually configured or replaced by more specialized prefabs:\n";
29 protected const string DESCRIPTION_GENERATION_MORE_STEPS_2 = "\nEach has a comment attached explaining necessary steps, so you can return to them later.\n";
30
31 protected const string DESCRIPTION_MISSION_HEADER_EXISTS = "✅ Mission header config at '%1' is configured correctly.\n";
32
33 protected const string SCENARIOS_PATH = "Missions";
34
35 //------------------------------------------------------------------------------------------------
40 bool ValidateWorld(out string dialogMessage, out bool canAutogenerate)
41 {
42 canAutogenerate = true;
43
44 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
45 WorldEditorAPI api = worldEditor.GetApi();
46
47 string worldPath;
48 api.GetWorldPath(worldPath);
49
50 set<typename> worldTypes = GetWorldEntityTypes();
51
52 //--- Check for present and missing types
53 string presentTypes;
54 string missingTypes;
55 bool isSuccess = true;
56 foreach (GameModeSetupConfigEntry entry : m_aRequiredTypes)
57 {
58 //--- ToDo: Different format for log messages
59 if (worldTypes.Contains(entry.m_Type))
60 {
61 presentTypes += string.Format(DESCRIPTION_ENTITY_PRESENT, entry.m_Type);
62 }
63 else
64 {
65 missingTypes += string.Format(DESCRIPTION_ENTITY_MISSING, entry.m_Type);
66 isSuccess = false;
67 }
68 }
69
70 //--- Check if incompatible game mode is present (there can only be one game mode per world)
71 typename wrongGameMode;
72 foreach (typename type : worldTypes)
73 {
74 if (type.IsInherited(BaseGameMode) && !type.IsInherited(m_GameModeType))
75 {
76 wrongGameMode = type;
77 isSuccess = false;
78 canAutogenerate = false;
79 break;
80 }
81 }
82
83 //--- Check if the world is a subscene
84 bool noSubscene;
85 if (api.GetNumSubScenes() == 1)
86 {
87 noSubscene = true;
88 isSuccess = false;
89 canAutogenerate = false;
90 }
91
92 if (isSuccess)
93 {
94 dialogMessage += DESCRIPTION_VALIDATION_SUCCESS;
95 }
96 else
97 {
98 if (noSubscene)
99 dialogMessage += DESCRIPTION_VALIDATION_NO_SUBSCENE;
100
101 if (wrongGameMode)
102 dialogMessage += string.Format(DESCRIPTION_VALIDATION_WRONG_GAME_MODE, wrongGameMode, m_GameModeType);
103
104 //if (presentTypes)
105 // dialogMessage += DESCRIPTION_VALIDATION_PRESENT_ENTITIES + presentTypes;
106
107 if (missingTypes && !noSubscene && !wrongGameMode)
108 dialogMessage += DESCRIPTION_VALIDATION_MISSING_ENTITIES + missingTypes;
109 }
110
111 return isSuccess;
112 }
113
114 //------------------------------------------------------------------------------------------------
118 bool GenerateWorld(out string dialogMessage)
119 {
120 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
121 WorldEditorAPI api = worldEditor.GetApi();
122
123 BaseWorld world = api.GetWorld();
124 vector min, max;
125 world.GetBoundBox(min, max);
126 vector worldCenter = vector.Lerp(min, max, 0.5);
127
128 api.BeginEntityAction("GameModeSetupPlugin");
129 int layerID = api.GetCurrentEntityLayerId();// api.CreateSubsceneLayer(api.GetCurrentSubScene(), "GameMode");
130
131 int asciiLineBreak = 10;
132 string lineBreak = asciiLineBreak.AsciiToString();
133
134 IEntitySource entitySource;
135 foreach (int i, GameModeSetupConfigEntry entry : m_Prefabs)
136 {
137 string entityName = FilePath.StripExtension(FilePath.StripPath(entry.m_Prefab));
138
139 worldCenter += vector.Forward;
140 worldCenter[1] = world.GetSurfaceY(worldCenter[0], worldCenter[2]);
141
142 //--- Entity
143 entitySource = entry.CreateEntity(api, entityName, layerID, worldCenter, vector.Zero);
144 api.AddToEntitySelection(entitySource);
145
146 //--- Comment entity
147 if (entry.m_Comment)
148 {
149 entitySource = api.CreateEntity("CommentEntity", string.Empty, layerID, entitySource, vector.Up, vector.Zero);
150 api.SetVariableValue(entitySource, null, "m_Comment", entityName + ": " + lineBreak + entry.m_Comment);
151 api.SetVariableValue(entitySource, null, "m_Color", "0 0 0 1");
152 api.SetVariableValue(entitySource, null, "m_FaceCamera", "1");
153 api.SetVariableValue(entitySource, null, "m_TextBackground", "1");
154 api.SetVariableValue(entitySource, null, "m_BackgroundColor", "1 0.6 0 1");
155 api.SetVariableValue(entitySource, null, "m_BackgroundTransparency", "0");
156
157 dialogMessage += string.Format(DESCRIPTION_GENERATION_ENTITY, entityName, entry.m_Comment);
158 }
159 }
160 api.UpdateSelectionGui();
161 api.EndEntityAction("GameModeSetupPlugin");
162
163 //--- Move camera to entities to give user clear feedback
164 api.SetCamera(worldCenter + Vector(0, 4, 2), Vector(180, -60, 0).AnglesToVector());
165
166 if (dialogMessage)
167 dialogMessage = DESCRIPTION_GENERATION_MORE_STEPS_1 + dialogMessage + DESCRIPTION_GENERATION_MORE_STEPS_2;
168
169 return true;
170 }
171
172 //------------------------------------------------------------------------------------------------
176 bool ValidateMissionHeader(out string dialogMessage)
177 {
178 array<ResourceName> resources = {};
179 string missionsPath = m_sFileSystem + SCENARIOS_PATH;
180
181 SearchResourcesFilter filter = new SearchResourcesFilter();
182 filter.fileExtensions = { "conf" };
183 filter.rootPath = missionsPath;
184 ResourceDatabase.SearchResources(filter, resources.Insert);
185
186 Resource missionResource;
187 BaseContainer missionContainer;
188 ResourceName missionWorld;
189
190 for (int i = resources.Count() - 1; i >= 0; i--)
191 {
192 missionResource = Resource.Load(resources[i]);
193 if (!missionResource.IsValid())
194 continue;
195
196 missionContainer = missionResource.GetResource().ToBaseContainer();
197 missionContainer.Get("World", missionWorld);
198 if (m_sFileSystem + missionWorld.GetPath() == m_sWorldPath)
199 {
200 dialogMessage = string.Format(DESCRIPTION_MISSION_HEADER_EXISTS, resources[i].GetPath());
201 return true;
202 }
203 }
204 return false;
205 }
206
207 //------------------------------------------------------------------------------------------------
212 bool GenerateMissionHeader(string templatePath, out string dialogMessage)
213 {
214 //--- Get mission header from the template config (can't use m_MissionHeader directly, it's engine-controlled class that cannot have reference in script)
215 Resource templateResource = Resource.Load(templatePath);
216 BaseContainer templateContainer = templateResource.GetResource().ToBaseContainer();
217 BaseContainer missionHeaderContainer = templateContainer.GetObject("m_MissionHeader");
218
219 //--- Get world path with GUID and save it to the header
220 ResourceManager resourceManager = Workbench.GetModule(ResourceManager);
221 string absWorldPath;
222 Workbench.GetAbsolutePath(m_sWorldPath, absWorldPath);
223 MetaFile worldMeta = resourceManager.GetMetaFile(absWorldPath);
224 string fullWorldPath = worldMeta.GetResourceID();
225 missionHeaderContainer.Set("World", fullWorldPath);
226
227 //--- Get target config path
228 string worldName = FilePath.StripExtension(FilePath.StripPath(m_sWorldPath));
229 string relativeDirPath = m_sFileSystem + SCENARIOS_PATH;
230 string absoluteDirPath;
231 if (!Workbench.GetAbsolutePath(relativeDirPath, absoluteDirPath, true)) // the Missions directory does not exist
232 {
233 if (!Workbench.GetAbsolutePath(relativeDirPath, absoluteDirPath, false))
234 {
235 Print("Unable to obtain the " + SCENARIOS_PATH + " directory path at " + relativeDirPath, LogLevel.ERROR);
236 return false;
237 }
238
239 if (!FileIO.MakeDirectory(absoluteDirPath))
240 {
241 Print("Unable to create the " + SCENARIOS_PATH + " directory at " + absoluteDirPath, LogLevel.ERROR);
242 return false;
243 }
244
245 Print("Successfully created the " + SCENARIOS_PATH + " directory at " + absoluteDirPath, LogLevel.NORMAL);
246 }
247
248 string missionHeaderPath = FilePath.Concat(relativeDirPath, worldName);
249 missionHeaderPath = FilePath.AppendExtension(missionHeaderPath, "conf");
250
251 //--- Create the config
252 if (!BaseContainerTools.SaveContainer(missionHeaderContainer, ResourceName.Empty, missionHeaderPath))
253 {
254 Print(string.Format("Unable to create mission header at %1!", missionHeaderPath), LogLevel.ERROR);
255 return false;
256 }
257
258 //--- Open the config in Resource Manager, so the user can edit it straight away
259 string missionHeaderAbsPath;
260 Workbench.GetAbsolutePath(missionHeaderPath, missionHeaderAbsPath, false);
261 resourceManager.RegisterResourceFile(missionHeaderAbsPath, false);
262 resourceManager.SetOpenedResource(missionHeaderPath);
263
264 dialogMessage = string.Format(DESCRIPTION_MISSION_HEADER_EXISTS, missionHeaderPath);
265
266 return true;
267 }
268
269 //------------------------------------------------------------------------------------------------
270 protected set<typename> GetWorldEntityTypes()
271 {
272 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
273
274 WBProgressDialog progress = new WBProgressDialog("Validating world entities...", worldEditor);
275
276 set<typename> types = new set<typename>();
277 IEntitySource entitySource;
278 array<IEntitySource> queue = {};
279 int containerCount = worldEditor.GetNumContainers();
280
281 float prevProgress, currProgress;
282 for (int i; i < containerCount; i++)
283 {
284 entitySource = IEntitySource.Cast(worldEditor.GetContainer(i));
285
286 if (entitySource.GetNumChildren() > 0)
287 queue.Insert(entitySource);
288 else
289 types.Insert(entitySource.GetClassName().ToType());
290
291 currProgress = i / containerCount;
292 if (currProgress - prevProgress >= 0.01) // min 1%
293 {
294 progress.SetProgress(currProgress); // expensive
295 prevProgress = currProgress;
296 }
297 }
298
299 while (!queue.IsEmpty())
300 {
301 entitySource = queue[0];
302 queue.Remove(0);
303
304 types.Insert(entitySource.GetClassName().ToType());
305
306 for (int i = 0, count = entitySource.GetNumChildren(); i < count; i++)
307 {
308 queue.Insert(entitySource.GetChild(i));
309 }
310 }
311
312 return types;
313 }
314
315 //------------------------------------------------------------------------------------------------
317 void Init()
318 {
319 //--- Get file system
320 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
321 WorldEditorAPI api = worldEditor.GetApi();
322
323 api.GetWorldPath(m_sWorldPath);
324 m_sFileSystem = FilePath.FileSystemNameFromFileName(m_sWorldPath);
325 m_sFileSystem = SCR_AddonTool.ToFileSystem(m_sFileSystem);
326 }
327
328 //------------------------------------------------------------------------------------------------
329 // constructor
330 void GameModeSetupConfig()
331 {
332 int r = 0;
333
334 //--- Get types of generated prefabs and their children
335 array<ref Resource> prefabResources = {}; // reference MUST be kept
336 Resource prefabResource;
337 IEntitySource prefabSource;
338 array<IEntitySource> queue = {};
339 array<ref GameModeSetupConfigEntry> queuePrefabs = {}; //--- Must be 'ref', otherwise entries will be null
340
341 foreach (GameModeSetupConfigEntry entry : m_Prefabs)
342 {
343 prefabResource = Resource.Load(entry.m_Prefab);
344 if (!prefabResource.IsValid())
345 continue;
346
347 prefabResources.Insert(prefabResource);
348 prefabSource = prefabResource.GetResource().ToEntitySource();
349 queue.Insert(prefabSource);
350 queuePrefabs.Insert(entry);
351 }
352
353 map<string, ref GameModeSetupConfigEntry> entries = new map<string, ref GameModeSetupConfigEntry>();
354 GameModeSetupConfigEntry entry;
355 string requiredNamesSortedStatic[256];
356 string className;
357 typename type;
358 while (!queue.IsEmpty())
359 {
360 prefabSource = queue[0];
361 entry = queuePrefabs[0];
362
363 queue.Remove(0);
364 queuePrefabs.Remove(0);
365
366 className = prefabSource.GetClassName();
367 m_iLongestTypeLength = Math.Max(m_iLongestTypeLength, className.Length());
368 requiredNamesSortedStatic[r] = className;
369 r++;
370
371 type = className.ToType();
372 entry.m_Type = type;
373 entries.Insert(className, entry);
374
375 if (type.IsInherited(BaseGameMode))
376 m_GameModeType = type;
377
378 for (int i, count = prefabSource.GetNumChildren(); i < count; i++)
379 {
380 queue.Insert(prefabSource.GetChild(i));
381 queuePrefabs.Insert(new GameModeSetupConfigEntry());
382 }
383 }
384
385 if (!m_GameModeType)
386 Print("No game mode entity found among prefabs to be auto-generated!", LogLevel.WARNING);
387
388 //--- Alphabetically sort the array of types
389 StaticArray.Sort(requiredNamesSortedStatic);
390 string sortedClassName;
391 for (int i = 0; i < 256; i++)
392 {
393 sortedClassName = requiredNamesSortedStatic[i];
394 if (!sortedClassName.IsEmpty())
395 m_aRequiredTypes.Insert(entries[sortedClassName]);
396 }
397 }
398}
399
401class GameModeSetupConfigEntry
402{
403 [Attribute(desc: "Entity prefab to be spawned.", params: "et")]
404 ResourceName m_Prefab;
405
406 [Attribute(desc: "When defined, user will be notified that the entity requires additional configuration\nand a comment with this text will be created next to the entity.")]
407 string m_Comment;
408
409 typename m_Type;
410
411 //------------------------------------------------------------------------------------------------
420 IEntitySource CreateEntity(WorldEditorAPI api, string entityName, int layerID, vector pos, vector dir)
421 {
422 return api.CreateEntity(m_Prefab, entityName, layerID, null, pos, dir);
423 }
424}
425#endif // WORKBENCH
override void Init()
ResourceName m_Prefab
SCR_AIAnimation_Loitering BaseContainerProps
Commanding menu commanding element class.
SCR_CampaignMilitaryBaseComponent SCR_MilitaryBaseComponent SCR_BaseContainerCustomTitleResourceName("m_sBaseName", true)
EDamageType type
EEditableEntityType m_Type
string m_sFileSystem
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
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 vector Vector(float x, float y, float z)