Created
July 17, 2022 00:48
-
-
Save object-Object/8b35f0367dea533e12eb59386c30c1ef to your computer and use it in GitHub Desktop.
A parser to display pattern names for Hex Casting hexes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import annotations | |
import regex | |
from typing import Literal | |
from enum import Enum | |
# working as of https://github.com/gamma-delta/HexMod/blob/c00815b7b9d90593dc33e3a7539ce87c2ece4fc9/Common/src/main/java/at/petrak/hexcasting/common/casting/RegisterPatterns.java | |
# setup: | |
# - run "pip install regex" | |
# - download this script somewhere | |
# - go to https://github.com/gamma-delta/HexMod/blob/main/Common/src/main/java/at/petrak/hexcasting/common/casting/RegisterPatterns.java | |
# - right click the "Raw" button, click "Save Link As", and save it with the name "RegisterPatterns.java" to the same folder as this script | |
# - note: you'll have to do this again after each update, if new patterns are added or old patterns are changed | |
# - (optional) disable highlighting by changing the value on following line from False to True | |
NO_HIGHLIGHTING = False | |
# usage: | |
# - put your hex on the stack | |
# - cast Reveal | |
# - open <game folder>/logs/latest.log, scroll to the bottom, and copy your hex | |
# - paste your hex inside the quotes on the following line (it should look like this: data = "[HexPattern(...), HexPattern(...)]") | |
data = "" | |
# - save and run the script | |
regexes = { | |
"registry": regex.compile(r"PatternRegistry\s*\.\s*mapPattern\s*\(\s*HexPattern\s*\.\s*fromAngles\s*\(\s*\"([aqwed]+)\"\s*,\s*HexDir\s*\.\s*\w+\s*\)\s*,\s*modLoc\s*\(\s*\"([\w/]+)\"\s*\)", regex.M), | |
"list_iota": regex.compile(r"\[(.*)\]"), | |
"list_iota_item": regex.compile(r"(\[(?>[^\[\]]+|(?R))*\]|[^,]+)(?:, )?"), | |
"pattern_iota": regex.compile(r"HexPattern\((\w+)(?: ([aqwed]+))?\)"), | |
"number_constant": regex.compile(r"\d+(?:\.\d+)?"), | |
} | |
class Angle(Enum): | |
FORWARD = 0 | |
w = 0 | |
RIGHT = 1 | |
e = 1 | |
RIGHT_BACK = 2 | |
d = 2 | |
BACK = 3 | |
LEFT_BACK = 4 | |
a = 4 | |
LEFT = 5 | |
q = 5 | |
class Direction(Enum): # numbers increase clockwise | |
NORTH_EAST = 0 | |
EAST = 1 | |
SOUTH_EAST = 2 | |
SOUTH_WEST = 3 | |
WEST = 4 | |
NORTH_WEST = 5 | |
def angle_from(self, other: Direction) -> Angle: | |
return Angle((self.value - other.value) % len(Angle)) | |
def rotated(self, angle: str | Angle) -> Direction: | |
offset = Angle[angle].value if type(angle) is str else angle.value | |
return Direction((self.value + offset) % len(Direction)) | |
# parse the pattern definitions | |
pattern_lookup = {} | |
with open("./RegisterPatterns.java", "r") as file: | |
for match in regexes["registry"].finditer(file.read()): | |
(pattern, name) = match.groups() | |
pattern_lookup[pattern] = name | |
def print_indented(value: str, level: int, value_type: Literal["pattern", "number_constant", "unknown", "none"]): | |
indent = " " * level | |
if NO_HIGHLIGHTING: | |
print(indent + value) | |
return | |
match value_type: | |
case "pattern": | |
highlight = "\033[93m" # yellow | |
case "number_constant": | |
highlight = "\033[92m" # light green | |
case "unknown": | |
highlight = "\033[91m" # light red | |
case "none" | None: | |
highlight = "" | |
print(indent + highlight + value + "\033[00m") | |
def get_pattern_directions(starting_direction: Direction, pattern: str) -> list[Direction]: | |
directions = [starting_direction] | |
for c in pattern: | |
directions.append(directions[-1].rotated(c)) | |
return directions | |
def parse_mask(starting_direction: Direction, pattern: str) -> str | None: | |
if not pattern: | |
return "⁻" | |
directions = get_pattern_directions(starting_direction, pattern) | |
flat_direction = starting_direction.rotated(Angle.LEFT) if pattern[0] == "a" else starting_direction | |
mask = "" | |
skip = False | |
for index, direction in enumerate(directions): | |
if skip: | |
skip = False | |
continue | |
angle = direction.angle_from(flat_direction) | |
if angle == Angle.FORWARD: | |
mask += "⁻" | |
continue | |
if index >= len(directions) - 1: | |
return None | |
angle2 = directions[index + 1].angle_from(flat_direction) | |
if angle == Angle.RIGHT and angle2 == Angle.LEFT: | |
mask += "v" | |
skip = True | |
continue | |
return None | |
return mask | |
def parse_iota(iota: str, level: int = 0) -> int: | |
list_contents_match = regexes["list_iota"].fullmatch(iota) | |
if list_contents_match: | |
list_contents = list_contents_match.group(1) | |
print_indented("[", level, "none") | |
for item_match in regexes["list_iota_item"].finditer(list_contents): | |
level += parse_iota(item_match.group(1), level + 1) | |
print_indented("]", level, "none") | |
else: | |
pattern_match = regexes["pattern_iota"].fullmatch(iota) | |
if pattern_match: | |
direction = Direction[pattern_match.group(1)] | |
pattern = pattern_match.group(2) or "" | |
name = pattern_lookup.get(pattern) | |
mask = parse_mask(direction, pattern) | |
if name: | |
if name == "open_paren": | |
print_indented("{", level, "pattern") | |
return 1 | |
elif name == "close_paren": | |
print_indented("}", level - 1, "pattern") | |
return -1 | |
else: | |
print_indented(name, level, "pattern") | |
elif mask: | |
print_indented(f"mask {mask}", level, "pattern") | |
elif pattern.startswith(("aqaa", "dedd")): | |
negate = pattern.startswith("dedd") | |
accumulator = 0. | |
for c in pattern[4:]: | |
match c: | |
case "w": | |
accumulator += 1 | |
case "q": | |
accumulator += 5 | |
case "e": | |
accumulator += 10 | |
case "a": | |
accumulator *= 2 | |
case "d": | |
accumulator /= 2 | |
if negate: | |
accumulator = -accumulator | |
print_indented(f"number {str(accumulator).rstrip('0').rstrip('.')}", level, "pattern") | |
else: | |
print_indented(pattern, level, "unknown") | |
else: | |
if regexes["number_constant"].fullmatch(iota): | |
print_indented(iota, level, "number_constant") | |
else: | |
print_indented(iota, level, "none") | |
return 0 | |
parse_iota(data) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment