// Game: King's Quest VI: Heir Today, Gone Tomorrow // Emulator: DOSBox ECE (any recent version, or r4230, or r4180) // // Copyright 2021 Dan Lynch // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . state("DOSBox", "35627008") { short RoomInitial : "DOSBox.exe", 0x00524670, 0x00023C4A; short ScoreInitial : "DOSBox.exe", 0x00524670, 0x00023C52; short XPositionInitial : "DOSBox.exe", 0x00524670, 0x00023DCC; short YPositionInitial : "DOSBox.exe", 0x00524670, 0x00023DCE; short RoomRestart : "DOSBox.exe", 0x00524670, 0x00023C6E; short ScoreRestart : "DOSBox.exe", 0x00524670, 0x00023C76; short XPositionRestart : "DOSBox.exe", 0x00524670, 0x00023DF0; short YPositionRestart : "DOSBox.exe", 0x00524670, 0x00023DF2; } state("DOSBox", "35631104") { short RoomInitial : "DOSBox.exe", 0x00525670, 0x00023C4A; short ScoreInitial : "DOSBox.exe", 0x00525670, 0x00023C52; short XPositionInitial : "DOSBox.exe", 0x00525670, 0x00023DCC; short YPositionInitial : "DOSBox.exe", 0x00525670, 0x00023DCE; short RoomRestart : "DOSBox.exe", 0x00525670, 0x00023C6E; short ScoreRestart : "DOSBox.exe", 0x00525670, 0x00023C76; short XPositionRestart : "DOSBox.exe", 0x00525670, 0x00023DF0; short YPositionRestart : "DOSBox.exe", 0x00525670, 0x00023DF2; } state("DOSBox", "36634624") { short RoomInitial : "DOSBox.exe", 0x00519670, 0x00023C4A; short ScoreInitial : "DOSBox.exe", 0x00519670, 0x00023C52; short XPositionInitial : "DOSBox.exe", 0x00519670, 0x00023DCC; short YPositionInitial : "DOSBox.exe", 0x00519670, 0x00023DCE; short RoomRestart : "DOSBox.exe", 0x00519670, 0x00023C6E; short ScoreRestart : "DOSBox.exe", 0x00519670, 0x00023C76; short XPositionRestart : "DOSBox.exe", 0x00519670, 0x00023DF0; short YPositionRestart : "DOSBox.exe", 0x00519670, 0x00023DF2; } state("DOSBox", "36995072") { short RoomInitial : "DOSBox.exe", 0x00571670, 0x00023C4A; short ScoreInitial : "DOSBox.exe", 0x00571670, 0x00023C52; short XPositionInitial : "DOSBox.exe", 0x00571670, 0x00023DCC; short YPositionInitial : "DOSBox.exe", 0x00571670, 0x00023DCE; short RoomRestart : "DOSBox.exe", 0x00571670, 0x00023C6E; short ScoreRestart : "DOSBox.exe", 0x00571670, 0x00023C76; short XPositionRestart : "DOSBox.exe", 0x00571670, 0x00023DF0; short YPositionRestart : "DOSBox.exe", 0x00571670, 0x00023DF2; } startup { print("KQ6AS: startup action"); settings.Add("magic_map", true, "Magic map"); settings.SetToolTip("magic_map", "Split when the genie cutscene begins after you buy the magic map"); settings.Add("gnomes", false, "Gnomes"); settings.SetToolTip("gnomes", "Split when you either fool or bypass the gnomes for the first time"); settings.Add("cliff_base", false, "Base of cliffs"); settings.SetToolTip("cliff_base", "Split when you arrive at the base of the cliffs for the first time after fooling or bypassing the gnomes"); settings.Add("cliff_top", true, "Top of cliffs"); settings.SetToolTip("cliff_top", "Split when you arrive at the top of the cliffs after solving the logic puzzles"); settings.Add("catacombs", true, "Catacombs"); settings.SetToolTip("catacombs", "Split when you leave the catacombs for the first time"); settings.Add("beast", true, "Beauty & Beast"); settings.SetToolTip("beast", "Split when the gate closes on Beauty and Beast"); settings.Add("nightmare", false, "Nightmare"); settings.SetToolTip("nightmare", "Split when you start riding nightmare to travel to the Realm of the Dead"); settings.Add("samhain", false, "Samhain"); settings.SetToolTip("samhain", "Split when you start riding nightmare to return from the Realm of the Dead"); settings.Add("castle", false, "Castle"); settings.SetToolTip("castle", "Split when you enter the castle either by disguise or magic door"); settings.Add("vizier", true, "Abdul Alhazred"); settings.SetToolTip("vizier", "Split when you land the final blow on Abdul Alhazred"); vars.completed = new HashSet(); vars.towerPoints = 0; } init { print("KQ6AS: init action"); var firstModule = modules.First(); if (firstModule.ModuleName.ToLower().Contains("dosbox")) { version = firstModule.ModuleMemorySize.ToString(); } else { throw new Exception("KQ6AS: unexpected first module name: " + firstModule.ModuleName); } vars.performSplitWithExtra = (Func) ((splitName, extra) => { if (!vars.completed.Contains(splitName)) { bool splitting = settings[splitName]; print("KQ6AS: " + (splitting ? "split at" : "skip") + " the " + splitName + " split" + (extra != null ? " (" + extra + ")" : "")); vars.completed.Add(splitName); return splitting; } else { return false; } }); vars.performSplit = (Func) ((splitName) => { return vars.performSplitWithExtra(splitName, null); }); } update { if (current.RoomInitial >= 0 && current.RoomInitial <= 1000 && current.ScoreInitial >= 0 && current.ScoreInitial <= 231 && current.XPositionInitial >= 0 && current.XPositionInitial <= 320 && current.YPositionInitial >= 0 && current.YPositionInitial <= 320) { current.Room = current.RoomInitial; current.Score = current.ScoreInitial; current.XPosition = current.XPositionInitial; current.YPosition = current.YPositionInitial; } else if (current.RoomRestart >= 0 && current.RoomRestart <= 1000 && current.ScoreRestart >= 0 && current.ScoreRestart <= 231 && current.XPositionRestart >= 0 && current.XPositionRestart <= 320 && current.YPositionRestart >= 0 && current.YPositionRestart <= 320) { current.Room = current.RoomRestart; current.Score = current.ScoreRestart; current.XPosition = current.XPositionRestart; current.YPosition = current.YPositionRestart; } else { return false; } if (!((IDictionary) old).ContainsKey("Room")) { return false; } if (current.Room != old.Room) { print("KQ6AS: moved from room " + old.Room + " to room " + current.Room); } int points = current.Score - old.Score; if (points != 0) { string location = "(" + current.XPosition + ", " + current.YPosition + ")"; print("KQ6AS: earned " + points + " point" + (Math.Abs(points) == 1 ? "" : "s") + " at " + location + " in room " + current.Room); } if (current.Room == 750 && points > 0) { print("KQ6AS: add tower points"); vars.towerPoints += points; } } start { if (old.Room == 100 && current.Room == 200 && current.Score == 0) { print("KQ6AS: start the run"); vars.completed.Clear(); vars.towerPoints = 0; return true; } } reset { if (current.Room == 100) { print("KQ6AS: reset the run"); vars.completed.Clear(); vars.towerPoints = 0; return true; } } split { switch ((int) current.Room) { case 145: if (old.Room == 280) { return vars.performSplit("magic_map"); } return false; case 155: if (old.Room == 340) { return vars.performSplit("nightmare"); } if (old.Room == 680) { return vars.performSplit("samhain"); } return false; case 300: if (vars.completed.Contains("gnomes")) { return vars.performSplit("cliff_base"); } return false; case 340: if (old.Room == 320) { return vars.performSplit("cliff_top"); } if (old.Room == 440) { return vars.performSplit("catacombs"); } return false; case 450: if (current.Score - old.Score == 2 && current.XPosition == 231 && current.YPosition == 129) { return vars.performSplitWithExtra("gnomes", "fooled them"); } return false; case 460: if (old.Room == 450) { return vars.performSplitWithExtra("gnomes", "glitched to bookworm"); } return false; case 470: if (old.Room == 450) { return vars.performSplitWithExtra("gnomes", "glitched to swamp"); } return false; case 540: if (current.Score - old.Score == 2) { return vars.performSplit("beast"); } return false; case 710: if (old.Room == 230) { return vars.performSplitWithExtra("castle", "long path"); } return false; case 730: if (old.Room == 220) { return vars.performSplitWithExtra("castle", "short path"); } return false; case 750: if (current.Score - old.Score == 5 && vars.towerPoints >= 7) { return vars.performSplit("vizier"); } return false; default: return false; } }