/*
 * KeyWe AutoSplitter for LiveSplit
 * 
 * Designed to work from a save file starting from level 0
 * Splits on completion of every level.
 *
 * Start: When the level is selected from the calendar
 * Split: When the DONE button is pressed on the level-end screen
 * Reset: When a different save file is loaded (including deleting a save file)
 * 
 * Pauses the in game timer if any of the following conditions are met:
 *  - after a level ends
 *  - during the loading screen that appears after selecting a level from the calendar
 *  - if the in-level timer is not running
 *  - when no level is selected
 *
 * The in-game timer is resumed when a level is selected and has loaded.
 * The in-game timer is recalculated off sum-of-levels when splitting - they may appear to rewind at split time to cut out times the timer was incorrectly running.
 *
 * Issues:
 *  - segment times may round incorrectly? Uncertain what method the in-game timer uses to round - seemed to be truncation in some cases, but not in others...
 *  - does not count time spent in a level that is restarted
 *
 * TODO
 * Options to choose when splitting happens
 * Can we tell if we've finished a level with a better check than old.currentLevelIndex < current.currentLevelIndex ? (Allows for timing non-fresh runs)
 */


/* Finding the pointers
 * In CheatEngine, classes can be found in the Mono Dissector under Assembly-CSharp
 * Search for the GameFlowController class and check the offset of the dataKeeper field (Currently: 0x28)
 * Search for the DataKeeper class and check for the offset of the profile field (Currently: 0x68)
 * Scan for instances of the DataKeeper class
 * 
 * Determine which of the objects is the most plausible DataKeeper in use. A good way to do this is to observe the currentState value:
 * currentState enum values
 * 	1 - Not in a level
 * 	2 - In a timed level (including loading the level and viewing its results. Stays in this state if choosing "Restart" on results screen)
 * 	3 - In an untimed level (eg overtime shift)
 * 	4 - Playing a cutscene
 * 
 * In the pointer search, search for the pointer of the DataKeeper.
 * Filtering by the last pointer being the offset from the GameFlowController class can be useful
 * Repeat this process, filtering the results of the pointer scanner after restarting the game multiple times
*/

state("KeyWe") {
	// ProfileData
	ushort currentLevelIndex : 	"UnityPlayer.dll", 0x0180E7F8, 0x128, 0x88, 0x30, 0x28, 0x28, 0x68, 0x62;
	ushort playthroughId : 		"UnityPlayer.dll", 0x0180E7F8, 0x128, 0x88, 0x30, 0x28, 0x28, 0x68, 0x66;
	// int levelRecords: 			"UnityPlayer.dll", 0x0180E7F8, 0x128, 0x88, 0x30, 0x28, 0x28, 0x68, 0x10;

	// LevelLoader
	bool isLevelLoading:		"UnityPlayer.dll", 0x0180E7F8, 0x128, 0x88, 0x30, 0x38, 0x28, 0xab;
	bool isFromRestart:		    "UnityPlayer.dll", 0x0180E7F8, 0x128, 0x88, 0x30, 0x38, 0x28, 0xac;
	// isWaitingForPlayer may be useful for online games? Appears unused in local play
	// bool isWaitingForPlayer:	"UnityPlayer.dll", 0x0180E7F8, 0x128, 0x88, 0x30, 0x38, 0x28, 0xa9;
	
	// DataKeeper
	ushort currentState : 		"UnityPlayer.dll", 0x0180E7F8, 0x128, 0x88, 0x30, 0x28, 0x28, 0x98;
	ushort activeLevelIndex : 	"UnityPlayer.dll", 0x0180E7F8, 0x128, 0x88, 0x30, 0x28, 0x28, 0x8e;
	
	// ModeFeedback =20> ModeTimer =38> ElapsedTime
	float activeLevelTime :		"UnityPlayer.dll", 0x017CB888, 0x10, 0x100, 0x30, 0x10, 0xD0, 0x60, 0x20, 0x38;
	// alternative pointer path
	// float activeLevelTime :		"UnityPlayer.dll", 0x017CB888, 0x18, 0x100, 0x30, 0x10, 0xD0, 0x60, 0x20, 0x38;

	// Other possible paths...?
	// ...? GameMode =78> ModeTimer =38> ElapsedTime
	// ...? Timer => ModeTimer => ElapsedTime
}

startup {
	/* TODO: settings */
	settings.Add("start_anylevel", false, "Start on any level");
	settings.Add("reset_change_savefile", true, "Experimental: Reset on save file change");	
	settings.Add("track_ingame_loads", true, "Experimental: pause IGT when not in level");
}

init {
	
	timer.IsGameTimePaused = true;
	vars.isInMenu = current.currentState != 2;
	vars.hasSplit = false;

	vars.gameTime = 0.0;
	vars.extraAttemptsTime = 0.0;
}
 
exit
{
    // Pause the timer if the game is exited
	vars.isInMenu = true;
	
	// Track whether a level was just completed
	vars.levelCompleted = false;
}

update
{	

	if (current.currentLevelIndex == null) return false;
	
	// On the split screen
	if (old.currentLevelIndex < current.currentLevelIndex) {
		vars.isInMenu = true;
	}
	// Going from calendar to level
	if (old.currentState == 1 && current.currentState == 2) {
		vars.isInMenu = false;
	}
}

start
{
	if (old.currentState == 1 && current.currentState == 2) {
		if (settings["start_anylevel"]) {
			return true;
		}
		// Only start on level 0
		return current.activeLevelIndex == 0; 
	}
}

isLoading
{
	if (!settings["track_ingame_loads"]) {
		return false;
	}
	// Pause timer when there's no active level
	if (current.currentState != 2) {
		return true;
	}
	if (current.isLevelLoading) {
		return true;
	}
	return true;
}

gameTime
{
	// update game time on split
	if (old.currentLevelIndex < current.currentLevelIndex || old.playthroughId != current.playthroughId ) {
		// Recalculate all the times for all levels to be sure
		vars.gameTime = 0.0;
		for (int i = 0; i < 36; i++) {
			vars.levelTime = new DeepPointer("UnityPlayer.dll", 0x0180E7F8, 0x128, 0x88, 0x30, 0x28, 0x28, 0x68, 0x10,
			0x20 + i * 0x8, 0x2C).Deref<float>(game);

			if (vars.levelTime != -1) {
				// Truncate the level time to 2 decimal places (the value displayed in the UI)
				// vars.levelTime = Math.Round(vars.levelTime, 2, MidpointRounding.AwayFromZero);
				vars.levelTime = Math.Truncate(vars.levelTime * 100) / 100;
				vars.gameTime = vars.gameTime + vars.levelTime;
			}
		}
		return TimeSpan.FromSeconds(vars.gameTime);
	} else if (!vars.isInMenu && current.currentState == 2 && current.activeLevelTime > 0.1) {
		return TimeSpan.FromSeconds(vars.gameTime + current.activeLevelTime);
	}
	return TimeSpan.FromSeconds(vars.gameTime);
}	

reset
{ 
	if (settings["reset_change_savefile"] && old.playthroughId != current.playthroughId) {
		return true;
	}
}

split
{
	if (old.currentLevelIndex < current.currentLevelIndex) {
		vars.hasSplit = false;
		return false;
	}
	// Split when the Done button is pressed
	if (!vars.hasSplit && old.currentState == 2 && (current.currentState == 1 || current.currentState == 4)) {
		vars.hasSplit = true;
		return true;
	}

}