From 53c958f087fedf20d3932beead3de4b254e0b59e Mon Sep 17 00:00:00 2001 From: Trysdyn Black Date: Sun, 13 Oct 2024 02:34:33 -0700 Subject: [PATCH 1/9] Support MORPH fields in monster data --- main.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/main.py b/main.py index f47d2f9..a236a75 100644 --- a/main.py +++ b/main.py @@ -72,6 +72,13 @@ class Parser: info["special"] = {special_name: special_desc} else: info["special"] = {} + # Morph results, with a percent chance in each one + elif line.startswith("MORPH"): + _, chance, items = line.split(" ", 2) + chance = int(chance[1:-3]) + items = items.split(", ") + if "morph" not in info: + info["morph"] = {"percent_chance": chance, "items": items} # Everything else is a simple k: v list where v is comma-delimited else: for k in ["immune", "auto", "skills", "steal", "drops", "location"]: From ed29b321736ecd551d803c1622b26aab202b000e Mon Sep 17 00:00:00 2001 From: Trysdyn Black Date: Sun, 13 Oct 2024 16:02:27 -0700 Subject: [PATCH 2/9] Add support for ITEM EFFECTS section --- README.md | 1 - main.py | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index be4dce8..3a2db6b 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ 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. -- ITEM EFFECTS - SHOPS - TREASURE CHESTS - JUNCTIONS diff --git a/main.py b/main.py index a236a75..f8145d5 100644 --- a/main.py +++ b/main.py @@ -10,12 +10,11 @@ import sys from pathlib import Path -class Parser: +class Parser: # noqa: PLR0904 """ BCEX/BCCE spoiler logfile parser. Sections missing support: - - ITEM EFFECTS - SHOPS - TREASURE CHESTS - JUNCTIONS @@ -501,6 +500,7 @@ class Parser: need. """ result = {} + mode = None for line in data.split("\n"): if "-----" in line or not line.strip(): @@ -517,6 +517,68 @@ class Parser: return result + @staticmethod + def parse_ITEM_EFFECTS(data: str) -> dict[str, dict]: # noqa: C901, PLR0912 + """ + Parse the BCCE-only ITEM EFFECTS section. + + This is a weird chimera section with multiple sub-sections. Most of these + sub-sections can just be k:v or k:v, v, v parsed. Elemental properties is + a special indented list and special features can contain command changers + so we need a lot of special parsing. + """ + result = {} + mode = None + item = None + + for line in data.split("\n"): + if "-----" in line or not line.strip(): + continue + + if line == line.upper(): + mode = line.lower().strip().replace(" ", "_") + result[mode] = {} + # Everything here is k:v splitable except the elemental properties + # section which is k:v but with a return and indent and multiple values. + elif mode == "elemental_properties": + if line.startswith(" "): + tok_line = line.split() + operator = "+" if "Gained" in tok_line else "-" if "Lost" in tok_line else "" + element = tok_line[-1] + effect = tok_line[2].strip(":") + + if item not in result[mode]: + result[mode][item] = [] + + result[mode][item].append(f"{operator}{element} {effect}") + else: + item = line.split(":")[0] + result[mode][item] = [] + else: + item, effect = line.split(":") + item = item.strip() + effect = effect.strip() + + for effect_token in effect.split(", "): + # This is a command change. This can appear in command_changers *or* features + # In either case the result goes in command changers for consistency + if "->" in effect_token: + old, new = effect_token.split("->") + # We don't have a guarantee COMMAND CHANGERS is in yet... + if "command_changers" not in result: + result["command_changers"] = {} + if item not in result["command_changers"]: + result["command_changers"][item] = {} + + result["command_changers"][item][old.strip()] = new.strip() + # Everything else should hopefully just be a basic effect list + else: + if item not in result[mode]: + result[mode][item] = [] + result[mode][item].append(effect_token.strip()) + + return result + @staticmethod def cleanup_STATS(data: dict) -> bool: """ From 5234a6bd1536a20310e0e12c5d191683b160a24b Mon Sep 17 00:00:00 2001 From: Trysdyn Black Date: Sun, 13 Oct 2024 16:15:33 -0700 Subject: [PATCH 3/9] Support SHOPS section Though its format is not entirely to my liking. --- README.md | 1 - main.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3a2db6b..ca55bd1 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,5 @@ 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. -- SHOPS - TREASURE CHESTS - JUNCTIONS diff --git a/main.py b/main.py index f8145d5..fad53b5 100644 --- a/main.py +++ b/main.py @@ -15,7 +15,6 @@ class Parser: # noqa: PLR0904 BCEX/BCCE spoiler logfile parser. Sections missing support: - - SHOPS - TREASURE CHESTS - JUNCTIONS """ @@ -579,6 +578,36 @@ class Parser: # noqa: PLR0904 return result + @staticmethod + def parse_SHOPS(data: str) -> dict[str, dict]: + """ + Parse SHOPS section. + + For now this is just a dict of shop name => {item => cost}. I would like to split + this up better so you have, for example narshe["wob"]["after_kefka"]["weapons"] but + that's probably more lifting than is necessary. Most people will just be searching + for buyable items period. + """ + result = {} + shop = None + + for line in data.split("\n"): + if "-----" in line or not line.strip(): + continue + + if line == line.upper(): + shop = line.strip() + result[shop] = {"stock": {}, "female_discount": False} + elif line.startswith("Discounts for female characters"): + result[shop]["female_discount"] = True + else: + tok_line = line.split() + item = " ".join(tok_line[:-1]) + cost = tok_line[-1] + result[shop]["stock"][item.strip()] = int(cost.strip()) + + return result + @staticmethod def cleanup_STATS(data: dict) -> bool: """ From 51cd2f03ccd7f5ed68cd8699adbfa28ea82fc2ce Mon Sep 17 00:00:00 2001 From: Trysdyn Black Date: Sun, 13 Oct 2024 16:17:17 -0700 Subject: [PATCH 4/9] Bump version to 0.5.0 --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index fad53b5..58f4124 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ """Parse BCEX (or BCCE) logs into json objects.""" -__version__ = "0.4.2" +__version__ = "0.5.0" __author__ = "Trysdyn Black" import json From f733ebb66d1282f5eec0eec42730c800b7b1c3c1 Mon Sep 17 00:00:00 2001 From: Trysdyn Black Date: Wed, 16 Oct 2024 00:28:48 -0700 Subject: [PATCH 5/9] Support BCEX 5.0 - We have to work around a bug where morph chances have double % - The skill names in monster specials got changed to single-quoted instead of double quoted, go figure It runs; a quick peek makes it look like everything's okay. There'll be bugs I'm sure. --- README.md | 2 ++ main.py | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ca55bd1..3779e65 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ The parser is quite brittle since the inconsistency of the original spoiler log Additionally the tool only parses spoiler log data needed for known use-cases, so if you plan to use it you may need to request the inclusion of spoiler log sections. The tool can be expanded by adding new functions named `parse_SECTION` where `SECTION` is the full name of a section in the log, as presented in the log. +Development targed BCEX 4.0, but BCEX 5.0 is in limited support. It works but all the bugs haven't been found yet. + BCCE (The community revival of the BCEX project) is supported, but support is geared toward taking BCCE's spoiler logs and producing identical output to BCEX. This means stats are not their own data object, but are folded into character data just like BCEX outputs it. Remonsterate is supported and inserts its data into the monsters object. BCCE is in active development and this may break at any time; see the first paragraph in this section. diff --git a/main.py b/main.py index 58f4124..1394bee 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ """Parse BCEX (or BCCE) logs into json objects.""" -__version__ = "0.5.0" +__version__ = "0.5.1" __author__ = "Trysdyn Black" import json @@ -65,7 +65,8 @@ class Parser: # noqa: PLR0904 elif line.startswith("SPECIAL"): content = line.split(" ", 1)[1] if len(content) > 1: - special_name = content.split('"')[1] + # BCEX 5.0 changes the skill name to be single quoted instead of double + special_name = content.split('"')[1] if '"' in content else content.split("'")[1] special_desc = content.split(": ")[1] info["special"] = {special_name: special_desc} else: @@ -73,7 +74,8 @@ class Parser: # noqa: PLR0904 # Morph results, with a percent chance in each one elif line.startswith("MORPH"): _, chance, items = line.split(" ", 2) - chance = int(chance[1:-3]) + # BCEX 5.0 has a bug where % can be doubled sometimes + chance = int(chance[1:-3].replace("%", "")) items = items.split(", ") if "morph" not in info: info["morph"] = {"percent_chance": chance, "items": items} From 82ff5f86f4449b8c1fa14106894ed6c4eae7c0c4 Mon Sep 17 00:00:00 2001 From: Trysdyn Black Date: Wed, 16 Oct 2024 22:58:57 -0700 Subject: [PATCH 6/9] Fix formatting oddness for foes with no weakness --- main.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main.py b/main.py index 1394bee..7b2798d 100644 --- a/main.py +++ b/main.py @@ -60,6 +60,11 @@ class Parser: # noqa: PLR0904 weak_text = "WEAK: " info["nullifies"] = null_text.split(": ")[1].split(", ") info["weak"] = weak_text.split(": ")[1].split(", ") + + # Due to split oddness we can populate a blank string into weakness + # Delete it if we did. + if info["weak"] == [""]: + del info["weak"] # Specials are name=>desc as k:v # I *think* you can only have one special... elif line.startswith("SPECIAL"): From 1af6a89e6b20bf939693caaf7c8ec003a9b7e19f Mon Sep 17 00:00:00 2001 From: Trysdyn Black Date: Wed, 16 Oct 2024 23:09:59 -0700 Subject: [PATCH 7/9] Add names to large data objects This is so if you do a jq query like `.CHARACTERS[] | select(originally == "Mog")` you'll get their name in the data output. --- main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 7b2798d..3daaa48 100644 --- a/main.py +++ b/main.py @@ -43,6 +43,7 @@ class Parser: # noqa: PLR0904 if "(Level " in line: name = line.split(" (")[0] info["stats"]["level"] = int(line.split("(Level ")[1][:-1]) + info["name"] = name # Stat chart rows elif line.startswith("|"): for stat in line[1:-1].split("|"): @@ -144,6 +145,7 @@ class Parser: # noqa: PLR0904 # Name if line[0:2].isdigit(): name = line[4:] + info["name"] = name # Stat chart rows: BCEX Version only elif line.startswith("|"): @@ -422,7 +424,7 @@ class Parser: # noqa: PLR0904 elif line.strip(): dance = line.strip() if dance not in result: - result[dance] = {} + result[dance] = {"name": dance} return result @@ -450,7 +452,7 @@ class Parser: # noqa: PLR0904 elif next_esper: esper = line.strip() if esper not in result: - result[esper] = {"learnset": {}} + result[esper] = {"learnset": {}, "name": esper} next_esper = False # Any line with ":" is a k=v we should just shove into the dict elif ": " in line: From 8f74ce3a28f59f86dda1b84b24712b3cfd642f53 Mon Sep 17 00:00:00 2001 From: Trysdyn Black Date: Sat, 19 Oct 2024 17:40:07 -0700 Subject: [PATCH 8/9] Add license to prepare for use by another project --- LICENSE.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..b53c985 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,29 @@ +"i'm so tired" software license 1.0 + +copyright (c) 2024 Trysdyn Black + +this is anti-capitalist, anti-bigotry software, made by people who are tired of ill-intended organisations and individuals, and would rather not have those around their creations. + +permission is granted, free of charge, to any user (be they a person or an organisation) obtaining a copy of this software, to use it for personal, commercial, or educational purposes, subject to the following conditions: + +1. the above copyright notice and this permission notice shall be included in all copies or modified versions of this software. + +2. the user is one of the following: + a. an individual person, labouring for themselves + b. a non-profit organisation + c. an educational institution + d. an organization that seeks shared profit for all of its members, and allows non-members to set the cost of their labor + +3. if the user is an organization with owners, then all owners are workers and all workers are owners with equal equity and/or equal vote. + +4. if the user is an organization, then the user is not law enforcement or military, or working for or under either. + +5. the user does not use the software for ill-intentioned reasons, as determined by the authors of the software. said reasons include but are not limited to: + a. bigotry, including but not limited to racism, xenophobia, homophobia, transphobia, ableism, sexism, antisemitism, religious intolerance + b. pedophilia, zoophilia, and/or incest + c. support for cops and/or the military + d. any blockchain-related technology, including but not limited to cryptocurrencies + +6. the user does not promote or engage with any of the activities listed in the previous item, and is not affiliated with any group that promotes or engages with any of such activities. + +this software is provided as is, without any warranty or condition. in no event shall the authors be liable to anyone for any damages related to this software or this license, under any kind of legal claim. From 03fb76e0fcb587f03a08cffa736c5cf450780f2e Mon Sep 17 00:00:00 2001 From: Trysdyn Black Date: Mon, 21 Oct 2024 20:33:32 -0700 Subject: [PATCH 9/9] Remove unused mode variable --- main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/main.py b/main.py index 3daaa48..0d16b67 100644 --- a/main.py +++ b/main.py @@ -508,7 +508,6 @@ class Parser: # noqa: PLR0904 need. """ result = {} - mode = None for line in data.split("\n"): if "-----" in line or not line.strip():