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()
|