Experimental stuff for now. Don't think too much about it. Branched off Rabi-Ribi Autosplitter.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rabiribi-display/rabi_splitter_WPF/RabiRibiDisplay.cs

406 lines
16 KiB

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<int>(snapshot.bossList.Select(bossStats => bossStats.id));
var prevBosses = new HashSet<int>(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<int>(baseArrayPtr + i, false);
float value_float = memoryHelper.GetMemoryValue<float>(baseArrayPtr + i, false);
entityStatsList[index].IntVal = value_int;
entityStatsList[index].FloatVal = value_float;
}
}
}
private void UpdateDebugArea(MemoryHelper memoryHelper)
{
int ptr = snapshot.entityArrayPtr;
// List<int> bosses = new List<int>();
// List<int> HPS = new List<int>();
// 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<int>(
ptr + StaticData.EnenyEnitiyIDOffset[mainContext.veridx], false);
debugContext.BossList[i].BossHP = memoryHelper.GetMemoryValue<int>(
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");
}
}
}