diff --git a/README.md b/README.md index a9672d8..be4dce8 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,7 @@ BCCE is in active development and this may break at any time; see the first para These sections in the BCEX/BCCE spoiler logs currently have no logic and I'm aware of it. That doesn't mean sections *not* listed here have support; they may not and I'm not aware of them. -- MAGITEK -- DANCES -- ESPERS -- ITEM MAGIC - ITEM EFFECTS -- COLOSSEUM - SHOPS - TREASURE CHESTS - JUNCTIONS diff --git a/main.py b/main.py index f31a827..f47d2f9 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ """Parse BCEX (or BCCE) logs into json objects.""" -__version__ = "0.4.1" +__version__ = "0.4.2" __author__ = "Trysdyn Black" import json @@ -15,12 +15,7 @@ class Parser: BCEX/BCCE spoiler logfile parser. Sections missing support: - - MAGITEK - - DANCES - - ESPERS - - ITEM MAGIC - ITEM EFFECTS - - COLOSSEUM - SHOPS - TREASURE CHESTS - JUNCTIONS @@ -365,6 +360,156 @@ class Parser: return replacements + @staticmethod + def parse_MAGITEK(data: str) -> dict[str, list]: + """ + Parse the BCCE-only MAGITEK section. + + This section contains info on what Terra and the guards have in their magitek skill + lists at the start of the game. Unfortunately like the STATS block, this keys on slot + name and not randomized name, but at least it's super simple. + """ + result = {"terra": [], "others": []} + + mode = None + for line in data.split("\n"): + if line.startswith("Terra Magitek"): + mode = "terra" + elif line.startswith("Other Actor"): + mode = "others" + elif line.strip() and mode: + result[mode].append(line.strip()) + + return result + + @staticmethod + def parse_DANCES(data: str) -> dict[str, dict]: + """ + Parse the BCCE-only DANCES section. + + This section's a list of dances with their effects chances. Each dance seems to + always have four effects, spaced at specific locations in the string, so we chomp + by hardcoded locations. Brittle. + """ + result = {} + + dance = None + for line in data.split("\n"): + # Formatting line + if "-----" in line: + continue + # Result list + if line.startswith(" "): + # This is more brittle than I'd like but the logs space results out by + # character position and some dance results have spaces so it's hard to + # split properly. + for i in range(2, 57, 18): + chance, effect = line[i : i + 18].split(" ", 1) + result[dance][effect.strip()] = chance.strip() + # New dance section + elif line.strip(): + dance = line.strip() + if dance not in result: + result[dance] = {} + + return result + + @staticmethod + def parse_ESPERS(data: str) -> dict[str, dict]: + """ + Parse the ESPERS section. + + This section lists espers, what they teach, their bonuses, and locations. We + assume any line with a : in it is either BONUS or LOCATION and just k=v it. Any + line after a blank line is an esper name, and anything else is a spell learn option. + """ + result = {} + + esper = None + next_esper = False + for line in data.split("\n"): + # Formatting line + if "-----" in line: + continue + # Blank lines divide esper sections + if not line.strip(): + next_esper = True + # The first line in a new section is the esper name + elif next_esper: + esper = line.strip() + if esper not in result: + result[esper] = {"learnset": {}} + next_esper = False + # Any line with ":" is a k=v we should just shove into the dict + elif ": " in line: + k, v = line.split(": ") + result[esper][k.lower()] = v.strip() + # Everything else should be spell learnset + else: + spell, mult = line.split(" x") + result[esper]["learnset"][spell.strip()] = f"x{mult}" + + return result + + @staticmethod + def parse_ITEM_MAGIC(data: str) -> dict[str, dict]: + """ + Parse the ITEM MAGIC section. + + This section is actually three distinct subsections. Breakable items and procs + we can just store as k=v in sub-dicts. The spell-teaching items section we treat + like the esper learnset and store spell_name = learn multiplier. + """ + result = {} + + section = None + for line in data.split("\n"): + # Formatting lines + if "-----" in line or not line.strip(): + continue + # Anything with ":" is a k=v to insert + if ":" in line: + k, v = line.split(": ") + # Spell-teaching needs special logic to get the {spell: multiplier} format + if section == "spell-teaching": + spell, mult = v.split(" x") + result[section][k.strip()] = {spell.strip(): f"x{mult}"} + else: + result[section][k.strip()] = v.strip() + # Anything else should be a section header. Use the first word unless it's "ITEM" + else: + tok_line = line.split() + section = tok_line[1].lower() if "ITEM" in tok_line[0] else tok_line[0].lower() + result[section] = {} + + return result + + @staticmethod + def parse_COLOSSEUM(data: str) -> dict[str, dict]: + """ + Parse the COLOSSEUM section. + + Each line is one item offered, which has a resulting item, a monster level, and a + a monster name. This results in basic string splitting giving us all the data we + need. + """ + result = {} + + for line in data.split("\n"): + if "-----" in line or not line.strip(): + continue + + item, tok_line = line.split("->") + new_item, tok_line = tok_line.split(": LV ") + level, name = tok_line.split(" ", 1) + + result[item.strip()] = { + "becomes": new_item.strip(), + "battle": {"monster": name.strip(), "level": int(level.strip())}, + } + + return result + @staticmethod def cleanup_STATS(data: dict) -> bool: """