583 lines
20 KiB
Python
583 lines
20 KiB
Python
# Preprocessor to convert Blasphemous Randomizer logic into a StringWorldDefinition for use with APHKLogicExtractor
|
|
# https://github.com/BrandenEK/Blasphemous.Randomizer
|
|
# https://github.com/ArchipelagoMW-HollowKnight/APHKLogicExtractor
|
|
|
|
|
|
import json, requests, argparse
|
|
from typing import List, Dict, Any
|
|
|
|
|
|
def load_resource_local(file: str) -> List[Dict[str, Any]]:
|
|
print(f"Reading from {file}")
|
|
loaded = []
|
|
with open(file, encoding="utf-8") as f:
|
|
loaded = read_json(f.readlines())
|
|
f.close()
|
|
|
|
return loaded
|
|
|
|
|
|
def load_resource_from_web(url: str) -> List[Dict[str, Any]]:
|
|
req = requests.get(url, timeout=1)
|
|
print(f"Reading from {url}")
|
|
req.encoding = "utf-8"
|
|
lines: List[str] = []
|
|
for line in req.text.splitlines():
|
|
while "\t" in line:
|
|
line = line[1::]
|
|
if line != "":
|
|
lines.append(line)
|
|
return read_json(lines)
|
|
|
|
|
|
def read_json(lines: List[str]) -> List[Dict[str, Any]]:
|
|
loaded = []
|
|
creating_object: bool = False
|
|
obj: str = ""
|
|
for line in lines:
|
|
stripped = line.strip()
|
|
if "{" in stripped:
|
|
creating_object = True
|
|
obj += stripped
|
|
continue
|
|
elif "}," in stripped or "}" in stripped and "]" in lines[lines.index(line)+1]:
|
|
creating_object = False
|
|
obj += "}"
|
|
#print(f"obj = {obj}")
|
|
loaded.append(json.loads(obj))
|
|
obj = ""
|
|
continue
|
|
|
|
if not creating_object:
|
|
continue
|
|
else:
|
|
try:
|
|
if "}," in lines[lines.index(line)+1] and stripped[-1] == ",":
|
|
obj += stripped[:-1]
|
|
else:
|
|
obj += stripped
|
|
except IndexError:
|
|
obj += stripped
|
|
|
|
return loaded
|
|
|
|
|
|
def get_room_from_door(door: str) -> str:
|
|
return door[:door.find("[")]
|
|
|
|
|
|
def preprocess_logic(is_door: bool, id: str, logic: str) -> str:
|
|
if id in logic and not is_door:
|
|
index: int = logic.find(id)
|
|
logic = logic[:index] + logic[index+len(id)+4:]
|
|
|
|
while ">=" in logic:
|
|
index: int = logic.find(">=")
|
|
logic = logic[:index-1] + logic[index+3:]
|
|
|
|
while ">" in logic:
|
|
index: int = logic.find(">")
|
|
count = int(logic[index+2])
|
|
count += 1
|
|
logic = logic[:index-1] + str(count) + logic[index+3:]
|
|
|
|
while "<=" in logic:
|
|
index: int = logic.find("<=")
|
|
logic = logic[:index-1] + logic[index+3:]
|
|
|
|
while "<" in logic:
|
|
index: int = logic.find("<")
|
|
count = int(logic[index+2])
|
|
count += 1
|
|
logic = logic[:index-1] + str(count) + logic[index+3:]
|
|
|
|
#print(logic)
|
|
return logic
|
|
|
|
|
|
def build_logic_conditions(logic: str) -> List[List[str]]:
|
|
all_conditions: List[List[str]] = []
|
|
|
|
parts = logic.split()
|
|
sub_part: str = ""
|
|
current_index: int = 0
|
|
parens: int = -1
|
|
current_condition: List[str] = []
|
|
parens_conditions: List[List[List[str]]] = []
|
|
|
|
for index, part in enumerate(parts):
|
|
#print(current_index, index, parens, part)
|
|
|
|
# skip parts that have already been handled
|
|
if index < current_index:
|
|
continue
|
|
|
|
# break loop if reached final part
|
|
try:
|
|
parts[index+1]
|
|
except IndexError:
|
|
#print("INDEXERROR", part)
|
|
if parens < 0:
|
|
current_condition.append(part)
|
|
if len(parens_conditions) > 0:
|
|
for i in parens_conditions:
|
|
for j in i:
|
|
all_conditions.append(j + current_condition)
|
|
else:
|
|
all_conditions.append(current_condition)
|
|
break
|
|
|
|
#print(current_condition, parens, sub_part)
|
|
|
|
# prepare for subcondition
|
|
if "(" in part:
|
|
# keep track of nested parentheses
|
|
if parens == -1:
|
|
parens = 0
|
|
for char in part:
|
|
if char == "(":
|
|
parens += 1
|
|
|
|
# add to sub part
|
|
if sub_part == "":
|
|
sub_part = part
|
|
else:
|
|
sub_part += f" {part}"
|
|
#if not ")" in part:
|
|
continue
|
|
|
|
# end of subcondition
|
|
if ")" in part:
|
|
# read every character in case of multiple closing parentheses
|
|
for char in part:
|
|
if char == ")":
|
|
parens -= 1
|
|
|
|
sub_part += f" {part}"
|
|
|
|
# if reached end of parentheses, handle subcondition
|
|
if parens == 0:
|
|
#print(current_condition, sub_part)
|
|
parens = -1
|
|
|
|
try:
|
|
parts[index+1]
|
|
except IndexError:
|
|
#print("END OF LOGIC")
|
|
if len(parens_conditions) > 0:
|
|
parens_conditions.append(build_logic_subconditions(current_condition, sub_part))
|
|
#print("PARENS:", parens_conditions)
|
|
|
|
temp_conditions: List[List[str]] = []
|
|
|
|
for i in parens_conditions[0]:
|
|
for j in parens_conditions[1]:
|
|
temp_conditions.append(i + j)
|
|
|
|
parens_conditions.pop(0)
|
|
parens_conditions.pop(0)
|
|
|
|
while len(parens_conditions) > 0:
|
|
temp_conditions2 = temp_conditions
|
|
temp_conditions = []
|
|
for k in temp_conditions2:
|
|
for l in parens_conditions[0]:
|
|
temp_conditions.append(k + l)
|
|
|
|
parens_conditions.pop(0)
|
|
|
|
#print("TEMP:", remove_duplicates(temp_conditions))
|
|
all_conditions += temp_conditions
|
|
else:
|
|
all_conditions += build_logic_subconditions(current_condition, sub_part)
|
|
else:
|
|
#print("NEXT PARTS:", parts[index+1], parts[index+2])
|
|
if parts[index+1] == "&&":
|
|
parens_conditions.append(build_logic_subconditions(current_condition, sub_part))
|
|
#print("PARENS:", parens_conditions)
|
|
else:
|
|
if len(parens_conditions) > 0:
|
|
parens_conditions.append(build_logic_subconditions(current_condition, sub_part))
|
|
#print("PARENS:", parens_conditions)
|
|
|
|
temp_conditions: List[List[str]] = []
|
|
|
|
for i in parens_conditions[0]:
|
|
for j in parens_conditions[1]:
|
|
temp_conditions.append(i + j)
|
|
|
|
parens_conditions.pop(0)
|
|
parens_conditions.pop(0)
|
|
|
|
while len(parens_conditions) > 0:
|
|
temp_conditions2 = temp_conditions
|
|
temp_conditions = []
|
|
for k in temp_conditions2:
|
|
for l in parens_conditions[0]:
|
|
temp_conditions.append(k + l)
|
|
|
|
parens_conditions.pop(0)
|
|
|
|
#print("TEMP:", remove_duplicates(temp_conditions))
|
|
all_conditions += temp_conditions
|
|
else:
|
|
all_conditions += build_logic_subconditions(current_condition, sub_part)
|
|
|
|
current_index = index+2
|
|
|
|
current_condition = []
|
|
sub_part = ""
|
|
|
|
continue
|
|
|
|
# collect all parts until reaching end of parentheses
|
|
if parens > 0:
|
|
sub_part += f" {part}"
|
|
continue
|
|
|
|
current_condition.append(part)
|
|
|
|
# continue with current condition
|
|
if parts[index+1] == "&&":
|
|
current_index = index+2
|
|
continue
|
|
|
|
# add condition to list and start new one
|
|
elif parts[index+1] == "||":
|
|
if len(parens_conditions) > 0:
|
|
for i in parens_conditions:
|
|
for j in i:
|
|
all_conditions.append(j + current_condition)
|
|
parens_conditions = []
|
|
else:
|
|
all_conditions.append(current_condition)
|
|
current_condition = []
|
|
current_index = index+2
|
|
continue
|
|
|
|
return remove_duplicates(all_conditions)
|
|
|
|
|
|
def build_logic_subconditions(current_condition: List[str], subcondition: str) -> List[List[str]]:
|
|
#print("STARTED SUBCONDITION", current_condition, subcondition)
|
|
subconditions = build_logic_conditions(subcondition[1:-1])
|
|
final_conditions = []
|
|
|
|
for condition in subconditions:
|
|
final_condition = current_condition + condition
|
|
final_conditions.append(final_condition)
|
|
|
|
#print("ENDED SUBCONDITION")
|
|
#print(final_conditions)
|
|
return final_conditions
|
|
|
|
|
|
def remove_duplicates(conditions: List[List[str]]) -> List[List[str]]:
|
|
final_conditions: List[List[str]] = []
|
|
for condition in conditions:
|
|
final_conditions.append(list(dict.fromkeys(condition)))
|
|
|
|
return final_conditions
|
|
|
|
|
|
def handle_door_visibility(door: Dict[str, Any]) -> Dict[str, Any]:
|
|
if door.get("visibilityFlags") == None:
|
|
return door
|
|
else:
|
|
flags: List[str] = str(door.get("visibilityFlags")).split(", ")
|
|
#print(flags)
|
|
temp_flags: List[str] = []
|
|
this_door: bool = False
|
|
#required_doors: str = ""
|
|
|
|
if "ThisDoor" in flags:
|
|
this_door = True
|
|
|
|
#if "requiredDoors" in flags:
|
|
# required_doors: str = " || ".join(door.get("requiredDoors"))
|
|
|
|
if "DoubleJump" in flags:
|
|
temp_flags.append("DoubleJump")
|
|
|
|
if "NormalLogic" in flags:
|
|
temp_flags.append("NormalLogic")
|
|
|
|
if "NormalLogicAndDoubleJump" in flags:
|
|
temp_flags.append("NormalLogicAndDoubleJump")
|
|
|
|
if "HardLogic" in flags:
|
|
temp_flags.append("HardLogic")
|
|
|
|
if "HardLogicAndDoubleJump" in flags:
|
|
temp_flags.append("HardLogicAndDoubleJump")
|
|
|
|
if "EnemySkips" in flags:
|
|
temp_flags.append("EnemySkips")
|
|
|
|
if "EnemySkipsAndDoubleJump" in flags:
|
|
temp_flags.append("EnemySkipsAndDoubleJump")
|
|
|
|
# remove duplicates
|
|
temp_flags = list(dict.fromkeys(temp_flags))
|
|
|
|
original_logic: str = door.get("logic")
|
|
temp_logic: str = ""
|
|
|
|
if this_door:
|
|
temp_logic = door.get("id")
|
|
|
|
if temp_flags != []:
|
|
if temp_logic != "":
|
|
temp_logic += " || "
|
|
temp_logic += ' && '.join(temp_flags)
|
|
|
|
if temp_logic != "" and original_logic != None:
|
|
if len(original_logic.split()) == 1:
|
|
if len(temp_logic.split()) == 1:
|
|
door["logic"] = f"{temp_logic} && {original_logic}"
|
|
else:
|
|
door["logic"] = f"({temp_logic}) && {original_logic}"
|
|
else:
|
|
if len(temp_logic.split()) == 1:
|
|
door["logic"] = f"{temp_logic} && ({original_logic})"
|
|
else:
|
|
door["logic"] = f"({temp_logic}) && ({original_logic})"
|
|
elif temp_logic != "" and original_logic == None:
|
|
door["logic"] = temp_logic
|
|
|
|
return door
|
|
|
|
|
|
def get_state_provider_for_condition(condition: List[str]) -> str:
|
|
for item in condition:
|
|
if (item[0] == "D" and item[3] == "Z" and item[6] == "S")\
|
|
or (item[0] == "D" and item[3] == "B" and item[4] == "Z" and item[7] == "S"):
|
|
return item
|
|
return None
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('-l', '--local', action="store_true", help="Use local files in the same directory instead of reading resource files from the BrandenEK/Blasphemous-Randomizer repository.")
|
|
args = parser.parse_args()
|
|
return args
|
|
|
|
|
|
def main(args: argparse.Namespace):
|
|
doors = []
|
|
locations = []
|
|
|
|
if (args.local):
|
|
doors = load_resource_local("doors.json")
|
|
locations = load_resource_local("locations_items.json")
|
|
|
|
else:
|
|
doors = load_resource_from_web("https://raw.githubusercontent.com/BrandenEK/Blasphemous-Randomizer/main/resources/data/Randomizer/doors.json")
|
|
locations = load_resource_from_web("https://raw.githubusercontent.com/BrandenEK/Blasphemous-Randomizer/main/resources/data/Randomizer/locations_items.json")
|
|
|
|
original_connections: Dict[str, str] = {}
|
|
rooms: Dict[str, List[str]] = {}
|
|
output: Dict[str, Any] = {}
|
|
logic_objects: List[Dict[str, Any]] = []
|
|
|
|
for door in doors:
|
|
if door.get("originalDoor") != None:
|
|
if not door.get("id") in original_connections:
|
|
original_connections[door.get("id")] = door.get("originalDoor")
|
|
original_connections[door.get("originalDoor")] = door.get("id")
|
|
|
|
room: str = get_room_from_door(door.get("originalDoor"))
|
|
if not room in rooms.keys():
|
|
rooms[room] = [door.get("id")]
|
|
else:
|
|
rooms[room].append(door.get("id"))
|
|
|
|
def flip_doors_in_condition(condition: List[str]) -> List[str]:
|
|
new_condition = []
|
|
for item in condition:
|
|
if item in original_connections:
|
|
new_condition.append(original_connections[item])
|
|
else:
|
|
new_condition.append(item)
|
|
|
|
return new_condition
|
|
|
|
for room in rooms.keys():
|
|
obj = {
|
|
"Name": room,
|
|
"Logic": [],
|
|
"Handling": "Default"
|
|
}
|
|
|
|
for door in rooms[room]:
|
|
logic = {
|
|
"StateProvider": door,
|
|
"Conditions": [],
|
|
"StateModifiers": []
|
|
}
|
|
obj["Logic"].append(logic)
|
|
|
|
logic_objects.append(obj)
|
|
|
|
for door in doors:
|
|
if door.get("direction") == 5:
|
|
continue
|
|
|
|
handling: str = "Transition"
|
|
if "Cell" in door.get("id"):
|
|
handling = "Default"
|
|
obj = {
|
|
"Name": door.get("id"),
|
|
"Logic": [],
|
|
"Handling": handling
|
|
}
|
|
|
|
visibility_flags: List[str] = []
|
|
if door.get("visibilityFlags") != None:
|
|
visibility_flags = str(door.get("visibilityFlags")).split(", ")
|
|
if "1" in visibility_flags:
|
|
visibility_flags.remove("1")
|
|
visibility_flags.append("ThisDoor")
|
|
|
|
required_doors: List[str] = []
|
|
if door.get("requiredDoors"):
|
|
required_doors = door.get("requiredDoors")
|
|
|
|
if len(visibility_flags) > 0:
|
|
for flag in visibility_flags:
|
|
if flag == "RequiredDoors":
|
|
continue
|
|
|
|
if flag == "ThisDoor":
|
|
flag = original_connections[door.get("id")]
|
|
|
|
if door.get("logic") != None:
|
|
logic: str = door.get("logic")
|
|
logic = f"{flag} && ({logic})"
|
|
logic = preprocess_logic(True, door.get("id"), logic)
|
|
conditions = build_logic_conditions(logic)
|
|
for condition in conditions:
|
|
condition = flip_doors_in_condition(condition)
|
|
state_provider: str = get_room_from_door(door.get("id"))
|
|
|
|
if get_state_provider_for_condition(condition) != None:
|
|
state_provider = get_state_provider_for_condition(condition)
|
|
condition.remove(state_provider)
|
|
|
|
logic = {
|
|
"StateProvider": state_provider,
|
|
"Conditions": condition,
|
|
"StateModifiers": []
|
|
}
|
|
obj["Logic"].append(logic)
|
|
else:
|
|
logic = {
|
|
"StateProvider": get_room_from_door(door.get("id")),
|
|
"Conditions": [flag],
|
|
"StateModifiers": []
|
|
}
|
|
obj["Logic"].append(logic)
|
|
|
|
if "RequiredDoors" in visibility_flags:
|
|
for d in required_doors:
|
|
flipped = original_connections[d]
|
|
if door.get("logic") != None:
|
|
logic: str = preprocess_logic(True, door.get("id"), door.get("logic"))
|
|
conditions = build_logic_conditions(logic)
|
|
for condition in conditions:
|
|
condition = flip_doors_in_condition(condition)
|
|
state_provider: str = flipped
|
|
|
|
if flipped in condition:
|
|
condition.remove(flipped)
|
|
|
|
logic = {
|
|
"StateProvider": state_provider,
|
|
"Conditions": condition,
|
|
"StateModifiers": []
|
|
}
|
|
obj["Logic"].append(logic)
|
|
else:
|
|
logic = {
|
|
"StateProvider": flipped,
|
|
"Conditions": [],
|
|
"StateModifiers": []
|
|
}
|
|
obj["Logic"].append(logic)
|
|
|
|
else:
|
|
if door.get("logic") != None:
|
|
logic: str = preprocess_logic(True, door.get("id"), door.get("logic"))
|
|
conditions = build_logic_conditions(logic)
|
|
for condition in conditions:
|
|
condition = flip_doors_in_condition(condition)
|
|
stateProvider: str = get_room_from_door(door.get("id"))
|
|
|
|
if get_state_provider_for_condition(condition) != None:
|
|
stateProvider = get_state_provider_for_condition(condition)
|
|
condition.remove(stateProvider)
|
|
|
|
logic = {
|
|
"StateProvider": stateProvider,
|
|
"Conditions": condition,
|
|
"StateModifiers": []
|
|
}
|
|
obj["Logic"].append(logic)
|
|
else:
|
|
logic = {
|
|
"StateProvider": get_room_from_door(door.get("id")),
|
|
"Conditions": [],
|
|
"StateModifiers": []
|
|
}
|
|
obj["Logic"].append(logic)
|
|
|
|
logic_objects.append(obj)
|
|
|
|
for location in locations:
|
|
obj = {
|
|
"Name": location.get("id"),
|
|
"Logic": [],
|
|
"Handling": "Location"
|
|
}
|
|
|
|
if location.get("logic") != None:
|
|
for condition in build_logic_conditions(preprocess_logic(False, location.get("id"), location.get("logic"))):
|
|
condition = flip_doors_in_condition(condition)
|
|
stateProvider: str = location.get("room")
|
|
|
|
if get_state_provider_for_condition(condition) != None:
|
|
stateProvider = get_state_provider_for_condition(condition)
|
|
condition.remove(stateProvider)
|
|
|
|
if stateProvider == "Initial":
|
|
stateProvider = None
|
|
|
|
logic = {
|
|
"StateProvider": stateProvider,
|
|
"Conditions": condition,
|
|
"StateModifiers": []
|
|
}
|
|
obj["Logic"].append(logic)
|
|
else:
|
|
stateProvider: str = location.get("room")
|
|
if stateProvider == "Initial":
|
|
stateProvider = None
|
|
logic = {
|
|
"StateProvider": stateProvider,
|
|
"Conditions": [],
|
|
"StateModifiers": []
|
|
}
|
|
obj["Logic"].append(logic)
|
|
|
|
logic_objects.append(obj)
|
|
|
|
output["LogicObjects"] = logic_objects
|
|
|
|
with open("StringWorldDefinition.json", "w") as file:
|
|
print("Writing to StringWorldDefinition.json")
|
|
file.write(json.dumps(output, indent=4))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(parse_args())
|