# OpenMFOR # credits manually reinstated due to the comments being lost from the object code decompilation # Original release is Copyright (C) 2022 Kazuto88 # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # Source Generated with Decompyle++ # File: Fusion_Graph.pyc (Python 3.8) import struct import sys import time class Game: def __init__(self, vanillaGame, randoSettings=None): self.graph = dict() self.areaConnections = dict() self.areaConnectionOffsets = dict() self.doorConnections = dict() self.rooms = dict() self.requirements = dict() self.visited = list() self.queue = list() self.majorItemLocations = list() self.minorItemLocations = list() self.itemLocations = set() self.patcher = dict() self.oldtime = 0.0 self.bfstime = 0.0 self.dfstime = 0.0 # print('DEBUG: Opening ROM to pick stuff') try: with open(vanillaGame, 'rb') as sourceRom: sourceRom.seek(3967888, 0) sourceArea = int.from_bytes(sourceRom.read(1), 'little') sourceDoor = int.from_bytes(sourceRom.read(1), 'little') targetOffset = sourceRom.tell() targetArea = int.from_bytes(sourceRom.read(1), 'little') while sourceArea != 255: self.areaConnections.update({ 'S{}-{:02X}'.format(sourceArea, sourceDoor): targetArea }) self.areaConnectionOffsets.update({ 'S{}-{:02X}'.format(sourceArea, sourceDoor): targetOffset }) sourceArea = int.from_bytes(sourceRom.read(1), 'little') sourceDoor = int.from_bytes(sourceRom.read(1), 'little') targetOffset = sourceRom.tell() targetArea = int.from_bytes(sourceRom.read(1), 'little') for currentArea in range(7): # print('DEBUG: Parsing sector {}'.format(currentArea)) sourceRom.seek(7977108 + currentArea * 4, 0) data = sourceRom.read(4) unpacked = struct.unpack('= depth: return None for point in self.graph[start]: if point in self.itemLocations: if point not in end: continue if point in path: continue if LimitArea and point != end: for area in range(0, 7): if 'S{}'.format(area) in start: if 'S{}'.format(area) not in point: return None edge = (start, point) self.queue.append(edge) while self.queue: edge = self.queue.pop() if edge not in self.visited: self.visited.append(edge) node = edge[1] pathReqs = self.get_requirements(start, node) if pathReqs == None: newpath = self._get_path(node, end, LimitArea, path, depth) if newpath: path = path + [node] return newpath elif pathReqs == True: newpath = self._get_path(node, end, LimitArea, path, depth) if newpath: path = path + [node] return newpath def has_path(self, start, end, LimitArea=False, depth=100): if LimitArea: area = self.itemArea.get(start) if area == None: for n in range(0, 7): if 'S{}'.format(n) in start: area = n if area == None: for n in range(0, 7): if 'S{}'.format(n) in end: area = n else: area = None t = time.perf_counter() result = self.has_path_bfs(start, end, area=area, max_depth=depth) self.bfstime += time.perf_counter() - t t = time.perf_counter() r2 =self.has_path_dfs(start, end, area=area) assert result == r2, (start, end, result, r2, area) self.dfstime += time.perf_counter() - t return result # NOTE: both the start and end node need to be excluded from # area checks because they can be things like bosses or the Water Pump # that aren't associated with an area. all the intermediate nodes # should be doors, and doors all have their sector number in their names. def has_path_bfs(self, start, end, area=None, max_depth=100): if start not in self.graph: return False if start == end: return True if area != None: areaStr = 'S{}'.format(area) seen = {start} frontier = [start] next_frontier = [] depth = 0 while frontier: depth += 1 #if depth > max_depth: break for node in frontier: for neighbor in self.graph[node]: if neighbor in seen: continue if neighbor in self.itemLocations: if neighbor == end: pathReqs = self.get_requirements(node, neighbor) if pathReqs == None or pathReqs == True: return True #seen.add(neighbor) continue if area is not None: if areaStr not in neighbor and neighbor != end: continue pathReqs = self.get_requirements(node, neighbor) if pathReqs != None and pathReqs != True: continue if neighbor == end: return True next_frontier.append(neighbor) # since this is a breadth-first search on an unweighted graph, # we know that no other paths can be shorter than this one, # so we can immediately mark the node as seen to prevent # adding it to the frontier again seen.add(neighbor) frontier.clear() frontier, next_frontier = next_frontier, frontier return False def has_path_dfs(self, start, end, area=None): if start not in self.graph: return False if start == end: return True visited = set() stack = [start] if area != None: areaStr = 'S{}'.format(area) while stack: node = stack.pop() if node in visited: continue visited.add(node) if node == end: return True if node is not start: if node in self.itemLocations: continue if area is not None: if areaStr not in node: continue for neighbor in reversed(self.graph[node]): if neighbor in visited: continue pathReqs = self.get_requirements(node, neighbor) if pathReqs != None and pathReqs != True: continue stack.append(neighbor) return False