736 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			736 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",
 | |
|                 "grindy",
 | |
|                 "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()
 | 
