737 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			737 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
	
	
from BaseClasses import Item, Location
 | 
						|
from typing import Tuple, Union, Set, List, Dict
 | 
						|
import string
 | 
						|
import pkgutil
 | 
						|
 | 
						|
 | 
						|
class TerrariaItem(Item):
 | 
						|
    game = "Terraria"
 | 
						|
 | 
						|
 | 
						|
class TerrariaLocation(Location):
 | 
						|
    game = "Terraria"
 | 
						|
 | 
						|
 | 
						|
def add_token(
 | 
						|
    tokens: List[Tuple[int, int, Union[str, int, None]]],
 | 
						|
    token: Union[str, int],
 | 
						|
    token_index: int,
 | 
						|
):
 | 
						|
    if token == "":
 | 
						|
        return
 | 
						|
    if type(token) == str:
 | 
						|
        tokens.append((token_index, 0, token.rstrip()))
 | 
						|
    elif type(token) == int:
 | 
						|
        tokens.append((token_index, 1, token))
 | 
						|
 | 
						|
 | 
						|
IDENT = 0
 | 
						|
NUM = 1
 | 
						|
SEMI = 2
 | 
						|
HASH = 3
 | 
						|
AT = 4
 | 
						|
NOT = 5
 | 
						|
AND = 6
 | 
						|
OR = 7
 | 
						|
LPAREN = 8
 | 
						|
RPAREN = 9
 | 
						|
END_OF_LINE = 10
 | 
						|
 | 
						|
CHAR_TO_TOKEN_ID = {
 | 
						|
    ";": SEMI,
 | 
						|
    "#": HASH,
 | 
						|
    "@": AT,
 | 
						|
    "~": NOT,
 | 
						|
    "&": AND,
 | 
						|
    "|": OR,
 | 
						|
    "(": LPAREN,
 | 
						|
    ")": RPAREN,
 | 
						|
}
 | 
						|
 | 
						|
TOKEN_ID_TO_CHAR = {id: char for char, id in CHAR_TO_TOKEN_ID.items()}
 | 
						|
 | 
						|
 | 
						|
def tokens(rule: str) -> List[Tuple[int, int, Union[str, int, None]]]:
 | 
						|
    token_list = []
 | 
						|
    token = ""
 | 
						|
    token_index = 0
 | 
						|
    escaped = False
 | 
						|
    for index, char in enumerate(rule):
 | 
						|
        if escaped:
 | 
						|
            if token == "":
 | 
						|
                token_index = index
 | 
						|
            token += char
 | 
						|
            escaped = False
 | 
						|
        elif char == "\\":
 | 
						|
            if type(token) == int:
 | 
						|
                add_token(token_list, token, token_index)
 | 
						|
                token = ""
 | 
						|
            escaped = True
 | 
						|
        elif char == "/" and token.endswith("/"):
 | 
						|
            add_token(token_list, token[:-1], token_index)
 | 
						|
            return token_list
 | 
						|
        elif token == "" and char.isspace():
 | 
						|
            pass
 | 
						|
        elif token == "" and char.isdigit():
 | 
						|
            token_index = index
 | 
						|
            token = int(char)
 | 
						|
        elif type(token) == int and char.isdigit():
 | 
						|
            token = token * 10 + int(char)
 | 
						|
        elif (id := CHAR_TO_TOKEN_ID.get(char)) != None:
 | 
						|
            add_token(token_list, token, token_index)
 | 
						|
            token_list.append((index, id, None))
 | 
						|
            token = ""
 | 
						|
        else:
 | 
						|
            if token == "":
 | 
						|
                token_index = index
 | 
						|
            token += char
 | 
						|
 | 
						|
    add_token(token_list, token, token_index)
 | 
						|
    return token_list
 | 
						|
 | 
						|
 | 
						|
NAME = 0
 | 
						|
NAME_SEMI = 1
 | 
						|
FLAG_OR_SEMI = 2
 | 
						|
