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