Created
November 6, 2021 00:08
-
-
Save bladecoding/0036ba135d3bb53193ef04798d29b15d to your computer and use it in GitHub Desktop.
https://raw.githubusercontent.com/Krakenos/autosplitters/master/isaac/isaac-repentance.asl updated to automatically find checkpoint and reset item ids
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// AutoSplitter for The Binding of Isaac: Repentance | |
// Updated by Krakenos | |
// Original code by Hyphen-ated | |
// Checkpoint code & pointer annotations by blcd/Zamiel | |
state("isaac-ng_ v1.06.J820", "1.06.J820") | |
{ | |
// 0x0078A46C - GlobalsPtr | |
int wins: 0x0078A46C, 0xE40; | |
int character: 0x0078A46C, 0x108C58; | |
int winstreak: 0x0078A46C, 0x31C; | |
int frameCounter: 0x0078A46C, 0x4899C; | |
int itemConfigsStart: 0x0078A46C, 0x28714; | |
int itemConfigsEnd: 0x0078A46C, 0x28718; | |
// 0x0078A454 - GamePtr (which is the same thing as the Lua "game" pointer) | |
int timer: 0x0078A454, 0x2027B0; | |
int floor: 0x0078A454, 0x0; | |
int curse: 0x0078A454, 0xC; | |
int itemCountsArray: 0x0078A454, 0x1B710, 0x0, 0x15D0; | |
// Equivalent Lua: Game():GetPlayer(0):GetCollectibleNum(x) | |
// 0x1B710 - PlayerVectorPtr | |
// 0x0 - Player1 | |
// 0x15D0 - Player1 CollectibleNum Vector Ptr | |
} | |
startup | |
{ | |
settings.Add("character_run", true, "Multi-character run"); | |
settings.SetToolTip("character_run", "Disables auto-resetting when you're past the first split."); | |
settings.Add("racing_plus_custom_challenge", false, "You're using the Racing+ custom challenge for multi-character runs", "character_run"); | |
settings.Add("floor_splits", false, "Split on floors"); | |
settings.Add("grouped_floors", false, "Combine Basement, Caves, Depths, and Womb into one split each", "floor_splits"); | |
settings.Add("blck_cndl", false, "You're using the \"BLCK CNDL\" seed (the \"Total Curse Immunity\" Easter Egg) or using the Racing+ mod (which disables curses)", "floor_splits"); | |
} | |
init | |
{ | |
vars.timerDuringFloorChange = 0; | |
vars.runStartFrame = 0; | |
// Checkpoint is a custom item planted at the end of a run in the Racing+ mod | |
vars.checkPointName = "Checkpoint"; | |
vars.checkPointId = -1; | |
vars.oldCheckPointCount = 0; | |
// Reset is a custom item used by the Racing+ mod to signal the AutoSplitter that the mod is sending the player back to the first character | |
vars.resetName = "Reset"; | |
vars.resetId = -1; | |
vars.oldResetCount = 0; | |
} | |
isLoading | |
{ | |
// In order to sync gametime without any real time estimations this has to return true always. | |
return true; | |
} | |
gameTime | |
{ | |
double fps = 1.0/60.0; | |
int elapsedFrames = current.frameCounter - vars.runStartFrame; | |
double seconds = Convert.ToDouble(elapsedFrames) * fps; | |
double nanosecondTicks = seconds * 10000000; | |
long ticks = (long)nanosecondTicks; | |
TimeSpan time = new TimeSpan(ticks); | |
return time; | |
} | |
update | |
{ | |
//print("wins: " + current.wins + ", floor: " + current.floor + ", character: " + current.character + ", timer: " + current.timer + ", curse: " + current.curse); | |
//print("cpCount: " + current.cpCount); | |
} | |
start | |
{ | |
if (old.timer == 0 && current.timer != 0) | |
{ | |
vars.timerDuringFloorChange = 0; | |
vars.runStartFrame = current.frameCounter; | |
// Get checkpoint/reset item ids | |
if (settings["racing_plus_custom_challenge"]) { | |
vars.checkPointId = -1; | |
vars.oldCheckPointCount = 0; | |
vars.resetId = -1; | |
vars.oldResetCount = 0; | |
var start = current.itemConfigsStart; | |
var end = current.itemConfigsEnd; | |
var len = end - start; | |
var itemCount = len / 0x4; | |
var itemConfigPtrs = memory.ReadBytes(new IntPtr(start), (int)len); | |
// Walk backwards since the modded items are closer to the end | |
for (int i = itemCount - 1; i >= 0 && (vars.checkPointId == -1 || vars.resetId == -1); --i) { | |
var itemConfigPtr = BitConverter.ToInt32(itemConfigPtrs, i * 0x4); | |
if (itemConfigPtr == 0) | |
continue; | |
/** | |
struct ItemConfig { | |
int type; | |
int id; | |
// "std::string name" expanded out for clarity. | |
struct std_string { | |
union { | |
char c_str[16]; // Used when name plus null-term can fit in 16 bytes. | |
void *c_str_ptr; // Used when name plus null-term exceeds 16 bytes | |
}; | |
int c_str_len; | |
int c_str_capacity; | |
} name; | |
} | |
*/ | |
var itemConfig = memory.ReadBytes(new IntPtr(itemConfigPtr), 0x20); | |
var strLen = BitConverter.ToInt32(itemConfig, 0x18); | |
if (strLen != vars.checkPointName.Length && strLen != vars.resetName.Length) | |
continue; | |
var str = Encoding.ASCII.GetString(itemConfig, 0x8, strLen); | |
if (str == vars.checkPointName) | |
vars.checkPointId = BitConverter.ToInt32(itemConfig, 0x4); | |
else if (str == vars.resetName) | |
vars.resetId = BitConverter.ToInt32(itemConfig, 0x4); | |
} | |
print("checkPointId: " + vars.checkPointId); | |
print("resetId: " + vars.resetId); | |
if (vars.checkPointId == -1 || vars.resetId == -1) | |
return false; | |
} | |
return true; | |
} | |
} | |
reset | |
{ | |
// old.timer is 0 immediately during a reset, and also when you're on the main menu | |
// this "current.timer < 10" is to stop a reset from happening when you s+q. | |
// (unless you s+q during the first 1/3 second of the run, but why would you) | |
if (old.timer == 0 && current.timer != 0 && current.timer < 10 | |
&& (!settings["character_run"] || timer.CurrentSplitIndex == 0)) | |
{ | |
vars.timerDuringFloorChange = 0; | |
return true; | |
} | |
if (settings["racing_plus_custom_challenge"] && vars.resetId != -1) { | |
var oldResetCount = vars.oldResetCount; | |
var curResetCount = memory.ReadValue<int>(new IntPtr(current.itemCountsArray + (vars.resetId * 0x4))); | |
vars.oldResetCount = curResetCount; | |
if (curResetCount == 1 && oldResetCount != 1 && current.floor != 0) | |
return true; | |
} | |
} | |
split | |
{ | |
if (current.wins == old.wins + 1) | |
{ | |
return true; | |
} | |
if (settings["racing_plus_custom_challenge"] && vars.checkPointId != -1) { | |
var oldCpCount = vars.oldCheckPointCount; | |
var curCpCount = memory.ReadValue<int>(new IntPtr(current.itemCountsArray + (vars.checkPointId * 0x4))); | |
vars.oldCheckPointCount = curCpCount; | |
if (curCpCount == 1 && oldCpCount != 1 && current.floor != 0) | |
return true; | |
} | |
if (settings["floor_splits"]) | |
{ | |
if (current.floor > old.floor && current.floor > 1 && old.floor > 0 | |
&& (!settings["grouped_floors"] || (current.floor != 2 && current.floor != 4 && current.floor != 6 && current.floor != 8))) | |
{ | |
// when using floor splits, if they just got into an xl floor, we are going to doublesplit | |
vars.timerDuringFloorChange = current.timer; | |
return true; | |
} | |
if (vars.timerDuringFloorChange != -1 | |
&& current.timer > vars.timerDuringFloorChange) | |
{ | |
vars.timerDuringFloorChange = -1; | |
// if they're in blck_cndl mode, there is no xl even if the xl curse looks like it's on | |
// similarly, with grouped floors, there's no split to skip | |
if (current.curse == 2 && !settings["blck_cndl"] && !settings["grouped_floors"]) | |
{ | |
var model = new TimerModel { CurrentState = timer }; | |
model.SkipSplit(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment