Arma Reforger Explorer  1.1.0.42
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
SCR_AmbientPatrolSystem.c
Go to the documentation of this file.
1 //------------------------------------------------------------------------------------------------
3 {
4  protected static const int CHECK_INTERVAL = 3; //s, how often should an individual patrol spawn be checked
5  protected static const int DESPAWN_TIMEOUT = 10000; //ms
6  protected static const int SPAWN_RADIUS_MIN_SQ = 500 * 500; // Square value for distance checks
7  protected static const int SPAWN_RADIUS_MAX_SQ = 1000 * 1000; // Square value for distance checks
8  protected static const int SPAWN_RADIUS_BLOCK_SQ = 150 * 150; // Square value for distance checks
9  protected static const int DESPAWN_RADIUS_DIFF_SQ = 200 * 200; // Square value for distance checks
10 
11  protected ref array<SCR_AmbientPatrolSpawnPointComponent> m_aPatrols = {};
12  protected ref array<IEntity> m_aPlayers = {};
13 
14  protected int m_iLastAssignedIndex;
15  protected int m_iIndexToCheck;
16  protected int m_iSpawnDistanceSq;
17  protected int m_iDespawnDistanceSq;
18 
19  protected float m_fTimer;
20  protected float m_fCheckInterval;
21 
22  //------------------------------------------------------------------------------------------------
23  override event protected void OnInit()
24  {
25  // No need to run updates unless some patrols are actually registered
26  if (m_aPatrols.IsEmpty())
27  Enable(false);
28 
29  RefreshPlayerList();
30 
31  // Calculate (de)spawn distance based on view distance, have it squared for faster distance calculation
32  int fractionOfVD = GetGame().GetViewDistance() * 0.3;
33  m_iSpawnDistanceSq = fractionOfVD * fractionOfVD;
34  m_iSpawnDistanceSq = Math.Min(SPAWN_RADIUS_MAX_SQ, m_iSpawnDistanceSq);
35  m_iSpawnDistanceSq = Math.Max(SPAWN_RADIUS_MIN_SQ, m_iSpawnDistanceSq);
36  m_iDespawnDistanceSq = m_iSpawnDistanceSq + DESPAWN_RADIUS_DIFF_SQ;
37 
39 
40  if (!gameMode)
41  return;
42 
43  gameMode.GetOnPlayerSpawned().Insert(OnPlayerSpawnedOrDeleted);
44  gameMode.GetOnPlayerKilled().Insert(OnPlayerKilled);
45  gameMode.GetOnPlayerDeleted().Insert(OnPlayerSpawnedOrDeleted);
46  gameMode.GetOnPlayerDisconnected().Insert(OnPlayerDisconnected);
47  }
48 
49  //------------------------------------------------------------------------------------------------
50  override event protected void OnCleanup()
51  {
53 
54  if (!gameMode)
55  return;
56 
57  gameMode.GetOnPlayerSpawned().Remove(OnPlayerSpawnedOrDeleted);
58  gameMode.GetOnPlayerKilled().Remove(OnPlayerKilled);
59  gameMode.GetOnPlayerDeleted().Remove(OnPlayerSpawnedOrDeleted);
60  gameMode.GetOnPlayerDisconnected().Remove(OnPlayerDisconnected);
61  }
62 
63  //------------------------------------------------------------------------------------------------
64  override event protected void OnUpdate(ESystemPoint point)
65  {
66  if (!GetGame().AreGameFlagsSet(EGameFlags.SpawnAI))
67  {
68  Enable(false);
69  return;
70  }
71 
72  float timeSlice = GetWorld().GetFixedTimeSlice();
73 
74  m_fTimer += timeSlice;
75 
76  if (m_fTimer < m_fCheckInterval)
77  return;
78 
79  m_fTimer = 0;
80 
81  // HOTFIX: Don't process spawning at the very start - wait for a save to be applied if it exists
82  // Otherwise full-size groups get spawned even if they are marked as eliminated in the save file
83  // TODO: Come up with a better solution
84  if (GetGame().GetWorld().GetWorldTime() < SCR_GameModeCampaign.BACKEND_DELAY)
85  return;
86 
87  ProcessSpawnpoint(m_iIndexToCheck);
88  m_iIndexToCheck++;
89 
90  if (!m_aPatrols.IsIndexValid(m_iIndexToCheck))
91  m_iIndexToCheck = 0;
92  }
93 
94  //------------------------------------------------------------------------------------------------
95  override event bool ShouldBePaused()
96  {
97  return true;
98  }
99 
100  //------------------------------------------------------------------------------------------------
101  static SCR_AmbientPatrolSystem GetInstance()
102  {
103  World world = GetGame().GetWorld();
104 
105  if (!world)
106  return null;
107 
108  return SCR_AmbientPatrolSystem.Cast(world.FindSystem(SCR_AmbientPatrolSystem));
109  }
110 
111  //------------------------------------------------------------------------------------------------
112  protected void UpdateCheckInterval()
113  {
114  m_fCheckInterval = CHECK_INTERVAL / m_aPatrols.Count();
115  }
116 
117  //------------------------------------------------------------------------------------------------
118  protected void RefreshPlayerList()
119  {
120  m_aPlayers.Clear();
121  array<int> playerIds = {};
122  PlayerManager pc = GetGame().GetPlayerManager();
123  int playersCount = pc.GetPlayers(playerIds);
124 
125  foreach (int playerId : playerIds)
126  {
127  IEntity player = pc.GetPlayerControlledEntity(playerId);
128 
129  if (!player)
130  continue;
131 
132  CharacterControllerComponent comp = CharacterControllerComponent.Cast(player.FindComponent(CharacterControllerComponent));
133 
134  if (!comp || comp.IsDead())
135  continue;
136 
137  m_aPlayers.Insert(player);
138  }
139  }
140 
141  //------------------------------------------------------------------------------------------------
142  protected void OnPlayerSpawnedOrDeleted(int playerId, IEntity player)
143  {
144  RefreshPlayerList();
145  }
146 
147  //------------------------------------------------------------------------------------------------
149  int GetRemainingPatrolsInfo(out notnull array<int> remnantsInfo)
150  {
151  int size;
152  ChimeraWorld world = GetWorld();
153  WorldTimestamp curTime = world.GetServerTimestamp();
154 
155  foreach (SCR_AmbientPatrolSpawnPointComponent presence : m_aPatrols)
156  {
157  if (!presence)
158  continue;
159 
160  SCR_AIGroup grp = presence.GetSpawnedGroup();
161 
162  if (presence.GetMembersAlive() < 0 && !grp && !presence.GetIsSpawned())
163  continue;
164 
165  remnantsInfo.Insert(presence.GetID());
166 
167  if (grp)
168  {
169  size = remnantsInfo.Insert(grp.GetAgentsCount());
170  }
171  else
172  {
173  if (presence.GetIsSpawned())
174  size = remnantsInfo.Insert(0);
175  else
176  size = remnantsInfo.Insert(presence.GetMembersAlive());
177  }
178 
179  // Subtract current time so when data is loaded at scenario start, the delay is correct
180  remnantsInfo.Insert(presence.GetRespawnTimestamp().DiffMilliseconds(curTime));
181  }
182 
183  return size;
184  }
185 
186  //------------------------------------------------------------------------------------------------
187  protected void ProcessSpawnpoint(int spawnpointIndex)
188  {
189  SCR_AmbientPatrolSpawnPointComponent spawnpoint = m_aPatrols[spawnpointIndex];
190 
191  if (!spawnpoint || (spawnpoint.GetMembersAlive() == 0 && !spawnpoint.GetIsSpawned()))
192  return;
193 
194  ChimeraWorld world = GetWorld();
195  WorldTimestamp currentTime = world.GetServerTimestamp();
196  if (spawnpoint.GetRespawnTimestamp().Greater(currentTime))
197  return;
198 
199  if (!spawnpoint.GetIsSpawned())
200  {
201  spawnpoint.SpawnPatrol();
202  return;
203  }
204 
205  bool playersNear;
206  bool playersVeryNear;
207  bool playersFar = true;
208  vector location = spawnpoint.GetOwner().GetOrigin();
209  int distance;
210 
211  // Define if any player is close enough to spawn or if all players are far enough to despawn
212  foreach (IEntity player : m_aPlayers)
213  {
214  if (!player)
215  continue;
216 
217  distance = vector.DistanceSq(player.GetOrigin(), location);
218 
219  if (distance > m_iDespawnDistanceSq)
220  continue;
221 
222  playersFar = false;
223 
224  if (distance > m_iSpawnDistanceSq)
225  continue;
226 
227  playersNear = true;
228 
229  if (distance > SPAWN_RADIUS_BLOCK_SQ)
230  continue;
231 
232  playersVeryNear = true;
233  break;
234  }
235 
236  bool isAIOverLimit;
237  AIWorld aiWorld = GetGame().GetAIWorld();
238 
239  if (aiWorld)
240  {
241  int maxChars = aiWorld.GetLimitOfActiveAIs();
242 
243  if (maxChars <= 0)
244  isAIOverLimit = true;
245  else
246  isAIOverLimit = ((float)aiWorld.GetCurrentNumOfActiveAIs() / (float)maxChars) > spawnpoint.GetAILimitThreshold();
247  }
248 
249  if (!isAIOverLimit && !playersVeryNear)
250  spawnpoint.SetIsPaused(false);
251 
252  if (playersNear && !spawnpoint.GetIsPaused() && !spawnpoint.IsGroupActive())
253  {
254  // Do not spawn the patrol if the AI threshold setting has been reached
255  if (isAIOverLimit)
256  {
257  spawnpoint.SetIsPaused(true); // Make sure a patrol is not spawned too close to players when AI limit suddenly allows spawning of this group
258  return;
259  }
260 
261  spawnpoint.ActivateGroup();
262  return;
263  }
264 
265  // Delay is used so dying players don't see the despawn happen
266  if (spawnpoint.GetIsSpawned() && playersFar && spawnpoint.IsGroupActive())
267  {
268  WorldTimestamp despawnT = spawnpoint.GetDespawnTimer();
269 
270  if (despawnT == 0)
271  spawnpoint.SetDespawnTimer(currentTime.PlusMilliseconds(DESPAWN_TIMEOUT));
272  else if (currentTime.Greater(despawnT))
273  spawnpoint.DeactivateGroup();
274  }
275  else
276  {
277  spawnpoint.SetDespawnTimer(null);
278  }
279  }
280 
281  //------------------------------------------------------------------------------------------------
282  void RegisterPatrol(notnull SCR_AmbientPatrolSpawnPointComponent patrol)
283  {
284  if (!IsEnabled())
285  Enable(true);
286 
287  m_aPatrols.Insert(patrol);
288  patrol.SetID(m_iLastAssignedIndex);
289  m_iLastAssignedIndex++;
290  UpdateCheckInterval();
291  }
292 
293  //------------------------------------------------------------------------------------------------
294  void UnregisterPatrol(notnull SCR_AmbientPatrolSpawnPointComponent patrol)
295  {
296  m_aPatrols.RemoveItem(patrol);
297  m_iIndexToCheck = 0;
298 
299  if (!m_aPatrols.IsEmpty())
300  {
301  UpdateCheckInterval();
302  return;
303  }
304 
305  Enable(false);
306  }
307 
308  //------------------------------------------------------------------------------------------------
309  int GetPatrols(out array<SCR_AmbientPatrolSpawnPointComponent> patrols)
310  {
311  if (patrols)
312  return patrols.Copy(m_aPatrols);
313  else
314  return m_aPatrols.Count();
315  }
316 
317  //------------------------------------------------------------------------------------------------
318  void OnPlayerDisconnected(int playerId, KickCauseCode cause = KickCauseCode.NONE, int timeout = -1)
319  {
320  RefreshPlayerList();
321  }
322 
323  //------------------------------------------------------------------------------------------------
324  void OnPlayerKilled(int playerId, IEntity playerEntity, IEntity killerEntity, notnull Instigator killer)
325  {
326  RefreshPlayerList();
327  }
328 }
ChimeraWorld
Definition: ChimeraWorld.c:12
SCR_BaseGameMode
Definition: SCR_BaseGameMode.c:137
GetGame
ArmaReforgerScripted GetGame()
Definition: game.c:1424
AreGameFlagsSet
bool AreGameFlagsSet(EGameFlags checkGameFlags)
Definition: game.c:567
EGameFlags
EGameFlags
GameMode Game Flags represented by bit mask.
Definition: game.c:16
KickCauseCode
KickCauseCode
Definition: KickCauseCode.c:19
Instigator
Definition: Instigator.c:6
m_fTimer
protected float m_fTimer
Definition: SCR_CampaignMilitaryBaseComponent.c:94
GetGameMode
SCR_BaseGameMode GetGameMode()
Definition: SCR_BaseGameModeComponent.c:15
SCR_GameModeCampaign
void SCR_GameModeCampaign(IEntitySource src, IEntity parent)
Definition: SCR_GameModeCampaign.c:1927
Enable
void Enable(bool enable)
Definition: SCR_TabViewComponent.c:806
GameSystem
Definition: GameSystem.c:12
distance
float distance
Definition: SCR_DestructibleTreeV2.c:29
SCR_AmbientPatrolSystem
Definition: SCR_AmbientPatrolSystem.c:2
IsEnabled
int IsEnabled()
Definition: SCR_BaseManualCameraComponent.c:99
SCR_AIGroup
Definition: SCR_AIGroup.c:68
PlayerManager
Definition: PlayerManager.c:12