4 protected int m_iPlayerID;
10 protected ref array<float> m_aStats = {};
13 protected int m_iSecondsAsController = 0, m_iSecondsAsKeyboard = 0;
14 protected int m_iLastEvaluationCurrentController = 0;
20 protected ref array<float> m_aPreviousStats = {};
26 protected ref array<float> m_aAccumulatedActions = {};
27 protected int m_iAccumulatedActionsTick;
28 protected int m_iLatestActionTick;
29 protected int m_iLatestCriminalScoreUpdateTick;
30 protected float m_fCriminalScore;
36 protected int m_iTimeOut = 0;
43 protected ref array<float> m_aStatsGained = {};
55 protected ref array<float> m_aEarnt = {};
68 protected int m_iSessionStartedTickCount;
74 protected bool m_bIsEmptyProfile;
80 protected ref ScriptInvoker m_OnDataReady =
new ScriptInvoker();
82 static ref ScriptInvoker s_OnStatAdded =
new ScriptInvoker();
93 bool accumulationEnabled =
false;
96 void SCR_PlayerData (
int id,
bool hasPlayerBeenAuditted,
bool requestFromBackend =
true)
106 m_iSessionStartedTickCount = System.GetTickCount();
109 for (
int i = tEnum.GetVariableCount(); i > 0; i--)
111 m_aAccumulatedActions.Insert(0);
114 if (requestFromBackend)
115 RequestLoadData(hasPlayerBeenAuditted);
116 else if (!hasPlayerBeenAuditted)
124 m_iLastEvaluationCurrentController = m_iSessionStartedTickCount;
125 EInputDeviceType device =
GetGame().GetInputManager().GetLastUsedInputDevice();
126 UpdateTrackingController(device, device);
127 GetGame().OnInputDeviceUserChangedInvoker().Insert(UpdateTrackingController);
132 void SetTimeOut(
int time)
144 protected void UpdateTrackingController(EInputDeviceType oldDevice, EInputDeviceType newDevice)
146 int currentTickCount = System.GetTickCount();
148 int newTime = (currentTickCount - m_iLastEvaluationCurrentController) * 0.001;
151 case EInputDeviceType.GAMEPAD:
case EInputDeviceType.JOYSTICK:
152 m_iSecondsAsController += newTime;
154 case EInputDeviceType.MOUSE:
case EInputDeviceType.KEYBOARD:
155 m_iSecondsAsKeyboard += newTime;
159 m_iLastEvaluationCurrentController = currentTickCount;
165 void SetPreviousStats(array<float> previousStats)
167 m_aPreviousStats = previousStats;
171 array<float> GetPreviousStats()
173 return m_aPreviousStats;
179 void SetStats(array<float> stats)
185 array<float> GetStats()
191 void RequestLoadData(
bool HasPlayerBeenAuditted =
false)
193 BackendApi ba =
GetGame().GetBackendApi();
197 Print(
"SCR_PlayerData:RequestLoadData: Backend api is null!", LogLevel.WARNING);
198 m_bIsEmptyProfile =
true;
202 if (HasPlayerBeenAuditted)
209 if (!m_CharacterDataCallback)
211 Print(
"SCR_PlayerData:RequestLoadData: The character data callback is null", LogLevel.WARNING);
212 m_bIsEmptyProfile =
true;
216 if (ba.IsAuthenticated())
218 Print(
"Making menuCallback request for PlayerData", LogLevel.VERBOSE);
219 ba.PlayerRequest(EBackendRequest.EBREQ_GAME_CharacterGet, m_CharacterDataCallback,
this,
m_iPlayerID);
224 Print(
"SCR_PlayerData:RequestLoadData: Requesting data before player has been authenticated!", LogLevel.WARNING);
225 m_bIsEmptyProfile =
true;
230 ScriptInvoker GetDataReadyInvoker()
232 return m_OnDataReady;
236 void BackendDataReady()
240 if (m_aStats.Count() != characterDataEnum.GetVariableCount())
242 Print(
"SCR:PlayerDataStats:FillWithProfile: The CharacterData from this player seems incomplete or empty. A full profile should have "+characterDataEnum.GetVariableCount()+
" stats, but this one has "+m_aStats.Count()+
". Is it a new player?", LogLevel.VERBOSE);
244 if (!m_aStats.IsEmpty())
246 Print(
"SCR_PlayerData of this player is not empty, but the size is not correct either. We will override their profile with a new one, which will erase their old profile.", LogLevel.DEBUG);
253 m_bIsEmptyProfile =
false;
254 m_aPreviousStats.InsertAll(m_aStats);
255 m_OnDataReady.Invoke();
259 void LoadEmptyProfile()
262 int count = characterDataEnum.GetVariableCount();
266 for (
int i = 0; i < count; i++)
269 m_aStats.Insert(m_Configs.XP_NEEDED_FOR_LEVEL);
271 m_aStats.Insert(m_Configs.GetMinAcceleration());
277 m_bIsEmptyProfile =
true;
279 m_aPreviousStats.InsertAll(m_aStats);
280 m_OnDataReady.Invoke();
284 sealed
void StoreProfile()
286 if (!m_CharacterDataCallback || !m_aStats || m_aStats.IsEmpty())
289 BackendApi ba =
GetGame().GetBackendApi();
293 if (!IsDataProgressionReady())
294 CalculateStatsChange();
297 ba.PlayerRequest(EBackendRequest.EBREQ_GAME_CharacterUpdateS2S, m_StoringCallback,
this,
m_iPlayerID);
299 ba.PlayerRequest(EBackendRequest.EBREQ_GAME_DevCharacterUpdate, m_StoringCallback,
this,
m_iPlayerID);
309 Managed GetDataEventStats()
311 if (!IsDataProgressionReady() || m_aStatsGained.IsEmpty())
313 Print(
"Maybe a problem: DataProgression is not ready but the analytics event was requested", LogLevel.WARNING);
314 CalculateStatsChange();
317 EInputDeviceType device =
GetGame().GetInputManager().GetLastUsedInputDevice();
318 UpdateTrackingController(device, device);
320 Print(
"Time with gamepad was " + m_iSecondsAsController, LogLevel.DEBUG);
321 Print(
"Time with keyboard was " + m_iSecondsAsKeyboard, LogLevel.DEBUG);
323 dataEvent.amt_time_mission = m_aStatsGained[
SCR_EDataStats.SESSION_DURATION];
324 dataEvent.amt_distance_on_foot = m_aStatsGained[
SCR_EDataStats.DISTANCE_WALKED];
325 dataEvent.amt_distance_vehicle = m_aStatsGained[
SCR_EDataStats.DISTANCE_DRIVEN];
330 dataEvent.friendly_kills = m_aStatsGained[
SCR_EDataStats.FRIENDLY_KILLS];
335 dataEvent.friendly_vehicle_kills = m_aStatsGained[
SCR_EDataStats.FRIENDLY_ROADKILLS] + m_aStatsGained[
SCR_EDataStats.FRIENDLY_AI_ROADKILLS];
344 dataEvent.healing_allies = m_aStatsGained[
SCR_EDataStats.BANDAGE_FRIENDLIES];
345 dataEvent.healing_self = m_aStatsGained[
SCR_EDataStats.BANDAGE_SELF];
359 dataEvent.rank_points = m_aStatsGained[
SCR_EDataStats.LEVEL_EXPERIENCE];
360 dataEvent.rank_total_points = m_aStats[
SCR_EDataStats.LEVEL_EXPERIENCE];
361 dataEvent.warcrimes_points = m_aStatsGained[
SCR_EDataStats.WARCRIMES];
362 dataEvent.warcrimes_total_points = m_aStats[
SCR_EDataStats.WARCRIMES];
371 dataEvent.seconds_as_controller = m_iSecondsAsController;
372 dataEvent.seconds_as_keyboard = m_iSecondsAsKeyboard;
378 sealed
void DebugCalculateStats()
380 Print(
"SCR_PlayerData:DebugCalculateStats: This method calls calculateStatsChange providing fake stats for debugging purposes.", LogLevel.DEBUG);
395 if (!IsDataProgressionReady())
396 CalculateStatsChange();
400 sealed
void CalculateStatsChange()
404 m_aStatsGained = CalculateStatsDifference();
406 for (
int i = m_aStatsGained.Count()-1; i >= 0; i--)
416 Print(
"SCR_PlayerData:CalculateStatsChange: Specialization stats, experience or rank stats were modified prematurely. The career data for this session might be compromised", LogLevel.WARNING);
421 int totalSpecializationPointsGain = 0;
425 if (m_aStats[
SCR_EDataStats.SPPOINTS0] < m_Configs.SPECIALIZATION_MAX)
450 totalSpecializationPointsGain += m_aStatsGained[
SCR_EDataStats.SPPOINTS0];
455 if (m_aStats[
SCR_EDataStats.SPPOINTS1] < m_Configs.SPECIALIZATION_MAX)
460 m_aEarnt[
SCR_EDataStats.PLAYERS_DIED_IN_VEHICLE] = -1 * Math.Min(m_aStatsGained[
SCR_EDataStats.PLAYERS_DIED_IN_VEHICLE] * m_Configs.MODIFIER_PLAYERS_DIED_IN_VEHICLE, m_Configs.MAX_PLAYERS_DIED_IN_VEHICLE_PENALTY);
471 totalSpecializationPointsGain += m_aStatsGained[
SCR_EDataStats.SPPOINTS1];
476 if (m_aStats[
SCR_EDataStats.SPPOINTS2] < m_Configs.SPECIALIZATION_MAX)
482 m_aEarnt[
SCR_EDataStats.BANDAGE_FRIENDLIES] = m_aStatsGained[
SCR_EDataStats.BANDAGE_FRIENDLIES] * m_Configs.MODIFIER_BANDAGE_FRIENDLIES;
484 m_aEarnt[
SCR_EDataStats.TOURNIQUET_FRIENDLIES] = m_aStatsGained[
SCR_EDataStats.TOURNIQUET_FRIENDLIES] * m_Configs.MODIFIER_TOURNIQUET_FRIENDLIES;
488 m_aEarnt[
SCR_EDataStats.MORPHINE_FRIENDLIES] = m_aStatsGained[
SCR_EDataStats.MORPHINE_FRIENDLIES] * m_Configs.MODIFIER_MORPHINE_FRIENDLIES;
496 totalSpecializationPointsGain += m_aStatsGained[
SCR_EDataStats.SPPOINTS2];
501 float qualityTimeRatio = totalSpecializationPointsGain / (1 +(m_Configs.STD_POINTS_QUALITY_TIME * 2.5 * (m_aStatsGained[
SCR_EDataStats.SESSION_DURATION] / 3600)));
504 if (qualityTimeRatio > 1.5)
505 Print(
"SCR_PlayerData:CalculateStatsChange: totalSpecializationPointsGain is suspiciously high. That means player performed better than we expected which could point an an oversight from our side, or even a player cheating. Player with session id " +
m_iPlayerID +
" gained " + totalSpecializationPointsGain +
" points on a session of " + (m_aStatsGained[
SCR_EDataStats.SESSION_DURATION] / 60) +
" minutes, ending with a qualityTimeRatio of " + qualityTimeRatio, LogLevel.WARNING);
507 Print(
"SCR_PlayerData:CalculateStatsChange: QualityTimeRatio (aka how well player performed) is: " + qualityTimeRatio, LogLevel.DEBUG);
513 int warCrimesDown = qualityTimeRatio * (m_aStatsGained[
SCR_EDataStats.SESSION_DURATION]/ 3600) * m_Configs.WARCRIMES_DECREASE_PER_HOUR;
522 if (m_aStats[
SCR_EDataStats.WARCRIMES] > m_Configs.MAX_WARCRIMES_VALUE)
523 m_aStats[
SCR_EDataStats.WARCRIMES] = m_Configs.MAX_WARCRIMES_VALUE;
533 int currentSpPoints, gainedRawPoints;
534 for (i = 0; i < m_Configs.SPECIALIZATIONS_COUNT; i++)
554 if (currentSpPoints >= m_Configs.SPECIALIZATION_MAX)
559 gainedRawPoints *= (1 - m_Configs.WARCRIMES_PUNISHMENT);
561 while (j < m_Configs.INTERVALS_COUNT && m_Configs.INTERVALS_END[j] < currentSpPoints)
566 if (j >= m_Configs.INTERVALS_COUNT)
568 Print (
"SCR_PlayerData:CalculateStatsChange: currentSpPoints outside of the accepted ranges.", LogLevel.WARNING);
574 AddPointsToSpecialization(i, gainedRawPoints * m_Configs.INTERVALS_C1[j] * (Math.Pow(m_Configs.INTERVALS_C2[j], currentSpPoints * m_Configs.XP_NEEDED_FOR_LEVEL_DIVIDER)));
579 if (m_aStats[
SCR_EDataStats.LEVEL_EXPERIENCE] >= m_Configs.MAX_EXP)
588 m_aEarnt[
SCR_EDataStats.LEVEL_EXPERIENCE] = m_aEarnt[
SCR_EDataStats.SESSION_DURATION] + totalSpecializationPointsGain * m_Configs.MODIFIER_SPECIALIZATIONS;
595 if (m_aStats[
SCR_EDataStats.LEVEL_EXPERIENCE] > m_Configs.MAX_EXP)
605 array<float> GetArrayEarntPoints()
613 return !m_aStats.IsEmpty();
617 bool IsDataProgressionReady()
619 return m_aEarnt && !m_aEarnt.IsEmpty();
623 void FillArrayWithSpecializationPoints(inout array<float> SpPoints,
bool newStats =
true)
625 for (
int i = 0; i < m_Configs.SPECIALIZATIONS_COUNT; i++)
627 SpPoints.Insert(GetSpecializationPoints(i, newStats));
632 sealed array<float> CalculateStatsDifference()
634 array<float> StatsToReturn = {};
636 int count = m_aStats.Count();
637 CalculateSessionDuration();
639 for (
int i = 0; i < count; i++)
641 StatsToReturn.Insert(m_aStats[i] - m_aPreviousStats[i]);
644 return StatsToReturn;
648 float GetSpecializationPoints(
int n,
bool newStats =
true)
652 if (!m_aStats || m_aStats.IsEmpty())
665 Print (
"SCR_PlayerData:GetSpecializationPoints: WRONG SPECIALIZATION ID.", LogLevel.WARNING);
669 if (!m_aPreviousStats || m_aPreviousStats.IsEmpty())
682 Print (
"SCR_PlayerData:GetSpecializationPoints: WRONG SPECIALIZATION ID.", LogLevel.WARNING);
687 protected void AddPointsToSpecialization(
int n,
float value)
689 if (!m_aStats || m_aStats.IsEmpty())
704 Print (
"SCR_PlayerData:AddPointsToSpecialization: WRONG SPECIALIZATION ID.", LogLevel.WARNING);
709 void PrepareSpecializationStatsDisplay()
732 void PrepareSpecializationProgressionStatsDisplay()
734 if (m_aStatsGained.IsEmpty())
736 Print(
"Requesting statsDifference in PrepareSpecializationProgressionStatsDisplay but array is empty. Recalculating it!", LogLevel.DEBUG);
737 m_aStatsGained = CalculateStatsDifference();
760 bool IsEmptyProfile()
762 return m_bIsEmptyProfile;
776 void AddStat(
SCR_EDataStats stat,
float amount = 1,
bool temp =
true)
778 if (!accumulationEnabled)
783 m_iLatestActionTick = System.GetTickCount();
784 if (m_iAccumulatedActionsTick == 0)
785 m_iAccumulatedActionsTick = m_iLatestActionTick;
787 m_aAccumulatedActions[stat] = m_aAccumulatedActions[stat] + amount;
791 m_aStats[stat] = m_aStats[stat] + amount;
794 s_OnStatAdded.Invoke(
m_iPlayerID, stat, amount, temp);
801 m_aStats[stat] = amount;
810 return m_aStats[stat];
812 return m_aPreviousStats[stat];
820 int GetAccumulatedActionsTick()
822 return m_iAccumulatedActionsTick;
826 int GetLatestActionTick()
828 return m_iLatestActionTick;
831 int GetLatestCriminalScoreUpdateTick()
833 return m_iLatestCriminalScoreUpdateTick;
843 array<float> GetAccumulatedActions()
845 array<float> copy = {};
846 copy.Copy(m_aAccumulatedActions);
851 float GetCriminalScore()
853 return m_fCriminalScore;
857 void SetCriminalScore(
float score)
859 m_iLatestCriminalScoreUpdateTick = System.GetTickCount();
860 m_fCriminalScore = score;
864 void ResetAccumulatedActions()
866 for (
int i = 0, count = m_aAccumulatedActions.Count(); i < count; i++)
868 m_aAccumulatedActions[i] = 0;
871 m_iAccumulatedActionsTick = 0;
876 void CalculateSessionDuration()
886 m_aStats[
SCR_EDataStats.SESSION_DURATION] = m_aStats[
SCR_EDataStats.SESSION_DURATION] + (System.GetTickCount() - m_iSessionStartedTickCount) * 0.001;
887 m_iSessionStartedTickCount = System.GetTickCount();
895 float GetSpecializationCount(
int n)
899 case 0:
return m_Configs.SPECIALIZATION_0_COUNT;
900 case 1:
return m_Configs.SPECIALIZATION_1_COUNT;
901 case 2:
return m_Configs.SPECIALIZATION_2_COUNT;
915 m_PlayerDataInstance = instance;
922 override void OnError(
int code,
int restCode,
int apiCode)
924 Print(
"[CharacterDataLoadingCallback] OnError: "+
GetGame().GetBackendApi().GetErrorCode(code), LogLevel.ERROR);
925 m_PlayerDataInstance.LoadEmptyProfile();
932 override void OnSuccess(
int code)
934 Print(
"[CharacterDataLoadingCallback] OnSuccess.", LogLevel.DEBUG);
935 m_PlayerDataInstance.BackendDataReady();
941 override void OnTimeout()
943 Print(
"[CharacterDataLoadingCallback] OnTimeout", LogLevel.ERROR);
944 m_PlayerDataInstance.LoadEmptyProfile();