/* The following variables may be observed (e.g., with ASL Var Viewer) during the speedrun: current.dreams/old.dreams, for the number of dream pieces collected current.book/old.book (1 or 2), for when you have the book open */ state("dream2") { int level1 : 0x186BFC, 0x2694; //usually shows chapter number; shows random (?) value on main menu, shows 0 in some instances (e.g., opening book) int level2 : 0x186C00, 0x2694; //May or may not be used; this description and the above description may need to be swapped int cutscene : 0x1B1598, 0x3C8; //1 if in a cutscene, 0 when it ends, other value randomly (?) otherwise; 0x186C24, 0xC is also valid int timerchange : 0x186BFC, 0x0, 0x30; //0 when timer1 is invalid (i.e. when going into or out of the menu) int timer1 : 0x186BFC, 0x0, 0x30, 0x8; //Resets only when going to the main menu, so we need to axe it accordingly int timer2 : 0x186C3C, 0x3C, 0x30, 0x8; //Sometimes points to the same place as timer1, sometimes points to a new timer in certain situations int timer3 : 0x186C24, 0x8; //Time elapsed since game was launched int book1 : 0x186C3C, 0xC, 0x44; //1 when book is open, 0 when not int book2 : 0x186C18, 0x0, 0x3A4; //Backup pointer; might never be needed int rootpot : 0x186BFC, 0x210, 0x28, 0x124; //In chapter 25: set from 1 to 0 when using the root potion, which triggers the end of the run int menu : 0x186C00; //0 in the menu, non-0 otherwise int dreams : 0x186BA8, 0x60; //Number of dream pieces collected int loading : 0x187BA0; //Increases while in loading screen } startup { settings.Add("exploredMerrow", false, "If starting in chapter 11 or 12/13: other is already done?"); settings.SetToolTip("exploredMerrow", "If you start in the watermill, have you already been upstairs; or, if you start in Merrow's house entryway/upstairs, have you already been to the watermill"); settings.Add("explored25", true, "If startng in chapter 25: entered the maze yet?"); settings.SetToolTip("explored25", "If you start below ground, have you already entered the Eternal Maze"); settings.Add("split0", true, "Split for First Cutscene"); settings.SetToolTip("split0", "\"I had a dream...\""); settings.Add("split1", true, "Split for Chapter 1"); settings.SetToolTip("split1", "Stone chamber with the book"); settings.Add("split2", true, "Split for Chapter 2"); settings.SetToolTip("split2", "Tree of Truth; Chamber of Fauna and Flora"); settings.Add("split3", true, "Split for Chapter 3"); settings.SetToolTip("split3", "Water chamber"); settings.Add("split4", true, "Split for Second Cutscene"); settings.SetToolTip("split4", "\"Aeval, my husband's mother..."); settings.Add("split5", true, "Split for Chapter 4"); settings.SetToolTip("split5", "\"Stonehenge; first room of the Ancients' Place"); settings.Add("split6", true, "Split for Chapter 5"); settings.SetToolTip("split6", "Print Stone Machine"); settings.Add("split7", true, "Split for Chapter 6"); settings.SetToolTip("split7", "Crystal Print Machine"); settings.Add("split8", true, "Split for Chapter 7"); settings.SetToolTip("split8", "Waterfal"); settings.Add("split9", true, "Split for Chapter 8"); settings.SetToolTip("split9", "Door leading to the Mortals\' Maze"); settings.Add("split10", true, "Split for Chapter 9"); settings.SetToolTip("split10", "The Mortals\' Maze"); settings.Add("split11", true, "Split for Third Cutscene"); settings.SetToolTip("split11", "\"Finally I am back...\""); settings.Add("split12", true, "Split for Chapter 10"); settings.SetToolTip("split12", "Courtyard of Merrow\'s Cottage"); settings.Add("split13", true, "Split for Chapter 11"); settings.SetToolTip("split13", "Watermill"); settings.Add("split14", true, "Split for Chapter 12"); settings.SetToolTip("split14", "Entrance of Merrow\'s Cottage"); settings.Add("split15", true, "Split for Chapter 13"); settings.SetToolTip("split15", "Upstairs; gramophone room"); settings.Add("split16", true, "Split for Chapter 14"); settings.SetToolTip("split16", "Downstairs; laboratory"); settings.Add("split17", true, "Split for Fourth Cutscene"); settings.SetToolTip("split17", "\"Merrow is guiding...\""); settings.Add("split18", true, "Split for Chapter 15"); settings.SetToolTip("split18", "Dock"); settings.Add("split19", true, "Split for Chapter 16"); settings.SetToolTip("split19", "Across the water; first visit to the Eternal Tree"); settings.Add("split20", true, "Split for Chapter 17"); settings.SetToolTip("split20", "Doors to the Tower of Dreams"); settings.Add("split21", true, "Split for Chapter 18"); settings.SetToolTip("split21", "Organ"); settings.Add("split22", true, "Split for Chapter 19"); settings.SetToolTip("split22", "Library"); settings.Add("split23", true, "Split for Chapter 20"); settings.SetToolTip("split23", "Music"); settings.Add("split24", true, "Split for Chapter 21"); settings.SetToolTip("split24", "Chess"); settings.Add("split25", true, "Split for Chapter 22"); settings.SetToolTip("split25", "Knights"); settings.Add("split26", true, "Split for Chapter 23"); settings.SetToolTip("split26", "Bed"); settings.Add("split27", true, "Split for Fifth Cutscene"); settings.SetToolTip("split27", "\"I close my eyes...\""); settings.Add("split28", true, "Split for Chapter 24"); settings.SetToolTip("split28", "Second visit to the Eternal Tree"); settings.Add("split29", true, "Split for Chapter 25"); settings.SetToolTip("split29", "Giving the root potion from the Eternal Maze to Fidget"); vars.loadingChapters = new HashSet() {1, 2, 3, 9, 14, 15, 17, 23, 25}; //Contains chapter numbers that precede a loading screen (25 pre-maze has a loading screen into 25 maze/post-maze) vars.cutsceneChapters = new HashSet() {4, 11, 17, 27}; //Ironically, its only use is to counteract a certain timing bug where the IGT continues to increment for one frame after the cutscene starts vars.resetAll = (Action)(() => { vars.loadingTimes = new int[] {0, 0x8, 0x8, 0x18, 0x16, 0x16, 0x16, 0x16, 0x16, 0x12, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0xE, 0x16, 0x16, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1E, 0x1E};//How much the loading variable will increase by from the menu to the given chaptern number; if settings["explored25"] is true and applicable, vars.loadingTimes[25] = 0x12 vars.loadingOldVal = 0; //Syncs with current.loading during loading screen from menu vars.loadingOldTime = 0; //Syncs with current.timer1 during loading screens of various kinds vars.loadingOldTime2 = 0; //Used for loading to the main menu vars.startTime = 0; //Set and continually adjusted as necessary such that current.timer1 minus vars.startTime is the number of IGT milliseconds elapsed vars.bookAddTime = 0; //Ditto, but added to current.timer2 when Aeval's diary is open vars.merrowSwap = -1; //Set to 1 if the player enters Merrow's house before his watermill, or if the player loads chapter 11 with settings[exploredMerrow] checked/chapter 12 or 13 with settings[exploredMerrow] unchecked; set to 0 if the player enters Merrow's house after his watermill, or if the player loads chapter 11 with settings[exploredMerrow] unchecked/chapter 12 or 13 with settings[exploredMerrow] checked vars.latestLevel = 0; //Set to whichever chapter the player loads into, and then incremented whenever the player reaches a higher level; if vars.merrowSwap is set to 1, then this will display 11 in chapter 12, 12 in chapter 13, and 13 in chapter 11 vars.starting = 3; //3 when the timer starts, 2 if in menu with timer already started, 1 when vars.splitsIndex has been established, 0 when loading is done vars.justSplit = false; //true if a split is to be performed, false in all other cycles vars.splitsIndices = Enumerable.Range(0,30).ToArray(); //For the most part, vars.splitsIndices[x] == x; the exception is if vars.merrowSwap = 1 vars.splitsIndex = -1; //0-29 for which entry in vars.splits should be evaluated, -1 when it's not yet known vars.splitsIndicesReverse = new int[] {0,0,2,3,4,6,7,8,9,10,11,13,14,15,16,17,19,20,21,22,23,24,25,26,27,29}; //Maps from chapter number to split number }); vars.resetAll(); vars.splits = new Func[] { /*S1*/ (cur, prev) => (cur.cutscene == 0), /*C1*/ (cur, prev) => (cur.level1 == 2), /*C2*/ (cur, prev) => (cur.level1 == 3), /*C3*/ (cur, prev) => (cur.level1== 4), /*S2*/ (cur, prev) => (cur.cutscene == 0), /*C4*/ (cur, prev) => (cur.level1== 5), /*C5*/ (cur, prev) => (cur.level1== 6), /*C6*/ (cur, prev) => (cur.level1== 7), /*C7*/ (cur, prev) => (cur.level1== 8), /*C8*/ (cur, prev) => (cur.level1== 9), /*C9*/ (cur, prev) => (cur.level1== 10), /*S3*/ (cur, prev) => (cur.cutscene == 0), /*C10*/ (cur, prev) => (cur.level1== 11), /*C11*/ (cur, prev) => (cur.level1== 12), /*C12*/ (cur, prev) => (cur.level1== 13), /*C13*/ (cur, prev) => (cur.level1== 14), /*C14*/ (cur, prev) => (cur.level1== 15), /*S4*/ (cur, prev) => (cur.cutscene == 0), /*C15*/ (cur, prev) => (cur.level1== 16), /*C16*/ (cur, prev) => (cur.level1== 17), /*C17*/ (cur, prev) => (cur.level1== 18), /*C18*/ (cur, prev) => (cur.level1== 19), /*C19*/ (cur, prev) => (cur.level1== 20), /*C20*/ (cur, prev) => (cur.level1== 21), /*C21*/ (cur, prev) => (cur.level1== 22), /*C22*/ (cur, prev) => (cur.level1== 23), /*C23*/ (cur, prev) => (cur.level1== 24), /*S5*/ (cur, prev) => (cur.cutscene == 0), /*C24*/ (cur, prev) => (cur.level1== 25), /*c25*/ (cur, prev) => (cur.level1== 25 && cur.rootpot == 0 && prev.rootpot == 1) }; } start { if (current.menu != 0 && old.menu == 0) { return true; } } onStart { } update { vars.justSplit = false; //Coming in from the game to the menu if (old.menu != 0 && current.timer1 < old.timer1) { vars.loadingOldTime = vars.loadingOldTime2; vars.starting = 2; } if (current.menu == 0) { return; } //Time within Aeval's diary if (current.book1 == 1 && old.book1 == 0) { vars.bookAddTime = current.timer1 - vars.startTime - current.timer2; } else if (current.book1 == 0 && old.book1 == 1) { vars.startTime -= old.timer2; } //Grabbing time from the main menu if (vars.starting >= 2 && current.menu != 0 && old.menu == 0) { vars.loadingOldVal = current.loading; } //Determining which split the run is starting on if (vars.starting >= 2 && current.menu != 0 && current.level1 > 0 && current.level1 <=25 && current.level1 == current.level2) { if (vars.starting == 3) { vars.splitsIndex = vars.splitsIndicesReverse[current.level1]; } if (settings["explored25"] && current.level1 == 25) { vars.loadingTimes[25] = 0x12; } else { vars.loadingTimes[25] = 0x1E; } vars.starting = 1; } //Swapping 11 with 12&13, if necessary if (vars.starting <= 1 && vars.merrowSwap == -1 && current.level1 >= 11 && current.level1 <= 13) { if ((vars.starting == 1 && (settings["exploredMerrow"] != (current.level1 == 11))) || (vars.starting == 0 && current.level1 == 11)) { print("Decided on stay"); vars.merrowSwap = 0; //This vars.splitsIndex => this test in vars.splits vars.splitsIndices[12] = 12; vars.splitsIndices[13] = 13; vars.splitsIndices[14] = 14; //Start in this chapter => set this as vars.splitsIndex vars.splitsIndicesReverse[11] = 13; vars.splitsIndicesReverse[12] = 14; vars.splitsIndicesReverse[13] = 15; } else if ((vars.starting == 1 && (settings["exploredMerrow"] == (current.level1 == 11))) || (vars.starting == 0 && current.level1 == 12)) { print("Decided on swap"); vars.merrowSwap = 1; vars.splitsIndices[12] = 13; vars.splitsIndices[13] = 14; vars.splitsIndices[14] = 12; vars.splitsIndicesReverse[11] = 15; vars.splitsIndicesReverse[12] = 13; vars.splitsIndicesReverse[13] = 14; } } //Splitting if (vars.starting == 0) { current.splitTest = vars.splitsIndices[vars.splitsIndex]; if (vars.splits[current.splitTest](current,old)) { vars.justSplit = true; vars.splitsIndex ++; } } //Potentially calculating the offset from previous time after loading screen from another chapter: making the calculation, or identifying the beginning of the offset, or correcting the calculation (the timer will sometimes advance between loading and a cutscene) if (vars.starting == 0 && current.loading != old.loading) { if (vars.justSplit && vars.loadingChapters.Contains(old.level1)) { vars.startTime += (current.timer1 - vars.loadingOldTime); vars.loadingOldTime = current.timer1; } else if (vars.loadingChapters.Contains(current.level1)) { vars.loadingOldTime = current.timer1; } } else if (vars.justSplit && vars.cutsceneChapters.Contains(vars.splitsIndex)) { vars.startTime += (current.timer1 - vars.loadingOldTime); } //Potentially calculating the offset from previous time after loading from menu if (vars.starting == 0 && old.timer1 != old.timer2 && current.timer1 == current.timer2) { vars.loadingOldTime2 = old.timer1 - vars.startTime; } if (vars.starting == 1 && ((vars.loadingTimes[current.level1] <= old.loading - vars.loadingOldVal) || (0x4 >= old.loading - vars.loadingOldVal && old.level1 == 1))) { vars.startTime = (current.timer1 - vars.loadingOldTime); vars.starting = 0; } } isLoading { return true; } gameTime { if (vars.starting != 0) { return TimeSpan.FromMilliseconds(vars.loadingOldTime); } else if (current.book1 == 1 && old.book1 == 1) { return TimeSpan.FromMilliseconds(current.timer2 + vars.bookAddTime); } else { return TimeSpan.FromMilliseconds(current.timer1 - vars.startTime); } } split { return (vars.justSplit && settings["split" + current.splitTest] && vars.starting == 0); } reset { if (current.menu == 0) { return true; } } onReset { vars.resetAll(); }