POST_FLAG = 3
 | 
						|
FLAG = 4
 | 
						|
FLAG_ARG = 5
 | 
						|
FLAG_ARG_END = 6
 | 
						|
COND_OR_SEMI = 7
 | 
						|
POST_COND = 8
 | 
						|
COND = 9
 | 
						|
LOC = 10
 | 
						|
FN = 11
 | 
						|
POST_FN = 12
 | 
						|
FN_ARG = 13
 | 
						|
FN_ARG_END = 14
 | 
						|
END = 15
 | 
						|
GOAL = 16
 | 
						|
 | 
						|
POS_FMT = [
 | 
						|
    "name or `#`",
 | 
						|
    "`;`",
 | 
						|
    "flag or `;`",
 | 
						|
    "`;`, `|`, or `(`",
 | 
						|
    "flag",
 | 
						|
    "text or number",
 | 
						|
    "`)`",
 | 
						|
    "name, `#`, `@`, `~`, `(`, or `;`",
 | 
						|
    "`;`, `&`, `|`, or `)`",
 | 
						|
    "name, `#`, `@`, `~`, or `(`",
 | 
						|
    "name",
 | 
						|
    "name",
 | 
						|
    "`(`, `;`, `&`, `|`, or `)`",
 | 
						|
    "text or number",
 | 
						|
    "`)`",
 | 
						|
    "end of line",
 | 
						|
    "goal",
 | 
						|
]
 | 
						|
 | 
						|
RWD_NAME = 0
 | 
						|
RWD_NAME_SEMI = 1
 | 
						|
RWD_FLAG = 2
 | 
						|
RWD_FLAG_SEMI = 3
 | 
						|
RWD_END = 4
 | 
						|
RWD_LABEL = 5
 | 
						|
 | 
						|
RWD_POS_FMT = ["name or `#`", "`;`", "flag", "`;`", "end of line", "name"]
 | 
						|
 | 
						|
 | 
						|
