Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_RandomSpawnManagerComponent.c
Go to the documentation of this file.
1[ComponentEditorProps(category: "GameMode/Respawn", description: "Dynamically generates and manages random spawn points.")]
5
6class SCR_RandomSpawnManagerComponent : SCR_BaseGameModeComponent
7{
8 [Attribute("20", params: "1 inf", desc: "Target cached spawnpoint amount for quicker simultaneous spawn")]
9 protected int m_iCachedSpawnPointCount;
10
11 [Attribute("300", params: "0 inf", desc: "Safe distance from other spawnpoints")]
12 protected float m_fSafeDistance;
13
14 [Attribute("2500", params: "1 inf", desc: "Target amount of cells to split the terrain into")]
15 protected int m_iTargetCellDensity;
16
17 [Attribute("10", params: "1 inf", desc: "Number of spawn points to generate per batch frame")]
18 protected int m_iGenerationBatchSize;
19
20 [Attribute("20", params: "1 inf", desc: "Max emergency spawn generation attempts")]
21 protected int m_iEmergencyAttempts;
22
23 [Attribute(category: "Blacklist Zones", desc: "Blacklisted parts of the map")]
24 protected ref array<ref SCR_SpawnPointBlacklistZone> m_aBlacklistZones;
25
26 protected const float MAX_SURFACE_DELTA = 0.8;
27 protected const float PLAYER_CYLINDER_HEIGHT = 2;
28 protected const float PLAYER_CYLINDER_RADIUS = 0.5;
29 protected const float WATER_LEVEL_THRESHOLD = 1;
30 protected const float RAYCAST_HEIGHT_OFFSET = 10;
31 protected const float MIN_CELL_SIZE = 10;
32 protected const float JUMP_GOLDEN_RATIO = 0.6180339887;
33
34 protected ref array<vector> m_aPrecalculatedSpawns = {};
35 protected ref array<vector> m_aSafeCells = {};
36 protected bool m_bIsGenerating;
40 protected ref TraceParam m_TraceParam;
42 protected BaseWorld m_World;
43
44 //------------------------------------------------------------------------------------------------
45 override void OnGameModeStart()
46 {
47 if (!Replication.IsServer())
48 return;
49
50 super.OnGameModeStart();
51 if (!GetGame().InPlayMode())
52 return;
53
55 }
56
57 //------------------------------------------------------------------------------------------------
58 protected void InitializeGrid()
59 {
60 vector mins, maxs, cellCenter = vector.Zero;
61 float worldWidth, worldLength, totalArea, cellArea;
62 float halfCell, safeDistLimit, safeDistSq, effectiveRadius;
63 int blCount, basesCount;
64 bool bNeedsRandomSpawns = false;
65 bool cellIsSafe;
66
67 array<SCR_SpawnPoint> initialSpawns;
68 array<vector> staticBaseOrigins = {};
69 array<vector> blacklistCenters = {};
70 array<float> blacklistSqRadii = {};
71
72 m_aSafeCells.Clear();
73 m_World = GetGame().GetWorld();
74
75 if (!m_World)
76 return;
77
78 initialSpawns = SCR_SpawnPoint.GetSpawnPoints();
79
80 if (initialSpawns)
81 {
82 foreach (SCR_SpawnPoint spawnPoint : initialSpawns)
83 {
84 if (!spawnPoint)
85 continue;
86
87 if (spawnPoint.IsSpawnPointRandom())
88 {
89 bNeedsRandomSpawns = true;
90 }
91 else
92 {
93 IEntity parent = spawnPoint.GetParent();
94 if (parent)
95 {
97 if (base && !base.IsInitialized())
98 continue;
99 }
100
101 staticBaseOrigins.Insert(spawnPoint.GetOrigin());
102 }
103 }
104 }
105
106 if (!bNeedsRandomSpawns)
107 return;
108
109 m_fRayLength = RAYCAST_HEIGHT_OFFSET * 2;
110 m_vTraceOffset = vector.Up * RAYCAST_HEIGHT_OFFSET;
111
112 GetGame().GetWorldEntity().GetTerrain(0, 0).GetTerrainBoundBox(mins, maxs);
113 worldWidth = maxs[0] - mins[0];
114 worldLength = maxs[2] - mins[2];
115 totalArea = worldWidth * worldLength;
116
117 cellArea = totalArea / m_iTargetCellDensity;
118 m_vCylinderVectorOffset = { 0, PLAYER_CYLINDER_HEIGHT * 0.5, 0 };
119 m_fSafeDistanceSq = m_fSafeDistance * m_fSafeDistance;
120 m_fCellSize = Math.Sqrt(cellArea);
121
122 if (m_fCellSize < MIN_CELL_SIZE)
123 m_fCellSize = MIN_CELL_SIZE;
124
126 halfCell = m_fCellSize * 0.5;
127
128 if (m_aBlacklistZones)
129 {
130 foreach (SCR_SpawnPointBlacklistZone zone : m_aBlacklistZones)
131 {
132 if (!zone || zone.m_fRadius <= 0)
133 continue;
134
135 blacklistCenters.Insert(zone.m_vCenter);
136 effectiveRadius = zone.m_fRadius + halfCell;
137 blacklistSqRadii.Insert(effectiveRadius * effectiveRadius);
138 }
139 }
140
141 safeDistLimit = m_fSafeDistance + halfCell;
142 safeDistSq = safeDistLimit * safeDistLimit;
143 blCount = blacklistCenters.Count();
144 basesCount = staticBaseOrigins.Count();
145
146 for (float x = mins[0], maxX = maxs[0]; x < maxX; x += m_fCellSize)
147 {
148 for (float z = mins[2], maxY = maxs[2]; z < maxY; z += m_fCellSize)
149 {
150 cellCenter[0] = x + halfCell;
151 cellCenter[2] = z + halfCell;
152
153 if (m_World.GetSurfaceY(cellCenter[0], cellCenter[2]) <= WATER_LEVEL_THRESHOLD)
154 continue;
155
156 cellIsSafe = true;
157 for (int i; i < blCount; i++)
158 {
159 if (vector.DistanceSqXZ(cellCenter, blacklistCenters[i]) <= blacklistSqRadii[i])
160 {
161 cellIsSafe = false;
162 break;
163 }
164 }
165
166 if (cellIsSafe && basesCount > 0)
167 {
168 for (int i; i < basesCount; i++)
169 {
170 if (vector.DistanceSqXZ(cellCenter, staticBaseOrigins[i]) < safeDistSq)
171 {
172 cellIsSafe = false;
173 break;
174 }
175 }
176 }
177
178 if (cellIsSafe)
179 m_aSafeCells.Insert(cellCenter);
180 }
181 }
182
183 if (!m_aSafeCells.IsEmpty())
184 {
185 m_iLastUsedIndex = Math.RandomInt(0, m_aSafeCells.Count());
187 }
188 }
189
190 //------------------------------------------------------------------------------------------------
195 bool RequestSpawnPosition(FactionKey playerFaction, array<SCR_SpawnPoint> currentWorldSpawns, out vector outPos)
196 {
197 vector candidatePos;
198 for (int i = m_aPrecalculatedSpawns.Count() - 1; i >= 0; i--)
199 {
200 candidatePos = m_aPrecalculatedSpawns[i];
201 m_aPrecalculatedSpawns.Remove(i);
202
203 if (IsDynamicallySafe(candidatePos, playerFaction, currentWorldSpawns))
204 {
205 outPos = candidatePos;
207 return true;
208 }
209 }
210
211 vector emergencyPos;
212 int emergencyAttempts = 0;
213 while (emergencyAttempts < m_iEmergencyAttempts && !m_aSafeCells.IsEmpty())
214 {
215 if (TryGenerateSinglePoint(emergencyPos) && IsDynamicallySafe(emergencyPos, playerFaction, currentWorldSpawns))
216 {
217 outPos = emergencyPos;
219 return true;
220 }
221
222 emergencyAttempts++;
223 }
224
225 return false;
226 }
227
228 //------------------------------------------------------------------------------------------------
229 protected void WakeUpGenerator()
230 {
231 if (m_bIsGenerating || m_aSafeCells.IsEmpty())
232 return;
233
234 if (m_aPrecalculatedSpawns.Count() < m_iCachedSpawnPointCount)
235 {
236 m_bIsGenerating = true;
237 GetGame().GetCallqueue().CallLater(ProcessGenerationBatch, m_iBatchDelayMs, false);
238 }
239 }
240
241 //------------------------------------------------------------------------------------------------
242 protected void ProcessGenerationBatch()
243 {
244 int processedThisFrame = 0;
245 vector newPos;
246
247 while (m_aPrecalculatedSpawns.Count() < m_iCachedSpawnPointCount && !m_aSafeCells.IsEmpty())
248 {
249 if (processedThisFrame >= m_iGenerationBatchSize)
250 {
251 GetGame().GetCallqueue().CallLater(ProcessGenerationBatch, m_iBatchDelayMs, false);
252 return;
253 }
254
255 if (TryGenerateSinglePoint(newPos))
256 m_aPrecalculatedSpawns.Insert(newPos);
257
258 processedThisFrame++;
259 }
260
261 m_bIsGenerating = false;
262 }
263
264 //------------------------------------------------------------------------------------------------
265 protected bool TryGenerateSinglePoint(out vector outPos)
266 {
267 int availableCells = m_aSafeCells.Count();
268 if (availableCells == 0)
269 return false;
270
271 // Quasirandom jump roughly 61.8% across the sequentially-ordered array to find furthest point almost randomly
272 int jumpAmount = availableCells * JUMP_GOLDEN_RATIO;
273 m_iLastUsedIndex = (m_iLastUsedIndex + jumpAmount) % availableCells;
274
275 int chosenIndex = m_iLastUsedIndex;
276 vector cellCenter = m_aSafeCells[chosenIndex];
277
278 vector testPos;
279 float surfaceY, traceCoef, hitY;
280
281 for (int attempt = 0; attempt < 20; attempt++)
282 {
283 testPos = cellCenter;
284 testPos[0] = testPos[0] + Math.RandomFloat(-m_fHalfCellOffset, m_fHalfCellOffset);
285 testPos[2] = testPos[2] + Math.RandomFloat(-m_fHalfCellOffset, m_fHalfCellOffset);
286
287 surfaceY = m_World.GetSurfaceY(testPos[0], testPos[2]);
288 testPos[1] = surfaceY;
289
290 if (testPos[1] <= WATER_LEVEL_THRESHOLD)
291 continue;
292
293 if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(m_World, testPos))
294 continue;
295
296 if (!m_TraceParam)
297 m_TraceParam = new TraceParam();
298
299 m_TraceFlags = TraceFlags.ENTS | TraceFlags.OCEAN;
300 m_TraceParam.Flags = m_TraceFlags | TraceFlags.WORLD;
301 m_TraceParam.Start = testPos + m_vTraceOffset;
302 m_TraceParam.End = testPos - m_vTraceOffset;
303
304 traceCoef = m_World.TraceMove(m_TraceParam, null);
305 hitY = m_TraceParam.Start[1] - (traceCoef * m_fRayLength);
306
307 if (hitY - surfaceY > MAX_SURFACE_DELTA)
308 continue;
309
310 testPos[1] = Math.Max(hitY + 0.01, surfaceY);
311
312 if (SCR_WorldTools.TraceCylinder(testPos + m_vCylinderVectorOffset, PLAYER_CYLINDER_RADIUS, PLAYER_CYLINDER_HEIGHT, m_TraceFlags, m_World))
313 {
314 outPos = testPos;
315 return true;
316 }
317 }
318
319 int lastIndex = availableCells - 1;
320 m_aSafeCells[chosenIndex] = m_aSafeCells[lastIndex];
321 m_aSafeCells.Remove(lastIndex);
322
323 if (m_iLastUsedIndex == lastIndex)
324 m_iLastUsedIndex = chosenIndex;
325
326 return false;
327 }
328
329 //------------------------------------------------------------------------------------------------
330 protected bool IsDynamicallySafe(vector pos, FactionKey playerFaction, array<SCR_SpawnPoint> currentWorldSpawns)
331 {
332 if (!currentWorldSpawns)
333 return true;
334
335 FactionKey spFaction;
336 foreach (SCR_SpawnPoint spawnPoint : currentWorldSpawns)
337 {
338 if (!spawnPoint || !spawnPoint.IsSpawnPointEnabled())
339 continue;
340
341 spFaction = spawnPoint.GetFactionKey();
342 if (spFaction != playerFaction && spFaction != string.Empty)
343 {
344 if (vector.DistanceSqXZ(pos, spawnPoint.GetOrigin()) < m_fSafeDistanceSq)
345 return false;
346 }
347 }
348
349 return true;
350 }
351}
ArmaReforgerScripted GetGame()
Definition game.c:1398
enum EAIGroupCombatMode ComponentEditorProps(category:"GameScripted/AI", description:"Component for utility AI system for groups")
void SCR_BaseGameModeComponent(IEntityComponentSource src, IEntity ent, IEntity parent)
override void OnGameModeStart()
it takes to automatically cancel a task when no enemy has been killed in the zone
bool TryGenerateSinglePoint(out vector outPos)
bool RequestSpawnPosition(FactionKey playerFaction, array< SCR_SpawnPoint > currentWorldSpawns, out vector outPos)
bool IsDynamicallySafe(vector pos, FactionKey playerFaction, array< SCR_SpawnPoint > currentWorldSpawns)
ref TraceParam m_TraceParam
TraceFlags m_TraceFlags
ref array< vector > m_aSafeCells
void ProcessGenerationBatch()
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
proto external Managed FindComponent(typename typeName)
proto external IEntity GetParent()
Definition Math.c:13
Main replication API.
Definition Replication.c:14
Spawn point entity defines positions on which players can possibly spawn.
SCR_FieldOfViewSettings Attribute
TraceFlags
Definition TraceFlags.c:13