diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af42b2a --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.vscode/ +maps/ +working/ +build/ +dist/ +*.bak +*.bst +*.png +*.bin +*.smc +*.spec +*.srm +*.spoiler.json +*.sym \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5dfaef3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,129 @@ +# Changelog +## v16 (In Progress) +- Ancient Cave: New spawn plate enemy group generation. (Experimental) +- Enemy stats: Friendly NPCs removed from stat intershuffle. +### Bugfixes +- Ancient Cave: Logic errors that caused occasional incorrect enemies to appear in Fourside fixed. +- Ancient Cave: Potential softlock from Frank-guarding Shark fixed. + +## v15 (2018-05-19) +- Ancient Cave: DDD Mine dug out (fixes potential routing bug). +### Bugfixes +- Normal mode: Keysanity special fixes no longer applied. +- NPC sprites: Fixed glitches with Diamond-granting cutscene and post-Clumsy cutscene. +- Ancient Cave: Fixed bug involving status effects turned off and softlocks on scripted encounter death. + +## v14 (2018-05-12) +- Ancient Cave: Credits roll at end of game (with random music, because why not). +### Bugfixes +- Keysanity: Ness's house lights now behave as normal. +- Ancient Cave: Further logic errors that caused occasional incorrect enemies to appear in Electro Specter cave areas fixed. + +## v13 (2018-04-28) +- Gift box contents: Completely new item distribution algorithm for Ancient Cave mode. +- Ancient Cave: Moonside teleporters that go to gift box locations re-enabled. (You will not be teleported away from Moonside overworld.) +### Bugfixes +- Ancient Cave: Softlock causing events disabled: Captain Strong, Bubble Monkey, Sky Runner. +- Ancient Cave: Big Foot store will appear in Andonuts' lab. + +## v12 (2018-04-21) +- PC sprites: New method of randomizing allows for much wider variety of sprites to appear. +### Bugfixes +- Ancient Cave: Logic errors that caused occasional incorrect enemies to appear in Electro Specter cave areas fixed. + +## v11 (2018-04-10) +- Keysanity: Now features a PSI Teleport destination of North Onett. This allows access to north locations if you teleport before clearing certain flags. +- Keysanity: Suporma location added to distribution pool. +- Keysanity: Meteorite piece added to items pool (Suporma will not appear in pool). +### Bugfixes +- NPC sprites: Hang at Buzz Buzz will no longer occur. +- Keysanity: Venus will always give you her item. +- Keysanity: Pyramid can be accessed without fighting Kraken. + +## v10 (2018-04-05) +- Keysanity: Now features a PSI Teleport destination of South Winters. This allows much easier routing and can actually avoid the Jeff-alone events now. +### Bugfixes +- Gift box contents: Dummy item named "Null" should no longer appear in randomized chests. +- Keysanity: Gerard Montague always appears outside of the mine, so you cannot get locked out of Diamond location. +- Keysanity: Mr Spoon can always request the autograph, so you cannot get locked out of Signed banana location. +- Keysanity: Bubble Monkey should always be available on the north shore after he runs off the first time. +- Keysanity: Various impossible item routings no longer will occur. + +## v9 (2018-03-31) +- Keysanity mode added. Certain key items have been mixed up, but all teleports are available from the start of the game! +- Here are the item locations that have been shuffled in Keysanity mode (OUTDATED): + - Franklin badge + - Shyness book + - King banana + - Key to the shack + - Hawk eye + - Bicycle + - Wad of bills + - Diamond + - Signed banana + - Pencil eraser + - Key to the tower + - Town map + - Carrot key + - Tendakraut - but the Tendakraut has been transformed into a Jar of Fly Honey! +- Because there is a Jar of Fly Honey in the item pool, proceeding through Winters as Jeff is not required; however, if you choose to, the Bubble Monkey rope event has been patched to work even if you have extra companions. +- Keysanity mode is incompatible with Ancient Cave mode, and probably needs a lot of testing. Let me know if you encounter issues. + +## v8 (2018-03-26) +- Non-essential dialogs shuffle with the 'd' flag (with special surprises possible at Pokey). +- Chests in non-AC mode have a 20% chance to have any non-key item. +- Use the code 'wildgifts' in your flags to make that chance 100%. +- Money chests in non-AC mode randomize to (low-cost) items. +- Chests that were not properly randomized in AC mode fixed: + - Tracy's room + - Bracer of kings room + - Cold remedy room (Giant Step) + - Bag of dragonite room (Pyramid) + - Magicant red area + +## v7 (2018-03-21) +- Sanctuary Boss rooms in AC now always go front-to-back as they do in the base game. +- Non-Euclidian room layouts at Electro Specter no longer happen. +- Spoiler site at http://eb.compnode.net/ now displays chest locations and contents. +- Spoiler site at http://eb.compnode.net/ now displays boss locations and enemies. +- Odd-shaped PCs no longer appear in PC Sprite randomization by default due to graphical glitches. +- Use the code 'funsize' in your flags to get the odd-shaped PCs back. + +## v6 (2018-03-19) +- Program now outputs a spoiler file in AC mode. +- Use this file at http://eb.compnode.net/ to view a map of doors and destinations. (More features coming soon.) + +## v5 (2018-03-18) +- "Help" on ATM Card displays randomizer version info, seed, flags chosen. +- Mom's state set to heal you as normal. +- Phone in Ness's house no longer removes existing phone numbers. +- Non-gift-box NPCs no longer receive gift-box sprites. +- Chaos Theater show skipped if 'a' and 'p' flags selected due to panning bug. + +## v4 (2018-03-09) +- Topolla Theater routing bug fixed. + +## v3 (2018-03-09) +- NPC Sprite Swap and PC Sprite Swap flags added. +- Run button optional patch added. +- Enemy PP removed from intershuffle (fewer enemies will have no PP for their PP attacks). + +## v2 (2018-03-05) +- Battle backgrounds randomized. +- Enemy battle sprite pallettes randomized. +- Hotels fixed. +- Phones in Winters locations enabled. +- Belch removed from Sanctuary Spot boss possibilities. + +## v1 (2018-01-22) +- Praying at Giygas no longer softlocks. +- Exit Mice are now functional. They will take you back to the most recent Sanctuary Spot you visited, or home if you have not visited any. +- Sanctuary Spots are working now. +- Many items will now appear only once per dungeon, notably items that can only be equipped by one character. +- Many items that were unsellable/undroppable now can be. +- Added various protections against the enemy despawn bug. +- Ness starts with the Escargo Express phone number memorized. +- Sanctuary bosses now report their proper order. +- Some hotspots and other events have been modified, such as Poo leaving after the Pyramid. +- NPCs that start battles should never appear in the first zone. +- Various fixes to routing. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..16c8221 --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +# Bizarre Dimension – EarthBound Ancient Cave Randomizer + +NOTE: I (pickfifteen) have suspended development on this fork of Bizarre Dimension, and have ported all features thus far to the new browser-based, online [EarthBound Randomizer](https://github.com/pickfifteen/eb-randomizer) available at https://earthbound.app. Many thanks to Abyssonym for the original creation of Bizarre Dimension and the blessing to port it and his framework to allow for this new online randomizer. + +Bizarre Dimension is a program that randomizes a ROM for the Super Nintendo game EarthBound, providing endless unique gameplay experiences with many distinct modes. + +Windows users can simply download the `eb_bizarre_dimension.exe` file and run it; other platforms can run `randomizer.py` with Python 2. You will need to provide a ROM file of EarthBound; we will not provide this file. When you run the program, it will first ask for the location of this file; simply put the file in the same directory as the program and enter the filename. + +Next, the program will ask for a seed. Simply leave this field blank to get a random seed, or enter a specific integer if you want to play the same generated game as a friend. Any ROMs generated with the same version, seed, and flags will be identical. + +Next, the program will ask you which flags you would like to set. You can pick and choose which features of the randomizer you want enabled, or you can simply enter a blank line to get the default, all features enabled. After all options are selected, a new ROM file will be created, as well as a `.spoiler.json` file. If you get stuck during the game, you can use this file at [https://eb.compnode.net](https://eb.compnode.net) to get a visual map of where doors lead to, what chest contents are, and other spoiler information. + +By using `Help!` on the ATM card, you will be able to get details on the version of the randomizer used, the flags selected, and the seed of the particular game. + +Most flags are self-explanatory, but additional details for some modes are included below. + +## Ancient Cave mode – `a` + +Ancient Cave mode completely changes how the game is played. Instead of proceeding through the normal storyline of EarthBound, all rooms and doors have been shuffled around into a multi-level maze. You start with all four party members, and your goal is to proceed through all 8 levels of the maze, each level guarded by a Shiny Spot, and reach and defeat Giygas. Enemy spawn locations grow in difficulty as you proceed deeper into the maze, as do gift box rewards (if randomize gift box contents is on, which is recommended). + +You will not have to do any storyline events, like riding the Sky Runner or beating Carpainter, to beat the game in this mode. Often, you can do story events to "skip" around in the cave. This may take you much deeper into the maze. Use common sense when taking skips, as some may break the game; giving an item to a monkey in Monkey Cave is safe, for example, while riding the bus is often not. If you take no skips, you will encounter each of the 8 shiny spots before reaching Giygas. + +Known skips: +- Any of the Monkey Cave monkeys who want items +- Having a theater ticket attendant move out of the way, in either direction +- Removing pencil, eraser, or rabbit statues +- Getting abducted by ghosts in the tunnels +- Giving the Tiny Ruby to the museum guard +- Getting teleported by Carpainter's lightning +- Entering Dungeon Man +- Riding the bus (dangerous, can lock game) + +## Keysanity mode – `k` + +Keysanity mode also radically changes how the game is played. 15 different key items have been shuffled around throughout the world; Mayor Pirkle may give you the Bicycle, while the Bike Shop guy may give you the Carrot key. To help you on this more complicated quest, however, Ness already knows PSI Teleport, and all available teleport locations are unlocked at the start of the game (including bonus teleports to South Winters and North Onett). Your goal is to beat the game as normal, but getting to all 8 Your Sanctuary locations will be more of a challenge. + +The list of items that have had their locations shuffled in this mode is as follows: +- Franklin badge +- Shyness book +- King banana +- Key to the shack +- Hawk eye +- Bicycle +- Wad of bills +- Diamond +- Signed banana +- Pencil eraser +- Key to the tower +- Town map +- Carrot key +- Tendakraut - but the Tendakraut has been transformed into a Jar of Fly Honey +- Suporma - but the Suporma has been transformed into a Meteorite piece + +Because you can get a Jar of Fly Honey through one of these 15 locations, it is not necessary to do the Jeff-alone-in-Winters part of the storyline. However, you can still do so if you wish, as the Boogey Tent will still contain a Jar of Fly Honey as well. + +Be careful with how you proceed through storyline events! If you take a very unusual order, it is possible you may lock yourself out of having a character available until you complete Magicant. Since you can teleport anywhere instantly, you can get the game into very unusual states; this is expected and encouraged to take advantage of to get a lower time. + +A few events have been made more lenient with regards to in-game triggers; notably, Venus will always give you her item right away, you can access the Pyramid without fighting Kraken (but you still must see the heiroglyphs), and Montague should always appear. It is also possible to get the game into a "spawns-off" condition. This is expected, but note if you game over in a spawns-off condition, you may softlock and have to reset the game, so be sure to save if necessary! + +Keysanity mode is incompatible with Ancient Cave mode, and if both modes are selected, Keysanity will be disabled. (Therefore, if you simply enter a blank line at flag selection, you will be playing Ancient Cave, not Keysanity.) + +## Gift box contents – `g` + +Randomizing gift box contents works differently in Ancient Cave mode and in non-Ancient Cave mode. In Ancient Cave mode, gift boxes will generally increase in value as you proceed deeper into the cave. About 60% of the gift boxes will be equipment. In early levels of the cave, you may get "skip-enabling" items such as the Pencil Eraser that could allow you to skip around in the cave. + +In non-Ancient Cave mode, gift boxes will be replaced with an item that is similar in value to their contents in the normal game. However, every gift box has a 20% chance of being replaced by an item of any value, so there is a chance you could get a very good item very early. + +## PC and NPC sprites – `p` and `n` + +A special note should be made when playing with these flags enabled in non-Ancient-Cave modes, either Keysanity or "normal" EarthBound: There are a few scripted events that are bugged when these flags are used. It will appear that the game is softlocked, but usually it will proceed after waiting 5-10 minutes. (Usually an NPC is walking all the way across the entire world map after spawning at an incorrect location.) + +While they won't actually lock your game, these bugs are obviously annoying, so you may want to not use these flags if you are playing a non-Ancient-Cave mode. If you do encounter one of these situations, let us know where it is, as we would like to eventually fix all of them. + +Currently known bugged events: +- Post Twoson show +- Delivering the Diamond on the bridge +- Defeating Clumsy Robot (possibly a true softlock?) + +## Backgrounds – `b` + +Our method of randomizing the battle backgrounds often leads to extremely good-looking results, but rarely, there will be a bright flashing palette used. If you are sensitive to bright flashing lights causing epilepsy or seizures, you may not want to enable this mode. + +## Run button patch + +Bizarre Dimension includes a patch that adds a run button to the game. Holding `Y` will make the characters run as if they had used a Skip Sandwich. If you do not want this feature, follow the instructions in the program to disable it. + +## Special codes + +There are a few special codes you can enter along with the flags. For example, to add `wildgifts` and `funsize` to a game with the `a`, `g`, and `p` flags only set, you would enter `agpwildgiftsfunsize`. + +- `wildgifts` – In Non-Ancient Cave mode, with gift box randomization, 20% chance of any item increased to 100% chance. +- `funsize` – With PC Sprite randomization, additional PC sprites that don't conform to normal PC size are added to the pool of options. These sprites occasionally cause small graphical hiccups, but they can add a lot of variety. +- `devmode` – Additional details are added to the `.spoiler.json` file, usually only useful for development purposes. + +## Known issues + +- Possible Lost World "geyser softlock" in Ancient Cave mode. More information is needed. + +## Contact + +Bizarre Dimension was originally created by Abyssonym; you can find him at [Twitter](https://www.twitter.com/abyssonym) or [his website](http://www.abyssonym.com/). + +Current development on this fork is being led by pickfifteen; you can contact him either via the Issue Tracker on this repo, [Twitter](https://www.twitter.com/pickfifteen), or on the [EarthBound Speedrunning Discord](https://discord.gg/WWVYwkE). diff --git a/changelog.txt b/changelog.txt deleted file mode 100644 index 95a8e86..0000000 --- a/changelog.txt +++ /dev/null @@ -1,16 +0,0 @@ -v0.1 --Praying at Giygas no longer softlocks. --Exit Mice are now functional. They will take you back to the most recent Sanctuary Spot you visited, or home if you have not visited any. --Sanctuary Spots are working now. --Many items will now appear only once per dungeon, notably items that can only be equipped by one character. --Many items that were unsellable/undroppable now can be. --Added various protections against the enemy despawn bug. --Ness starts with the Escargo Express phone number memorized. --Sanctuary bosses now report their proper order. --Some hotspots and other events have been modified, such as Poo leaving after the Pyramid. --NPCs that start battles should never appear in the first zone. --Various fixes to routing. - -NOT FIXED --The Lost World "geyser softlock". I'm not sure what caused this. --Fadeouts after Hotels. This is still pretty low priority in terms of necessity vs. effort required. Use the Sound Stone to turn the lights back on. diff --git a/eb_bizarre_dimension.exe b/eb_bizarre_dimension.exe index 4c7c453..bd4a9b1 100755 Binary files a/eb_bizarre_dimension.exe and b/eb_bizarre_dimension.exe differ diff --git a/randomizer.py b/randomizer.py index f8371b6..f7b91d6 100644 --- a/randomizer.py +++ b/randomizer.py @@ -11,19 +11,28 @@ from os import path from time import time, sleep from collections import Counter +from array import array +import json -VERSION = 0 +VERSION = 15.04 ALL_OBJECTS = None DEBUG_MODE = False TEXT_MAPPING = {} +TEXT_INVERSE_MAPPING = [] + + +hexify = lambda x: "{0:0>2}".format("%x" % x) +numify = lambda x: "{0: >3}".format(x) +minmax = lambda x: (min(x), max(x)) text_map_filename = path.join(tblpath, "text_mapping.txt") for line in open(text_map_filename): - line = line.strip("\n") + line = line.strip("\n").strip("\r") code, text = line.split("=", 1) TEXT_MAPPING[int(code, 0x10)] = text + TEXT_INVERSE_MAPPING.append((text, [int(a, 0x10) for a in map(''.join, zip(*[iter(code)]*2))])) TEXT_MAPPING[0] = None @@ -85,6 +94,56 @@ def bytes_to_text(s): s = s[len(key):] return result +def value_to_text(value): + if value not in TEXT_MAPPING: + return None + return TEXT_MAPPING[value] + +def values_to_text(values): + result = "" + while values: + if len(values) > 1: + text = value_to_text(values[0] * 256 + values[1]) + if text is not None: + result += text + values = values[2:] + continue + text = value_to_text(values[0]) + if text is None: + raise Exception("Value %x not valid text." % values[0]) + result += text + values = values[1:] + return result + +def text_to_values(s, allow_multi=True): + result = [] + mapping = [i for i in TEXT_INVERSE_MAPPING if allow_multi or len(i[0]) == 1] + while s: + found = False + for text, codes in mapping: + if s.startswith(text): + result.extend(codes) + s = s[len(text):] + found = True + break + if found == False: + raise Exception("String not able to be mapped: %s" % s) + return tuple(result) + +def text_to_bytes(s, min_length=25): + bytelist = list(text_to_values(s, False)) + while len(bytelist) < min_length: + bytelist.append(0) + return array('B', bytelist).tostring() + +def ccode_address(address): + return (address & 0xff, (address & 0xff00) / 0xff,((address + 0xc00000) & 0xff0000) / 0xffff, 0x00) + +def ccode_call_address(address): + return (0x08,) + ccode_address(address) + +def ccode_goto_address(address): + return (0x0a,) + ccode_address(address) def load_areas(area_filename=None): if area_filename is None: @@ -156,14 +215,20 @@ def all_areas(self): class Script: _all_scripts = [] + _freespace = (0x1545c0, 0x154fff) + + @classproperty + def every(cls): + return Script._all_scripts def __init__(self, pointer, endpointer=None): - for s in Script._all_scripts: - assert s.pointer != pointer self.pointer = pointer self.endpointer = endpointer self.subpointers = set([]) - self.read_script() + if pointer is not None: + for s in Script._all_scripts: + assert s.pointer != pointer + self.read_script() Script._all_scripts.append(self) def __eq__(self, other): @@ -189,6 +254,20 @@ def replace_sanctuary_boss(self, boss): s.lines = newlines s.write_script() + def replace_item(self, old_item, new_item): + for s in self.subscript_closure: + newlines = [] + needs_write = False + for line in s.lines: + if (tuple(line[:2]) == (0x1d, 0x00) or tuple(line[:2]) == (0x1d, 0x0e)) and line[3] == old_item.index: + needs_write = True + newlines.append([line[0], line[1], line[2], new_item.index]) + else: + newlines.append(line) + s.lines = newlines + if needs_write: + s.write_script() + @property def is_battle_source(self): self.has_battle_trigger @@ -227,11 +306,17 @@ def is_sanctuary_door(self): return True return False + @property + def is_swap_safe(self): + # Currently, only allow scripts with a depth of 1 to be swapped. + return self._swap_safe and all(len(ss.subscripts) == 0 and ss._swap_safe for ss in self.subscripts) + def make_sanctuary_door_always_activate(self): assert self.is_sanctuary_door assert self.lines[0][0] == 0x07 assert self.lines[1][0] == 0x1b self.lines = self.lines[2:] + self.remove_status_effects_off() self.lines.insert(-1, (0x05, 0x0B, 0x00)) # encounters on self.lines.insert(-1, (0x1F, 0x68)) # exit mouse assert tuple(self.lines[-1]) == (0x02,) @@ -241,9 +326,16 @@ def remove_encounters_off(self): if hasattr(self, "_removed_encounters") and self._removed_encounters: return keys = [(0x04, 0x0b, 0x00),] - self.remove_instructions(keys) + self.remove_instructions(keys, []) self._removed_encounters = True + def remove_status_effects_off(self): + if hasattr(self, "_removed_status_effects") and self._removed_status_effects: + return + keys = [(0x1f, 0x41, 0x05),] + self.remove_instructions(keys, []) + self._removed_status_effects = True + def remove_exit_mouse_store(self): if self.is_sanctuary_door: return @@ -252,7 +344,7 @@ def remove_exit_mouse_store(self): if hasattr(self, "_removed_exit_mouse") and self._removed_exit_mouse: return keys = [(0x1f, 0x68),] - self.remove_instructions(keys) + self.remove_instructions(keys, []) self._removed_exit_mouse = True def remove_teleports(self): @@ -263,7 +355,47 @@ def remove_teleports(self): (0x1f, 0x21), (0x1f, 0x69), ] - self.remove_instructions(keys) + exceptions = [ + # All post hotel/sleep/bench teleports. Either they normally + # teleport you to the same map, or they have been manually + # changed to do so in the TeleportObject cleanup. + (0x1f, 0x21, 0x02), + (0x1f, 0x21, 0x0b), + (0x1f, 0x21, 0x0d), + (0x1f, 0x21, 0x0e), + (0x1f, 0x21, 0x11), + (0x1f, 0x21, 0x13), + (0x1f, 0x21, 0x27), + (0x1f, 0x21, 0x2b), + (0x1f, 0x21, 0x2c), + (0x1f, 0x21, 0x2e), + (0x1f, 0x21, 0x2f), + (0x1f, 0x21, 0x30), + (0x1f, 0x21, 0x31), + (0x1f, 0x21, 0x32), + (0x1f, 0x21, 0x33), + (0x1f, 0x21, 0x34), + (0x1f, 0x21, 0x35), + (0x1f, 0x21, 0x36), + (0x1f, 0x21, 0x37), + (0x1f, 0x21, 0x38), + (0x1f, 0x21, 0x39), + (0x1f, 0x21, 0x3A), + (0x1f, 0x21, 0x66), + (0x1f, 0x21, 0xA2), + (0x1f, 0x21, 0xC4), + # Below are Moonside same-screen teleports + (0x1f, 0x21, 0x6A), + (0x1f, 0x21, 0x6B), + (0x1f, 0x21, 0x6C), + (0x1f, 0x21, 0x6F), + (0x1f, 0x21, 0x70), + (0x1f, 0x21, 0x71), + (0x1f, 0x21, 0x72), + (0x1f, 0x21, 0xE8), + (0x1f, 0x21, 0xE9), # Unused value - for testing + ] + self.remove_instructions(keys, exceptions) self._removed_teleports = True def remove_party_changes(self): @@ -273,14 +405,29 @@ def remove_party_changes(self): (0x1f, 0x11), (0x1f, 0x12), ] - self.remove_instructions(keys) + self.remove_instructions(keys, []) self._removed_party = True - def remove_instructions(self, keys): + def fix_hotels(self): + if hasattr(self, "_fixed_hotels") and self._fixed_hotels: + return + keys = [ + (0x04, 0x7f, 0x01), # Flag that indicates you just slept. + # Disabling the above flag stops the bug that prevents you from + # resleeping at a hotel until you luck into flipping it back off, + # but at some hotels it does cause the music to not return. + (0x04, 0x02, 0x02), # Flag that indicates you just slept at home. + ] + self.remove_instructions(keys, []) + self._fixed_hotels = True + + def remove_instructions(self, keys, exceptions): for s in self.subscript_closure: newlines = [] for line in s.lines: - if tuple(line[:2]) in keys: + if tuple(line[:2]) in keys and tuple(line[:3]) not in exceptions: + continue + if tuple(line[:3]) in keys: continue newlines.append(line) if len(newlines) < len(s.lines): @@ -316,6 +463,16 @@ def enemy_encounters(self): es.append(e) return sorted(es, key=lambda e: e.index) + @cached_property + def items_given(self): + items = [] + for s in self.subscript_closure: + for line in self.lines: + if tuple(line[:2]) == (0x1d, 0x00) or tuple(line[:2]) == (0x1d, 0x0e): + i = ItemObject.get(line[3]) + items.append(i) + return sorted(items, key=lambda i: i.index) + @classmethod def get_by_pointer(self, pointer): if not hasattr(self, "_cached_by_pointer"): @@ -391,6 +548,9 @@ def scriptdict(self): instruction, description = line.split(':') instruction = instruction.strip() description = description.strip() + prefix = instruction[:1] + safe = (prefix == '!') + instruction = instruction[1:] try: key = [int(v, 0x10) for v in instruction.split()[:2]] except ValueError: @@ -398,38 +558,84 @@ def scriptdict(self): key = tuple(key) if key in [(0x09,), (0x1f, 0xc0)]: - scriptdict[key] = (instruction, description, None) + scriptdict[key] = (instruction, description, None, safe) continue length = len(instruction.split()) assert key not in scriptdict - scriptdict[key] = (instruction, description, length) + scriptdict[key] = (instruction, description, length, safe) Script._scriptdict = scriptdict return Script.scriptdict + + @classproperty + def newlines(self): + if hasattr(Script, "_newlines"): + return Script._newlines + + newlines = [] + newitem = [] + for line in open(path.join(tblpath, "newlines.txt")): + line = line.strip() + if len(line) == 0: + if len(newitem) > 0: + newlines.append(newitem) + newitem = [] + elif line.startswith("\""): + line = line.strip("\"") + newitem.append(text_to_values(line)) + else: + newitem.append(tuple([int(a, 0x10) for a in line.split()])) + if len(newitem) > 0: + newlines.append(newitem) + Script._newlines = newlines + return Script.newlines + def read_script(self): f = open(get_outfile(), "r+b") pointer = self.pointer + self._swap_safe = True self.lines = [] nesting = 0 + current_text_line = None while True: f.seek(pointer) key = (ord(f.read(1)),) + + if key[0] >= 0x20: + # Plain text + if not current_text_line: + current_text_line = [] + current_text_line.extend(key) + pointer += 1 + continue + if key not in self.scriptdict: f.seek(pointer) key = tuple(map(ord, f.read(2))) if key in self.scriptdict: - instruction, description, length = self.scriptdict[key] + instruction, description, length, safe = self.scriptdict[key] else: key = (key[0],) - instruction, description, length = ( - ("%x" % key[0]).upper(), "ERROR", 1) - self.scriptdict[key] = (instruction, description, length) - + instruction, description, length, safe = ( + ("%x" % key[0]).upper(), "ERROR", 1, False) + self.scriptdict[key] = (instruction, description, length, safe) + if "Display Compressed" in description: - description = "ERROR" - self.scriptdict[key] = (instruction, description, length) + if not current_text_line: + current_text_line = [] + f.seek(pointer) + current_text_line.extend(tuple(map(ord, f.read(length)))) + pointer += length + continue + + if current_text_line: + self.lines.append(tuple(current_text_line)) + current_text_line = None + + if safe is False: + self._swap_safe = False if length is None: f.seek(pointer+len(key)) @@ -480,12 +686,27 @@ def write_script(self, pointer=None): f.close() self.please_write = False + @classmethod + def write_new_script(self, lines): + new_script = Script(None) + new_script.lines = lines + if Script._freespace[0] + new_script.length > Script._freespace[1]: + raise Exception("No free space for new script.") + new_script.pointer = Script._freespace[0] + new_script.old_length = new_script.length + Script._freespace = (Script._freespace[0] + new_script.length, Script._freespace[1]) + new_script.write_script() + return new_script + @classmethod def get_pretty_line_description(self, line): + if line[0] >= 0x20 or line[0] in (0x15, 0x16, 0x17): + # Plain text + return values_to_text(line), "Plain Text" key = (line[0],) if key not in self.scriptdict: key = tuple(line[:2]) - instruction, description, length = self.scriptdict[key] + instruction, description, length, safe = self.scriptdict[key] pretty_line = " ".join(["{0:0>2}".format("%x" % v) for v in line]) return pretty_line, description @@ -662,12 +883,79 @@ class AncientCave(TableObject): flag = 'a' flag_description = "with ancient cave mode" + @classproperty + def after_order(self): + return [PsiTeleportObject] + @classmethod def full_randomize(cls): AncientCave.class_reseed("ancient") generate_cave() super(AncientCave, cls).full_randomize() + @classmethod + def full_cleanup(cls): + # Always give ATM Card help text, regardless of flags + lines = [ + (0x01, ), + text_to_values("@EarthBound Ancient Cave randomizer version %s." % VERSION), + (0x03, 0x00), + text_to_values("@Seed: %s" % get_seed()), + (0x03, 0x00), + text_to_values("@Flags: %s" % get_flags()), + (0x1f, 0x21, 0xe9), # Teleport to test location + (0x13, 0x02)] + new_atm_help = Script.write_new_script(lines) + + old_atm_help = Script.get_by_pointer(0x5566b) + old_atm_help.lines = [ccode_goto_address(new_atm_help.pointer)] + old_atm_help.write_script() + + super(AncientCave, cls).full_cleanup() + + +class Dialog(TableObject): + flag = 'd' + flag_description = "dialogs" + + @classproperty + def after_order(self): + return [PsiTeleportObject] + + @classmethod + def intershuffle(cls): + cls.class_reseed("inter") + candidates = [tpt for tpt in TPTObject.every if tpt.script and tpt.script.is_swap_safe] + shuffled = shuffle_normal(candidates, random_degree=1) + + for a, b in zip(candidates, shuffled): + a.address = b.old_data["address"] + + @classmethod + def mutate_all(cls): + cls.class_reseed("mut") + pokey_scripts = [ + (Script.get_by_pointer(0x57e1c), 0, 1), + (Script.get_by_pointer(0x8fb1b), 5, 4), + (Script.get_by_pointer(0x8fc2e), 4, 4), + (Script.get_by_pointer(0x8fd11), 5, 4), + (Script.get_by_pointer(0x8ff31), 4, 4) + ] + game_scripts = [tpt.script for tpt in TPTObject.every if tpt.script and tpt.script.is_swap_safe] + candidates = Script.newlines + candidates.extend(random.sample(game_scripts, len(candidates) * 3)) + chosen = random.sample(candidates, len(pokey_scripts)) + + for (pokey_script, pre_lines, post_lines), new_script in zip(pokey_scripts, chosen): + if not isinstance(new_script, Script): + new_script = Script.write_new_script(new_script) + pointer = new_script.pointer + call_line = ccode_call_address(pointer) + new_lines = pokey_script.lines[0:pre_lines] + new_lines.append(call_line) + new_lines.extend(pokey_script.lines[-post_lines:]) + pokey_script.lines = new_lines + pokey_script.write_script() class EventObject(GetByPointerMixin, TableObject): def __repr__(self): @@ -731,6 +1019,26 @@ def __repr__(self): *["%x" % v for v in [self.global_x, self.global_y, self.event_type, self.event_index, self.enemy_cell.index]]) + def door_description(self): + if not self.is_exit: + return ("Enemy Cell: %4x Loc: (%4d, %4d) NON-DOOR" % + (self.enemy_cell.index, self.global_x, self.global_y)) + return ("Enemy Cell: %4x Loc: (%4d, %4d) Dest: (%4d, %4d)" % + (self.enemy_cell.index, self.global_x, self.global_y, self.new_event.x * 8, self.new_event.y * 8)) + + def serialize(self): + result = { + "index": self.index, + "enemyCell": self.enemy_cell.index, + "x": self.global_x, + "y": self.global_y, + "onShortestPath": self.on_shortest_path + } + if self.is_exit: + result["xDestination"] = self.new_event.x * 8 + result["yDestination"] = self.new_event.y * 8 + return result + def connect_exit(self, other, override=False): if not override: assert not self.connected @@ -763,12 +1071,35 @@ def connected(self): if hasattr(self, "_connected"): return self._connected return False + + @property + def incoming_exit(self): + if hasattr(self, "_incoming_exit"): + return self._incoming_exit + return False + + @property + def outgoing_exit(self): + if hasattr(self, "_outgoing_exit"): + return self._outgoing_exit + return False + + @property + def on_shortest_path(self): + if hasattr(self, "_on_shortest_path"): + return self._on_shortest_path + return False @cached_property def event(self): return EventObject.get_by_pointer( 0xF0000 | self.old_data["event_index"]) + @property + def new_event(self): + return EventObject.get_by_pointer( + 0xF0000 | self.event_index) + @property def script(self): if self.event: @@ -891,15 +1222,40 @@ def global_y(self): class MapSpriteObject(GetByPointerMixin, ZonePositionMixin, TableObject): flag = 'g' flag_description = "gift box contents" + + @classproperty + def after_order(self): + return [PsiTeleportObject, AncientCave] + + @classproperty + def unassigned_chests(cls): + return [o for o in cls.every if o.is_chest and o.cave_rank is not None and (not hasattr(o, "mutated") or not o.mutated)] def __repr__(self): return "{0:0>2} {1:0>2} {3:0>5} {2:0>4}".format( *["%x" % v for v in [self.x, self.y, self.tpt_number, self.pointer]]) - @classproperty - def after_order(self): - return [AncientCave] + def serialize(self): + result = { + "index": self.index, + "cave_rank": self.cave_rank, + "x": self.global_x, + "y": self.global_y, + } + if self.is_money: + result["money"] = self.money_value + if self.is_chest and not self.is_money: + i = self.chest_contents + result["name"] = i.name + result["isEquipment"] = i.is_equipment + result["itemType"] = i.item_type + if self.is_sanctuary_boss: + other = [m for m in MapSpriteObject.every if m.old_data["tpt_number"] == self.tpt_number] + assert len(other) == 1 + result["bossIndex"] = SANCTUARY_BOSS_INDEXES.index(other[0].index) + result["enemyEncounters"] = self.script.enemy_encounters + return result @property def tpt(self): @@ -990,63 +1346,158 @@ def nearest_cluster(self): if self.nearest_exit in c.exits: return c + def replace_item(self, old_item, new_item): + assert(self.chest_contents == old_item) + self.tpt.argument = new_item.index + @classmethod def mutate_all(cls): cls.class_reseed("mut") - objs = list(cls.every) - random.shuffle(objs) - for o in cls.every: - if hasattr(o, "mutated") and o.mutated: - continue - o.reseed(salt="mut") - o.mutate() - o.mutate_bits() - o.mutated = True + if 'a' not in get_flags(): + for o in cls.every: + if hasattr(o, "mutated") and o.mutated: + continue + o.reseed(salt="mut") + o.mutate() + o.mutate_bits() + o.mutated = True + return + + # Ancient Cave + # 0) Set non-in-cave chests to empty, for spoiler clarity + # Also set chests that are unreachable to be empty + inaccessible_chests = [o for o in cls.every if o.is_chest and o.cave_rank is None and (not hasattr(o, "mutated") or not o.mutated)] + inaccessible_chests += [ + cls.get(182), cls.get(135), # Dungeon man + ] + for chest in inaccessible_chests: + chest.tpt.argument = 0x100 + chest.mutated = True + + # 1) Place skip-granting items early in the cave + early_items_index = [ + 0x7d, # Backstage pass + 0xa6, # King banana + 0xb8, # Pencil eraser + 0xd2, # Eraser eraser + 0xfd, # Carrot key + ] + early_chests = sorted(cls.unassigned_chests, key=lambda c: c.cave_rank) + early_chests = early_chests[:len(early_chests)/3 * 2] + chosen = random.sample(early_chests, len(early_items_index)) + for chest, item_index in zip(chosen, early_items_index): + chest.tpt.argument = item_index + chest.mutated = True + + # 2) Fill up to 60% of remaining chests with equipment + equipment_once = [i for i in ItemObject.ranked if i.rank >= 0 and i.is_equipment] + equipment = [] + for item in equipment_once: + equipment.append(item) + if not item.limit_one: + equipment.append(item) + equipment.append(item) + franklin_badge = ItemObject.get(0x01) + equipment.insert(int(len(equipment) * 0.3), franklin_badge) + equipment.insert(int(len(equipment) * 0.7), franklin_badge) + + chests = cls.unassigned_chests + reduced_equipment_count = int(len(chests) * 0.6) + if reduced_equipment_count < len(equipment): + reduced_equipment_indexes = random.sample(range(len(equipment)), reduced_equipment_count) + equipment = [equipment[i] for i in sorted(reduced_equipment_indexes)] + + chosen = sorted(random.sample(chests, len(equipment)), key=lambda c: c.cave_rank) + equipment = shuffle_normal(equipment) + for chest, new_item in zip(chosen, equipment): + chest.tpt.argument = new_item.index + chest.mutated = True + + # 3) Fill remaining chests - candidates are non-equipment, non-key-item, non-condiment + candidates = [i for i in ItemObject.ranked if i.rank >= 0 and not i.is_equipment and not i.is_key_item and i.item_type != 0x28] + candidates = shuffle_normal(candidates) + chests = sorted(cls.unassigned_chests, key=lambda c: c.cave_rank) + for i, chest in enumerate(chests): + index = int(round(float(i) / len(chests) * (len(candidates)-1))) + new_item = candidates[index] + chest.tpt.argument = new_item.index + chest.mutated = True + def mutate(self): if not self.is_chest: return if 'a' not in get_flags(): - if self.is_money: - return i = self.chest_contents - i = i.get_similar() + if self.is_money: + i = ItemObject.get(0x5a) #Hamburger + if 'wildgifts' in get_activated_codes() or random.random() < 0.2: + i = random.choice([item for item in ItemObject.every if not item.is_key_item]) + else: + i = i.get_similar() assert self.tpt.argument == self.tpt.old_data["argument"] self.tpt.argument = i.index return - # Ancient Cave - if not hasattr(ItemObject, "done_ones"): - ItemObject.done_ones = set([]) - cave_rank = self.cave_rank - if cave_rank is None: - return +class SpriteGroupObject(GetByPointerMixin, TableObject): + flag = 'p' + flag_description = 'pc sprites' + + def __repr__(self): + s = "{0:0>4} {1:0>4} {2:0>2} {3:0>2} {4:0>2} ({5:0>2} {6:0>2}) ({7:0>2} {8:0>2}) {9:0>2}".format(* + ["%x" % v for v in + [self.index, self.pointer, self.height, self.width, self.size, self.collision_ew_h, self.collision_ew_w, self.collision_ns_h, self.collision_ns_w, self.sprite_count]]) + return s - if random.random() < (cave_rank ** 2): - candidates = [i for i in ItemObject.ranked - if i.rank >= 0 and not i.is_buyable] - else: - candidates = [i for i in ItemObject.ranked if i.rank >= 0] - - if (random.random()**4) > cave_rank: - temp = [c for c in candidates if c.is_equipment] - if temp: - cave_rank = cave_rank ** 0.75 - candidates = temp - - candidates = [c for c in candidates - if not (c.limit_one and c.index in ItemObject.done_ones)] - index = int(round(cave_rank * (len(candidates)-1))) - chosen = candidates[index] - new_item = chosen.get_similar(candidates=candidates) - if new_item.limit_one: - ItemObject.done_ones.add(new_item.index) - self.tpt.argument = new_item.index + @property + def specsattrs(self): + specsattrs = super(SpriteGroupObject, self).specsattrs + if self.sprite_count <= 8: + specsattrs = [s for s in specsattrs if s[0] != "sprites_diagonal"] + return specsattrs + + @property + def collision(self): + return (self.collision_ns_w, self.collision_ns_h, self.collision_ew_w, self.collision_ew_h) + + @property + def sprite_count(self): + if self.index == 463: + return 8 + try: + raw_size = SpriteGroupObject.get(self.index + 1).pointer - self.pointer + return max(0, (raw_size - 9) / 2) + except KeyError: + return 16 # If we are not yet loaded, assume maximum size + + + def valid_swap(self, other, exclusions): + if self.index in exclusions or other.index in exclusions: + return False + return self.sprite_count == other.sprite_count and self.collision == other.collision + + def mutate(self): + if 'funsize' in get_activated_codes(): + return + if self.index not in [1, 2, 3, 4]: # Only randomize 4 main PCs + return + candidates = [sg for sg in SpriteGroupObject.every if self.size == sg.size and self.collision == sg.collision] # Different than normal valid_swap + chosen = random.choice(candidates) + self.copy_data(chosen) + #self.collision_ns_w = self.old_data["collision_ns_w"] + #self.collision_ns_h = self.old_data["collision_ns_h"] + #self.collision_ew_w = self.old_data["collision_ew_w"] + #self.collision_ew_h = self.old_data["collision_ew_h"] + if chosen.sprite_count <= 8: + self.sprites_diagonal = chosen.sprites_cardinal class TPTObject(TableObject): + flag = 'n' + flag_description = 'npc sprites' + @property def is_chest(self): return self.tpt_type == 2 @@ -1065,6 +1516,104 @@ def script(self): assert pointer & 0xFFC00000 == 0xC00000 return Script.get_by_pointer(pointer & 0x3FFFFF) + def mutate(self): + chests = [33, 195, 214, 233, 262, 322, 408] + tpt_exclusions = [198, # Meteorite (causes Buzz Buzz scene problems) + 884] # Runaway 5 in Clumsy room (causes softlock) + sprite_exclusions = [0, 106, 200, 247, 295, 314, 316, 368, + 369, 371, 373, 374, 375, 376, 381, 410, 420, 428, 430, 431, 439, + 440, 441, 456, 462, 463, + # Also exclude all chest sprites + 33, 195, 214, 233, 262, 322, 408] + + if self.index in tpt_exclusions: + return + + if self.sprite in chests: + self.sprite = random.choice(chests) + return + + if self.sprite in sprite_exclusions: + return + + current_sprite = SpriteGroupObject.get(self.sprite) + candidates = [sg for sg in SpriteGroupObject.every if current_sprite.valid_swap(sg, sprite_exclusions)] + self.sprite = random.choice(candidates).index + + # Special fixes + if self.index == 795: # Backhoe on bridge + f = open(get_outfile(), "r+b") + f.seek(0x307AF) + f.write(chr(self.sprite % 256)) + f.write(chr(self.sprite / 256)) + f.close() + + def cleanup(self): + if 'a' in get_flags() and self.address == 0xc75909: + self.address = 0xc68017 # Make home phone a normal phone + + +class PcGfxObject(TableObject): + #flag = 'p' + #flag_description = 'pc sprites' + + @classmethod + def full_cleanup(cls): + if 'funsize' not in get_activated_codes(): + return + cls.class_reseed("mut") + # Most values from original EarthBound Reshuffler + # Table order: 0 1 2 3 5 6 4 + potential_pcs = [ + [1, 8, 17, 21, 5, 27, 34, 16, 1, 453], + [2, 9, 18, 22, 25, 28, 34, 393, 2, 454], + [3, 10, 19, 23, 25, 29, 34, 394, 3, 3], + [4, 11, 20, 24, 25, 30, 34, 295, 4, 362], + [5, 8, 5, 5, 5, 27, 34, 457, 5, 453], + [6, 8, 6, 6, 5, 27, 34, 16, 6, 453], + [25, 11, 25, 25, 25, 29, 34, 26, 25, 25], + [39, 8, 39, 39, 25, 35, 34, 39, 39, 39], + [44, 10, 44, 44, 48, 29, 34, 394, 48, 44], + [45, 10, 45, 45, 25, 29, 34, 394, 45, 382], + [46, 8, 47, 47, 25, 35, 34, 46, 46, 46], + [51, 8, 51, 51, 51, 35, 34, 51, 51, 51], + [149, 8, 149, 149, 25, 31, 34, 149, 149, 149], + [150, 8, 150, 150, 25, 32, 34, 150, 150, 150], + [182, 10, 182, 182, 25, 29, 34, 357, 182, 182], + [435, 8, 435, 435, 25, 27, 34, 435, 435, 435]] + if 'funsize' in get_activated_codes(): + potential_pcs = [ + [1, 8, 17, 21, 5, 27, 34, 16, 1, 453], + [27, 34, 27, 27, 27, 1, 8, 16, 1, 453], + [2, 9, 18, 22, 25, 28, 34, 393, 2, 454], + [3, 10, 19, 23, 25, 29, 34, 394, 3, 3], + [4, 11, 20, 24, 25, 30, 34, 295, 4, 362], + [5, 457, 5, 5, 5, 27, 34, 457, 5, 453], + [6, 8, 6, 6, 5, 27, 34, 16, 6, 453], + [25, 26, 25, 25, 25, 29, 34, 26, 25, 25], + [39, 8, 39, 39, 25, 35, 34, 39, 39, 39], + [40, 8, 42, 43, 25, 399, 34, 359, 359], + [44, 10, 44, 44, 458, 29, 34, 394, 48, 44], + [45, 10, 45, 45, 25, 29, 34, 394, 45, 382], + [46, 8, 47, 47, 25, 35, 34, 46, 46, 46], + [51, 8, 51, 51, 51, 35, 34, 51, 51, 51], + [149, 8, 149, 149, 25, 31, 34, 149, 149, 149], + [150, 8, 150, 150, 25, 32, 34, 150, 150, 150], + [182, 10, 182, 182, 25, 29, 34, 357, 182, 182], + [435, 8, 435, 435, 25, 27, 34, 435, 435, 435]] + new_pcs = random.sample(potential_pcs, 4) + + for index in [0, 1, 2, 3]: # Only randomize 4 main PCs + o = cls.get(index) + new_pc = new_pcs[index] + o.default = new_pc[0] + o.dead = new_pc[1] + o.ladder = new_pc[2] + o.rope = new_pc[3] + o.tiny = new_pc[5] + o.tiny_dead = new_pc[6] + o.robot = new_pc[4] + class MapEnemyObject(GridMixin, TableObject): flag = 'a' @@ -1072,6 +1621,18 @@ class MapEnemyObject(GridMixin, TableObject): rows = 160 columns = 128 + def serialize(self): + result = { + "index": self.index, + "xBounds": self.x_bounds, + "yBounds": self.y_bounds, + "caveRank": self.cave_rank, + "canonicalExit": self.canonical_exit.index if self.canonical_exit else None, + "enemyGroup": self.enemy_group, + "area": self.area.label + } + return result + def set_area(self, area): self._area = area @@ -1202,14 +1763,21 @@ def cave_sanitize_events(self): continue script.remove_exit_mouse_store() script.remove_encounters_off() + script.remove_status_effects_off() script.remove_teleports() script.remove_party_changes() + script.fix_hotels() def randomize(self): assert 'a' in get_flags() # ANCIENT CAVE + if not EnemyPlaceObject.recreated: + print "Recreating enemy placement groups..." + EnemyPlaceObject.recreate() + if self.cave_rank is None: + self.enemy_place_index = 0 return if not self.enemy_adjacent and random.random() > get_random_degree(): @@ -1250,6 +1818,44 @@ class MapMusicObject(GridMixin, TableObject): rows = 80 columns = 32 +class TeleportObject(TableObject): + flag = 'a' + + def cleanup(self): + if 'a' in get_flags(): + if self.index == 0x02: # Fourside hotel + self.x = 748 + self.y = 764 + if self.index == 0x0B: # Onett hotel + self.x = 1004 + self.y = 188 + if self.index == 0x11: # Threed hotel + self.x = 842 + self.y = 1164 + if self.index == 0x13: # Twoson hotel + self.x = 948 + self.y = 908 + if self.index == 0x27: # Happy Happy hotel + self.x = 879 + self.y = 206 + if self.index == 0x32: # Tenda hotel + self.x = 55 + self.y = 18 + if self.index == 0x0E: # Summers hotel + self.x = 838 + self.y = 1182 + if self.index == 0xA2: # Moonside hotel + self.x = 817 + self.y = 733 + if self.index == 0xC4: # Ness's house + self.x = 954 + self.y = 45 + if self.index == 0xE8: # Moonside skipping chests + self.x = TeleportObject.get(0x70).x + self.y = TeleportObject.get(0x70).y + if self.index == 0xE9: # Unused value - for testing + self.x = 492 + self.y = 1210 class ZoneMixin(GridMixin): rows = 40 @@ -1321,6 +1927,12 @@ def __init__(self): self.exits = [] self.optional = False + def __repr__(self): + s = "%s %6x - Rank %3d" % (self.__class__.__name__, self.index, self.rank or 0) + for o in self.exits: + s += "\n- %s" % o.door_description() + return s.strip() + def __eq__(self, other): return self.index == other.index @@ -1333,6 +1945,27 @@ def __lt__(self, other): assert type(self) is type(other) return self.rank < other.rank + def serialize(self): + result = { + "index": self.index, + "rank": self.rank, + "onShortestPath": self.on_shortest_path, + "explicitBounds": { + "x1": min([ec.x_bounds[0] for ec in self.explicit_enemy_cells]), + "x2": max([ec.x_bounds[1] for ec in self.explicit_enemy_cells]), + "y1": min([ec.y_bounds[0] for ec in self.explicit_enemy_cells]), + "y2": max([ec.y_bounds[1] for ec in self.explicit_enemy_cells]) + }, + "areaBounds": { + "x1": min([ec.x_bounds[0] for ec in self.enemy_cells]), + "x2": max([ec.x_bounds[1] for ec in self.enemy_cells]), + "y1": min([ec.y_bounds[0] for ec in self.enemy_cells]), + "y2": max([ec.y_bounds[1] for ec in self.enemy_cells]) + }, + "doors": self.exits + } + return result + def generate_image(self, filename=None, chosen_exit=None): from subprocess import call x1 = min([ec.x_bounds[0] for ec in self.enemy_cells]) @@ -1377,6 +2010,22 @@ def find_shortest_path(cls, home=None, goal=None): if goal in shortest_paths: return shortest_paths[goal] + [goal] + @classmethod + def mark_shortest_path(cls): + shortest_path = Cluster.find_shortest_path() + for (i, c) in enumerate(shortest_path): + c._on_shortest_path = True + try: + c2 = shortest_path[i+1] + for x in c.exits: + if c.get_connected_cluster(x) is c2: + x._on_shortest_path = True + break + else: + raise Exception("Break in path??") + except IndexError: + break + @classmethod def generate_map(cls): shortest_path = Cluster.find_shortest_path() @@ -1407,6 +2056,10 @@ def area(self): def enemy_cells(self): return self.area.enemy_cells + @property + def explicit_enemy_cells(self): + return [exit.enemy_cell for exit in self.exits] + @property def map_sprites(self): return self.area.map_sprites @@ -1420,6 +2073,12 @@ def rank(self): if hasattr(self, "_rank"): return self._rank return None + + @property + def on_shortest_path(self): + if hasattr(self, "_on_shortest_path"): + return self._on_shortest_path + return False @classproperty def ranked_clusters(self): @@ -1446,6 +2105,16 @@ def add_exit(self, s): if s.startswith("!"): force = True s = s[1:] + + incoming = False + if s.startswith("("): + incoming = True + s = s[1:] + + outgoing = False + if s.startswith(")"): + outgoing = True + s = s[1:] meid, x, y = map(lambda v: int(v, 0x10), s.split()) if not force: @@ -1467,6 +2136,8 @@ def add_exit(self, s): chosen.force = False if force: chosen.force = True + chosen._incoming_exit = incoming + chosen._outgoing_exit = outgoing self.exits.append(chosen) self.exits = sorted(self.exits, key=lambda x: x.pointer) @@ -1494,6 +2165,22 @@ def unassigned_exits(self): unassigned.append(x) return unassigned + @property + def unassigned_incoming_exits(self): + unassigned = self.unassigned_exits + incoming = [e for e in unassigned if e.incoming_exit] + if len(incoming) > 0: + return incoming + return unassigned + + @property + def unassigned_outgoing_exits(self): + unassigned = self.unassigned_exits + outgoing = [e for e in unassigned if e.outgoing_exit] + if len(outgoing) > 0: + return outgoing + return unassigned + @classmethod def rank_clusters(self): home = Cluster.home @@ -1650,6 +2337,19 @@ def completion_sample(stuff): sclusters = [mso.nearest_cluster for mso in sbosses] random.shuffle(sclusters) + # Remove the middle door from the Electro Specter cluster. + # This turns this cluster into a two-exit cluster, where the left exit + # arrives from the middle hole and departs from the left hole. + # This prevents non-Euclidian layouts and can guarantee the boss cannot be + # skipped if incoming-outgoing preferences are set on the remaining doors. + # This must be done after the above section to find the boss cluster. + electro_specter = [c for c in all_clusters if c.index == 0xf2696] + assert len(electro_specter) == 1 + electro_specter = electro_specter[0] + bad_exit = [e for e in electro_specter.exits if e.enemy_cell.index == 0x0138] + assert len(bad_exit) == 1 + electro_specter.exits.remove(bad_exit[0]) + checkpoints = [Cluster.home] + sclusters + [Cluster.goal] for c in checkpoints: all_clusters.remove(c) @@ -1725,8 +2425,8 @@ def completion_sample(stuff): assert bb in candidates assert bb in chosens assert bb.unassigned_exits - a, b = (random.choice(aa.unassigned_exits), - random.choice(bb.unassigned_exits)) + a, b = (random.choice(aa.unassigned_outgoing_exits), + random.choice(bb.unassigned_incoming_exits)) Cluster.assign_exit_pair(a, b) assert bb.unassigned_exits done = [aa, bb] @@ -1745,8 +2445,8 @@ def completion_sample(stuff): assert aa.unassigned_exits if len(candidates) == 1 and len(aa.unassigned_exits) == 1 and len(bb.unassigned_exits) == 1: raise Exception("Something weird here.") - a, b = (random.choice(aa.unassigned_exits), - random.choice(bb.unassigned_exits)) + a, b = (random.choice(aa.unassigned_outgoing_exits), + random.choice(bb.unassigned_incoming_exits)) Cluster.assign_exit_pair(a, b) assert aa in done done.append(bb) @@ -1817,6 +2517,7 @@ def completion_sample(stuff): s = Script(0x5e70b) lines = [] lines += [ + (0x04, 0x58, 0x00), # enable Winters phones (0x04, 0x62, 0x00), # enable home phone (0x04, 0x68, 0x00), # normal music in onett (0x04, 0xC7, 0x00), # know dad's phone number @@ -1824,6 +2525,9 @@ def completion_sample(stuff): (0x04, 0xC9, 0x00), # know escargo express phone number (0x04, 0xA6, 0x01), # daytime in onett (0x04, 0x05, 0x02), # turn on lights at home + (0x04, 0xD5, 0x01), # Mom heal part 1 + (0x04, 0x5E, 0x00), # Mom heal part 2 + (0x04, 0xAE, 0x00), # hole dug in dusty dunes #(0x04, 0x74, 0x01), # become robots @@ -1851,11 +2555,68 @@ def completion_sample(stuff): exit_mouse.write_script() print "Sanitizing cave events..." + # Special Events + # Giygas - flags necessary to be set upon entering for battle to function giygas_enter = Script.get_by_pointer(0x9af3a) assert tuple(giygas_enter.lines[0]) == ( 0x06, 0x49, 0x00, 0x2f, 0x99, 0xc9, 0x00) giygas_enter.lines[0] = (0x04, 0x74, 0x01) giygas_enter.write_script() + + # Mom in Ness's house - manual changes to get to heal state + mom_talk = Script.get_by_pointer(0x750e3) + assert tuple(mom_talk.lines[0]) == ( + 0x06, 0x49, 0x00, 0x7d, 0x54, 0xc7, 0x00) + mom_talk.lines[0] = (0x0a, 0x24, 0x51, 0xc7, 0x00) + mom_talk.write_script() + + # Chaos Theater - remove show, due to panning bug when PC sprites randomized + if 'p' in get_flags(): + chaos_show_trigger = Script.get_by_pointer(0x99fe0) + assert tuple(chaos_show_trigger.lines[0]) == ( + 0x06, 0x3f, 0x01, 0x2f, 0x99, 0xc9, 0x00) + chaos_show_trigger.lines[0] = (0x0a, 0x2f, 0x99, 0xc9, 0x00) + chaos_show_trigger.write_script() + + # Strong - prevent softlock + strong = TPTObject.get(71) + assert strong.address == 0xc7699e + strong.address = 0xc76b0b + + # Moonside right side #3 teleporter - return to central Moonside + teleporter = TPTObject.get(1383) + assert teleporter.address == 0xc96fe2 + teleporter.address = 0xc96e22 + + # Bubble Monkey - prevent joining + bubble_monkey = Script.get_by_pointer(0x6af6c) + assert tuple(bubble_monkey.lines[0]) == ( + 0x1D, 0x05, 0xFF, 0x68) + bubble_monkey.lines = bubble_monkey.lines[2:] + bubble_monkey.write_script() + + # Andonuts - prevent activating Sky Runner + andonuts_tpt = TPTObject.get(0x267) + assert andonuts_tpt.address == 0xc6b18d + andonuts_tpt.address = 0xc6b4bb + + # Big Foot - always appear + bigfoot = TPTObject.get(0x26b) + assert bigfoot.address == 0xc6504b + bigfoot.flag = TPTObject.get(0x05).flag + + # Shark guarding Frank - always fight + shark = TPTObject.get(0x1) + shark.address = shark.address + 14 + + # War against Giygas is over - go to credits + war_over = Script.get_by_pointer(0x9c293) + assert len(war_over.lines) == 273 + war_over.lines = war_over.lines[:69] + war_over.lines.append([0x1f, 0x00, 0x00, random.randint(1, 191)]) # Music + war_over.lines.append([0x1f, 0x41, 0x0c]) # Credits + war_over.lines.append(ccode_goto_address(0x9C96E)) # The End + war_over.write_script() #for meo in MapEnemyObject.every: # meo.cave_sanitize_events() @@ -1865,8 +2626,10 @@ def completion_sample(stuff): s = Script.get_by_pointer(pointer) s.remove_exit_mouse_store() s.remove_encounters_off() + s.remove_status_effects_off() s.remove_teleports() s.remove_party_changes() + s.fix_hotels() f.close() for s in Script._all_scripts: s.fulfill_scheduled_write() @@ -1889,7 +2652,7 @@ def replace_sanctuary_bosses(): bosses = sorted(bosses, key=lambda b: (b.rank, random.random(), b.index)) - BANNED = [0x1ce] # clumsy robot + BANNED = [0x1ce, 0x1c8] # clumsy robot and master belch bosses = [b for b in bosses if b.index not in BANNED] chosens = [] @@ -1919,6 +2682,9 @@ def replace_sanctuary_bosses(): class EnemyPlaceObject(TableObject): + recreated = False + _write_pointer = None + @classproperty def after_order(self): return [BattleEntryObject] @@ -1932,6 +2698,31 @@ def __repr__(self): for prob, beo in zip(self.odds[i], self.battle_entries[i]): s += "\n %s %s" % (prob, str(beo).replace("\n", "\n ")) return s.strip() + + def serialize(self): + if self.index == 0: + return None + result = { + "index": self.index, + "flag": self.event_flag, + "subgroups": [] + } + for i, rate in enumerate(self.sub_group_rates): + if rate == 0: + continue + subgroup = { + "subgroup": i, + "rate": rate, + "entries": [] + } + for prob, beo in zip(self.odds[i], self.battle_entries[i]): + subgroup["entries"].append({ + "probability": prob, + "enemyEncounter": beo + }) + result["subgroups"].append(subgroup) + return result + def read_data(self, filename, pointer=None): super(EnemyPlaceObject, self).read_data(filename, pointer) @@ -1956,27 +2747,97 @@ def read_data(self, filename, pointer=None): if sum(self.odds[i]) == 8: break f.close() + + def write_data(self, filename=None, pointer=None, syncing=False): + if not EnemyPlaceObject.recreated: + return + if EnemyPlaceObject._write_pointer is None: + EnemyPlaceObject._write_pointer = self.placement_group_pointer & 0x3FFFFF + else: + self.placement_group_pointer = EnemyPlaceObject._write_pointer | 0xC00000 + + f = open(filename, "r+b") + f.seek(EnemyPlaceObject._write_pointer) + write_multi(f, self.event_flag, length=2) + for rate in self.sub_group_rates: + f.write(chr(rate)) + for i, rate in enumerate(self.sub_group_rates): + if rate == 0: + continue + if sum(self.odds[i]) < 8: + raise Exception("Invalid subgroup odds") + for prob, battle_entry in zip(self.odds[i], self.battle_entries[i]): + f.write(chr(prob)) + write_multi(f, battle_entry.index, length=2) + + EnemyPlaceObject._write_pointer = f.tell() + if EnemyPlaceObject._write_pointer > 0x10c60d: + raise Exception("EnemyPlaceObject data too long") + f.close() + super(EnemyPlaceObject, self).write_data(filename, pointer, syncing) - @cached_property + + @classmethod + def recreate(cls): + # All of the potential sets of enemies used in the existing placements + mobs = set(reduce((lambda x, y: x + reduce(lambda a, b: a + b, y.itervalues(), [])), map(lambda x: x.battle_entries, EnemyPlaceObject.every), [])) + mobs.remove(BattleEntryObject.get(0x0)) + mobs = sorted(mobs, reverse=True) + butterfly = mobs.pop() + mobs = shuffle_normal(mobs) + butterfly_place = EnemyPlaceObject.valid_ranked_placements[0] + + for place in EnemyPlaceObject.every: + #Erase rank cache + place._rank = None + # Save the 0-index object as it is the magical no-enemy object + if place.index == 0: + continue + # Save the Magic Butterfly alone object + if place == butterfly_place: + continue + # Create a basic enemy placement. No flags, high chance of encounter, even odds of 4 enemies + place.event_flag = 0 + place.sub_group_rates[1] = 0 + if len(mobs) == 0: + place.odds = { 0: [], 1: [] } + place.sub_group_rates[0] = 0 + continue + + place.sub_group_rates[0] = random.randint(50,100) + place.odds = { 0: [2, 2, 2, 2], 1: [] } + place.battle_entries = defaultdict(list) + for i in xrange(4): + place.battle_entries[0].append(mobs.pop() if len(mobs) else butterfly) + + # Erase current rank info + EnemyPlaceObject._valid_ranked_placements = None + c = EnemyPlaceObject.ranked + EnemyPlaceObject.recreated = True + + @property def rank(self): + if hasattr(self, "_rank") and self._rank is not None: + return self._rank if self.index == 0: - return -1 + self._rank = -1 + return self.rank if 0 not in self.sub_group_rates or self.sub_group_rates[0] == 0: - return -1 - - #if sum(self.sub_group_rates.values()) == 0: - # return -1 + self._rank = -1 + return self.rank try: - return max([beo.rank for i in self.battle_entries.keys() + self._rank = max([beo.rank for i in self.battle_entries.keys() for beo in self.battle_entries[i]]) + return self.rank except ValueError: - return -1 + self._rank = -1 + return self.rank @classproperty def valid_ranked_placements(cls): - if hasattr(EnemyPlaceObject, "_valid_ranked_placements"): + if hasattr(EnemyPlaceObject, "_valid_ranked_placements") and EnemyPlaceObject._valid_ranked_placements is not None: return EnemyPlaceObject._valid_ranked_placements EnemyPlaceObject._valid_ranked_placements = [ @@ -1995,6 +2856,12 @@ def __repr__(self): s += "\n{0:0>2} {1}".format("%x" % a, e) return s.strip() + def serialize(self): + result = [] + for a, e in zip(self.activities, self.enemies): + result.append({ "activity": a, "enemy": e.name }) + return result + def read_data(self, filename, pointer=None): super(BattleEntryObject, self).read_data(filename, pointer) f = open(filename, "r+b") @@ -2048,6 +2915,9 @@ def is_key_item(self): @property def rank(self): + if self.index == 0: # 'Null' item has a cost so will get incorrectly ranked + return -1 + if self.is_key_item: if 'a' in get_flags() and not self.get_bit("nogive"): return 1000001 @@ -2077,11 +2947,42 @@ def limit_one(self): return True return False + + @property + def script_sources(self): + return [s for s in Script.every if self in s.items_given] + + @property + def chest_sources(self): + return [t for t in MapSpriteObject.every if t.is_chest and self == t.chest_contents] + + @property + def all_sources(self): + sources = self.script_sources + sources.extend(self.chest_sources) + return sources def cleanup(self): if 'a' in get_flags() and not ( self.is_sellable or self.get_bit("nogive")): self.price = max(self.price, 2) + + if "devmode" in get_activated_codes() and self.index == 0x9e: # spawn toggler + self.name_text = text_to_bytes("Spawn Toggler", 25) + toggle_script = Script.get_by_pointer(0x6fc94) + toggle_script.lines = [ + (0x01,), + (0x06, 0x0b, 0x00, 0xaf, 0xfc, 0xc6, 0x00), + text_to_values("@Spawns set off", False), + (0x04, 0x0b, 0x00), + (0x13,), + (0x02,), + text_to_values("@Spawns set on", False), + (0x05, 0x0b, 0x00), + (0x13,), + (0x02,), + ] + toggle_script.write_script() class ShopObject(TableObject): @@ -2134,6 +3035,33 @@ def mutate(self): self.item_ids = new_item_ids +class BgDataObject(TableObject): + flag = 'b' + flag_description = "backgrounds" + + def mutate(self): + matching_depths = [b for b in BgDataObject.every if b.color_depth == self.color_depth] + source = random.choice(matching_depths) + self.palette = source.old_data["palette"] + self.palette_cycle = source.old_data["palette_cycle"] + self.palette_cycle_1_begin = source.old_data["palette_cycle_1_begin"] + self.palette_cycle_1_end = source.old_data["palette_cycle_1_end"] + self.palette_cycle_2_begin = source.old_data["palette_cycle_2_begin"] + self.palette_cycle_2_end = source.old_data["palette_cycle_2_end"] + self.palette_changing_speed = source.old_data["palette_changing_speed"] + + source = random.choice(BgDataObject.every) + self.scrolling_movement_1 = source.old_data["scrolling_movement_1"] + self.scrolling_movement_2 = source.old_data["scrolling_movement_2"] + self.scrolling_movement_3 = source.old_data["scrolling_movement_3"] + self.scrolling_movement_4 = source.old_data["scrolling_movement_4"] + + source = random.choice(BgDataObject.every) + self.distortion_1 = source.old_data["distortion_1"] + self.distortion_2 = source.old_data["distortion_2"] + self.distortion_3 = source.old_data["distortion_3"] + self.distortion_4 = source.old_data["distortion_4"] + class ExperienceObject(TableObject): def cleanup(self): if 'a' in get_flags(): @@ -2182,12 +3110,14 @@ class EnemyObject(TableObject): "weakness_flash": None, "weakness_paralysis": None, "weakness_hypnosis": None, + "battle_palette": None, } intershuffle_attributes = [ - "hp", "pp", "xp", "money", "level", + "hp", "xp", "money", "level", "offense", "defense", "speed", "guts", "iq", "miss_rate", ("drop_item_index", "drop_frequency"), "status", "mirror_success_rate", + "battle_palette", ] randomize_attributes = [ #"order", @@ -2196,13 +3126,21 @@ class EnemyObject(TableObject): #("action1", "action2", "action3", "action4"), ] + def __repr__(self): + s = "%s %x %s" % (self.__class__.__name__, self.index, self.name) + return s.strip() + @property def is_boss(self): return self.boss_flag or self.death_sound + @property + def is_npc(self): + return not self.is_boss and self.out_of_battle_sprite == 0 + @property def intershuffle_valid(self): - return not self.is_boss + return not self.is_boss and not self.is_npc @property def name(self): @@ -2297,12 +3235,194 @@ def cleanup(self): self.add_item(0x11) # cracked bat self.add_item(0xC5) # exit mouse + if "devmode" in get_activated_codes() and self.index == 0: + self.add_item(0x9E) # spawn toggler + if "easymodo" in get_activated_codes(): self.level = 99 + self.add_item(0x01) # franklin badge + self.add_item(0x3E) # star pendant if self.index == 0: self.money = 65000 +class PsiAbilityObject(TableObject): + def cleanup(self): + if 'k' in get_flags() and 'a' not in get_flags() and self.name_index == 0x11 and self.greek_letter == 0x01: # Teleport Alpha + self.ness_level = 1 + +class PsiTeleportObject(TableObject): + flag = 'k' + flag_description = 'with keysanity mode' + _results = None + + @property + def name(self): + return bytes_to_text(self.name_text) + + def mutate(self): + if 'a' in get_flags(): + return # Disable Keysanity if Ancient Cave on + if len(self.name) > 0: + self.flag = 0xd9 # Pyramid entrance ready + if self.index == 13: # Add South Winters teleport + self.name_text = text_to_bytes("South Winters", 25) + self.x = 26 + self.y = 595 + self.flag = 0xd9 # Pyramid entrance ready + if self.index == 15: # Add North Onett teleport + self.name_text = text_to_bytes("North Onett", 25) + self.x = 322 + self.y = 54 + self.flag = 0xd9 # Pyramid entrance ready + + + @classmethod + def check_legal_keysanity(cls): + # Rather than a post-check, this would be better served as placing things logically in the first place. + # But this is sufficient for now. + if PsiTeleportObject._results is None: + return False + mapping = dict(PsiTeleportObject._results) + if mapping[ItemObject.get(0xb4)] is ItemObject.get(0x01): # Franklin badge at Wad of bills + return False + if mapping[ItemObject.get(0x01)] is ItemObject.get(0xb8): # Pencil eraser at Franklin badge + return False + if mapping[ItemObject.get(0xb4)] is ItemObject.get(0xb8): # Pencil eraser at Wad of bills + return False + if mapping[ItemObject.get(0xfd)] is ItemObject.get(0xb7): # Signed banana at Carrot key + return False + if mapping[ItemObject.get(0xfd)] is ItemObject.get(0xb6): # Diamond at Carrot key + return False + if mapping[ItemObject.get(0xa6)] is ItemObject.get(0xb6): # Diamond at King banana + return False + if mapping[ItemObject.get(0xb7)] is ItemObject.get(0xb6): # Diamond at Signed banana + return False + if mapping[ItemObject.get(0xd3)] is ItemObject.get(0xa4): # Shyness book at Tendakraut + return False + return True + + + @classmethod + def serialize(cls): + def serialize_result(result): + return { + "item": result[1].name, + "destination": result[0].name + } + return map(serialize_result, PsiTeleportObject._results) + + @classmethod + def intershuffle(cls): + cls.class_reseed("inter") + if 'a' in get_flags(): + return # Disable Keysanity if Ancient Cave on + key_items_index = [ + 0x01, # Franklin badge + #0x69, # Jar of Fly Honey - Chest handled differently, at 0x7dacb. + 0xa4, # Shyness book + 0xa6, # King banana + 0xaa, # Key to the shack + 0xaf, # Hawk eye + 0xb0, # Bicycle + 0xb4, # Wad of bills + 0xb6, # Diamond + 0xb7, # Signed banana + 0xb8, # Pencil eraser + 0xc0, # Key to the tower + 0xca, # Town map + 0xcc, # Suporma + 0xd3, # Tendakraut + 0xfd, # Carrot key + ] + # TODO: Cache this when final key items list decided. + a = [me for me in MapEventObject.every] + b = [ms for ms in MapSpriteObject.every] + for o in a + b: + o.script + Script.get_by_pointer(0x9d95e) # Tendakraut + + source_items = map(lambda x: ItemObject.get(x), key_items_index) + destination_sets = map(lambda x: x.all_sources, source_items) + new_items = list(source_items) + + new_items.remove(ItemObject.get(0xd3)) # Tendakraut + new_items.append(ItemObject.get(0x69)) # Jar of Fly Honey + new_items.remove(ItemObject.get(0xcc)) # Suporma + new_items.append(ItemObject.get(0xc1)) # Meteorite piece + + while not cls.check_legal_keysanity(): + random.shuffle(new_items) + PsiTeleportObject._results = zip(source_items, new_items) + + for (source_item, new_item, destination_set) in zip(source_items, new_items, destination_sets): + for destination in destination_set: + destination.replace_item(source_item, new_item) + destination.mutated = True + + + @classmethod + def full_cleanup(cls): + if 'a' in get_flags(): + print "WARNING: Keysanity and Ancient Cave modes are incompatible. Keysanity has been disabled." + super(PsiTeleportObject, cls).full_cleanup() + return + + if 'k' not in get_flags(): + super(PsiTeleportObject, cls).full_cleanup() + return + + # Patch Bubble Monkey rope interaction + bubble_monkey_rope = Script.get_by_pointer(0x97f72) + lines = bubble_monkey_rope.lines + bubble_monkey_rope.lines = lines[:1] + lines[-2:] + bubble_monkey_rope.write_script() + + # Patch intro script to set all teleports available immediately + intro = Script.get_by_pointer(0x5e70b) + patch_lines = intro.lines[:2] + [ + (0x04, 0xd9, 0x00), # Enable Pyramid entrance and all teleports + (0x04, 0x8c, 0x00), # Enable Venus giving item + (0x02, )] + patch = Script.write_new_script(patch_lines) + assert patch.length == 13 + intro.lines = [ccode_call_address(patch.pointer)] + [(0x00, )] + intro.lines[2:] + intro.write_script() + + # Patch Montague to always show up + montague = TPTObject.get(0x2f8) + assert montague.address == 0xc60349 + montague.flag = 0 + + # Patch Mr Spoon to request autograph even after he's received it + spoon = TPTObject.get(0x38d) + #assert spoon.address == 0xc826bc - could be changed in Dialog shuffle + spoon.address = 0xc82468 + + # Patch Bubble Monkey to appear at north shore as soon as he runs off with his gal + monkey = Script.get_by_pointer(0x882bd) + patch_lines = monkey.lines[:2] + [(0x04, 0x76, 0x02), (0x02, )] # Enable Monkey at north shore + patch = Script.write_new_script(patch_lines) + assert patch.length == 9 + monkey.lines = [ccode_call_address(patch.pointer)] + monkey.lines[2:] + monkey.write_script() + + # Patch Dr Andonuts to recognize Ness isn't Jeff + andonuts = Script.get_by_pointer(0x6b18d) + patch_lines = andonuts.lines[:2] + [ # check the normal flags first + (0x19, 0x10, 0x01), # check character in slot 1 + (0x0b, 0x03), # is it Jeff? + (0x1b, 0x03) + ccode_address(0x6b18d), # go to normal andonuts text + ccode_goto_address(0x6b56e), # go to generic text for Ness + (0x02, )] + patch = Script.write_new_script(patch_lines) + andonuts_tpt = TPTObject.get(0x267) + assert andonuts_tpt.address == 0xc6b18d + andonuts_tpt.address = 0xc00000 + patch.pointer + + super(PsiTeleportObject, cls).full_cleanup() + + if __name__ == "__main__": try: print ("You are using the Earthbound Ancient Cave " @@ -2317,16 +3437,41 @@ def cleanup(self): "easymodo": ["easymodo"], "mapper": ["mapper"], "giygastest": ["giygastest"], + "funsize": ["funsize"], + "wildgifts": ["wildgifts"], + "devmode": ["devmode"] } run_interface(ALL_OBJECTS, snes=True, codes=codes) - hexify = lambda x: "{0:0>2}".format("%x" % x) - numify = lambda x: "{0: >3}".format(x) - minmax = lambda x: (min(x), max(x)) - clean_and_write(ALL_OBJECTS) rewrite_snes_meta("EB-AC", VERSION, lorom=False) + # Spoiler / Map data generation + print("Making spoiler/map file...") + spoiler_file = open((get_outfile()[:-4] + ".spoiler.json"), "w") + spoiler_object = { + "info": { + "version": VERSION, + "seed": get_seed(), + "flags": get_flags(), + "codes": get_activated_codes(), + "timestamp": int(time() * 1000) + }, + "chests": [m for m in MapSpriteObject.every if m.is_chest] + } + if 'k' in get_flags() and 'a' not in get_flags(): + spoiler_object["keysanity"] = PsiTeleportObject.serialize() + if 'a' in get_flags(): + Cluster.mark_shortest_path() + if 'a' in get_flags() or "devmode" in get_activated_codes(): + spoiler_object["clusters"] = Cluster.generate_clusters() + spoiler_object["bosses"] = [mso for mso in MapSpriteObject.every if mso.index in SANCTUARY_BOSS_INDEXES] + if "devmode" in get_activated_codes(): + # This adds multiple MB to the file and is only useful for developers, so do not generate it by default. + spoiler_object["enemies"] = MapEnemyObject.every + json.dump(spoiler_object, spoiler_file, default=(lambda x: x.serialize())) + spoiler_file.close() + if "mapper" in get_activated_codes(): Cluster.generate_map() diff --git a/tables/areas.txt b/tables/areas.txt index 1260c0a..88c7892 100644 --- a/tables/areas.txt +++ b/tables/areas.txt @@ -1249,7 +1249,7 @@ .... 443c 4440 .... 453c 4540 -:TRILLIONAGE SPROUT DUNGEON? +:THUNDER AND STORM DEAD END WITH GIFTS 4240 4244 4340 4344 .... 4444 diff --git a/tables/bg_data_table.txt b/tables/bg_data_table.txt new file mode 100644 index 0000000..636ae6d --- /dev/null +++ b/tables/bg_data_table.txt @@ -0,0 +1,17 @@ +graphics_arrangement,1 +palette,1 +color_depth,1 +palette_cycle,1 +palette_cycle_1_begin,1 +palette_cycle_1_end,1 +palette_cycle_2_begin,1 +palette_cycle_2_end,1 +palette_changing_speed,1 +scrolling_movement_1,1 +scrolling_movement_2,1 +scrolling_movement_3,1 +scrolling_movement_4,1 +distortion_1,1 +distortion_2,1 +distortion_3,1 +distortion_4,1 \ No newline at end of file diff --git a/tables/exits.txt b/tables/exits.txt index d8179f9..e664294 100644 --- a/tables/exits.txt +++ b/tables/exits.txt @@ -62,12 +62,12 @@ 0d87 01c8 06d8 :0030 ELECTRO SPECTER SANCTUARY BOSS CAVE -!0131 0c58 0088 -!0131 0c60 0088 -0138 0e38 00a8 -0139 0e40 00a8 -013c 0f08 0088 -013c 0f10 0088 +!)0131 0c58 0088 +!)0131 0c60 0088 +)0138 0e38 00a8 +)0139 0e40 00a8 +(013c 0f08 0088 +(013c 0f10 0088 0331 0c58 0188 0331 0c60 0188 @@ -97,8 +97,8 @@ 0747 11d0 0390 :004c CAVE WITH SANCTUARY BOSS AND BUBBLE MONKEY ROPE -01cc 1328 00e0 -0150 1410 00a0 +(01cc 1328 00e0 +)0150 1410 00a0 0353 14d0 01a0 @@ -260,9 +260,9 @@ .07fc 1f28 03c8 :0834 MOLE PLAYING ROUGH LAIR -08be 0f88 0450 -08be 0f90 0450 -0d30 0c28 0688 +)08be 0f88 0450 +)08be 0f90 0450 +(0d30 0c28 0688 :0648 PEACEFUL REST VALLEY 09da 16b8 04c8 @@ -418,9 +418,9 @@ 1a08 0238 0d10 :1010 TITANIC ANT ROOM -1090 0438 0850 -1091 0440 0850 -1391 0470 09c8 +)1090 0438 0850 +)1091 0440 0850 +(1391 0470 09c8 :1030 LIER X. AGERATE TUNNEL 10b1 0c58 0848 @@ -439,9 +439,9 @@ 16b9 0e68 0b70 :106c TRILLIONAGE SPROUT ROOM -10ed 1b48 0860 -11f2 1ca8 08f0 -11f2 1cb0 08f0 +(10ed 1b48 0860 +)11f2 1ca8 08f0 +)11f2 1cb0 08f0 :1074 .10f7 1df0 0868 @@ -529,12 +529,12 @@ 1878 1e28 0c08 :1830 PLAGUE RAT ROOM -1930 0c00 0cb0 -1930 0c00 0cb8 -19b0 0c00 0cd0 -19b0 0c00 0cd8 -1837 0dd8 0c18 -1935 0d48 0c98 +(1930 0c00 0cb0 +(1930 0c00 0cb8 +(19b0 0c00 0cd0 +(19b0 0c00 0cd8 +)1837 0dd8 0c18 +(1935 0d48 0c98 :183c CRYPT STAIRS #18bc 0f08 0c48 @@ -891,9 +891,9 @@ #2977 1dc0 1480 :2a20 THUNDER AND STORM SANCTUARY BOSS? -2b20 0808 1588 -!2ba6 09a8 15c8 -!2ba6 09b0 15c8 +)2b20 0808 1588 +!(2ba6 09a8 15c8 +!(2ba6 09b0 15c8 :2a28 MASTER BELCH'S BASE 2b2c 0b30 1588 @@ -931,6 +931,7 @@ 2a4b 12c8 1538 2a4b 12d0 1538 2ac9 1258 1558 + 2b4f 13f0 15a8 :2a50 @@ -1165,9 +1166,9 @@ 2ffb 1ef0 17e8 :3130 FIRE SPRINGS MAIN ROOM -31b4 0d08 18d0 -31b4 0d10 18d0 -3331 0c70 1990 +)31b4 0d08 18d0 +)31b4 0d10 18d0 +(3331 0c70 1990 3334 0d10 1990 diff --git a/tables/instructions.txt b/tables/instructions.txt index 6c4e740..8afeab9 100644 --- a/tables/instructions.txt +++ b/tables/instructions.txt @@ -1,216 +1,216 @@ -00 : Line Break -01 : Start on Blank Line -02 : Stop Parsing -03 : Halt Parsing with Prompt - Variable -04 XX XX : Toggle On Event Flag -05 XX XX : Toggle Off Event Flag -06 XX XX YY YY YY YY : Event-Dependent Pointer -07 XX XX : Return Event Flag -08 XX XX XX XX : Reference Pointer -09 XX [YY YY YY YY] : Multiple-Address Jump Pointer -0A XX XX XX XX : Jump Pointer -0B XX : Perform Boolean True Check - Equal to Working Memory -0C XX : Perform Boolean False Check - Equal to Working Memory -0D XX : Copy to Argumentative Memory -0E XX : Store to Secondary Memory -0F : Increment Secondary Memory -10 XX : Delay Parsing -11 : Create Selection Menu from Strings -12 : Clear Text Line -13 : Halt Parsing without Prompt -14 : Halt Parsing with Prompt -15 XX : Display Compressed Text/Bank 0 -16 XX : Display Compressed Text/Bank 1 -17 XX : Display Compressed Text/Bank 2 -18 00 : Close Current Window -18 01 XX : Open Text Window -18 02 : UNKNOWN -18 03 XX : Switch To Window -18 04 : Close All Windows -18 05 XX YY : Force Text Alignment -18 06 : Clear Current Window -18 07 XX XX XX XX YY : UNKNOWN -18 08 XX XX XX : UNKNOWN -18 09 XX XX XX : UNKNOWN -18 0A : Show Wallet Window -18 0D XX XX : UNKNOWN -19 02 : Load String to Memory -19 04 : UNKNOWN -19 05 XX YY YY : Inflict Status Change -19 10 XX : Return Character Number -19 11 XX : Return One Letter from a Character's Name -19 14 : UNKNOWN -19 16 XX YY : Return Byte YY of Character's Status -19 18 XX : UNKNOWN -19 19 00 00 : Pass Item Number to Working Memory -19 1A XX : UNKNOWN -19 1B XX : UNKNOWN -19 1C XX YY : UNKNOWN -19 1D XX YY : UNKNOWN -19 1E : UNKNOWN -19 1F : UNKNOWN -19 20 : UNKNOWN -19 21 XX : UNKNOWN -19 22 XX YY ZZ ZZ : Return Direction from Character to Object? -19 23 XX XX YY YY : Return Direction from NPC to Object? -19 24 XX XX YY YY : Return Direction from Sprite to Object? -19 25 XX : UNKNOWN -19 26 XX : UNKNOWN -19 27 XX : UNKNOWN -19 28 XX : UNKNOWN -1A 00 : UNKNOWN INCOMPLETE -1A 01 WW WW WW WW XX XX XX XX YY YY YY YY ZZ ZZ ZZ ZZ : Current Party Member Selection Menu -1A 04 : UNKNOWN INCOMPLETE -1A 05 XX YY : Display the inventory of character YY in window XX. -1A 06 XX : Display Shop Menu -1A 07 : Related to Escargo Express stored goods window -1A 08 : UNKNOWN INCOMPLETE -1A 09 : UNKNOWN INCOMPLETE -1A 0A : Open Phone Menu - Dummy? -1A 0B : UNKNOWN -1B 00 : Copy Active Memory to Storage -1B 01 : Copy Storage to Active Memory -1B 02 XX XX XX XX : Boolean-False Pointer -1B 03 XX XX XX XX : Boolean-True Pointer -1B 04 : Swap Working and Argumentative Memory -1B 05 : Copy Active Memory to WRAM -1B 06 : Copy WRAM to Active Memory -1C 00 XX : Text Color Effects -1C 01 XX : Display Statistics -1C 02 XX : Display Character Name -1C 03 XX : Display Text Character -1C 04 : Open HP/PP Windows -1C 05 XX : Display Item Name -1C 06 XX : Display Teleport Destination Name -1C 07 XX : Display Text Strings Horizontally -1C 08 XX : Display Text Graphics -1C 09 : UNKNOWN -1C 0A XX XX XX XX : Display Numeric Value -1C 0B XX XX XX XX : Display Numeric Value as Money -1C 0C XX : Display Text Strings Vertically -1C 0D : Display Action User Name -1C 0E : Display Action Target Name -1C 0F : UNKNOWN -1C 11 XX : UNKNOWN -1C 12 XX : Display PSI Name -1C 13 XX YY : Display Battle Animation -1C 14 XX : UNKNOWN -1C 15 XX : UNKNOWN -1D 00 XX YY : Give Item to Character and Return Recipient -1D 01 XX YY : Take Item from Character -1D 02 XX : Perform Boolean False Check - Inventory Capacity -1D 03 XX : Perform Boolean True Check - Inventory Vacancy -1D 04 XX YY : Perform Boolean True Check - Absence of Item -1D 05 XX YY : Perform Boolean True Check - Presence of Item -1D 06 XX XX XX XX : Increase ATM Balance -1D 07 XX XX XX XX : Decrease ATM Balance -1D 08 XX XX : Increase Wallet Balance -1D 09 XX XX : Decrease Wallet Balance -1D 0A XX : Return Price of Item -1D 0B XX : Return Selling Price ofItem -1D 0C XX XX : UNKNOWN -1D 0D XX YY YY : Check for Status Ailment -1D 0E XX YY : Give Item to Character, Return Recipient and Number of Items -1D 0F XX XX : UNKNOWN -1D 10 XX XX : UNKNOWN -1D 11 XX XX : UNKNOWN -1D 12 XX XX : UNKNOWN -1D 13 XX XX : UNKNOWN -1D 14 XX XX XX XX : (Check for Cash on Hand) *True if X<$ -1D 15 XX XX : UNKNOWN -1D 17 XX XX XX XX : Check for Cash in ATM -1D 18 XX : UNKNOWN -1D 19 XX : (Boolean Check for Number of Party Members) NOTE - Teddy doesn't count! -1D 20 : Check for User Targeting Self -1D 21 XX : Generate Random Number -1D 22 : Check for Exit Mouse Compatibility -1D 23 XX : UNKNOWN -1D 24 XX : "UNKNOWN" (Return Cash Earned Since Last Call?) -1E 00 XX YY : Recover HP by Percent -1E 01 XX YY : Deplete HP by Percent -1E 02 XX YY : Recover HP by Amount -1E 03 XX YY : Deplete HP by Amount -1E 04 XX YY : Restore PP by Percent -1E 05 XX YY : Consume PP by Percent -1E 06 XX YY : Restore PP by Amount -1E 07 XX YY : Consume PP by Amount -1E 08 XX YY : Change Character Level Stat -1E 09 XX YY YY YY : Boost Experience Stat -1E 0A XX YY : Boost IQ Stat -1E 0B XX YY : Boost Guts Stat -1E 0C XX YY : Boost Speed Stat -1E 0D XX YY : Boost Vitality Stat -1E 0E XX YY : Boost Luck Stat -1F 00 XX YY : Play Music Track -1F 01 XX : "UNKNOWN" (Stop Music) -1F 02 XX : Play Sound Effect -1F 03 : Restore Default Music -1F 04 XX : Toggle Text Printing Sound -1F 05 : Disallow Sector Boundaries to Change Music -1F 06 : Allow Sector Boundaries to Change Music -1F 07 XX : Apply Music Effect -1F 11 XX : Add Party Member -1F 12 XX : Remove Party Member -1F 13 XX YY : Change Direction of Character -1F 14 XX : UNKNOWN -1F 15 XX XX YY YY ZZ : Generate Active Sprite -1F 16 XX XX YY : Change Direction of TPT Entry -1F 17 XX XX YY YY ZZ : Generate Active TPT Entry -1F 18 XX XX XX XX XX XX XX : UNKNOWN -1F 19 XX XX XX XX XX XX XX : UNKNOWN -1F 1A XX XX YY : Generate Floating Sprite near TPT Entry -1F 1B XX XX : Delete Floating Sprite - TPT -1F 1C XX YY : Generate Floating Sprite near Character -1F 1D XX : Delete Floating Sprite - Character -1F 1E XX XX YY : Delete TPT Entry from Screen -1F 1F XX XX YY : Delete Generated Sprite from Screen -1F 20 XX YY : Trigger PSI-style Teleport -1F 21 XX : Teleport to Preset Coordinates -1F 23 XX XX : Trigger Battle Scene -1F 30 : Set Normal Font -1F 31 : Set Mr. Saturn Font -1F 41 XX : Trigger Special Event -1F 50 : Disable Controller Input -1F 51 : Enable Controller Input -1F 52 XX : Generate Number Selector -1F 60 XX : UNKNOWN -1F 61 : Movement Code Trigger -1F 62 XX : UNKNOWN -1F 63 XX XX XX XX : Screen-Reload Pointer -1F 64 : Purge All NPCs -1F 65 : Purge First NPC -1F 66 XX YY ZZ ZZ ZZ ZZ : Activate Map Hotspot -1F 67 XX : Deactivate Map Hotspot -1F 68 : Store Coordinates to Memory -1F 69 : Teleport to Stored Coordinates -1F 71 XX YY : Realize PSI Power -1F 81 XX YY : Check If Character Can Use Item -1F 83 XX YY : Equip character XX with his or her YYth item -1F 90 : UNKNOWN -1F A0 : Change Direction of Current TPT Entry to Up -1F A1 : Change Direction of Current TPT Entry to Down -1F A2 : UNKNOWN -1F B0 : Save the Game -1F C0 XX YY YY YY YY : Multiple-entry pointer table (Reference address) -1F D0 XX : Attempt to Fix Items -1F D1 : Return Direction of Nearby Magic Truffle -1F D2 XX : Summon Traveling Photographer -1F D3 XX : Trigger Timed Event -1F E1 XX YY ZZ : Change Map Pallet -1F E4 XX XX YY : Change Direction of Generated Sprite -1F E5 XX : Lock Player Movement -1F E6 XX XX : Delay Appearance of TPT Entry -1F E7 XX XX : UNKNOWN -1F E8 XX : UNKNOWN -1F E9 XX XX : UNKNOWN -1F EA XX XX : UNKNOWN -1F EB XX YY : Render Character Invisible -1F EC XX YY : Render Character Visible -1F ED : UNKNOWN -1F EE XX XX : UNKNOWN -1F EF XX XX : UNKNOWN -1F F0 : Activate Bicycle -1F F1 XX XX YY YY : Give TPT Entry a New Movement Pattern -1F F2 XX XX YY YY : Give Sprite a New Movement Pattern -1F F3 XX XX YY : Generate Floating Sprite Near Generated Sprite -1F F4 XX XX : Delete Floating Sprite - Generated Sprite +!00 : Line Break +!01 : Start on Blank Line +!02 : Stop Parsing +!03 : Halt Parsing with Prompt - Variable +_04 XX XX : Toggle On Event Flag +_05 XX XX : Toggle Off Event Flag +!06 XX XX YY YY YY YY : Event-Dependent Pointer +!07 XX XX : Return Event Flag +!08 XX XX XX XX : Reference Pointer +_09 XX [YY YY YY YY] : Multiple-Address Jump Pointer +!0A XX XX XX XX : Jump Pointer +!0B XX : Perform Boolean True Check - Equal to Working Memory +!0C XX : Perform Boolean False Check - Equal to Working Memory +_0D XX : Copy to Argumentative Memory +_0E XX : Store to Secondary Memory +_0F : Increment Secondary Memory +!10 XX : Delay Parsing +_11 : Create Selection Menu from Strings +!12 : Clear Text Line +!13 : Halt Parsing without Prompt +!14 : Halt Parsing with Prompt +!15 XX : Display Compressed Text/Bank 0 +!16 XX : Display Compressed Text/Bank 1 +!17 XX : Display Compressed Text/Bank 2 +_18 00 : Close Current Window +_18 01 XX : Open Text Window +_18 02 : UNKNOWN +_18 03 XX : Switch To Window +_18 04 : Close All Windows +_18 05 XX YY : Force Text Alignment +_18 06 : Clear Current Window +_18 07 XX XX XX XX YY : UNKNOWN +_18 08 XX XX XX : UNKNOWN +_18 09 XX XX XX : UNKNOWN +_18 0A : Show Wallet Window +_18 0D XX XX : UNKNOWN +_19 02 : Load String to Memory +_19 04 : UNKNOWN +_19 05 XX YY YY : Inflict Status Change +!19 10 XX : Return Character Number +!19 11 XX : Return One Letter from a Character's Name +_19 14 : UNKNOWN +_19 16 XX YY : Return Byte YY of Character's Status +_19 18 XX : UNKNOWN +_19 19 00 00 : Pass Item Number to Working Memory +_19 1A XX : UNKNOWN +_19 1B XX : UNKNOWN +_19 1C XX YY : UNKNOWN +_19 1D XX YY : UNKNOWN +_19 1E : UNKNOWN +_19 1F : UNKNOWN +_19 20 : UNKNOWN +_19 21 XX : UNKNOWN +_19 22 XX YY ZZ ZZ : Return Direction from Character to Object? +_19 23 XX XX YY YY : Return Direction from NPC to Object? +_19 24 XX XX YY YY : Return Direction from Sprite to Object? +_19 25 XX : UNKNOWN +_19 26 XX : UNKNOWN +_19 27 XX : UNKNOWN +_19 28 XX : UNKNOWN +_1A 00 : UNKNOWN INCOMPLETE +_1A 01 WW WW WW WW XX XX XX XX YY YY YY YY ZZ ZZ ZZ ZZ : Current Party Member Selection Menu +_1A 04 : UNKNOWN INCOMPLETE +_1A 05 XX YY : Display the inventory of character YY in window XX. +_1A 06 XX : Display Shop Menu +_1A 07 : Related to Escargo Express stored goods window +_1A 08 : UNKNOWN INCOMPLETE +_1A 09 : UNKNOWN INCOMPLETE +_1A 0A : Open Phone Menu - Dummy? +_1A 0B : UNKNOWN +!1B 00 : Copy Active Memory to Storage +!1B 01 : Copy Storage to Active Memory +!1B 02 XX XX XX XX : Boolean-False Pointer +!1B 03 XX XX XX XX : Boolean-True Pointer +!1B 04 : Swap Working and Argumentative Memory +_1B 05 : Copy Active Memory to WRAM +_1B 06 : Copy WRAM to Active Memory +_1C 00 XX : Text Color Effects +_1C 01 XX : Display Statistics +_1C 02 XX : Display Character Name +_1C 03 XX : Display Text Character +_1C 04 : Open HP/PP Windows +!1C 05 XX : Display Item Name +_1C 06 XX : Display Teleport Destination Name +_1C 07 XX : Display Text Strings Horizontally +_1C 08 XX : Display Text Graphics +_1C 09 : UNKNOWN +_1C 0A XX XX XX XX : Display Numeric Value +_1C 0B XX XX XX XX : Display Numeric Value as Money +_1C 0C XX : Display Text Strings Vertically +_1C 0D : Display Action User Name +_1C 0E : Display Action Target Name +_1C 0F : UNKNOWN +_1C 11 XX : UNKNOWN +_1C 12 XX : Display PSI Name +_1C 13 XX YY : Display Battle Animation +_1C 14 XX : UNKNOWN +_1C 15 XX : UNKNOWN +_1D 00 XX YY : Give Item to Character and Return Recipient +_1D 01 XX YY : Take Item from Character +!1D 02 XX : Perform Boolean False Check - Inventory Capacity +!1D 03 XX : Perform Boolean True Check - Inventory Vacancy +!1D 04 XX YY : Perform Boolean True Check - Absence of Item +!1D 05 XX YY : Perform Boolean True Check - Presence of Item +_1D 06 XX XX XX XX : Increase ATM Balance +_1D 07 XX XX XX XX : Decrease ATM Balance +_1D 08 XX XX : Increase Wallet Balance +_1D 09 XX XX : Decrease Wallet Balance +!1D 0A XX : Return Price of Item +!1D 0B XX : Return Selling Price ofItem +_1D 0C XX XX : UNKNOWN +!1D 0D XX YY YY : Check for Status Ailment +_1D 0E XX YY : Give Item to Character, Return Recipient and Number of Items +_1D 0F XX XX : UNKNOWN +_1D 10 XX XX : UNKNOWN +_1D 11 XX XX : UNKNOWN +_1D 12 XX XX : UNKNOWN +_1D 13 XX XX : UNKNOWN +!1D 14 XX XX XX XX : (Check for Cash on Hand) *True if X<$ +_1D 15 XX XX : UNKNOWN +!1D 17 XX XX XX XX : Check for Cash in ATM +_1D 18 XX : UNKNOWN +!1D 19 XX : (Boolean Check for Number of Party Members) NOTE - Teddy doesn't count! +_1D 20 : Check for User Targeting Self +_1D 21 XX : Generate Random Number +_1D 22 : Check for Exit Mouse Compatibility +_1D 23 XX : UNKNOWN +_1D 24 XX : "UNKNOWN" (Return Cash Earned Since Last Call?) +_1E 00 XX YY : Recover HP by Percent +_1E 01 XX YY : Deplete HP by Percent +_1E 02 XX YY : Recover HP by Amount +_1E 03 XX YY : Deplete HP by Amount +_1E 04 XX YY : Restore PP by Percent +_1E 05 XX YY : Consume PP by Percent +_1E 06 XX YY : Restore PP by Amount +_1E 07 XX YY : Consume PP by Amount +_1E 08 XX YY : Change Character Level Stat +_1E 09 XX YY YY YY : Boost Experience Stat +_1E 0A XX YY : Boost IQ Stat +_1E 0B XX YY : Boost Guts Stat +_1E 0C XX YY : Boost Speed Stat +_1E 0D XX YY : Boost Vitality Stat +_1E 0E XX YY : Boost Luck Stat +_1F 00 XX YY : Play Music Track +_1F 01 XX : "UNKNOWN" (Stop Music) +!1F 02 XX : Play Sound Effect +_1F 03 : Restore Default Music +_1F 04 XX : Toggle Text Printing Sound +_1F 05 : Disallow Sector Boundaries to Change Music +_1F 06 : Allow Sector Boundaries to Change Music +_1F 07 XX : Apply Music Effect +_1F 11 XX : Add Party Member +_1F 12 XX : Remove Party Member +_1F 13 XX YY : Change Direction of Character +_1F 14 XX : UNKNOWN +_1F 15 XX XX YY YY ZZ : Generate Active Sprite +_1F 16 XX XX YY : Change Direction of TPT Entry +_1F 17 XX XX YY YY ZZ : Generate Active TPT Entry +_1F 18 XX XX XX XX XX XX XX : UNKNOWN +_1F 19 XX XX XX XX XX XX XX : UNKNOWN +_1F 1A XX XX YY : Generate Floating Sprite near TPT Entry +_1F 1B XX XX : Delete Floating Sprite - TPT +_1F 1C XX YY : Generate Floating Sprite near Character +_1F 1D XX : Delete Floating Sprite - Character +_1F 1E XX XX YY : Delete TPT Entry from Screen +_1F 1F XX XX YY : Delete Generated Sprite from Screen +_1F 20 XX YY : Trigger PSI-style Teleport +_1F 21 XX : Teleport to Preset Coordinates +_1F 23 XX XX : Trigger Battle Scene +_1F 30 : Set Normal Font +_1F 31 : Set Mr. Saturn Font +_1F 41 XX : Trigger Special Event +_1F 50 : Disable Controller Input +_1F 51 : Enable Controller Input +_1F 52 XX : Generate Number Selector +_1F 60 XX : UNKNOWN +_1F 61 : Movement Code Trigger +_1F 62 XX : UNKNOWN +_1F 63 XX XX XX XX : Screen-Reload Pointer +_1F 64 : Purge All NPCs +_1F 65 : Purge First NPC +_1F 66 XX YY ZZ ZZ ZZ ZZ : Activate Map Hotspot +_1F 67 XX : Deactivate Map Hotspot +_1F 68 : Store Coordinates to Memory +_1F 69 : Teleport to Stored Coordinates +_1F 71 XX YY : Realize PSI Power +_1F 81 XX YY : Check If Character Can Use Item +_1F 83 XX YY : Equip character XX with his or her YYth item +_1F 90 : UNKNOWN +_1F A0 : Change Direction of Current TPT Entry to Up +_1F A1 : Change Direction of Current TPT Entry to Down +_1F A2 : UNKNOWN +_1F B0 : Save the Game +_1F C0 XX YY YY YY YY : Multiple-entry pointer table (Reference address) +_1F D0 XX : Attempt to Fix Items +_1F D1 : Return Direction of Nearby Magic Truffle +_1F D2 XX : Summon Traveling Photographer +_1F D3 XX : Trigger Timed Event +_1F E1 XX YY ZZ : Change Map Pallet +_1F E4 XX XX YY : Change Direction of Generated Sprite +_1F E5 XX : Lock Player Movement +_1F E6 XX XX : Delay Appearance of TPT Entry +_1F E7 XX XX : UNKNOWN +_1F E8 XX : UNKNOWN +_1F E9 XX XX : UNKNOWN +_1F EA XX XX : UNKNOWN +_1F EB XX YY : Render Character Invisible +_1F EC XX YY : Render Character Visible +_1F ED : UNKNOWN +_1F EE XX XX : UNKNOWN +_1F EF XX XX : UNKNOWN +_1F F0 : Activate Bicycle +_1F F1 XX XX YY YY : Give TPT Entry a New Movement Pattern +_1F F2 XX XX YY YY : Give Sprite a New Movement Pattern +_1F F3 XX XX YY : Generate Floating Sprite Near Generated Sprite +_1F F4 XX XX : Delete Floating Sprite - Generated Sprite \ No newline at end of file diff --git a/tables/meo_canonical_exits.txt b/tables/meo_canonical_exits.txt index 75295ab..ca0a29e 100644 --- a/tables/meo_canonical_exits.txt +++ b/tables/meo_canonical_exits.txt @@ -48,14 +48,14 @@ f 6a 2f 6f 30 b4 31 b4 -32 b -33 b -34 b -35 b -36 b -37 b -38 b -39 b +32 d +33 d +34 d +35 d +36 d +37 d +38 d +39 d 3a d 3b d 3c d @@ -176,14 +176,14 @@ ae 6f af 6f b0 b4 b1 b4 -b2 b -b3 b -b4 b -b5 b -b6 b -b7 b -b8 b -b9 b +b2 d +b3 d +b4 d +b5 d +b6 d +b7 d +b8 d +b9 d ba d bb d bc d @@ -304,15 +304,15 @@ ff 66 12f 6f 130 b4 131 b4 -132 b -133 b -134 b -135 b -136 b -137 b -138 b -139 b -13a b +132 d +133 d +134 d +135 d +136 d +137 d +138 d +139 d +13a d 13b d 13c d 13d d @@ -432,15 +432,15 @@ ff 66 1af 6f 1b0 b4 1b1 b4 -1b2 b6 -1b3 b6 -1b4 b6 -1b5 b -1b6 b -1b7 b -1b8 b -1b9 b -1ba b +1b2 d +1b3 d +1b4 d +1b5 d +1b6 d +1b7 d +1b8 d +1b9 d +1ba d 1bb d 1bc d 1bd d @@ -562,13 +562,13 @@ ff 66 231 b4 232 b6 233 b6 -234 b6 -235 b6 -236 b -237 b -238 b -239 b -23a b +234 d +235 d +236 d +237 d +238 d +239 d +23a d 23b d 23c d 23d d @@ -690,13 +690,13 @@ ff 66 2b1 b4 2b2 b6 2b3 b6 -2b4 b6 -2b5 b6 -2b6 b6 -2b7 b -2b8 b -2b9 b -2ba b +2b4 d +2b5 d +2b6 d +2b7 d +2b8 d +2b9 d +2ba d 2bb d 2bc ba 2bd ba @@ -818,12 +818,12 @@ ff 66 331 b4 332 b6 333 b6 -334 b6 -335 b6 -336 b6 -337 b6 -338 b -339 b +334 d +335 d +336 d +337 d +338 d +339 d 33a ba 33b ba 33c ba @@ -948,9 +948,9 @@ ff 66 3b3 b6 3b4 b6 3b5 b6 -3b6 b6 -3b7 b6 -3b8 b6 +3b6 ba +3b7 ba +3b8 ba 3b9 ba 3ba ba 3bb ba @@ -1076,8 +1076,8 @@ ff 66 433 b6 434 b6 435 b6 -436 b6 -437 b6 +436 ba +437 ba 438 ba 439 ba 43a ba @@ -1204,8 +1204,8 @@ ff 66 4b3 b4 4b4 b6 4b5 b6 -4b6 b6 -4b7 b6 +4b6 ba +4b7 ba 4b8 ba 4b9 ba 4ba ba @@ -1330,10 +1330,10 @@ ff 66 531 b4 532 b4 533 b4 -534 b6 -535 b6 -536 b6 -537 b6 +534 b4 +535 b4 +536 b4 +537 b4 538 102 539 102 53a ba @@ -1394,10 +1394,10 @@ ff 66 571 df 572 df 573 df -574 None -575 None -576 None -577 None +574 e4 +575 e4 +576 e4 +577 e4 578 ea 579 e8 57a e8 @@ -1456,11 +1456,11 @@ ff 66 5af 6f 5b0 100 5b1 100 -5b2 100 -5b3 100 -5b4 b6 -5b5 b6 -5b6 b6 +5b2 102 +5b3 102 +5b4 102 +5b5 102 +5b6 102 5b7 102 5b8 102 5b9 102 @@ -1522,10 +1522,10 @@ ff 66 5f1 df 5f2 df 5f3 df -5f4 None -5f5 None -5f6 None -5f7 None +5f4 e4 +5f5 e4 +5f6 e4 +5f7 e4 5f8 ea 5f9 ea 5fa e8 @@ -1586,9 +1586,9 @@ ff 66 631 100 632 100 633 100 -634 100 -635 100 -636 100 +634 102 +635 102 +636 102 637 102 638 102 639 102 @@ -1843,10 +1843,10 @@ ff 66 732 100 733 100 734 100 -735 102 -736 102 -737 102 -738 102 +735 100 +736 100 +737 100 +738 100 739 102 73a 102 73b 102 @@ -1971,10 +1971,10 @@ ff 66 7b2 100 7b3 100 7b4 100 -7b5 102 -7b6 102 -7b7 102 -7b8 102 +7b5 100 +7b6 100 +7b7 100 +7b8 100 7b9 102 7ba 102 7bb 102 @@ -4398,11 +4398,11 @@ fff 1e1 112d None 112e None 112f None -1130 227 -1131 227 -1132 1fa -1133 1fa -1134 1fa +1130 1c6 +1131 1c6 +1132 1c6 +1133 1c6 +1134 1c6 1135 1c6 1136 1c6 1137 1c6 @@ -4526,17 +4526,17 @@ fff 1e1 11ad None 11ae None 11af None -11b0 227 -11b1 227 -11b2 1fa -11b3 1fa -11b4 1fa -11b5 1fa +11b0 1c6 +11b1 1c6 +11b2 1c6 +11b3 1c6 +11b4 1c6 +11b5 1c6 11b6 1c6 11b7 1c6 11b8 1c6 11b9 1c6 -11ba 1c6 +11ba 1d0 11bb 1d0 11bc 1d0 11bd 1d0 @@ -4654,17 +4654,17 @@ fff 1e1 122d 226 122e 226 122f 226 -1230 227 -1231 227 -1232 1fa -1233 1fa -1234 1fa -1235 1fa -1236 1fa +1230 1c6 +1231 1c6 +1232 1c6 +1233 1c6 +1234 1c6 +1235 1c6 +1236 1c6 1237 1c6 1238 1c6 1239 1c6 -123a 1c6 +123a 1d0 123b 1d0 123c 204 123d 204 @@ -4782,14 +4782,14 @@ fff 1e1 12ad 226 12ae 226 12af 226 -12b0 227 -12b1 227 -12b2 1fa -12b3 1fa -12b4 1fa -12b5 1fa -12b6 1fa -12b7 1fa +12b0 1c6 +12b1 1c6 +12b2 1c6 +12b3 1c6 +12b4 1c6 +12b5 1c6 +12b6 1c6 +12b7 1c6 12b8 1c6 12b9 1c6 12ba 206 @@ -4910,10 +4910,10 @@ fff 1e1 132d 226 132e 226 132f 226 -1330 227 -1331 227 -1332 1fa -1333 1fa +1330 1c6 +1331 1c6 +1332 1c6 +1333 1c6 1334 1fa 1335 1fa 1336 1fa @@ -5038,10 +5038,10 @@ fff 1e1 13ad 226 13ae 226 13af 226 -13b0 227 -13b1 227 -13b2 227 -13b3 1fa +13b0 1c6 +13b1 1c6 +13b2 1c6 +13b3 1c6 13b4 1fa 13b5 1fa 13b6 1fa @@ -5302,8 +5302,8 @@ fff 1e1 14b5 1fa 14b6 1fa 14b7 1fa -14b8 258 -14b9 258 +14b8 206 +14b9 206 14ba 206 14bb 206 14bc 206 @@ -5429,10 +5429,10 @@ fff 1e1 1534 1fa 1535 1fa 1536 1fa -1537 258 -1538 258 -1539 258 -153a 258 +1537 206 +1538 206 +1539 206 +153a 206 153b 206 153c 206 153d 206 @@ -5553,15 +5553,15 @@ fff 1e1 15b0 231 15b1 231 15b2 231 -15b3 231 -15b4 231 -15b5 231 -15b6 231 -15b7 258 -15b8 258 -15b9 258 -15ba 258 -15bb 258 +15b3 258 +15b4 258 +15b5 258 +15b6 206 +15b7 206 +15b8 206 +15b9 206 +15ba 206 +15bb 206 15bc 206 15bd 206 15be 206 @@ -5681,19 +5681,19 @@ fff 1e1 1630 231 1631 231 1632 231 -1633 231 -1634 231 -1635 231 -1636 258 -1637 258 -1638 258 -1639 258 -163a 258 -163b 258 -163c 258 -163d 258 -163e 258 -163f 258 +1633 258 +1634 258 +1635 258 +1636 206 +1637 206 +1638 206 +1639 206 +163a 206 +163b 206 +163c 206 +163d 206 +163e 206 +163f 206 1640 23b 1641 23b 1642 23b @@ -5809,19 +5809,19 @@ fff 1e1 16b0 231 16b1 231 16b2 231 -16b3 231 -16b4 231 +16b3 258 +16b4 258 16b5 258 16b6 258 16b7 258 -16b8 258 -16b9 258 -16ba 258 -16bb 258 -16bc 258 -16bd 258 -16be 258 -16bf 258 +16b8 206 +16b9 206 +16ba 206 +16bb 206 +16bc 206 +16bd 206 +16be 206 +16bf 206 16c0 23b 16c1 23b 16c2 23b @@ -5938,18 +5938,18 @@ fff 1e1 1731 231 1732 231 1733 231 -1734 231 +1734 258 1735 258 1736 258 1737 258 1738 258 1739 258 173a 258 -173b 258 -173c 258 -173d 258 -173e 258 -173f 258 +173b 206 +173c 206 +173d 206 +173e 206 +173f 206 1740 23b 1741 23b 1742 23b @@ -6073,11 +6073,11 @@ fff 1e1 17b8 258 17b9 258 17ba 258 -17bb 258 -17bc 258 -17bd 258 -17be 258 -17bf 258 +17bb 206 +17bc 206 +17bd 206 +17be 206 +17bf 206 17c0 23b 17c1 23b 17c2 23b @@ -6705,15 +6705,15 @@ fff 1e1 1a30 2e8 1a31 2e8 1a32 2e8 -1a33 2eb -1a34 2eb -1a35 2eb -1a36 2eb -1a37 2eb -1a38 2eb -1a39 2eb -1a3a 2eb -1a3b 2eb +1a33 2e8 +1a34 2e8 +1a35 2e8 +1a36 2e8 +1a37 2e8 +1a38 2e8 +1a39 2e8 +1a3a 2e8 +1a3b 2e8 1a3c 2b6 1a3d 2b6 1a3e 2b6 @@ -6833,15 +6833,15 @@ fff 1e1 1ab0 2e8 1ab1 2e8 1ab2 2e8 -1ab3 2eb -1ab4 2eb -1ab5 2eb -1ab6 2eb -1ab7 2eb -1ab8 2eb -1ab9 2eb -1aba 2eb -1abb 2eb +1ab3 2e8 +1ab4 2e8 +1ab5 2e8 +1ab6 2e8 +1ab7 2e8 +1ab8 2e8 +1ab9 2e8 +1aba 2e8 +1abb 2e8 1abc 2b6 1abd 2b6 1abe 2b6 @@ -6961,19 +6961,19 @@ fff 1e1 1b30 2e8 1b31 2e8 1b32 2e8 -1b33 2eb -1b34 2eb -1b35 2eb -1b36 2eb -1b37 2eb -1b38 2eb -1b39 2eb -1b3a 2eb -1b3b 2eb -1b3c 35c -1b3d 35c -1b3e 35c -1b3f 35c +1b33 2e8 +1b34 2e8 +1b35 2e8 +1b36 2e8 +1b37 2e8 +1b38 2e8 +1b39 2e8 +1b3a 2e8 +1b3b 2e8 +1b3c 2e8 +1b3d 2e8 +1b3e 2e8 +1b3f 2e8 1b40 2b8 1b41 2b8 1b42 2b8 @@ -7089,19 +7089,19 @@ fff 1e1 1bb0 2e8 1bb1 2e8 1bb2 2e8 -1bb3 2eb -1bb4 2eb -1bb5 2eb -1bb6 2eb -1bb7 2eb -1bb8 2eb -1bb9 2eb -1bba 2eb -1bbb 2eb -1bbc 35c -1bbd 35c -1bbe 35c -1bbf 35c +1bb3 2e8 +1bb4 2e8 +1bb5 2e8 +1bb6 2e8 +1bb7 2e8 +1bb8 2e8 +1bb9 2e8 +1bba 2e8 +1bbb 2e8 +1bbc 2e8 +1bbd 2e8 +1bbe 2e8 +1bbf 2e8 1bc0 2ec 1bc1 2ec 1bc2 2ec @@ -7217,19 +7217,19 @@ fff 1e1 1c30 2e8 1c31 2e8 1c32 2e8 -1c33 2eb -1c34 2eb -1c35 2eb -1c36 2eb -1c37 2eb -1c38 2eb -1c39 2eb -1c3a 2eb -1c3b 2eb -1c3c 35c -1c3d 35c -1c3e 35c -1c3f 35c +1c33 2e8 +1c34 2e8 +1c35 2e8 +1c36 2e8 +1c37 2e8 +1c38 2e8 +1c39 2e8 +1c3a 2e8 +1c3b 2e8 +1c3c 2e8 +1c3d 2e8 +1c3e 2e8 +1c3f 2e8 1c40 2ec 1c41 2ec 1c42 2ec @@ -7246,22 +7246,22 @@ fff 1e1 1c4d 31c 1c4e 31c 1c4f 31c -1c50 None -1c51 None -1c52 None -1c53 None -1c54 None -1c55 None -1c56 None -1c57 None -1c58 None -1c59 None -1c5a None -1c5b None -1c5c None -1c5d None -1c5e None -1c5f None +1c50 362 +1c51 362 +1c52 362 +1c53 362 +1c54 362 +1c55 362 +1c56 362 +1c57 362 +1c58 362 +1c59 362 +1c5a 362 +1c5b 362 +1c5c 362 +1c5d 362 +1c5e 362 +1c5f 362 1c60 2ee 1c61 2ee 1c62 2ee @@ -7345,19 +7345,19 @@ fff 1e1 1cb0 2e8 1cb1 2e8 1cb2 2e8 -1cb3 2eb -1cb4 2eb -1cb5 2eb -1cb6 2eb -1cb7 2eb -1cb8 2eb -1cb9 2eb -1cba 2eb -1cbb 2eb -1cbc 35c -1cbd 35c -1cbe 35c -1cbf 35c +1cb3 2e8 +1cb4 2e8 +1cb5 2e8 +1cb6 2e8 +1cb7 2e8 +1cb8 2e8 +1cb9 2e8 +1cba 2e8 +1cbb 2e8 +1cbc 2e8 +1cbd 2e8 +1cbe 2e8 +1cbf 2e8 1cc0 2ec 1cc1 2ec 1cc2 2ec @@ -7374,22 +7374,22 @@ fff 1e1 1ccd 31c 1cce 31c 1ccf 31c -1cd0 None -1cd1 None -1cd2 None -1cd3 None -1cd4 None -1cd5 None -1cd6 None -1cd7 None -1cd8 None -1cd9 None -1cda None -1cdb None -1cdc None -1cdd None -1cde None -1cdf None +1cd0 362 +1cd1 362 +1cd2 362 +1cd3 362 +1cd4 362 +1cd5 362 +1cd6 362 +1cd7 362 +1cd8 362 +1cd9 362 +1cda 362 +1cdb 362 +1cdc 362 +1cdd 362 +1cde 362 +1cdf 362 1ce0 2ee 1ce1 2ee 1ce2 2ee @@ -7472,20 +7472,20 @@ fff 1e1 1d2f 2e8 1d30 2e8 1d31 2e8 -1d32 2eb -1d33 2eb -1d34 2eb -1d35 2eb -1d36 2eb -1d37 2eb -1d38 2eb -1d39 2eb -1d3a 2eb -1d3b 2eb -1d3c 35c -1d3d 35c -1d3e 35c -1d3f 35c +1d32 2e8 +1d33 2e8 +1d34 2e8 +1d35 2e8 +1d36 2e8 +1d37 2e8 +1d38 2e8 +1d39 2e8 +1d3a 2e8 +1d3b 2e8 +1d3c 2e8 +1d3d 2e8 +1d3e 2e8 +1d3f 2e8 1d40 2ec 1d41 2ec 1d42 2ec @@ -7502,22 +7502,22 @@ fff 1e1 1d4d 31c 1d4e 31c 1d4f 31c -1d50 None -1d51 None -1d52 None -1d53 None -1d54 None -1d55 None -1d56 None -1d57 None -1d58 None -1d59 None -1d5a None -1d5b None -1d5c None -1d5d None -1d5e None -1d5f None +1d50 362 +1d51 362 +1d52 362 +1d53 362 +1d54 362 +1d55 362 +1d56 362 +1d57 362 +1d58 362 +1d59 362 +1d5a 362 +1d5b 362 +1d5c 362 +1d5d 362 +1d5e 362 +1d5f 362 1d60 2ee 1d61 2ee 1d62 2ee @@ -7599,21 +7599,21 @@ fff 1e1 1dae 2e8 1daf 2e8 1db0 2e8 -1db1 2eb -1db2 2eb -1db3 2eb -1db4 2eb -1db5 2eb -1db6 2eb -1db7 2eb -1db8 2eb -1db9 2eb -1dba 2eb -1dbb 2eb -1dbc 35c -1dbd 35c -1dbe 35c -1dbf 35c +1db1 2e8 +1db2 2e8 +1db3 2e8 +1db4 2e8 +1db5 2e8 +1db6 2e8 +1db7 2e8 +1db8 2e8 +1db9 2e8 +1dba 2e8 +1dbb 2e8 +1dbc 2e8 +1dbd 2e8 +1dbe 2e8 +1dbf 2e8 1dc0 31a 1dc1 31a 1dc2 31a @@ -7630,22 +7630,22 @@ fff 1e1 1dcd 31c 1dce 31c 1dcf 31c -1dd0 None -1dd1 None -1dd2 None -1dd3 None -1dd4 None -1dd5 None -1dd6 None -1dd7 None -1dd8 None -1dd9 None -1dda None -1ddb None -1ddc None -1ddd None -1dde None -1ddf None +1dd0 362 +1dd1 362 +1dd2 362 +1dd3 362 +1dd4 362 +1dd5 362 +1dd6 362 +1dd7 362 +1dd8 362 +1dd9 362 +1dda 362 +1ddb 362 +1ddc 362 +1ddd 362 +1dde 362 +1ddf 362 1de0 2ee 1de1 2ee 1de2 2ee @@ -7727,21 +7727,21 @@ fff 1e1 1e2e 2e8 1e2f 2e8 1e30 2e8 -1e31 2eb -1e32 2eb -1e33 2eb -1e34 2eb -1e35 2eb -1e36 2eb -1e37 2eb -1e38 2eb -1e39 2eb -1e3a 2eb -1e3b 35c -1e3c 35c -1e3d 35c -1e3e 35c -1e3f 35c +1e31 2e8 +1e32 2e8 +1e33 2e8 +1e34 2e8 +1e35 2e8 +1e36 2e8 +1e37 2e8 +1e38 2e8 +1e39 2e8 +1e3a 2e8 +1e3b 2e8 +1e3c 2e8 +1e3d 2e8 +1e3e 2e8 +1e3f 2e8 1e40 31a 1e41 31a 1e42 31a @@ -7758,22 +7758,22 @@ fff 1e1 1e4d 31c 1e4e 31c 1e4f 31c -1e50 None -1e51 None -1e52 None -1e53 None -1e54 None -1e55 None -1e56 None -1e57 None -1e58 None -1e59 None -1e5a None -1e5b None -1e5c None -1e5d None -1e5e None -1e5f None +1e50 362 +1e51 362 +1e52 362 +1e53 362 +1e54 362 +1e55 362 +1e56 362 +1e57 362 +1e58 362 +1e59 362 +1e5a 362 +1e5b 362 +1e5c 362 +1e5d 362 +1e5e 362 +1e5f 362 1e60 2ee 1e61 2ee 1e62 2ee @@ -7857,19 +7857,19 @@ fff 1e1 1eb0 2e8 1eb1 356 1eb2 356 -1eb3 2eb -1eb4 2eb -1eb5 2eb -1eb6 2eb -1eb7 2eb -1eb8 2eb -1eb9 2eb -1eba 35c -1ebb 35c -1ebc 35c -1ebd 35c -1ebe 35c -1ebf 35c +1eb3 2e8 +1eb4 2e8 +1eb5 2e8 +1eb6 2e8 +1eb7 2e8 +1eb8 2e8 +1eb9 2e8 +1eba 2e8 +1ebb 2e8 +1ebc 2e8 +1ebd 2e8 +1ebe 2e8 +1ebf 2e8 1ec0 31a 1ec1 31a 1ec2 31a @@ -7886,22 +7886,22 @@ fff 1e1 1ecd 31c 1ece 31c 1ecf 31c -1ed0 None -1ed1 None -1ed2 None -1ed3 None -1ed4 None -1ed5 None -1ed6 None -1ed7 None -1ed8 None -1ed9 None -1eda None -1edb None -1edc None -1edd None -1ede None -1edf None +1ed0 362 +1ed1 362 +1ed2 362 +1ed3 362 +1ed4 362 +1ed5 362 +1ed6 362 +1ed7 362 +1ed8 362 +1ed9 362 +1eda 362 +1edb 362 +1edc 362 +1edd 362 +1ede 362 +1edf 362 1ee0 2ee 1ee1 2ee 1ee2 2ee @@ -7992,12 +7992,12 @@ fff 1e1 1f37 359 1f38 359 1f39 359 -1f3a 35c -1f3b 35c -1f3c 35c -1f3d 35c -1f3e 35c -1f3f 35c +1f3a 359 +1f3b 359 +1f3c 359 +1f3d 359 +1f3e 359 +1f3f 359 1f40 31a 1f41 31a 1f42 31a @@ -8014,22 +8014,22 @@ fff 1e1 1f4d 31c 1f4e 31c 1f4f 31c -1f50 None -1f51 None -1f52 None -1f53 None -1f54 None -1f55 None -1f56 None -1f57 None -1f58 None -1f59 None -1f5a None -1f5b None -1f5c None -1f5d None -1f5e None -1f5f None +1f50 362 +1f51 362 +1f52 362 +1f53 362 +1f54 362 +1f55 362 +1f56 362 +1f57 362 +1f58 362 +1f59 362 +1f5a 362 +1f5b 362 +1f5c 362 +1f5d 362 +1f5e 362 +1f5f 362 1f60 2ee 1f61 2ee 1f62 2ee @@ -8120,12 +8120,12 @@ fff 1e1 1fb7 359 1fb8 359 1fb9 359 -1fba 35c -1fbb 35c -1fbc 35c -1fbd 35c -1fbe 35c -1fbf 35c +1fba 359 +1fbb 359 +1fbc 359 +1fbd 359 +1fbe 359 +1fbf 359 1fc0 31a 1fc1 31a 1fc2 31a @@ -8142,22 +8142,22 @@ fff 1e1 1fcd 31c 1fce 31c 1fcf 31c -1fd0 None -1fd1 None -1fd2 None -1fd3 None -1fd4 None -1fd5 None -1fd6 None -1fd7 None -1fd8 None -1fd9 None -1fda None -1fdb None -1fdc None -1fdd None -1fde None -1fdf None +1fd0 362 +1fd1 362 +1fd2 362 +1fd3 362 +1fd4 362 +1fd5 362 +1fd6 362 +1fd7 362 +1fd8 362 +1fd9 362 +1fda 362 +1fdb 362 +1fdc 362 +1fdd 362 +1fde 362 +1fdf 362 1fe0 2ee 1fe1 2ee 1fe2 2ee @@ -8278,14 +8278,14 @@ fff 1e1 2055 363 2056 364 2057 364 -2058 None -2059 None -205a None -205b None -205c None -205d None -205e None -205f None +2058 362 +2059 362 +205a 362 +205b 362 +205c 362 +205d 362 +205e 362 +205f 362 2060 366 2061 366 2062 366 @@ -8406,14 +8406,14 @@ fff 1e1 20d5 363 20d6 364 20d7 364 -20d8 None -20d9 None -20da None -20db None -20dc None -20dd None -20de None -20df None +20d8 362 +20d9 362 +20da 362 +20db 362 +20dc 362 +20dd 362 +20de 362 +20df 362 20e0 366 20e1 366 20e2 366 @@ -8534,14 +8534,14 @@ fff 1e1 2155 362 2156 362 2157 362 -2158 None -2159 None -215a None -215b None -215c None -215d None -215e None -215f None +2158 362 +2159 362 +215a 362 +215b 362 +215c 362 +215d 362 +215e 362 +215f 362 2160 366 2161 366 2162 366 @@ -8662,14 +8662,14 @@ fff 1e1 21d5 362 21d6 362 21d7 362 -21d8 None -21d9 None -21da None -21db None -21dc None -21dd None -21de None -21df None +21d8 362 +21d9 362 +21da 362 +21db 362 +21dc 362 +21dd 362 +21de 362 +21df 362 21e0 366 21e1 366 21e2 366 @@ -9018,10 +9018,10 @@ fff 1e1 2339 35a 233a 35a 233b 35a -233c 35c -233d 35c -233e 35c -233f 35c +233c 35a +233d 35a +233e 35a +233f 35a 2340 392 2341 392 2342 393 @@ -9146,10 +9146,10 @@ fff 1e1 23b9 35a 23ba 35a 23bb 35a -23bc 35c -23bd 35c -23be 35c -23bf 35c +23bc 35a +23bd 35a +23be 35a +23bf 35a 23c0 392 23c1 392 23c2 393 @@ -9275,9 +9275,9 @@ fff 1e1 243a 3ac 243b 3ac 243c 3ac -243d 35c -243e 35c -243f 35c +243d 3ac +243e 3ac +243f 3ac 2440 None 2441 None 2442 None @@ -9534,10 +9534,10 @@ fff 1e1 253d 3ac 253e 3ac 253f 3ac -2540 None -2541 None -2542 None -2543 None +2540 3af +2541 3af +2542 3af +2543 3af 2544 3b0 2545 3b0 2546 3b0 @@ -9662,10 +9662,10 @@ fff 1e1 25bd 3ac 25be 3ac 25bf 3ac -25c0 None -25c1 None -25c2 None -25c3 None +25c0 3af +25c1 3af +25c2 3af +25c3 3af 25c4 3b0 25c5 3b0 25c6 3b0 @@ -11574,10 +11574,10 @@ fff 1e1 2d35 49c 2d36 49c 2d37 49c -2d38 None -2d39 None -2d3a None -2d3b None +2d38 49f +2d39 49f +2d3a 49f +2d3b 49f 2d3c 4a8 2d3d 4a8 2d3e 4a8 @@ -11702,10 +11702,10 @@ fff 1e1 2db5 49c 2db6 49c 2db7 49c -2db8 None -2db9 None -2dba None -2dbb None +2db8 49f +2db9 49f +2dba 49f +2dbb 49f 2dbc 4a8 2dbd 4a8 2dbe 4a8 @@ -12406,8 +12406,8 @@ fff 1e1 3075 518 3076 518 3077 518 -3078 None -3079 None +3078 551 +3079 551 307a None 307b None 307c 51f @@ -12534,8 +12534,8 @@ fff 1e1 30f5 518 30f6 518 30f7 518 -30f8 None -30f9 None +30f8 551 +30f9 551 30fa None 30fb None 30fc 51f @@ -16963,9 +16963,9 @@ fff 1e1 4242 None 4243 None 4244 None -4245 None -4246 None -4247 None +4245 6f6 +4246 6f6 +4247 6f6 4248 6cf 4249 6cf 424a 6cf @@ -17091,9 +17091,9 @@ fff 1e1 42c2 None 42c3 None 42c4 None -42c5 None -42c6 None -42c7 None +42c5 6f6 +42c6 6f6 +42c7 6f6 42c8 6cf 42c9 6cf 42ca 6cf @@ -17219,9 +17219,9 @@ fff 1e1 4342 None 4343 None 4344 None -4345 None -4346 None -4347 None +4345 6f6 +4346 6f6 +4347 6f6 4348 6cf 4349 6cf 434a 6cf @@ -17347,9 +17347,9 @@ fff 1e1 43c2 None 43c3 None 43c4 None -43c5 None -43c6 None -43c7 None +43c5 6f6 +43c6 6f6 +43c7 6f6 43c8 776 43c9 776 43ca 776 @@ -17474,10 +17474,10 @@ fff 1e1 4441 713 4442 713 4443 713 -4444 None -4445 None -4446 None -4447 None +4444 6f6 +4445 6f6 +4446 6f6 +4447 6f6 4448 776 4449 776 444a 776 @@ -17602,10 +17602,10 @@ fff 1e1 44c1 713 44c2 713 44c3 713 -44c4 None -44c5 None -44c6 None -44c7 None +44c4 6f6 +44c5 6f6 +44c6 6f6 +44c7 6f6 44c8 776 44c9 776 44ca 776 @@ -17730,10 +17730,10 @@ fff 1e1 4541 713 4542 713 4543 713 -4544 None -4545 None -4546 None -4547 None +4544 6f6 +4545 6f6 +4546 6f6 +4547 6f6 4548 776 4549 776 454a 776 @@ -17858,10 +17858,10 @@ fff 1e1 45c1 713 45c2 713 45c3 713 -45c4 None -45c5 None -45c6 None -45c7 None +45c4 6f6 +45c5 6f6 +45c6 6f6 +45c7 6f6 45c8 776 45c9 776 45ca 776 @@ -17982,14 +17982,14 @@ fff 1e1 463d None 463e None 463f None -4640 None -4641 None -4642 None -4643 None -4644 None -4645 None -4646 None -4647 None +4640 6f6 +4641 6f6 +4642 6f6 +4643 6f6 +4644 6f6 +4645 6f6 +4646 6f6 +4647 6f6 4648 776 4649 776 464a 776 @@ -18110,14 +18110,14 @@ fff 1e1 46bd None 46be None 46bf None -46c0 None -46c1 None -46c2 None -46c3 None -46c4 None -46c5 None -46c6 None -46c7 None +46c0 6f6 +46c1 6f6 +46c2 6f6 +46c3 6f6 +46c4 6f6 +46c5 6f6 +46c6 6f6 +46c7 6f6 46c8 776 46c9 776 46ca 776 @@ -18238,14 +18238,14 @@ fff 1e1 473d 79c 473e 79c 473f 79c -4740 None -4741 None -4742 None -4743 None -4744 None -4745 None -4746 None -4747 None +4740 6f6 +4741 6f6 +4742 6f6 +4743 6f6 +4744 6f6 +4745 6f6 +4746 6f6 +4747 6f6 4748 776 4749 776 474a 776 @@ -18366,14 +18366,14 @@ fff 1e1 47bd 79c 47be 79c 47bf 79c -47c0 None -47c1 None -47c2 None -47c3 None -47c4 None -47c5 None -47c6 None -47c7 None +47c0 6f6 +47c1 6f6 +47c2 6f6 +47c3 6f6 +47c4 6f6 +47c5 6f6 +47c6 6f6 +47c7 6f6 47c8 776 47c9 776 47ca 776 diff --git a/tables/newlines.txt b/tables/newlines.txt new file mode 100644 index 0000000..b2525ec --- /dev/null +++ b/tables/newlines.txt @@ -0,0 +1,102 @@ +70 71 93 93 9f a2 94 15 1b 16 91 15 bf 15 a9 9e 50 9c 91 a7 16 5c 91 a6 99 15 5e 5c 50 +10 19 +15 be 16 46 9e 9f 16 80 a9 15 bc 92 95 95 15 ce 15 bd 15 6f 96 9c a9 5e +03 00 +16 f4 a3 50 a7 15 4e 15 fd a4 9f 9f 50 a3 9d 15 bf 16 91 16 23 15 6c a3 50 96 91 a4 15 5f 92 9f 94 a9 15 86 96 15 18 97 17 10 5e +03 00 +15 14 92 95 17 07 +10 11 +9f 96 50 17 bc a3 17 07 +10 11 +96 9c 99 17 52 17 bf 16 b0 50 +03 00 +70 92 16 a9 15 bd 17 52 94 15 f8 16 70 15 77 98 a5 17 d2 a3 15 92 99 a3 50 99 9d 17 e9 a3 99 16 86 5e +13 02 + +70 7c 99 96 15 34 +03 00 +70 74 15 b6 9d 15 53 +03 00 +70 78 17 59 15 03 +03 00 +17 18 a2 95 16 33 15 25 a9 16 c5 95 50 17 a4 6f 50 +10 11 +71 9e 94 50 a7 16 ca 16 33 15 25 a9 16 1b 6f +03 00 +70 7e 15 7e 15 4f a4 15 c6 9a a5 9e 9b 15 21 16 18 15 0c 16 e8 96 16 08 15 0e 17 90 a4 a3 51 +03 00 +70 74 15 f9 a2 a5 93 16 11 15 03 50 +03 +74 15 f9 a2 a5 93 16 11 15 21 17 4b 17 df 9b 17 52 9c 99 96 95 50 16 90 a4 98 50 9c 99 a6 15 4e 51 +03 +70 74 15 f9 a2 9f a9 51 50 +10 11 +74 15 f9 a2 9f a9 51 50 +10 11 +74 15 f9 a2 9f a9 51 50 +03 00 +70 7c 16 e3 94 15 f9 a2 9f a9 15 d2 15 9b 51 +13 02 + +15 30 17 8c 15 bc 92 a5 a2 a2 99 a4 9f 5c 50 +10 11 +17 4b 50 17 b7 16 34 91 17 4e a2 a2 99 16 91 a7 16 ec 15 22 92 95 6f +03 00 +70 7d 17 07 79 50 96 91 9e 93 a9 15 51 a7 16 ec 15 bd 15 bc 17 ff 93 a9 17 db a2 92 91 93 9f 91 17 4e a2 a2 99 a4 9f 5e +13 02 + +70 72 95 98 17 f9 5c 15 74 a0 a2 95 a4 a4 99 95 a3 51 50 +10 19 00 +74 15 f9 99 9e a9 5c 15 3f 99 17 da 9d 17 62 92 a2 a5 a4 17 1a 16 1c 9d 5e +03 00 +70 71 9c 9c 15 18 94 15 b6 9d 15 fc 9d 15 bb 15 42 92 95 95 9e 5e +03 00 +70 71 9c 9c 15 18 98 91 a0 a0 16 b9 a3 16 65 a3 9f a2 17 5b 15 22 9d 15 bb 15 42 95 a8 16 a1 99 95 17 ce 94 5e +03 00 +70 77 15 7e 16 12 17 31 51 51 51 +03 00 +70 76 17 e4 15 6a 15 25 a2 95 15 19 16 13 9e 9f 15 90 9d 9f a2 17 5b 51 +03 00 +15 14 72 9c 17 16 50 7f 9d 17 46 17 35 9e a3 93 16 74 a3 15 55 15 04 a3 a0 17 87 5c 50 +10 11 +a7 91 99 16 35 15 0f 7c 91 a6 9f 16 b4 91 a7 15 e3 9e 51 +03 00 +70 74 15 f9 99 9e a9 15 84 9c 15 57 15 6a 15 3e 50 +10 19 +71 9e 94 15 8d 15 6a 17 f5 15 bf 17 51 a4 16 12 17 31 5c 50 +10 11 +a5 9e 9c 15 df 15 22 94 95 96 17 39 15 b8 5c 50 +10 11 +16 4f a3 9d 91 a3 98 15 18 7f 9d 95 9e 51 +03 00 +70 80 95 a2 98 91 a0 a3 15 51 93 15 a2 16 a1 a3 a5 91 94 95 50 7c 91 a6 9f 16 b4 a3 98 17 89 16 55 50 94 15 b6 9d 16 b4 15 6a 51 +03 00 +70 74 99 94 15 51 a3 17 e1 94 15 b6 9d a3 6f 50 +10 19 +79 15 b8 16 dc 16 d9 95 16 06 9e 17 1a 16 e2 9d 91 a2 95 51 +13 02 + +15 7b a7 16 ec 16 0c a0 a0 17 46 +10 11 +99 96 15 25 a2 95 15 c4 15 a2 99 9e 9a a5 a2 a9 15 02 a4 98 15 b6 a4 95 9e 15 4a 15 ed 50 9c 99 96 15 43 15 68 80 9c 91 9e 95 a4 6f +03 00 +70 84 98 99 9e 9b 16 58 a7 15 62 50 95 17 e2 97 a9 15 2c 16 13 97 91 15 be 95 94 51 +03 00 +17 de 16 0c 16 0c 5e +03 00 +17 66 94 15 29 93 15 9e 15 e0 9f 96 15 02 99 9e 9a a5 a2 a9 5c 15 19 16 13 9d 95 5e 50 +03 00 +70 71 9c 9c 15 02 92 17 69 9c 15 df 50 95 17 e2 97 a9 15 19 16 13 17 84 95 5e +03 00 +70 72 a9 15 b8 a2 97 15 1b 17 19 16 1d 95 50 95 17 e2 97 a9 15 09 80 9c 91 9e 95 a4 5c 50 +10 11 +79 15 19 92 95 15 9c 91 50 9e 95 a7 50 9c 99 96 95 16 12 9d 5c 50 +10 11 +91 50 9e 95 a7 50 95 a8 17 56 17 ce 5e +03 00 +70 7d 95 9c 94 15 1b 17 19 15 18 80 9c 91 9e 95 15 99 +03 00 +15 3d a7 16 08 50 93 17 fa 15 56 95 a8 99 a3 a4 17 5f 17 3d 16 4c 5e 5e +03 00 +70 7f 9e 15 9d 16 91 16 13 a2 95 92 9f a2 9e 17 5f 91 50 57 77 9f 94 57 15 0c a2 a5 9c 95 15 cf 15 d2 16 7c a5 9c 5e +13 02 \ No newline at end of file diff --git a/tables/pc_gfx_table.txt b/tables/pc_gfx_table.txt new file mode 100644 index 0000000..7b3d4fa --- /dev/null +++ b/tables/pc_gfx_table.txt @@ -0,0 +1,8 @@ +default,2 +dead,2 +ladder,2 +rope,2 +tiny,2 +tiny_dead,2 +robot,2 +unknown,2 \ No newline at end of file diff --git a/tables/problematic_scripts.txt b/tables/problematic_scripts.txt index 343917a..9857320 100644 --- a/tables/problematic_scripts.txt +++ b/tables/problematic_scripts.txt @@ -44,12 +44,14 @@ 80f1c 80f94 81027 +82c6b 86e14 880b9 88772 894ac 89566 89580 +8a24c 9036b 903c5 904e3 @@ -91,6 +93,7 @@ 9794b 99cd8 9a08b +9a123 9aa91 9af4a 9af75 @@ -116,3 +119,4 @@ 9c7fa 9cb79 9e619 +2f952e diff --git a/tables/psi_ability_table.txt b/tables/psi_ability_table.txt new file mode 100644 index 0000000..c582796 --- /dev/null +++ b/tables/psi_ability_table.txt @@ -0,0 +1,11 @@ +name_index,1 +greek_letter,1 +type,1 +target,1 +effect,2 +ness_level,1 +paula_level,1 +poo_level,1 +greek_letter_position,1 +entry_number,1 +address,4 \ No newline at end of file diff --git a/tables/psi_teleport_table.txt b/tables/psi_teleport_table.txt new file mode 100644 index 0000000..46201e9 --- /dev/null +++ b/tables/psi_teleport_table.txt @@ -0,0 +1,4 @@ +name_text,25,str +flag,2 +x,2 +y,2 \ No newline at end of file diff --git a/tables/run_button_patch.txt b/tables/run_button_patch.txt new file mode 100644 index 0000000..bcd119e --- /dev/null +++ b/tables/run_button_patch.txt @@ -0,0 +1,4 @@ +94E3: 22 00 fe c4 +4FE00: b9 5a 12 8d 58 0a ad 40 98 29 ff 00 c9 01 00 f0 03 4c 20 fe 6b +4FE20: ad 66 00 29 40 00 f0 03 4c 40 fe 9c 40 98 6b +4FE40: ad 40 98 09 03 00 8d 40 98 6b \ No newline at end of file diff --git a/tables/sprite_group_pointers.txt b/tables/sprite_group_pointers.txt new file mode 100644 index 0000000..a94e711 --- /dev/null +++ b/tables/sprite_group_pointers.txt @@ -0,0 +1,464 @@ +2f1a7f +2f1a7f +2f1aa8 +2f1ad1 +2f1afa +2f1b23 +2f1b4c +2f1b75 +2f1b9e +2f1bc7 +2f1bf0 +2f1c19 +2f1c42 +2f1c6b +2f1c94 +2f1cad +2f1cc6 +2f1cdf +2f1cf8 +2f1d11 +2f1d2a +2f1d43 +2f1d5c +2f1d75 +2f1d8e +2f1da7 +2f1dd0 +2f1de9 +2f1e12 +2f1e3b +2f1e64 +2f1e8d +2f1ea6 +2f1ebf +2f1ed8 +2f1f01 +2f1f2a +2f1f53 +2f1f7c +2f1f95 +2f1fbe +2f1fe7 +2f2010 +2f2029 +2f2042 +2f206b +2f2094 +2f20bd +2f20e6 +2f210f +2f2128 +2f2141 +2f216a +2f2183 +2f219c +2f21b5 +2f21ce +2f21e7 +2f2200 +2f2219 +2f2232 +2f224b +2f2264 +2f227d +2f2296 +2f22af +2f22c8 +2f22e1 +2f22fa +2f2313 +2f232c +2f2345 +2f235e +2f2377 +2f2390 +2f23a9 +2f23c2 +2f23db +2f23f4 +2f240d +2f2426 +2f243f +2f2458 +2f2471 +2f248a +2f24a3 +2f24bc +2f24d5 +2f24ee +2f2507 +2f2520 +2f2539 +2f2552 +2f256b +2f2584 +2f259d +2f25b6 +2f25cf +2f25ea +2f2603 +2f261c +2f2635 +2f264e +2f2667 +2f2680 +2f2699 +2f26b2 +2f26cb +2f26e4 +2f26fd +2f2716 +2f272f +2f2748 +2f2761 +2f277a +2f2793 +2f27ac +2f27c5 +2f27de +2f27f7 +2f2810 +2f2829 +2f2842 +2f285b +2f2874 +2f288d +2f28a6 +2f28bf +2f28d8 +2f28f1 +2f290a +2f2923 +2f293c +2f2955 +2f296e +2f2987 +2f29a0 +2f29b9 +2f29d2 +2f29eb +2f2a04 +2f2a1d +2f2a36 +2f2a4f +2f2a68 +2f2a81 +2f2a9a +2f2ab3 +2f2acc +2f2ae5 +2f2b0e +2f2b37 +2f2b50 +2f2b69 +2f2b82 +2f2b9b +2f2bb4 +2f2bcd +2f2be6 +2f2bff +2f2c18 +2f2c31 +2f2c4a +2f2c63 +2f2c7c +2f2c95 +2f2cae +2f2cc7 +2f2ce0 +2f2cf9 +2f2d12 +2f2d2b +2f2d44 +2f2d5d +2f2d76 +2f2d8f +2f2da8 +2f2dc1 +2f2dda +2f2df3 +2f2e0c +2f2e25 +2f2e3e +2f2e67 +2f2e80 +2f2e99 +2f2eb2 +2f2ecb +2f2ee4 +2f2efd +2f2f16 +2f2f2f +2f2f48 +2f2f61 +2f2f7a +2f2f93 +2f2fac +2f2fc5 +2f2fde +2f2ff7 +2f3010 +2f3029 +2f3042 +2f305b +2f3074 +2f308d +2f30b6 +2f30df +2f30f8 +2f3111 +2f312a +2f3143 +2f315c +2f3175 +2f318e +2f31a7 +2f31c0 +2f31d9 +2f31f2 +2f320b +2f3224 +2f323d +2f3256 +2f326f +2f3288 +2f32a1 +2f32ba +2f32d3 +2f32ec +2f3305 +2f331e +2f3337 +2f3350 +2f3369 +2f3382 +2f339b +2f33b4 +2f33cd +2f33e6 +2f33ff +2f3418 +2f3431 +2f344a +2f3463 +2f348c +2f34a5 +2f34be +2f34d7 +2f34f0 +2f3509 +2f3522 +2f353b +2f3554 +2f356d +2f3586 +2f359f +2f35b8 +2f35d1 +2f35ea +2f3603 +2f361c +2f3635 +2f364e +2f3667 +2f3680 +2f3699 +2f36b2 +2f36cb +2f36e4 +2f36fd +2f3716 +2f372f +2f3748 +2f3761 +2f377a +2f3795 +2f37ae +2f37c7 +2f37e0 +2f37f9 +2f3812 +2f382b +2f3844 +2f385f +2f3878 +2f3891 +2f38ac +2f38c7 +2f38e0 +2f38f9 +2f3912 +2f392b +2f3946 +2f395f +2f3978 +2f3991 +2f39aa +2f39c3 +2f39dc +2f39f5 +2f3a0e +2f3a27 +2f3a40 +2f3a59 +2f3a72 +2f3a8b +2f3aa4 +2f3abd +2f3ad8 +2f3af1 +2f3b0c +2f3b25 +2f3b3e +2f3b57 +2f3b70 +2f3b89 +2f3ba2 +2f3bbb +2f3bd6 +2f3bef +2f3c08 +2f3c23 +2f3c3c +2f3c57 +2f3c70 +2f3c89 +2f3ca4 +2f3cbd +2f3cd6 +2f3cef +2f3d08 +2f3d21 +2f3d3a +2f3d53 +2f3d6e +2f3d87 +2f3da0 +2f3db9 +2f3dd2 +2f3deb +2f3e04 +2f3e1d +2f3e36 +2f3e4f +2f3e68 +2f3e81 +2f3e9a +2f3eb3 +2f3ecc +2f3ee5 +2f3efe +2f3f17 +2f3f30 +2f3f49 +2f3f62 +2f3f7b +2f3f94 +2f3fad +2f3fc6 +2f3fdf +2f3ff8 +2f4011 +2f402a +2f4043 +2f405c +2f4075 +2f408e +2f40a7 +2f40c0 +2f40d9 +2f40f2 +2f410b +2f4124 +2f413d +2f4156 +2f416f +2f4188 +2f41a1 +2f41ba +2f41d3 +2f41ec +2f4205 +2f421e +2f4237 +2f4250 +2f4269 +2f4282 +2f429b +2f42b4 +2f42cd +2f42e6 +2f42ff +2f4318 +2f4331 +2f434a +2f4363 +2f437c +2f4395 +2f43ae +2f43c7 +2f43e0 +2f43f9 +2f4412 +2f442b +2f4444 +2f445d +2f4476 +2f448f +2f44a8 +2f44c1 +2f44da +2f44f3 +2f450c +2f4525 +2f453e +2f4557 +2f4570 +2f4589 +2f45a2 +2f45bb +2f45d4 +2f45ed +2f4606 +2f461f +2f4638 +2f4651 +2f466a +2f4683 +2f469c +2f46b5 +2f46ce +2f46e7 +2f4700 +2f4719 +2f4732 +2f474b +2f4774 +2f478d +2f47b6 +2f47cf +2f47e8 +2f4801 +2f481a +2f4833 +2f484c +2f4865 +2f487e +2f4897 +2f48b0 +2f48c9 +2f48e2 +2f48fb +2f4914 +2f492d +2f4946 +2f495f +2f4978 +2f4991 +2f49aa +2f49c3 +2f49dc +2f49f5 +2f4a0e +2f4a27 \ No newline at end of file diff --git a/tables/sprite_group_table.txt b/tables/sprite_group_table.txt new file mode 100644 index 0000000..44b9816 --- /dev/null +++ b/tables/sprite_group_table.txt @@ -0,0 +1,11 @@ +height,1 +width,1 +size,1 +palette,1 +collision_ns_w,1 +collision_ns_h,1 +collision_ew_w,1 +collision_ew_h,1 +bank,1 +sprites_cardinal,8x2,list +sprites_diagonal,8x2,list \ No newline at end of file diff --git a/tables/tables_list.txt b/tables/tables_list.txt index 9b2d92d..d55d881 100644 --- a/tables/tables_list.txt +++ b/tables/tables_list.txt @@ -1,24 +1,32 @@ AncientCave empty.txt 0 1 +Dialog empty.txt 0 1 ZoneEventObject zone_event_table.txt zone_event_pointers.txt ZoneSpriteObject zone_event_table.txt zone_sprite_pointers.txt MapEventObject map_event_table.txt map_event_pointers.txt MapSpriteObject map_sprite_table.txt map_sprite_pointers.txt EventObject event_table.txt event_pointers.txt +SpriteGroupObject sprite_group_table.txt sprite_group_pointers.txt +PcGfxObject pc_gfx_table.txt 3f2b5 17 +BgDataObject bg_data_table.txt adca1 327 TPTObject tpt_table.txt f8985 1584 MapEnemyObject map_enemy_table.txt 101880 20480 EnemyPlaceObject enemy_place_table.txt 10b880 203 BattleEntryObject battle_entry_table.txt 10c60d 484 ItemObject item_table.txt 155000 254 ShopObject shop_table.txt 1576b9 65 +PsiTeleportObject psi_teleport_table.txt 157880 16 +PsiAbilityObject psi_ability_table.txt 158a50 53 ExperienceObject experience_table.txt 158f49 400 EnemyObject enemy_table.txt 159589 231 StatGrowthObject stat_growth_table.txt 15ea5b 4 +TeleportObject teleport_table.txt 15ebab 234 InitialStatsObject initial_stats_table.txt 15f5f5 4 MapPaletteObject map_palette_table.txt 17a800 2560 MapMusicObject map_music_table.txt 1cd637 2560 $start_y 1fe9b $start_x 1fe9e .patch show_sprites_no_intro.txt +.option run_button_patch.txt # full map size: 320 rows of 256 1x1 blocks (256x320) # events: 40 rows of 32 8x8 blocks diff --git a/tables/teleport_table.txt b/tables/teleport_table.txt new file mode 100644 index 0000000..669e084 --- /dev/null +++ b/tables/teleport_table.txt @@ -0,0 +1,5 @@ +x,2 +y,2 +facing,1 +style,1 +unknown,2 diff --git a/tables/text_mapping.txt b/tables/text_mapping.txt index 902f1f0..f7b5adf 100644 --- a/tables/text_mapping.txt +++ b/tables/text_mapping.txt @@ -777,9 +777,9 @@ 08=TONY 09=BUBBLE 50= -0A=() 51=! -52='' +52=" +53=· 54=$ 55=% 56=00 @@ -791,7 +791,7 @@ 5C=, 5D=- 5E=. -5F=\ +5F=/ 60=0 61=1 62=2 @@ -835,6 +835,11 @@ 88=X 89=Y 8A=Z +8B= +8C= +8D= +8E= +8F= 90=- 91=a 92=b @@ -862,5 +867,8 @@ A7=w A8=x A9=y AA=z -AC= -13=/ +AB=[ +AC= +AD=] +AE=~ +AF=()