def unexpected(line: int, char: int, id: int, token, pos, pos_fmt, file):
 | 
						|
    if id == IDENT or id == NUM:
 | 
						|
        token_fmt = f"`{token}`"
 | 
						|
    elif id == END_OF_LINE:
 | 
						|
        token_fmt = "end of line"
 | 
						|
    else:
 | 
						|
        token_fmt = f"`{TOKEN_ID_TO_CHAR[id]}`"
 | 
						|
 | 
						|
    raise Exception(
 | 
						|
        f"in `{file}`, found {token_fmt} at {line + 1}:{char + 1}; expected {pos_fmt[pos]}"
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
COND_ITEM = 0
 | 
						|
COND_LOC = 1
 | 
						|
COND_FN = 2
 | 
						|
COND_GROUP = 3
 | 
						|
 | 
						|
 | 
						|
def validate_conditions(
 | 
						|
    rule: str,
 | 
						|
    rule_indices: dict,
 | 
						|
    conditions: List[
 | 
						|
        Tuple[
 | 
						|
            bool, int, Union[str, Tuple[Union[bool, None], list]], Union[str, int, None]
 | 
						|
        ]
 | 
						|
    ],
 | 
						|
):
 | 
						|
    for _, type, condition, _ in conditions:
 | 
						|
        if type == COND_ITEM:
 | 
						|
            if condition not in rule_indices:
 | 
						|
                raise Exception(f"item `{condition}` in `{rule}` is not defined")
 | 
						|
        elif type == COND_LOC:
 | 
						|
            if condition not in rule_indices:
 | 
						|
                raise Exception(f"location `{condition}` in `{rule}` is not defined")
 | 
						|
        elif type == COND_FN:
 | 
						|
            if condition not in {
 | 
						|
                "npc",
 | 
						|
                "calamity",
 | 
						|
                "pickaxe",
 | 
						|
                "hammer",
 | 
						|
                "mech_boss",
 | 
						|
                "minions",
 | 
						|
            }:
 | 
						|
                raise Exception(f"function `{condition}` in `{rule}` is not defined")
 | 
						|
        elif type == COND_GROUP:
 | 
						|
            _, conditions = condition
 | 
						|
            validate_conditions(rule, rule_indices, conditions)
 | 
						|
 | 
						|
 | 
						|
def mark_progression(
 | 
						|
    conditions: List[
 | 
						|
        Tuple[
 | 
						|
            bool, int, Union[str, Tuple[Union[bool, None], list]], Union[str, int, None]
 | 
						|
        ]
 | 
						|
    ],
 | 
						|
    progression: Set[str],
 | 
						|
    rules: list,
 | 
						|
    rule_indices: dict,
 | 
						|
    loc_to_item: dict,
 | 
						|
):
 | 
						|
    for _, type, condition, _ in conditions:
 | 
						|
        if type == COND_ITEM:
 | 
						|
            prog = condition in progression
 | 
						|
            progression.add(loc_to_item[condition])
 | 
						|
            _, flags, _, conditions = rules[rule_indices[condition]]
 | 
						|
            if (
 | 
						|
                not prog
 | 
						|
                and "Achievement" not in flags
 | 
						|
                and "Location" not in flags
 | 
						|
                and "Item" not in flags
 | 
						|
            ):
 | 
						|
                mark_progression(
 | 
						|
                    conditions, progression, rules, rule_indices, loc_to_item
 | 
						|
                )
 | 
						|
        elif type == COND_LOC:
 | 
						|
            _, _, _, conditions = rules[rule_indices[condition]]
 | 
						|
            mark_progression(conditions, progression, rules, rule_indices, loc_to_item)
 | 
						|
        elif type == COND_GROUP:
 | 
						|
            _, conditions = condition
 | 
						|
            mark_progression(conditions, progression, rules, rule_indices, loc_to_item)
 | 
						|
 | 
						|
 | 
						|
def read_data() -> (
 | 
						|
    Tuple[
 | 
						|
        # Goal to rule index that ends that goal's range and the locations required
 | 
						|
        List[Tuple[int, Set[str]]],
 | 
						|
        # Rules
 | 
						|
        List[
 | 
						|
            Tuple[
 | 
						|
                # Rule
 | 
						|
                str,
 | 
						|
                # Flag to flag arg
 | 
						|
                Dict[str, Union[str, int, None]],
 | 
						|
                # True = or, False = and, None = N/A
 | 
						|
                Union[bool, None],
 | 
						|
                # Conditions
 | 
						|
                List[
 | 
						|
                    Tuple[
 | 
						|
                        # True = positive, False = negative
 | 
						|
                        bool,
 | 
						|
                        # Condition type
 | 
						|
                        int,
 | 
						|
                        # Condition name or list (True = or, False = and, None = N/A) (list shares type with outer)
 | 
						|
                        Union[str, Tuple[Union[bool, None], List]],
 | 
						|
                        # Condition arg
 | 
						|
                        Union[str, int, None],
 | 
						|
                    ]
 | 
						|
                ],
 | 
						|
            ]
 | 
						|
        ],
 | 
						|
        # Rule to rule index
 | 
						|
        Dict[str, int],
 | 
						|
        # Label to rewards
 | 
						|
        Dict[str, List[str]],
 | 
						|
        # Reward to flags
 | 
						|
        Dict[str, Set[str]],
 | 
						|
        # Item name to ID
 | 
						|
        Dict[str, int],
 | 
						|
        # Location name to ID
 | 
						|
        Dict[str, int],
 | 
						|
        # NPCs
 | 
						|
        List[str],
 | 
						|
        # Pickaxe to pick power
 | 
						|
        Dict[str, int],
 | 
						|
        # Hammer to hammer power
 | 
						|
        Dict[str, int],
 | 
						|
        # Mechanical bosses
 | 
						|
        List[str],
 | 
						|
        # Calamity final bosses
 | 
						|
        List[str],
 | 
						|
        # Progression rules
 | 
						|
        Set[str],
 | 
						|
        # Armor to minion count,
 | 
						|
        Dict[str, int],
 | 
						|
        # Accessory to minion count,
 | 
						|
        Dict[str, int],
 | 
						|
    ]
 | 
						|
):
 | 
						|
    next_id = 0x7E0000
 | 
						|
    item_name_to_id = {}
 | 
						|
 | 
						|
    goals = []
 | 
						|
    goal_indices = {}
 | 
						|
    rules = []
 | 
						|
    rule_indices = {}
 | 
						|
    loc_to_item = {}
 | 
						|
 | 
						|
    npcs = []
 | 
						|
    pickaxes = {}
 | 
						|
    hammers = {}
 | 
						|
    mech_boss_loc = []
 | 
						|
    mech_bosses = []
 | 
						|
    final_boss_loc = []
 | 
						|
    final_bosses = []
 | 
						|
    armor_minions = {}
 | 
						|
    accessory_minions = {}
 | 
						|
 | 
						|
    progression = set()
 | 
						|
 | 
						|
    for line, rule in enumerate(
 | 
						|
        pkgutil.get_data(__name__, "Rules.dsv").decode().splitlines()
 | 
						|
    ):
 | 
						|
        goal = None
 | 
						|
        name = None
 | 
						|
        flags = {}
 | 
						|
 | 
						|
        sign = True
 | 
						|
        operator = None
 | 
						|
        outer = []
 | 
						|
        conditions = []
 | 
						|
 | 
						|
        pos = NAME
 | 
						|
        for char, id, token in tokens(rule):
 | 
						|
            if pos == NAME:
 | 
						|
                if id == IDENT:
 | 
						|
                    name = token
 | 
						|
                    pos = NAME_SEMI
 | 
						|
                elif id == HASH:
 | 
						|
                    pos = GOAL
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == NAME_SEMI:
 | 
						|
                if id == SEMI:
 | 
						|
                    pos = FLAG_OR_SEMI
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == FLAG_OR_SEMI:
 | 
						|
                if id == IDENT:
 | 
						|
                    flag = token
 | 
						|
                    pos = POST_FLAG
 | 
						|
                elif id == SEMI:
 | 
						|
                    pos = COND_OR_SEMI
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == POST_FLAG:
 | 
						|
                if id == SEMI:
 | 
						|
                    if flag is not None:
 | 
						|
                        if flag in flags:
 | 
						|
                            raise Exception(
 | 
						|
                                f"set flag `{flag}` at {line + 1}:{char + 1} that was already set"
 | 
						|
                            )
 | 
						|
                        flags[flag] = None
 | 
						|
                        flag = None
 | 
						|
                    pos = COND_OR_SEMI
 | 
						|
                elif id == OR:
 | 
						|
                    pos = FLAG
 | 
						|
                elif id == LPAREN:
 | 
						|
                    pos = FLAG_ARG
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == FLAG:
 | 
						|
                if id == IDENT:
 | 
						|
                    if flag is not None:
 | 
						|
                        if flag in flags:
 | 
						|
                            raise Exception(
 | 
						|
                                f"set flag `{flag}` at {line + 1}:{char + 1} that was already set"
 | 
						|
                            )
 | 
						|
                        flags[flag] = None
 | 
						|
                        flag = None
 | 
						|
                    flag = token
 | 
						|
                    pos = POST_FLAG
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == FLAG_ARG:
 | 
						|
                if id == IDENT or id == NUM:
 | 
						|
                    if flag in flags:
 | 
						|
                        raise Exception(
 | 
						|
                            f"set flag `{flag}` at {line + 1}:{char + 1} that was already set"
 | 
						|
                        )
 | 
						|
                    flags[flag] = token
 | 
						|
                    flag = None
 | 
						|
                    pos = FLAG_ARG_END
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == FLAG_ARG_END:
 | 
						|
                if id == RPAREN:
 | 
						|
                    pos = POST_FLAG
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == COND_OR_SEMI:
 | 
						|
                if id == IDENT:
 | 
						|
                    conditions.append((sign, COND_ITEM, token, None))
 | 
						|
                    sign = True
 | 
						|
                    pos = POST_COND
 | 
						|
                elif id == HASH:
 | 
						|
                    pos = LOC
 | 
						|
                elif id == AT:
 | 
						|
                    pos = FN
 | 
						|
                elif id == NOT:
 | 
						|
                    sign = not sign
 | 
						|
                    pos = COND
 | 
						|
                elif id == LPAREN:
 | 
						|
                    outer.append((sign, None, conditions))
 | 
						|
                    conditions = []
 | 
						|
                    sign = True
 | 
						|
                    pos = COND
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == POST_COND:
 | 
						|
                if id == SEMI:
 | 
						|
                    if outer:
 | 
						|
                        raise Exception(
 | 
						|
                            f"found `;` at {line + 1}:{char + 1} after unclosed `(`"
 | 
						|
                        )
 | 
						|
                    pos = END
 | 
						|
                elif id == AND:
 | 
						|
                    if operator is True:
 | 
						|
                        raise Exception(
 | 
						|
                            f"found `&` at {line + 1}:{char + 1} in group containing `|`"
 | 
						|
                        )
 | 
						|
                    operator = False
 | 
						|
                    pos = COND
 | 
						|
                elif id == OR:
 | 
						|
                    if operator is False:
 | 
						|
                        raise Exception(
 | 
						|
                            f"found `|` at {line + 1}:{char + 1} in group containing `&`"
 | 
						|
                        )
 | 
						|
                    operator = True
 | 
						|
                    pos = COND
 | 
						|
                elif id == RPAREN:
 | 
						|
                    if not outer:
 | 
						|
                        raise Exception(
 | 
						|
                            f"found `)` at {line + 1}:{char + 1} without matching `(`"
 | 
						|
                        )
 | 
						|
                    condition = operator, conditions
 | 
						|
                    sign, operator, conditions = outer.pop()
 | 
						|
                    conditions.append((sign, COND_GROUP, condition, None))
 | 
						|
                    sign = True
 | 
						|
                    pos = POST_COND
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == COND:
 | 
						|
                if id == IDENT:
 | 
						|
                    conditions.append((sign, COND_ITEM, token, None))
 | 
						|
                    sign = True
 | 
						|
                    pos = POST_COND
 | 
						|
                elif id == HASH:
 | 
						|
                    pos = LOC
 | 
						|
                elif id == AT:
 | 
						|
                    pos = FN
 | 
						|
                elif id == NOT:
 | 
						|
                    sign = not sign
 | 
						|
                elif id == LPAREN:
 | 
						|
                    outer.append((sign, operator, conditions))
 | 
						|
                    conditions = []
 | 
						|
                    sign = True
 | 
						|
                    operator = None
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == LOC:
 | 
						|
                if id == IDENT:
 | 
						|
                    conditions.append((sign, COND_LOC, token, None))
 | 
						|
                    sign = True
 | 
						|
                    pos = POST_COND
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == FN:
 | 
						|
                if id == IDENT:
 | 
						|
                    function = token
 | 
						|
                    pos = POST_FN
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == POST_FN:
 | 
						|
                if id == LPAREN:
 | 
						|
                    pos = FN_ARG
 | 
						|
                elif id == SEMI:
 | 
						|
                    conditions.append((sign, COND_FN, function, None))
 | 
						|
                    pos = END
 | 
						|
                elif id == AND:
 | 
						|
                    conditions.append((sign, COND_FN, function, None))
 | 
						|
                    sign = True
 | 
						|
                    if operator is True:
 | 
						|
                        raise Exception(
 | 
						|
                            f"found `&` at {line + 1}:{char + 1} in group containing `|`"
 | 
						|
                        )
 | 
						|
                    operator = False
 | 
						|
                    pos = COND
 | 
						|
                elif id == OR:
 | 
						|
                    conditions.append((sign, COND_FN, function, None))
 | 
						|
                    sign = True
 | 
						|
                    if operator is False:
 | 
						|
                        raise Exception(
 | 
						|
                            f"found `|` at {line + 1}:{char + 1} in group containing `&`"
 | 
						|
                        )
 | 
						|
                    operator = True
 | 
						|
                    pos = COND
 | 
						|
                elif id == RPAREN:
 | 
						|
                    conditions.append((sign, COND_FN, function, None))
 | 
						|
                    if not outer:
 | 
						|
                        raise Exception(
 | 
						|
                            f"found `)` at {line + 1}:{char + 1} without matching `(`"
 | 
						|
                        )
 | 
						|
                    condition = operator, conditions
 | 
						|
                    sign, operator, conditions = outer.pop()
 | 
						|
                    conditions.append((sign, COND_GROUP, condition, None))
 | 
						|
                    sign = True
 | 
						|
                    pos = POST_COND
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == FN_ARG:
 | 
						|
                if id == IDENT or id == NUM:
 | 
						|
                    conditions.append((sign, COND_FN, function, token))
 | 
						|
                    sign = True
 | 
						|
                    pos = FN_ARG_END
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == FN_ARG_END:
 | 
						|
                if id == RPAREN:
 | 
						|
                    pos = POST_COND
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
            elif pos == END:
 | 
						|
                unexpected(line, char, id, token, pos)
 | 
						|
            elif pos == GOAL:
 | 
						|
                if id == IDENT:
 | 
						|
                    goal = token
 | 
						|
                    pos = END
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, POS_FMT, "Rules.dsv")
 | 
						|
 | 
						|
        if pos != NAME and pos != FLAG_OR_SEMI and pos != COND_OR_SEMI and pos != END:
 | 
						|
            unexpected(line, char + 1, END_OF_LINE, None, pos, POS_FMT, "Rules.dsv")
 | 
						|
 | 
						|
        if name:
 | 
						|
            if name in rule_indices:
 | 
						|
                raise Exception(
 | 
						|
                    f"rule `{name}` on line `{line + 1}` shadows a previous rule"
 | 
						|
                )
 | 
						|
            rule_indices[name] = len(rules)
 | 
						|
            rules.append((name, flags, operator, conditions))
 | 
						|
 | 
						|
            if "Item" in flags:
 | 
						|
                item_name = flags["Item"] or f"Post-{name}"
 | 
						|
                if item_name in item_name_to_id:
 | 
						|
                    raise Exception(
 | 
						|
                        f"item `{item_name}` on line `{line + 1}` shadows a previous item"
 | 
						|
                    )
 | 
						|
                item_name_to_id[item_name] = next_id
 | 
						|
                next_id += 1
 | 
						|
                loc_to_item[name] = item_name
 | 
						|
            else:
 | 
						|
                loc_to_item[name] = name
 | 
						|
 | 
						|
            if "Npc" in flags:
 | 
						|
                npcs.append(name)
 | 
						|
 | 
						|
            if (power := flags.get("Pickaxe")) is not None:
 | 
						|
                pickaxes[name] = power
 | 
						|
 | 
						|
            if (power := flags.get("Hammer")) is not None:
 | 
						|
                hammers[name] = power
 | 
						|
 | 
						|
            if "Mech Boss" in flags:
 | 
						|
                mech_bosses.append(flags["Item"] or f"Post-{name}")
 | 
						|
                mech_boss_loc.append(name)
 | 
						|
 | 
						|
            if "Final Boss" in flags:
 | 
						|
                final_bosses.append(flags["Item"] or f"Post-{name}")
 | 
						|
                final_boss_loc.append(name)
 | 
						|
 | 
						|
            if (minions := flags.get("ArmorMinions")) is not None:
 | 
						|
                armor_minions[name] = minions
 | 
						|
 | 
						|
            if (minions := flags.get("Minions")) is not None:
 | 
						|
                accessory_minions[name] = minions
 | 
						|
 | 
						|
        if goal:
 | 
						|
            if goal in goal_indices:
 | 
						|
                raise Exception(
 | 
						|
                    f"goal `{goal}` on line `{line + 1}` shadows a previous goal"
 | 
						|
                )
 | 
						|
            goal_indices[goal] = len(goals)
 | 
						|
            goals.append((len(rules), set()))
 | 
						|
 | 
						|
    for name, flags, _, _ in rules:
 | 
						|
        if "Goal" in flags:
 | 
						|
            _, items = goals[
 | 
						|
                goal_indices[
 | 
						|
                    name.translate(str.maketrans("", "", string.punctuation))
 | 
						|
                    .replace(" ", "_")
 | 
						|
                    .lower()
 | 
						|
                ]
 | 
						|
            ]
 | 
						|
            items.add(name)
 | 
						|
 | 
						|
    _, mech_boss_items = goals[goal_indices["mechanical_bosses"]]
 | 
						|
    mech_boss_items.update(mech_boss_loc)
 | 
						|
 | 
						|
    _, final_boss_items = goals[goal_indices["calamity_final_bosses"]]
 | 
						|
    final_boss_items.update(final_boss_loc)
 | 
						|
 | 
						|
    for name, _, _, conditions in rules:
 | 
						|
        validate_conditions(name, rule_indices, conditions)
 | 
						|
 | 
						|
    for name, flags, _, conditions in rules:
 | 
						|
        prog = False
 | 
						|
        if (
 | 
						|
            "Npc" in flags
 | 
						|
            or "Goal" in flags
 | 
						|
            or "Pickaxe" in flags
 | 
						|
            or "Hammer" in flags
 | 
						|
            or "Mech Boss" in flags
 | 
						|
            or "Minions" in flags
 | 
						|
            or "ArmorMinions" in flags
 | 
						|
        ):
 | 
						|
            progression.add(loc_to_item[name])
 | 
						|
            prog = True
 | 
						|
        if prog or "Location" in flags or "Achievement" in flags:
 | 
						|
            mark_progression(conditions, progression, rules, rule_indices, loc_to_item)
 | 
						|
 | 
						|
    # Will be randomized via `slot_randoms` / `self.multiworld.random`
 | 
						|
    label = None
 | 
						|
    labels = {}
 | 
						|
    rewards = {}
 | 
						|
 | 
						|
    for line in pkgutil.get_data(__name__, "Rewards.dsv").decode().splitlines():
 | 
						|
        reward = None
 | 
						|
        flags = set()
 | 
						|
 | 
						|
        pos = RWD_NAME
 | 
						|
        for char, id, token in tokens(line):
 | 
						|
            if pos == RWD_NAME:
 | 
						|
                if id == IDENT:
 | 
						|
                    reward = f"Reward: {token}"
 | 
						|
                    pos = RWD_NAME_SEMI
 | 
						|
                elif id == HASH:
 | 
						|
                    pos = RWD_LABEL
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, RWD_POS_FMT, "Rewards.dsv")
 | 
						|
            elif pos == RWD_NAME_SEMI:
 | 
						|
                if id == SEMI:
 | 
						|
                    pos = RWD_FLAG
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, RWD_POS_FMT, "Rewards.dsv")
 | 
						|
            elif pos == RWD_FLAG:
 | 
						|
                if id == IDENT:
 | 
						|
                    if token in flags:
 | 
						|
                        raise Exception(
 | 
						|
                            f"set flag `{token}` at {line + 1}:{char + 1} that was already set"
 | 
						|
                        )
 | 
						|
                    flags.add(token)
 | 
						|
                    pos = RWD_FLAG_SEMI
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, RWD_POS_FMT, "Rewards.dsv")
 | 
						|
            elif pos == RWD_FLAG_SEMI:
 | 
						|
                if id == SEMI:
 | 
						|
                    pos = RWD_END
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, RWD_POS_FMT, "Rewards.dsv")
 | 
						|
            elif pos == RWD_END:
 | 
						|
                unexpected(line, char, id, token, pos, RWD_POS_FMT, "Rewards.dsv")
 | 
						|
            elif pos == RWD_LABEL:
 | 
						|
                if id == IDENT:
 | 
						|
                    label = token
 | 
						|
                    if label in labels:
 | 
						|
                        raise Exception(
 | 
						|
                            f"started label `{label}` at {line + 1}:{char + 1} that was already used"
 | 
						|
                        )
 | 
						|
                    labels[label] = []
 | 
						|
                    pos = RWD_END
 | 
						|
                else:
 | 
						|
                    unexpected(line, char, id, token, pos, RWD_POS_FMT, "Rewards.dsv")
 | 
						|
 | 
						|
        if pos != RWD_NAME and pos != RWD_FLAG and pos != RWD_END:
 | 
						|
            unexpected(line, char + 1, END_OF_LINE, None, pos)
 | 
						|
 | 
						|
        if reward:
 | 
						|
            if reward in rewards:
 | 
						|
                raise Exception(
 | 
						|
                    f"reward `{reward}` on line `{line + 1}` shadows a previous reward"
 | 
						|
                )
 | 
						|
            rewards[reward] = flags
 | 
						|
 | 
						|
            if not label:
 | 
						|
                raise Exception(
 | 
						|
                    f"reward `{reward}` on line `{line + 1}` is not labeled"
 | 
						|
                )
 | 
						|
            labels[label].append(reward)
 | 
						|
 | 
						|
            if reward in item_name_to_id:
 | 
						|
                raise Exception(
 | 
						|
                    f"item `{reward}` on line `{line + 1}` shadows a previous item"
 | 
						|
                )
 | 
						|
            item_name_to_id[reward] = next_id
 | 
						|
            next_id += 1
 | 
						|
 | 
						|
    item_name_to_id["Reward: Coins"] = next_id
 | 
						|
    item_name_to_id["Victory"] = next_id + 1
 | 
						|
    next_id += 2
 | 
						|
 | 
						|
    location_name_to_id = {}
 | 
						|
 | 
						|
    for name, flags, _, _ in rules:
 | 
						|
        if "Location" in flags or "Achievement" in flags:
 | 
						|
            if name in location_name_to_id:
 | 
						|
                raise Exception(f"location `{name}` shadows a previous location")
 | 
						|
            location_name_to_id[name] = next_id
 | 
						|
            next_id += 1
 | 
						|
 | 
						|
    return (
 | 
						|
        goals,
 | 
						|
        rules,
 | 
						|
        rule_indices,
 | 
						|
        labels,
 | 
						|
        rewards,
 | 
						|
        item_name_to_id,
 | 
						|
        location_name_to_id,
 | 
						|
        npcs,
 | 
						|
        pickaxes,
 | 
						|
        hammers,
 | 
						|
        mech_bosses,
 | 
						|
        final_bosses,
 | 
						|
        progression,
 | 
						|
        armor_minions,
 | 
						|
        accessory_minions,
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
(
 | 
						|
    goals,
 | 
						|
    rules,
 | 
						|
    rule_indices,
 | 
						|
    labels,
 | 
						|
    rewards,
 | 
						|
    item_name_to_id,
 | 
						|
    location_name_to_id,
 | 
						|
    npcs,
 | 
						|
    pickaxes,
 | 
						|
    hammers,
 | 
						|
    mech_bosses,
 | 
						|
    final_bosses,
 | 
						|
    progression,
 | 
						|
    armor_minions,
 | 
						|
    accessory_minions,
 | 
						|
) = read_data()
 |