8 protected ref array<int> m_aPlayerIDs = {};
14 protected int m_iNextIndex;
20 [
Attribute(
"3", UIWidgets.Slider,
desc:
"Seconds after last action to evaluate buffer")]
21 protected float m_fLatestActionThreshold;
27 [
Attribute(
"5", UIWidgets.Slider,
desc:
"Max seconds of accumulating actions before evaluating them")]
28 protected float m_fMaxTimeAccumulationThreshold;
35 protected ref array<int> m_aLightBanPunishments;
41 [
Attribute(
"1800", UIWidgets.Slider,
desc:
"Seconds for reincidency of light severity")]
42 protected int m_iSecondsOfReincidencyLightBan;
49 protected ref array<int> m_aHeavyBanPunishments;
55 [
Attribute(
"14400", UIWidgets.Slider,
desc:
"Seconds for reincidency of heavy severity")]
56 protected int m_iSecondsOfReincidencyHeavyBan;
58 [
Attribute(
"1", UIWidgets.CheckBox,
desc:
"Are War Crimes detection enabled?")]
59 protected bool m_bWarCrimesEnabled;
61 [
Attribute(
"1", UIWidgets.CheckBox,
desc:
"Is the proportionality principle enabled?")]
62 protected bool m_bWarCrimesProportionalityPrincipleEnabled;
64 [
Attribute(
"2", UIWidgets.Slider,
desc:
"Negative proportionality points for killing a friendly human-controlled soldier")]
65 protected float MODIFIER_PROPORTIONALITY_FRIENDLY_KILLS;
67 [
Attribute(
"1", UIWidgets.Slider,
desc:
"Negative proportionality points for killing a friendly AI-controlled soldier")]
68 protected float MODIFIER_PROPORTIONALITY_FRIENDLY_AI_KILLS;
70 [
Attribute(
"1.5", UIWidgets.Slider,
desc:
"Negative proportionality points for killing an enemy AI-controlled soldier")]
71 protected float MODIFIER_PROPORTIONALITY_AI_KILLS;
73 [
Attribute(
"3", UIWidgets.Slider,
desc:
"Positive proportionality points for killing an enemy human-controlled soldier")]
74 protected float MODIFIER_PROPORTIONALITY_KILLS;
77 static const int MIN_AUTO_BAN_DURATION = 300;
80 override void OnPlayerAuditSuccess(
int playerID)
82 super.OnPlayerAuditSuccess(playerID, stuff);
85 if (!m_bWarCrimesEnabled)
96 playerData.SetTimeOut(0);
100 override void OnPlayerDisconnected(
int playerID, IEntity controlledEntity =
null)
102 super.OnPlayerDisconnected(playerID, controlledEntity);
111 EvaluatePlayerCrimes(playerID,
false);
114 UpdateCriminalAcceleration(playerID);
120 if (i < m_iNextIndex)
125 override void OnGameModeEnd()
135 override void Update(
float timeTick)
158 protected void CheckPlayer(
int playerId)
165 int accumulationTick = playerData.GetAccumulatedActionsTick();
166 if (accumulationTick == 0)
168 ProcessTemporalStats(playerData);
173 int currentTick = System.GetTickCount();
174 int latestActionTick = playerData.GetLatestActionTick();
175 if (currentTick - latestActionTick < m_fLatestActionThreshold * 1000 && currentTick - accumulationTick < m_fMaxTimeAccumulationThreshold * 1000)
179 EvaluatePlayerCrimes(playerId);
185 protected void EvaluatePlayerCrimes(
int playerId,
bool evaluatePunishment =
true)
192 float decreasingOfScore = (System.GetTickCount() - playerData.GetLatestCriminalScoreUpdateTick()) * 0.001 *
SCR_PlayerDataConfigs.GetInstance().GetScoreDecreasePerSecond();
198 float harmingFriendliesScore = EvaluateHarmingFriendlies(playerData) * playerData.GetStat(
SCR_EDataStats.CRIME_ACCELERATION);
205 ProcessTemporalStats(playerData);
208 UpdateCriminalScore(playerData, harmingFriendliesScore, decreasingOfScore);
210 float currentScore = playerData.GetCriminalScore();
212 if (harmingFriendliesScore > 0)
213 Print(
"Player with id " + playerId +
" got " + harmingFriendliesScore +
" more criminal points. Current criminal score is " + currentScore, LogLevel.DEBUG);
221 KickPlayer(playerId, playerData, SCR_PlayerManagerKickReason.FRIENDLY_FIRE);
226 SendHints(playerId, currentScore);
230 protected void KickPlayer(
int playerId, notnull
SCR_PlayerData playerData, SCR_PlayerManagerKickReason reason)
232 int durationInMinutes = RequestBanIfNecessary(playerId, playerData, reason);
233 if (durationInMinutes == -1)
239 Print(
"Player Manager is kicking a player with id " + playerId +
". Banning them for " + durationInMinutes +
" minutes.", LogLevel.DEBUG);
240 if (durationInMinutes == 0)
241 ExecutePunishment(playerId, SCR_PlayerManagerKickReason.KICK, 0);
243 ExecutePunishment(playerId, SCR_PlayerManagerKickReason.TEMP_BAN, durationInMinutes * 60);
256 protected int RequestBanIfNecessary(
int playerId, notnull
SCR_PlayerData playerData, SCR_PlayerManagerKickReason reason)
258 if (m_aLightBanPunishments.IsEmpty() || m_aHeavyBanPunishments.IsEmpty())
260 Print(
"Incorrect setup on DataCollectorCrimesModule: Light or Heavy ban duration array is empty. Cannot ban players!", LogLevel.ERROR);
265 playerData.CalculateSessionDuration();
268 float acceleration = playerData.GetStat(
SCR_EDataStats.CRIME_ACCELERATION);
269 float score = playerData.GetCriminalScore();
272 int durationInMinutes;
276 if (config.GetMaxAcceleration() * config.GetBanEvaluationLight() <= acceleration)
281 int timeDiffLightBan = playerData.GetStat(
SCR_EDataStats.SESSION_DURATION) - playerData.GetStat(
SCR_EDataStats.LIGHTBAN_SESSION_DURATION);
282 int streakLightBan = playerData.GetStat(
SCR_EDataStats.LIGHTBAN_STREAK);
283 int maxStreakLightBan = m_aLightBanPunishments.Count();
286 int timeDiffHeavyBan = playerData.GetStat(
SCR_EDataStats.SESSION_DURATION) - playerData.GetStat(
SCR_EDataStats.HEAVYBAN_SESSION_DURATION);
287 int streakHeavyBan = playerData.GetStat(
SCR_EDataStats.HEAVYBAN_STREAK);
288 int maxStreakHeavyBan = m_aHeavyBanPunishments.Count();
291 if (timeDiffLightBan > m_iSecondsOfReincidencyLightBan && timeDiffHeavyBan > m_iSecondsOfReincidencyHeavyBan)
293 durationInMinutes = m_aLightBanPunishments[0];
296 playerData.OverrideStat(
SCR_EDataStats.LIGHTBAN_STREAK, streakLightBan);
304 if (!(streakLightBan >= maxStreakLightBan && config.GetMaxAcceleration() * config.GetBanEvaluationHeavy() <= acceleration))
308 durationInMinutes = m_aLightBanPunishments[Math.Min(streakLightBan, maxStreakLightBan - 1)];
311 playerData.OverrideStat(
SCR_EDataStats.LIGHTBAN_STREAK, streakLightBan);
317 if (timeDiffHeavyBan > m_iSecondsOfReincidencyHeavyBan)
319 durationInMinutes = m_aHeavyBanPunishments[0];
321 playerData.OverrideStat(
SCR_EDataStats.HEAVYBAN_STREAK, streakHeavyBan);
325 durationInMinutes = m_aHeavyBanPunishments[Math.Min(streakHeavyBan, maxStreakHeavyBan - 1)];
329 playerData.OverrideStat(
SCR_EDataStats.HEAVYBAN_STREAK, streakHeavyBan);
332 BackendApi ba =
GetGame().GetBackendApi();
334 ExecutePunishment(playerId, SCR_PlayerManagerKickReason.BAN, durationInMinutes * 60);
337 Print(
"Banning service is heavybanning a player with id " + playerId +
". Banning them for " + durationInMinutes +
" minutes.", LogLevel.DEBUG);
340 durationInMinutes = -1;
355 durationInMinutes = 0;
361 return durationInMinutes;
365 protected float EvaluateHarmingFriendlies(notnull
SCR_PlayerData playerData)
367 array<float> accumulatedActions = playerData.GetAccumulatedActions();
369 float harmingAlliesPoints = accumulatedActions[
SCR_EDataStats.FRIENDLY_KILLS] * MODIFIER_PROPORTIONALITY_FRIENDLY_KILLS + accumulatedActions[
SCR_EDataStats.FRIENDLY_AI_KILLS] * MODIFIER_PROPORTIONALITY_FRIENDLY_AI_KILLS;
370 float proportionalityPoints = 0;
372 if (m_bWarCrimesProportionalityPrincipleEnabled)
373 proportionalityPoints = accumulatedActions[
SCR_EDataStats.KILLS] * MODIFIER_PROPORTIONALITY_KILLS + accumulatedActions[
SCR_EDataStats.AI_KILLS] * MODIFIER_PROPORTIONALITY_AI_KILLS;
375 if (harmingAlliesPoints <= proportionalityPoints)
378 playerData.AddStat(
SCR_EDataStats.WARCRIME_HARMING_FRIENDLIES, harmingAlliesPoints - proportionalityPoints,
false);
379 playerData.AddStat(
SCR_EDataStats.WARCRIMES, harmingAlliesPoints - proportionalityPoints,
false);
389 return PointsOfCrime * (harmingAlliesPoints - proportionalityPoints);
393 protected void ExecutePunishment(
int playerId, SCR_PlayerManagerKickReason reason,
int durationInSeconds)
395 GetGame().GetDataCollector().GetPlayerData(playerId).SetTimeOut(durationInSeconds);
398 VoteForKickOrBan(playerId, reason, durationInSeconds);
400 KickOrBanPlayer(playerId, reason, durationInSeconds);
404 void KickOrBanPlayer(
int playerId, SCR_PlayerManagerKickReason reason,
int minimmumDuration)
410 case SCR_PlayerManagerKickReason.KICK:
412 GetGame().GetPlayerManager().KickPlayer(playerId, reason, 0);
415 case SCR_PlayerManagerKickReason.KICK_VOTED:
417 GetGame().GetPlayerManager().KickPlayer(playerId, reason, durationInSeconds);
420 case SCR_PlayerManagerKickReason.TEMP_BAN:
422 GetGame().GetPlayerManager().KickPlayer(playerId, reason, durationInSeconds);
425 case SCR_PlayerManagerKickReason.BAN:
427 GetGame().GetBackendApi().GetBanServiceApi().CreateBanPlayerId(
null, playerId,
"Heavy ban", durationInSeconds);
428 GetGame().GetPlayerManager().KickPlayer(playerId, reason, 0);
443 void VoteForKickOrBan(
int playerId, SCR_PlayerManagerKickReason reason,
int minimmumDurationBackup)
447 KickOrBanPlayer(playerId, reason, minimmumDurationBackup);
451 case SCR_PlayerManagerKickReason.KICK:
452 case SCR_PlayerManagerKickReason.KICK_VOTED:
453 if (!votingManager.StartVoting(
EVotingType.AUTO_KICK, playerId))
454 KickOrBanPlayer(playerId, reason, minimmumDurationBackup);
456 case SCR_PlayerManagerKickReason.TEMP_BAN:
457 if (!votingManager.StartVoting(
EVotingType.AUTO_LIGHTBAN, playerId))
458 KickOrBanPlayer(playerId, reason, minimmumDurationBackup);
460 case SCR_PlayerManagerKickReason.BAN:
461 if (!votingManager.StartVoting(
EVotingType.AUTO_HEAVYBAN, playerId))
462 KickOrBanPlayer(playerId, reason, minimmumDurationBackup);
468 protected void SendHints(
int playerId,
float harmingFriendliesScore)
473 if (harmingFriendliesScore > 0)
475 IEntity playerController =
GetGame().GetPlayerManager().GetPlayerController(playerId);
476 if (!playerController)
479 SCR_KickHintComponent hintsComp = SCR_KickHintComponent.Cast(playerController.FindComponent(SCR_KickHintComponent));
488 protected void ProcessTemporalStats(notnull
SCR_PlayerData playerData)
490 array<float> accumulatedActions = playerData.GetAccumulatedActions();
493 for (
int i = 0, count = accumulatedActions.Count(); i < count; i++)
495 float value = accumulatedActions[i];
497 playerData.AddStat(i, value,
false);
500 playerData.ResetAccumulatedActions();
504 protected void UpdateCriminalScore(notnull
SCR_PlayerData playerData,
float addToScore,
float decreaseFromScore)
506 float currentScore = Math.Max(playerData.GetCriminalScore() - decreaseFromScore, 0);
507 currentScore += addToScore;
509 playerData.SetCriminalScore(currentScore);
513 protected void UpdateCriminalAcceleration(
int playerId)
519 playerData.CalculateSessionDuration();
520 float secondsSession = playerData.GetStat(
SCR_EDataStats.SESSION_DURATION) - playerData.GetStat(
SCR_EDataStats.SESSION_DURATION,
false);
522 float decreaseOfAcceleration, increaseOfAcceleration;
524 if (secondsSession < 600)
525 decreaseOfAcceleration = 0;
527 decreaseOfAcceleration = Math.Log10(secondsSession) - 1;
531 increaseOfAcceleration = playerData.GetCriminalScore() * config.GetScoreToAccelerationMultiplier();
533 float currentAcceleration = Math.Max(playerData.GetStat(
SCR_EDataStats.CRIME_ACCELERATION) - decreaseOfAcceleration, config.GetMinAcceleration());
534 currentAcceleration = Math.Min(currentAcceleration + increaseOfAcceleration, config.GetMaxAcceleration());
536 Print(
"Player with id " + playerId +
" disconnecting. Old acceleration was " + playerData.GetStat(
SCR_EDataStats.CRIME_ACCELERATION) +
", new acceleration is " + currentAcceleration, LogLevel.VERBOSE);
538 playerData.OverrideStat(
SCR_EDataStats.CRIME_ACCELERATION, currentAcceleration);