using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; namespace rabi_splitter_WPF { partial class RabiRibiDisplay { private MainContext mainContext; private DebugContext debugContext; private VariableExportContext variableExportContext; private MainWindow mainWindow; private RabiRibiState rabiRibiState; private InGameState inGameState; private MemorySnapshot prevSnapshot; private MemorySnapshot snapshot; // Variables used for tracking frequency of memory reads. private static readonly DateTime UNIX_START = new DateTime(1970, 1, 1); private double readFps = -1; private long previousFrameMillisecond = -1; private long lastUpdateMillisecond = -1; // internal frame counter. private int memoryReadCount; public RabiRibiDisplay(MainContext mainContext, DebugContext debugContext, VariableExportContext variableExportContext, MainWindow mainWindow) { this.rabiRibiState = new RabiRibiState(); this.mainContext = mainContext; this.debugContext = debugContext; this.variableExportContext = variableExportContext; this.mainWindow = mainWindow; this.memoryReadCount = 0; StartNewGame(); ConfigureVariableExports(); } public void ReadMemory(Process process) { ++memoryReadCount; var memoryHelper = new MemoryHelper(process); // Snapshot Game Memory snapshot = new MemorySnapshot(memoryHelper, mainContext.veridx); Update(); UpdateDebugArea(memoryHelper); UpdateEntityData(memoryHelper); UpdateFps(); UpdateVariableExport(); if (snapshot.musicid >= 0) rabiRibiState.lastValidMusicId = snapshot.musicid; prevSnapshot = snapshot; memoryHelper.Dispose(); } private void UpdateVariableExport() { variableExportContext.CheckForUpdates(); long currentFrameMillisecond = (long)(DateTime.Now - UNIX_START).TotalMilliseconds; var diff = currentFrameMillisecond - lastUpdateMillisecond; if (diff >= 1000) { if (diff >= 2000) lastUpdateMillisecond = currentFrameMillisecond; else lastUpdateMillisecond += 1000; variableExportContext.OutputUpdates(); } } private void UpdateFps() { long currentFrameMillisecond = (long)(DateTime.Now - UNIX_START).TotalMilliseconds; if (previousFrameMillisecond != -1) { double newFps = 1000.0 / (currentFrameMillisecond - previousFrameMillisecond); if (readFps < 0) readFps = newFps; else readFps = 0.9 * readFps + 0.1 * newFps; mainContext.Text19 = $"Reads Per Second:\n{readFps:.0.00}"; } previousFrameMillisecond = currentFrameMillisecond; } private void Update() { #region Game State Machine if (inGameState.CurrentActivityIs(InGameActivity.STARTING)) { // Detect start game if (0 < snapshot.playtime && snapshot.playtime < 200 || (prevSnapshot != null && prevSnapshot.playtime < snapshot.playtime && snapshot.playtime < prevSnapshot.playtime + 200)) { inGameState.currentActivity = InGameActivity.WALKING; DebugLog("IGT start?"); } } else { } #endregion #region Detect Reload bool reloading = snapshot.playtime == 0 || ((prevSnapshot != null) && (snapshot.playtime < prevSnapshot.playtime)); if (inGameState.IsGameStarted() && snapshot.playtime > 0) { if (snapshot.playtime < inGameState.lastNonZeroPlayTime) { if (InGame()) { inGameState.nRestarts++; UpdateTextFile(); } DebugLog("Reload Game! " + snapshot.playtime + " <- " + inGameState.lastNonZeroPlayTime); } inGameState.lastNonZeroPlayTime = snapshot.playtime; } #endregion #region Detect Music change if (MusicChanged() && !ValidMusicChanged()) { DebugLog($"Invalid Music Change: {StaticData.GetMusicName(prevSnapshot.musicid)} -> {StaticData.GetMusicName(snapshot.musicid)}"); } if (ValidMusicChanged()) { DebugLog($"Valid Music Change: {StaticData.GetMusicName(rabiRibiState.lastValidMusicId)} -> {StaticData.GetMusicName(snapshot.musicid)}"); } #endregion #region Detect Minimap Change if (prevSnapshot != null && (prevSnapshot.minimapPosition != snapshot.minimapPosition)) { DebugLog($"Minimap Shift! {prevSnapshot.minimapPosition} -> {snapshot.minimapPosition}"); if (snapshot.minimapPosition == 1) { var bossFight = BossFightIdentifier.IdentifyBossFight(snapshot); DebugLog($"BOSS FIGHT: {bossFight.displayName}"); DebugLog($"Fighting Bosses: {string.Join(", ", snapshot.bossList.Select(boss => StaticData.GetBossName(boss.id)))}"); inGameState.StartBossFight(bossFight); } else // snapshot.minimapPosition == 0 { if (reloading) { inGameState.StopBossFight(); } else { inGameState.FinishBossFight(); } } } #endregion #region Detect Boss Change if (prevSnapshot != null) { var currBosses = new HashSet(snapshot.bossList.Select(bossStats => bossStats.id)); var prevBosses = new HashSet(prevSnapshot.bossList.Select(bossStats => bossStats.id)); foreach (var enteringBoss in currBosses.Except(prevBosses)) { DebugLog($"Boss Enters: {StaticData.GetBossName(enteringBoss)}"); } foreach (var leavingBoss in prevBosses.Except(currBosses)) { DebugLog($"Boss Leaves: {StaticData.GetBossName(leavingBoss)}"); } } #endregion #region Detect Death if (prevSnapshot != null) { if (snapshot.hp == 0 && prevSnapshot.hp > 0) { if (InGame()) { inGameState.nDeaths++; UpdateTextFile(); } DebugLog("Death!"); } } if (prevSnapshot != null) { if (snapshot.IsDeathSprite() && !prevSnapshot.IsDeathSprite()) { if (InGame()) { inGameState.nDeathsAlt++; } DebugLog("Death (Alt)!"); } } #endregion #region Detect Start Game if (prevSnapshot != null && (snapshot.CurrentMusicIs(Music.MAIN_MENU) || snapshot.CurrentMusicIs(Music.ARTBOOK_INTRO)) && prevSnapshot.blackness == 0 && snapshot.blackness >= 100000) { // Sudden increase by 100000 DebugLog("Start Game!"); StartNewGame(); } #endregion if (prevSnapshot == null || prevSnapshot.mapid != snapshot.mapid) { DebugLog("newmap: " + snapshot.mapid + ":" + StaticData.GetMapName(snapshot.mapid)); } mainContext.Text1 = "Music: " + StaticData.GetMusicName(snapshot.musicid); mainContext.Text2 = "Map: " + StaticData.GetMapName(snapshot.mapid); mainContext.Text3 = inGameState == null ? "" : ("Deaths: " + inGameState.nDeaths// + " [" + gameState.nDeathsAlt + "]" + "\n" + "Resets: " + inGameState.nRestarts);// + " [" + gameState.nRestartsAlt + "]"); mainContext.Text4 = "HP: " + snapshot.hp + " / " + snapshot.maxhp; mainContext.Text5 = "Amulet: " + snapshot.amulet + "\n" + "Boost: " + snapshot.boost; mainContext.Text6 = "MP: " + snapshot.mana + "\n" + "SP: " + snapshot.stamina; var nextHammer = StaticData.GetNextHammerLevel(snapshot.hammerXp); var nextRibbon = StaticData.GetNextRibbonLevel(snapshot.ribbonXp); var nextCarrot = StaticData.GetNextCarrotLevel(snapshot.carrotXp); mainContext.Text7 = "Hammer: " + snapshot.hammerXp + (nextHammer == null ? "" : ("/" + nextHammer.Item1 + "\n" + "NEXT: " + nextHammer.Item2)); mainContext.Text8 = "Ribbon: " + snapshot.ribbonXp + (nextRibbon == null ? "" : ("/" + nextRibbon.Item1 + "\n" + "NEXT: " + nextRibbon.Item2)); mainContext.Text9 = "Carrot: " + snapshot.carrotXp + (nextCarrot == null ? "" : ("/" + nextCarrot.Item1 + "\n" + "NEXT: " + nextCarrot.Item2)); mainContext.Text10 = "x: " + snapshot.px + "\n" + "y: " + snapshot.py; mainContext.Text11 = "[A/H/M/P/R] ups:\n" + snapshot.nAttackUps + "/" + snapshot.nHpUps + "/" + snapshot.nManaUps + "/" + snapshot.nPackUps + "/" + snapshot.nRegenUps; mainContext.Text12 = "Entities: " + snapshot.entityArraySize + "\n" + "Active: " + snapshot.nActiveEntities; mainContext.Text13 = "Sprite: " + snapshot.GetCurrentSprite() + "\n" + "Action: " + snapshot.GetCurrentAction(); mainContext.Text14 = $"PLAYTIME: {snapshot.playtime}"; mainContext.Text15 = $"Map Tile: ({snapshot.mapTile.x}, {snapshot.mapTile.y})"; { if (inGameState.CurrentActivityIs(InGameActivity.BOSS_BATTLE)) { var time = DateTime.Now - inGameState.currentBossStartTime; mainContext.Text16 = $"Boss: {inGameState.currentBossFight.displayName}\n" + $"Time: {time:mm\\:ss\\.ff}"; } else { mainContext.Text16 = "Not in boss fight"; } if (inGameState.lastBossFight != null) { mainContext.Text17 = $"Last Boss: {inGameState.lastBossFight.displayName}\n" + $"Time: {inGameState.lastBossFightDuration:mm\\:ss\\.ff}"; } else { mainContext.Text17 = "Last Boss: None"; } } { string bosstext = "Boss Fight: " + (inGameState.currentActivity == InGameActivity.BOSS_BATTLE) + "\n"; bosstext += "Bosses: " + snapshot.bossList.Count + "\n"; foreach (var boss in snapshot.bossList) { bosstext += "[" + boss.entityArrayIndex + "] " + StaticData.GetBossName(boss.id) + ": " + boss.hp + "/" + boss.maxHp + "\n"; } mainContext.Text20 = bosstext; } } private void UpdateTextFile() { //return; string text = $"Deaths: {inGameState.nDeaths}\nResets: {inGameState.nRestarts}"; System.IO.StreamWriter file = new System.IO.StreamWriter("deaths_restarts.txt"); file.WriteLine(text); file.Close(); } private void StartNewGame() { inGameState = new InGameState(); rabiRibiState.gameStatus = GameStatus.INGAME; } private void ReturnToMenu() { rabiRibiState.gameStatus = GameStatus.MENU; inGameState = null; } private bool InGame() { return rabiRibiState.gameStatus == GameStatus.INGAME; } private bool MusicChanged() { return prevSnapshot != null && prevSnapshot.musicid != snapshot.musicid; } private bool ValidMusicChanged() { return rabiRibiState.lastValidMusicId >= 0 && snapshot.musicid >= 0 && rabiRibiState.lastValidMusicId != snapshot.musicid; } private bool MusicChangedTo(Music music) { return MusicChanged() && snapshot.CurrentMusicIs(music); } private void UpdateEntityData(MemoryHelper memoryHelper) { // Read entire entity data for specific entity { var entityStatsList = debugContext.EntityStatsListData; int entitySize = StaticData.EnenyEntitySize[mainContext.veridx]; int baseArrayPtr = snapshot.entityArrayPtr + entitySize * debugContext.EntityAnalysisIndex; int[] entitydataint = new int[entitySize / 4]; float[] entitydatafloat = new float[entitySize / 4]; debugContext.targetEntityListSize = entitySize / 4; int length = Math.Min(entitySize, entityStatsList.Count * 4); for (int i = 0; i < length; i += 4) { int index = i / 4; int value_int = memoryHelper.GetMemoryValue(baseArrayPtr + i, false); float value_float = memoryHelper.GetMemoryValue(baseArrayPtr + i, false); entityStatsList[index].IntVal = value_int; entityStatsList[index].FloatVal = value_float; } } } private void UpdateDebugArea(MemoryHelper memoryHelper) { int ptr = snapshot.entityArrayPtr; // List bosses = new List(); // List HPS = new List(); // this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => debugContext.BossList.Clear())); // ptr += StaticData.EnenyEntitySize[mainContext.veridx] * 3; for (var i = 0; i < 50; i++) { debugContext.BossList[i].BossID = memoryHelper.GetMemoryValue( ptr + StaticData.EnenyEnitiyIDOffset[mainContext.veridx], false); debugContext.BossList[i].BossHP = memoryHelper.GetMemoryValue( ptr + StaticData.EnenyEnitiyHPOffset[mainContext.veridx], false); ptr += StaticData.EnenyEntitySize[mainContext.veridx]; } debugContext.BossEvent = inGameState.currentActivity == InGameActivity.BOSS_BATTLE; } private void DebugLog(string log) { this.debugContext.Log($"[ {memoryReadCount:D8}] {log}"); } private void sendsplit() { mainWindow.SendMessage("split\r\n"); } private void sendreset() { mainWindow.SendMessage("reset\r\n"); } private void sendstarttimer() { mainWindow.SendMessage("starttimer\r\n"); } private void sendigt(float time) { mainWindow.SendMessage($"setgametime {time}\r\n"); } } }