the game where you go into mines and start crafting! but for consoles (forked directly from smartcmd's github)
1#include "stdafx.h"
2#include "Include\SenClientCore.h"
3#include "Include\SenClientMain.h"
4#include "Include\SenClientMain.h"
5
6#include "..\GameConfig\Minecraft.spa.h"
7#include "..\..\Minecraft.h"
8#include "..\..\MultiplayerLocalPlayer.h"
9#include "..\..\..\Minecraft.World\Dimension.h"
10#include "..\..\..\Minecraft.World\net.minecraft.world.level.h"
11#include "..\..\..\Minecraft.World\net.minecraft.world.level.storage.h"
12
13#include "SentientManager.h"
14#include "MinecraftTelemetry.h"
15#include "DynamicConfigurations.h"
16
17// Global instance
18CTelemetryManager *TelemetryManager = new CSentientManager();
19
20HRESULT CSentientManager::Init()
21{
22 Sentient::SenSysTitleID sentitleID;
23 sentitleID = (Sentient::SenSysTitleID)TITLEID_MINECRAFT;
24
25 HRESULT hr = SentientInitialize( sentitleID );
26
27 m_lastHeartbeat = m_initialiseTime;
28
29 m_bFirstFlush = true;
30
31 return hr;
32}
33
34HRESULT CSentientManager::Tick()
35{
36 HRESULT hr = S_OK;
37
38 // Update Sentient System
39 HRESULT sentientResult = Sentient::SentientUpdate();
40
41 switch(sentientResult)
42 {
43 case S_OK:
44 {
45 // Sentient is connected
46 //OutputDebugString ("\nSentient: CONNECTED\n");
47 if(g_NetworkManager.IsInSession())
48 {
49 float currentTime = app.getAppTime();
50 if(currentTime - m_lastHeartbeat > 60)
51 {
52 m_lastHeartbeat = currentTime;
53 for(DWORD i = 0; i < XUSER_MAX_COUNT; ++i)
54 {
55 if(Minecraft::GetInstance()->localplayers[i] != NULL)
56 {
57 SenStatHeartBeat(i, m_lastHeartbeat - m_initialiseTime);
58 }
59 }
60 }
61
62 if(m_bFirstFlush)
63 {
64 for(DWORD i = 0; i < XUSER_MAX_COUNT; ++i)
65 {
66 if(Minecraft::GetInstance()->localplayers[i] != NULL && m_fLevelStartTime[i] - currentTime > 60)
67 {
68 Flush();
69 }
70 }
71 }
72 }
73
74 MinecraftDynamicConfigurations::Tick();
75 }
76 break;
77
78 case Sentient::SENTIENT_S_NOT_SIGNED_IN_TO_LIVE:
79 {
80 // Login required
81 //DebugPrintf("\nSentient: WARNING: an Xbox LIVE-enabled user needs to be logged in.\n");
82
83 // Add title specific code here. . .
84 }
85 break;
86
87 case Sentient::SENTIENT_S_INITIALIZING_CONNECTION:
88 {
89 // Server connection in progress
90 app.DebugPrintf("Sentient: Establishing connection to sentient server.\n");
91
92 // Add title specific code here. . .
93 }
94 break;
95
96 case Sentient::SENTIENT_S_SERVER_CONNECTION_FAILED:
97 {
98 // Server connection failed
99 app.DebugPrintf("\nSentient: WARNING: connection to sentient server failed.\n");
100
101 // Add title specific code here. . .
102 }
103 break;
104
105 default:
106 {
107 // Unknown failure
108 app.DebugPrintf("Sentient: Unknown result from SentientUpdate()");
109
110 // Add title specific code here. . .
111 }
112 break;
113 }
114
115 return hr;
116}
117
118HRESULT CSentientManager::Flush()
119{
120 m_bFirstFlush = false;
121 return Sentient::SentientFlushStats();
122}
123
124BOOL CSentientManager::RecordPlayerSessionStart(DWORD dwUserId)
125{
126 return SenStatPlayerSessionStart( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetTitleBuildId(), 0, 0, 0, (INT)app.getDeploymentType() );
127}
128
129BOOL CSentientManager::RecordPlayerSessionExit(DWORD dwUserId, int _)
130{
131 return SenStatPlayerSessionExit( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId) );
132}
133
134BOOL CSentientManager::RecordHeartBeat(DWORD dwUserId)
135{
136 // Handled elswhere
137 return FALSE;
138}
139
140BOOL CSentientManager::RecordLevelStart(DWORD dwUserId, ESen_FriendOrMatch friendsOrMatch, ESen_CompeteOrCoop competeOrCoop, int difficulty, DWORD numberOfLocalPlayers, DWORD numberOfOnlinePlayers)
141{
142 if(dwUserId == ProfileManager.GetPrimaryPad() ) m_bFirstFlush = true;
143
144 ++m_levelInstanceID;
145 m_fLevelStartTime[dwUserId] = app.getAppTime();
146 return SenStatLevelStart( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID(), GetSingleOrMultiplayer(), friendsOrMatch, competeOrCoop, GetDifficultyLevel(difficulty), numberOfLocalPlayers, numberOfOnlinePlayers, GetLicense(), GetDefaultGameControls(), GetAudioSettings(dwUserId), 0, 0 );
147}
148
149BOOL CSentientManager::RecordLevelExit(DWORD dwUserId, ESen_LevelExitStatus levelExitStatus)
150{
151 float levelDuration = app.getAppTime() - m_fLevelStartTime[dwUserId];
152 return SenStatLevelExit( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID(), levelExitStatus, GetLevelExitProgressStat1(), GetLevelExitProgressStat2(), (INT)levelDuration );
153}
154
155BOOL CSentientManager::RecordLevelSaveOrCheckpoint(DWORD dwUserId, INT saveOrCheckPointID, INT saveSizeInBytes)
156{
157 float levelDuration = app.getAppTime() - m_fLevelStartTime[dwUserId];
158 return SenStatLevelSaveOrCheckpoint( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID(), GetLevelExitProgressStat1(), GetLevelExitProgressStat2(), (INT)levelDuration, saveOrCheckPointID, saveSizeInBytes );
159}
160
161BOOL CSentientManager::RecordLevelResume(DWORD dwUserId, ESen_FriendOrMatch friendsOrMatch, ESen_CompeteOrCoop competeOrCoop, int difficulty, DWORD numberOfLocalPlayers, DWORD numberOfOnlinePlayers, INT saveOrCheckPointID)
162{
163 return SenStatLevelResume( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID(), GetSingleOrMultiplayer(), friendsOrMatch, competeOrCoop, GetDifficultyLevel(difficulty), numberOfLocalPlayers, numberOfOnlinePlayers, GetLicense(), GetDefaultGameControls(), saveOrCheckPointID, GetAudioSettings(dwUserId), 0, 0 );
164}
165
166
167BOOL CSentientManager::RecordPauseOrInactive(DWORD dwUserId)
168{
169 return SenStatPauseOrInactive( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID() );
170}
171
172BOOL CSentientManager::RecordUnpauseOrActive(DWORD dwUserId)
173{
174 return SenStatUnpauseOrActive( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID() );
175}
176
177BOOL CSentientManager::RecordMenuShown(DWORD dwUserId, INT menuID, INT optionalMenuSubID)
178{
179 return SenStatMenuShown( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), menuID, optionalMenuSubID, GetLevelInstanceID(), GetMultiplayerInstanceID() );
180}
181
182BOOL CSentientManager::RecordAchievementUnlocked(DWORD dwUserId, INT achievementID, INT achievementGamerscore)
183{
184 return SenStatAchievementUnlocked( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID(), achievementID, achievementGamerscore );
185}
186
187BOOL CSentientManager::RecordMediaShareUpload(DWORD dwUserId, ESen_MediaDestination mediaDestination, ESen_MediaType mediaType)
188{
189 return SenStatMediaShareUpload( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID(), mediaDestination, mediaType );
190}
191
192BOOL CSentientManager::RecordUpsellPresented(DWORD dwUserId, ESen_UpsellID upsellId, INT marketplaceOfferID)
193{
194 return SenStatUpsellPresented( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID(), upsellId, marketplaceOfferID );
195}
196
197BOOL CSentientManager::RecordUpsellResponded(DWORD dwUserId, ESen_UpsellID upsellId, INT marketplaceOfferID, ESen_UpsellOutcome upsellOutcome)
198{
199 return SenStatUpsellResponded( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID(), upsellId, marketplaceOfferID, upsellOutcome );
200}
201
202BOOL CSentientManager::RecordPlayerDiedOrFailed(DWORD dwUserId, INT lowResMapX, INT lowResMapY, INT lowResMapZ, INT mapID, INT playerWeaponID, INT enemyWeaponID, ETelemetryChallenges enemyTypeID)
203{
204 INT secs = GetSecondsSinceInitialize();
205 return SenStatPlayerDiedOrFailed( dwUserId, GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID(), lowResMapX, lowResMapY, lowResMapZ, mapID, playerWeaponID, enemyWeaponID, enemyTypeID, secs, secs );
206}
207
208BOOL CSentientManager::RecordEnemyKilledOrOvercome(DWORD dwUserId, INT lowResMapX, INT lowResMapY, INT lowResMapZ, INT mapID, INT playerWeaponID, INT enemyWeaponID, ETelemetryChallenges enemyTypeID)
209{
210 INT secs = GetSecondsSinceInitialize();
211 return SenStatEnemyKilledOrOvercome( dwUserId, GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID(), lowResMapX, lowResMapY, lowResMapZ, mapID, playerWeaponID, enemyWeaponID, enemyTypeID, secs, secs );
212}
213
214BOOL CSentientManager::RecordSkinChanged(DWORD dwUserId, DWORD dwSkinId)
215{
216 return SenStatSkinChanged( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID(), dwSkinId );
217}
218
219BOOL CSentientManager::RecordBanLevel(DWORD dwUserId)
220{
221 return SenStatBanLevel( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID() );
222}
223
224BOOL CSentientManager::RecordUnBanLevel(DWORD dwUserId)
225{
226 return SenStatUnBanLevel( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID() );
227}
228
229BOOL CSentientManager::RecordTexturePackLoaded(DWORD dwUserId, INT texturePackId, INT purchased)
230{
231 return SenStatTexturePackChanged( dwUserId, GetSecondsSinceInitialize(), GetMode(dwUserId), GetSubMode(dwUserId), GetLevelId(dwUserId), GetSubLevelId(dwUserId), GetLevelInstanceID(), GetMultiplayerInstanceID(), texturePackId, purchased );
232}
233
234
235/*
236Number of seconds elapsed since Sentient initialize.
237Title needs to track this and report it as a property.
238These times will be used to create timelines and understand durations.
239This should be tracked independently of saved games (restoring a save should not reset the seconds since initialize)
240*/
241INT CSentientManager::GetSecondsSinceInitialize()
242{
243 return (INT)(app.getAppTime() - m_initialiseTime);
244}
245
246/*
247An in-game setting that significantly differentiates the play style of the game.
248(This should be captured as an integer and correspond to mode specific to the game.)
249Teams will have to provide the game mappings that correspond to the integers.
250The intent is to allow teams to capture data on the highest level categories of gameplay in their game.
251For example, a game mode could be the name of the specific mini game (eg: golf vs darts) or a specific multiplayer mode (eg: hoard vs beast.) ModeID = 0 means undefined or unknown.
252The intent is to answer the question "How are players playing your game?"
253*/
254INT CSentientManager::GetMode(DWORD dwUserId)
255{
256 INT mode = (INT)eTelem_ModeId_Undefined;
257
258 Minecraft *pMinecraft = Minecraft::GetInstance();
259
260 if( pMinecraft->localplayers[dwUserId] != NULL && pMinecraft->localplayers[dwUserId]->level != NULL && pMinecraft->localplayers[dwUserId]->level->getLevelData() != NULL )
261 {
262 GameType *gameType = pMinecraft->localplayers[dwUserId]->level->getLevelData()->getGameType();
263
264 if (gameType->isSurvival())
265 {
266 mode = (INT)eTelem_ModeId_Survival;
267 }
268 else if (gameType->isCreative())
269 {
270 mode = (INT)eTelem_ModeId_Creative;
271 }
272 else
273 {
274 mode = (INT)eTelem_ModeId_Undefined;
275 }
276 }
277 return mode;
278}
279
280/*
281Used when a title has more heirarchy required.
282OptionalSubMode ID = 0 means undefined or unknown.
283For titles that have sub-modes (Sports/Football).
284Mode is always an indicator of "How is the player choosing to play my game?" so these do not have to be consecutive.
285LevelIDs and SubLevelIDs can be reused as they will always be paired with a Mode/SubModeID, Mode should be unique - SubMode can be shared between modes.
286*/
287INT CSentientManager::GetSubMode(DWORD dwUserId)
288{
289 INT subMode = (INT)eTelem_SubModeId_Undefined;
290
291 if(Minecraft::GetInstance()->isTutorial())
292 {
293 subMode = (INT)eTelem_SubModeId_Tutorial;
294 }
295 else
296 {
297 subMode = (INT)eTelem_SubModeId_Normal;
298 }
299
300 return subMode;
301}
302
303/*
304This is a more granular view of mode, allowing teams to get a sense of the levels or maps players are playing and providing some insight into how players progress through a game.
305Teams will have to provide the game mappings that correspond to the integers.
306The intent is that a level is highest level at which modes can be dissected and provides an indication of player progression in a game.
307The intent is that level start and ends do not occur more than every 2 minutes or so, otherwise the data reported will be difficult to understand.
308Levels are unique only within a given modeID - so you can have a ModeID =1, LevelID =1 and a different ModeID=2, LevelID = 1 indicate two completely different levels.
309LevelID = 0 means undefined or unknown.
310*/
311INT CSentientManager::GetLevelId(DWORD dwUserId)
312{
313 INT levelId = (INT)eTelem_LevelId_Undefined;
314
315 levelId = (INT)eTelem_LevelId_PlayerGeneratedLevel;
316
317 return levelId;
318}
319
320/*
321Used when a title has more heirarchy required. OptionalSubLevel ID = 0 means undefined or unknown.
322For titles that have sub-levels.
323Level is always an indicator of "How far has the player progressed." so when possible these should be consecutive or at least monotonically increasing.
324LevelIDs and SubLevelIDs can be reused as they will always be paired with a Mode/SubModeID
325*/
326INT CSentientManager::GetSubLevelId(DWORD dwUserId)
327{
328 INT subLevelId = (INT)eTelem_SubLevelId_Undefined;
329
330 Minecraft *pMinecraft = Minecraft::GetInstance();
331
332 if(pMinecraft->localplayers[dwUserId] != NULL)
333 {
334 switch(pMinecraft->localplayers[dwUserId]->dimension)
335 {
336 case 0:
337 subLevelId = (INT)eTelem_SubLevelId_Overworld;
338 break;
339 case -1:
340 subLevelId = (INT)eTelem_SubLevelId_Nether;
341 break;
342 case 1:
343 subLevelId = (INT)eTelem_SubLevelId_End;
344 break;
345 };
346 }
347
348 return subLevelId;
349}
350
351/*
352Build version of the title, used to track changes in development as well as patches/title updates
353Allows developer to separate out stats from different builds
354*/
355INT CSentientManager::GetTitleBuildId()
356{
357 return (INT)VER_PRODUCTBUILD;
358}
359
360/*
361Generated by the game every time LevelStart or LevelResume is called.
362This should be a unique ID (can be sequential) within a session.
363Helps differentiate level attempts when a play plays the same mode/level - especially with aggregated stats
364*/
365INT CSentientManager::GetLevelInstanceID()
366{
367 return (INT)m_levelInstanceID;
368}
369
370/*
371MultiplayerinstanceID is a title-generated value that is the same for all players in the same multiplayer session.
372Link up players into a single multiplayer session ID.
373*/
374INT CSentientManager::GetMultiplayerInstanceID()
375{
376 return m_multiplayerInstanceID;
377}
378
379INT CSentientManager::GenerateMultiplayerInstanceId()
380{
381 FILETIME SystemTimeAsFileTime;
382 GetSystemTimeAsFileTime( &SystemTimeAsFileTime );
383
384 return *((INT *)&SystemTimeAsFileTime.dwLowDateTime);
385}
386
387void CSentientManager::SetMultiplayerInstanceId(INT value)
388{
389 m_multiplayerInstanceID = value;
390}
391
392/*
393Indicates whether the game is being played in single or multiplayer mode and whether multiplayer is being played locally or over live.
394How social is your game? How do people play it?
395*/
396INT CSentientManager::GetSingleOrMultiplayer()
397{
398 INT singleOrMultiplayer = (INT)eSen_SingleOrMultiplayer_Undefined;
399
400 // Unused
401 //eSen_SingleOrMultiplayer_Single_Player
402 //eSen_SingleOrMultiplayer_Multiplayer_Live
403
404 if(app.GetLocalPlayerCount() == 1 && g_NetworkManager.GetOnlinePlayerCount() == 0)
405 {
406 singleOrMultiplayer = (INT)eSen_SingleOrMultiplayer_Single_Player;
407 }
408 else if(app.GetLocalPlayerCount() > 1 && g_NetworkManager.GetOnlinePlayerCount() == 0)
409 {
410 singleOrMultiplayer = (INT)eSen_SingleOrMultiplayer_Multiplayer_Local;
411 }
412 else if(app.GetLocalPlayerCount() == 1 && g_NetworkManager.GetOnlinePlayerCount() > 0)
413 {
414 singleOrMultiplayer = (INT)eSen_SingleOrMultiplayer_Multiplayer_Live;
415 }
416 else if(app.GetLocalPlayerCount() > 1 && g_NetworkManager.GetOnlinePlayerCount() > 0)
417 {
418 singleOrMultiplayer = (INT)eSen_SingleOrMultiplayer_Multiplayer_Both_Local_and_Live;
419 }
420
421 return singleOrMultiplayer;
422}
423
424/*
425An in-game setting that differentiates the challenge imposed on the user.
426Normalized to a standard 5-point scale. Are players changing the difficulty?
427*/
428INT CSentientManager::GetDifficultyLevel(INT diff)
429{
430 INT difficultyLevel = (INT)eSen_DifficultyLevel_Undefined;
431
432 switch(diff)
433 {
434 case 0:
435 difficultyLevel = (INT)eSen_DifficultyLevel_Easiest;
436 break;
437 case 1:
438 difficultyLevel = (INT)eSen_DifficultyLevel_Easier;
439 break;
440 case 2:
441 difficultyLevel = (INT)eSen_DifficultyLevel_Normal;
442 break;
443 case 3:
444 difficultyLevel = (INT)eSen_DifficultyLevel_Harder;
445 break;
446 }
447
448 // Unused
449 //eSen_DifficultyLevel_Hardest = 5,
450
451 return difficultyLevel;
452}
453
454/*
455Differentiates trial/demo from full purchased titles
456Is this a full title or demo?
457*/
458INT CSentientManager::GetLicense()
459{
460 INT license = eSen_License_Undefined;
461
462 if(ProfileManager.IsFullVersion())
463 {
464 license = (INT)eSen_License_Full_Purchased_Title;
465 }
466 else
467 {
468 license = (INT)eSen_License_Trial_or_Demo;
469 }
470 return license;
471}
472
473/*
474This is intended to capture whether players played using default control scheme or customized the control scheme.
475Are players customizing your controls?
476*/
477INT CSentientManager::GetDefaultGameControls()
478{
479 INT defaultGameControls = eSen_DefaultGameControls_Undefined;
480
481 // Unused
482 //eSen_DefaultGameControls_Custom_controls
483
484 defaultGameControls = eSen_DefaultGameControls_Default_controls;
485
486 return defaultGameControls;
487}
488
489/*
490Are players changing default audio settings?
491This is intended to capture whether players are playing with or without volume and whether they make changes from the default audio settings.
492*/
493INT CSentientManager::GetAudioSettings(DWORD dwUserId)
494{
495 INT audioSettings = (INT)eSen_AudioSettings_Undefined;
496
497 if(dwUserId == ProfileManager.GetPrimaryPad())
498 {
499 BYTE volume = app.GetGameSettings(dwUserId,eGameSetting_SoundFXVolume);
500
501 if(volume == 0)
502 {
503 audioSettings = (INT)eSen_AudioSettings_Off;
504 }
505 else if(volume == DEFAULT_VOLUME_LEVEL)
506 {
507 audioSettings = (INT)eSen_AudioSettings_On_Default;
508 }
509 else
510 {
511 audioSettings = (INT)eSen_AudioSettings_On_CustomSetting;
512 }
513 }
514 return audioSettings;
515}
516
517/*
518Refers to the highest level performance metric for your game.
519For example, a performance metric could points earned, race time, total kills, etc.
520This is entirely up to you and will help us understand how well the player performed, or how far the player progressed �in the level before exiting.
521How far did users progress before failing/exiting the level?
522*/
523INT CSentientManager::GetLevelExitProgressStat1()
524{
525 // 4J Stu - Unused
526 return 0;
527}
528
529/*
530Refers to the highest level performance metric for your game.
531For example, a performance metric could points earned, race time, total kills, etc.
532This is entirely up to you and will help us understand how well the player performed, or how far the player progressed �in the level before exiting.
533How far did users progress before failing/exiting the level?
534*/
535INT CSentientManager::GetLevelExitProgressStat2()
536{
537 // 4J Stu - Unused
538 return 0;
539}