def setup(self): '''Called to initialize the game''' self.center = (self.size.w / 2, self.size.h / 2) self.player = Player(parent=self, speed=PLAYER_SPEED) self.controller = Controller(padding=40, scale=1.2, parent=self) self.firebtn = FireBtn(70, parent=self) self.background_color = '#003f68' self.objects = list() self.lasers = list() self.comets = list() self.pos = (.0, .0) self.firing = False self.player_sequence = Sequence( [Shot(x_offset=40, y_offset=20), Shot(x_offset=-40, y_offset=20)], origin=self.player, delay=0.2) self.spawn_area = ShapeNode(Path.rect(*self.bounds), alpha=0, parent=self, scale=2, position=self.size / 2)
def get_connected_halls(self, static_areas, room_name, debug): c = Controller(debug) connected_halls = [] for each_item, room_data in static_areas[room_name].items(): next_room_number, next_room_coords, direction = None, None, None # parse connected hallway data if each_item.startswith( 'door') and room_data['area_to'] != 1701736302: hallway_number = room_data['area_to'] hallway_name = next(name for name, data in static_areas.items() if data['id'] == hallway_number) for each_tile in static_areas[hallway_name]['tiles'].values(): if each_tile['type'] == 2 and each_tile['door_to']['area_to'] != self.room_number \ and not any(hall['next_room_number'] == each_tile['door_to']['area_to'] for hall in connected_halls): next_room_number = each_tile['door_to']['area_to'] break next_room_name = None if next_room_number is None \ else next(name for name, data in static_areas.items() if data['id'] == next_room_number) next_room_coords = static_areas[next_room_name]['tiles'][ 'tile0']['mappos'] # determine direction relative to current room x_diff = abs(next_room_coords[0] - self.room_coordinates[0]) y_diff = abs(next_room_coords[1] - self.room_coordinates[1]) if x_diff > y_diff and next_room_coords[ 0] > self.room_coordinates[0]: direction = 'right' controller_input = c.right_stick_right elif x_diff > y_diff and next_room_coords[ 0] < self.room_coordinates[0]: direction = 'left' controller_input = c.right_stick_left elif next_room_coords[1] > self.room_coordinates[1]: direction = 'down' controller_input = c.right_stick_down else: direction = 'up' controller_input = c.right_stick_up connected_halls.append({ 'hallway_name': hallway_name, 'hallway_number': hallway_number, 'next_room_number': next_room_number, 'next_room_name': next_room_name, 'direction': direction, 'controller_input': controller_input }) return connected_halls
class Game(Scene): ''' The Scene object where the gameplay is handled ''' def setup(self): '''Called to initialize the game''' self.center = (self.size.w / 2, self.size.h / 2) self.player = Player(parent=self, speed=PLAYER_SPEED) self.controller = Controller(padding=40, scale=1.2, parent=self) self.firebtn = FireBtn(70, parent=self) self.background_color = '#003f68' self.objects = list() self.lasers = list() self.comets = list() self.pos = (.0, .0) self.firing = False self.player_sequence = Sequence( [Shot(x_offset=40, y_offset=20), Shot(x_offset=-40, y_offset=20)], origin=self.player, delay=0.2) self.spawn_area = ShapeNode(Path.rect(*self.bounds), alpha=0, parent=self, scale=2, position=self.size / 2) def update(self): '''Called, preferably, 60 times a second''' if len(self.comets) <= COMET_MAX_COMETS and round(self.t) % 0.25 == 0: new_comet = Comet.spawn_in(self, self.spawn_area.frame, self.bounds, self.player.pos, self.comets, comet_size='big', scale=max(1, random() * 2), speed=randrange(0, COMET_MAX_SPEED)) self.objects.append(new_comet) self.comets.append(new_comet) if self.firing: self.player_sequence.shoot() self.controller.joystick.update_movement() joystick_vector = Vector2(*self.controller.joystick.movement[:2]) if self.player.velocity != joystick_vector: self.player.update_vel(self.controller.joystick.movement[2] / self.controller.radius) self.player.rotate(( (self.controller.joystick.movement[3] - self.player.rotation) % (2 * pi) - pi) * .05) self.player.move() self.pos = (self.center[0] - self.player.pos[0], self.center[1] - self.player.pos[1]) self.check_comet_collisions() self.check_laser_collisions() self.move_objects() def move_objects(self): '''Moves every Node in self.objects to give the impression self.player moved''' for obj in list(self.objects): obj.position = (obj.pos[0] - self.player.pos[0], obj.pos[1] - self.player.pos[1]) if isinstance(obj, Comet): if max([abs(x + self.size.w / 2) for x in obj.position]) > self.size.w * 2: self.objects.remove(obj) self.comets.remove(obj) obj.remove_from_parent() obj.pos += rotation_vector(obj.direction) * obj.speed def check_comet_collisions(self): '''Checks and handles Comet to Comet collisions''' for comet in list(self.comets): for other in list(self.comets): if other == comet: continue if comet.frame.intersects( other.frame) and comet.collides_with_other(other): x1, y1 = comet.position x2, y2 = other.position r1 = comet.radius r2 = other.radius collisionPoint = Point((x1 * r2 + x2 * r1) / (r1 + r2), (y1 * r2 + y2 * r1) / (r1 + r2)) mass1 = comet.mass mass2 = other.mass velX1, velY1 = rotation_vector( comet.direction) * comet.speed velX2, velY2 = rotation_vector( other.direction) * other.speed newVelX1 = (velX1 * (mass1 - mass2) + (2 * mass2 * velX2)) / (mass1 + mass2) newVelX2 = (velX2 * (mass2 - mass1) + (2 * mass1 * velX1)) / (mass1 + mass2) newVelY1 = (velY1 * (mass1 - mass2) + (2 * mass2 * velY2)) / (mass1 + mass2) newVelY2 = (velY2 * (mass2 - mass1) + (2 * mass1 * velY1)) / (mass1 + mass2) comet.health -= other.mass * other.speed / 100 other.health -= comet.mass * comet.speed / 100 comet.position += Vector2(-velX1, -velY1) comet.pos += Vector2(-velX1, -velY1) comet.direction = -atan2(newVelX1, newVelY1) comet.speed = abs(Vector2(newVelX1, newVelY1)) other.position += Vector2(-velX2, -velY2) other.pos += Vector2(-velX2, -velY2) other.direction = -atan2(newVelX2, newVelY2) other.speed = abs(Vector2(newVelX1, newVelY1)) comet.check_health() other.check_health() def check_laser_collisions(self): '''Moves, checks and handles Laser to Comet collisions ''' for laser in list(self.lasers): laser.move() if laser.dead: continue for comet in self.comets: if laser.frame.intersects( comet.frame) and comet.collides_with_other(laser): laser.counter = 0 laser.z_position = comet.z_position + .1 comet.health -= laser.damage comet.check_health() def touch_began(self, touch): '''Called when a touch is initiated''' if touch.location in self.controller.frame: self.controller.touch_id = touch.touch_id self.controller.run_action(Action.fade_to(1, .2)) self.controller.joystick.remove_all_actions() self.controller.joystick.touch_loc = self.controller.point_from_scene( touch.location) self.controller.joystick.move_to(touch.location) elif touch.location in self.firebtn.frame: self.firebtn.touch_id = touch.touch_id self.firebtn.start() def touch_moved(self, touch): '''Called when a touch moved''' if touch.touch_id == self.controller.touch_id: self.controller.joystick.move_to(touch.location) def touch_ended(self, touch): '''Called when a touch ended''' if touch.touch_id == self.controller.touch_id: self.controller.joystick.reset() self.controller.run_action(Action.fade_to(.4, .2)) elif touch.touch_id == self.firebtn.touch_id: self.firebtn.stop()
def enter_room(raid_info, areas, static_areas, area_name, inventory, reverse, debug, next_room_name=None): c = Controller(debug) print('Entering Room!') c.write(c.b) area_number = static_areas[area_name]['id'] if next_room_name is None: last_room_id = raid_info['last_room_id'] last_room_name = next(room_name for room_name, data in static_areas.items() if data['id'] == last_room_id) last_room = Room(static_areas, last_room_name, debug) current_hall = next(hall for hall in last_room.connected_halls if hall['hallway_name'] == area_name) next_room_name = current_hall['next_room_name'] # make sure torch is kept over 50% if there is a potential room battle # future - only use torch if not farmstead or courtyard if areas[next_room_name]['knowledge'] == 1 or areas[next_room_name][ 'tiles']['tile0']['mash_index'] != -1: torchlight = 0 if raid_info['torchlight'] == 0 else ( raid_info['torchlight'] / 131072) - 8448 buffer = 6 torches_used = 0 torches = [item for item in inventory.items if item.name == 'torch'] if torchlight < 50 + buffer and len(torches) > 0: c.write(c.torch) torches_used += 1 if torchlight < 25 + buffer: c.write(c.torch) torches_used += 1 if torchlight < buffer: c.write(c.torch) torches_used += 1 manage_inventory_empty_torch_slots(inventory, torches, torches_used) while not raid_info['inbattle']: # move forward and try again if didn't get the door sfr.decrypt_save_info('persist.map.json') f = open(Path(f'{sfr.save_editor_path()}/persist.map.json')) map_info = json.load(f)['base_root'] f.close() areas = map_info['map']['static_dynamic']['areas'] area_tiles = areas[area_name]['tiles'] area_length = len(area_tiles) - 1 sfr.decrypt_save_info('persist.raid.json') f = open(Path(f'{sfr.save_editor_path()}/persist.raid.json')) raid_info = json.load(f)['base_root'] f.close() party_tile = get_party_tile(raid_info, area_length, reverse) location_number = raid_info['in_area'] if party_tile == (area_length if not reverse else 0) or area_number != location_number: break c.keyDown(c.right) time.sleep(1.1) c.keyUp(c.right) c.write(c.up) c.write( c.b) # if accidentally activating a curio, make sure to close out time.sleep( .5 ) # need to give enough time for save file to update after entering room
def clear_obstacle(raid_info, static_areas, inventory, tile_number, area_name, reverse, debug): c = Controller(debug) print('Clearing Obstacle!') area_number = static_areas[area_name]['id'] while not raid_info['inbattle']: shovels = [item for item in inventory.items if item.name == 'shovel'] shovels.sort(key=lambda k: k.quantity) if len(shovels) > 0: c.write(c.y, 2) c.write(c.map) # press to avoid accidentally swapping party order if shovels[0].quantity == 1: if shovels[0].item_slot != max(i.item_slot for i in inventory.items): inventory.empty_slots.append(shovels[0].item_slot) inventory.items.remove(shovels[0]) else: c.write( c.a, 2 ) # pressing 'a' three times can cause unintentional party swap c.write(c.map) # press to avoid accidentally swapping party order c.write( c.up, 2 ) # if at end of hallway, obstacle is already cleared, go into next room # move forward and try again if didn't get the obstacle sfr.decrypt_save_info('persist.map.json') f = open(Path(f'{sfr.save_editor_path()}/persist.map.json')) map_info = json.load(f)['base_root'] f.close() areas = map_info['map']['static_dynamic']['areas'] area_tiles = areas[area_name]['tiles'] area_length = len(area_tiles) - 1 tile_name = f'tile{tile_number}' sfr.decrypt_save_info('persist.raid.json') f = open(Path(f'{sfr.save_editor_path()}/persist.raid.json')) raid_info = json.load(f)['base_root'] f.close() party_tile = 0 if area_length == 0 else get_party_tile( raid_info, area_length, reverse) location_number = raid_info['in_area'] # if party_tile > tile_number or area_tiles[tile_name]['content'] == 0 or area_number != location_number: if (party_tile > tile_number and not reverse) or (party_tile < tile_number and reverse) \ or area_tiles[tile_name]['content'] == 0 or area_number != location_number: break c.keyDown(c.right) time.sleep(1.5) c.keyUp(c.right)
def disarm_trap(raid_info, areas, tile_number, area_name, reverse, debug): c = Controller(debug) print('Disarming Trap!') # don't accidentally re-enter room or secret room area_tiles = areas[area_name]['tiles'] area_length = len(area_tiles) - 1 party_tile = 0 if area_length == 0 else get_party_tile( raid_info, area_length, reverse) if area_name.startswith('co') and area_tiles[f'tile{party_tile}']['content'] == 13 and \ area_tiles[f'tile{party_tile}']['crit_scout'] is True: c.press(c.right, 25) elif area_length > 0 and ((party_tile == 0 and not reverse) or (party_tile == area_length and reverse)): c.press(c.right, 10) while not raid_info['inbattle']: c.write(c.up) c.write(c.b) # don't accidentally activate curio c.press(c.right, 3) # move forward and try again if didn't get the trap sfr.decrypt_save_info('persist.map.json') f = open(Path(f'{sfr.save_editor_path()}/persist.map.json')) map_info = json.load(f)['base_root'] f.close() areas = map_info['map']['static_dynamic']['areas'] area_tiles = areas[area_name]['tiles'] area_length = len(area_tiles) - 1 sfr.decrypt_save_info('persist.raid.json') f = open(Path(f'{sfr.save_editor_path()}/persist.raid.json')) raid_info = json.load(f)['base_root'] f.close() party_tile = 0 if area_length == 0 else get_party_tile( raid_info, area_length, reverse) trap = area_tiles[f'tile{tile_number}']['trap'] # if tile_number == party_tile: if trap == -858993460 or trap == 0 or area_tiles[f'tile{tile_number}']['content'] == 0 \ or (party_tile > tile_number and not reverse) or (party_tile < tile_number and reverse): break
def activate_curio(party, provision, inventory, item, raid_info, area_name, tile_number, dungeon_path, reverse, debug): c = Controller(debug) sfr.decrypt_save_info('persist.map.json') f = open(Path(f'{sfr.save_editor_path()}/persist.map.json')) map_info = json.load(f)['base_root'] f.close() areas = map_info['map']['static_dynamic']['areas'] location = area_name static_areas = map_info['map']['static_dynamic']['static_save'][ 'base_root']['areas'] area_tiles = areas[area_name]['tiles'] area_length = len(area_tiles) - 1 tile_name = f'tile{tile_number}' curio = Curios[area_tiles[tile_name]['curio_prop']] print(f"Interacting with curio! ({curio['name']})") if provision is None and curio['name'] != 'ConfessionBooth': reward = curio['reward'][0] if curio['reward'] is not None else None elif provision is None and curio['name'] == 'ConfessionBooth': reward = 'purge_negative_quirk' else: reward_index = next((index for index, item in enumerate(curio['provision']) if provision == item), None) reward = curio['reward'][ reward_index] if reward_index is not None else None if reward is None: # drop off quest item pass elif 'treasure' == reward: hero = next( (hero for hero in party if 'antiquarian' == hero.heroClass), None) if hero is not None: select_hero(hero.rank, debug) elif 'stress_heal' == reward: party.sort(key=lambda k: k.stress, reverse=True) select_hero(party[0].rank, debug) elif 'purge_negative_quirk' == reward: # future - if heroes have the same probability, choose the one that's higher level removal_scores = get_quirk_removal_scores(party) best_score = removal_scores[0] if curio['name'] == 'ConfessionBooth': best_score = next( score for score in removal_scores if next(hero for hero in party if hero.rank == score['hero_rank']).stress < 60) select_hero(best_score['hero_rank'], debug) elif 'dmg_buff' == reward: # prioritize tier 1 dmg dealers that can hit all ranks dmg_dealer_rank = next( (hero.rank for hero in party if hero.heroClass == 'shieldbreaker' or hero.heroClass == 'highwayman' or hero.heroClass == 'hellion'), None) if dmg_dealer_rank is None: dmg_dealer_rank = next( (hero.rank for hero in party if hero.heroClass == 'grave_robber' or hero.heroClass == 'crusader' or hero.heroClass == 'houndmaster' or hero.heroClass == 'flagellant' or hero.heroClass == 'man_at_arms' or hero.heroClass == 'bounty_hunter'), None) if dmg_dealer_rank is None: dmg_dealer_rank = party[0].rank select_hero(dmg_dealer_rank, debug) elif 'heal' == reward: party.sort(key=lambda k: k.percentHp) select_hero(party[0].rank, debug) elif 'def_buff' == reward: # prioritize healer or non-tank heroes in ranks 1, 2, 3 buff_target_rank = next( (hero.rank for hero in party if hero.heroClass == 'vestal' or hero.heroClass == 'highwayman' or hero.heroClass == 'hellion' or hero.heroClass == 'shieldbreaker' or hero.heroClass == 'houndmaster'), None) if buff_target_rank is None: buff_target_rank = party[0].rank select_hero(buff_target_rank, debug) dd_window = win32gui.FindWindowEx(0, 0, 0, "Darkest Dungeon") desktop = win32gui.GetDesktopWindow() search_region = rf'{sfr.save_editor_path()}\search_region.png' hand_img = rf'{sfr.game_install_location()}\scrolls\byhand.png' use_item_img = rf'{sfr.game_install_location()}\scrolls\use_inventory.png' curio_found = False # don't accidentally re-enter room or secret room party_tile = 0 if area_length == 0 else get_party_tile( raid_info, area_length, reverse) if area_name.startswith('co') and area_tiles[f'tile{party_tile}']['content'] == 13 \ and area_tiles[f'tile{party_tile}']['crit_scout'] is True: c.press(c.right, 25) elif area_length > 0 and ((party_tile == 0 and not reverse) or (party_tile == area_length and reverse)): c.press(c.right, 10) # Activate Curio c.write(c.up, 2) if not area_name.startswith('co'): time.sleep(1.5) # wait for curio screen # if in hallway, may have to move and try again if didn't get curio while not raid_info['inbattle'] and area_name.startswith( 'co') and area_name == location: print('Activating curio') # take screenshot # need to minimize and reactivate window, otherwise can't see loot/curio window with a screenshot if not debug: win32gui.SetForegroundWindow(desktop) pydirectinput.doubleClick(x=1050, y=500) pydirectinput.doubleClick(x=1050, y=500) win32gui.SetForegroundWindow(dd_window) pydirectinput.doubleClick(x=1050, y=500) pydirectinput.doubleClick(x=1050, y=500) if os.path.exists(search_region): os.remove(search_region) pyautogui.screenshot(search_region, region=(1060, 400, 585, 215)) image = use_item_img if item is not None and item.type == 'quest' else hand_img found = list(pyautogui.locateAll(image, search_region, confidence=.45)) curio_found = True if len(found) > 0 else False print(f'curio_found: {curio_found}') # move forward and try again if didn't get the curio sfr.decrypt_save_info('persist.raid.json') f = open(Path(f'{sfr.save_editor_path()}/persist.raid.json')) raid_info = json.load(f)['base_root'] f.close() party_tile = 0 if area_length == 0 else get_party_tile( raid_info, area_length, reverse) location_number = raid_info['in_area'] # 1111584611 location = next(index for index, area in areas.items() if static_areas[index]['id'] == location_number) sfr.decrypt_save_info('persist.map.json') f = open(Path(f'{sfr.save_editor_path()}/persist.map.json')) map_info = json.load(f)['base_root'] f.close() areas = map_info['map']['static_dynamic']['areas'] hallway_tiles = areas[area_name]['tiles'] if curio_found or (party_tile > tile_number and not reverse) or (party_tile < tile_number and reverse) \ or hallway_tiles[tile_name]['content'] == 0: break c.press(c.right, 5) c.write(c.up, 2) if (curio_found or not area_name.startswith('co')) and area_name == location: if provision is None: if curio['name'] != 'Sack' and curio['name'] != 'DiscardedPack' and curio['name'] != 'Crate' \ and curio['name'] != 'Sconce': c.write( c.d_pad_right, 2 ) # necessary to make sure curio gets past use item screen c.write(c.a) time.sleep( 1.5 ) # wait for potential loot screen or for save file to reflect curio has been activated else: item_used = False while not item_used: use_item_on_curio(int(item.item_slot), debug) # take screenshot # need to minimize and reactivate window, otherwise can't see loot/curio window with screenshot if not debug: win32gui.SetForegroundWindow(desktop) pydirectinput.doubleClick(x=1050, y=500) pydirectinput.doubleClick(x=1050, y=500) win32gui.SetForegroundWindow(dd_window) pydirectinput.doubleClick(x=1050, y=500) pydirectinput.doubleClick(x=1050, y=500) if os.path.exists(search_region): os.remove(search_region) pyautogui.screenshot(search_region, region=(1300, 425, 100, 150)) found = list( pyautogui.locateAll(use_item_img, search_region, confidence=.8)) item_used = False if len(found) > 0 else True print(f'item_used: {item_used}') item.quantity -= 1 if item.quantity == 0: if item.item_slot != max(i.item_slot for i in inventory.items): inventory.empty_slots.append(item.item_slot) inventory.items.remove(item) time.sleep( 1.5 ) # wait for potential loot screen or for save file to reflect curio has been activated if (reward == 'treasure' and item is not None) or (reward == 'quest' and provision is None): loot_treasure(raid_info, inventory, areas, area_name, party, tile_number, dungeon_path, debug) # check in case curio does not always give loot elif 'treasure' in curio['reward'] and item is None: if not debug: win32gui.SetForegroundWindow(desktop) pydirectinput.doubleClick(x=1050, y=500) pydirectinput.doubleClick(x=1050, y=500) win32gui.SetForegroundWindow(dd_window) pydirectinput.doubleClick(x=1050, y=500) pydirectinput.doubleClick(x=1050, y=500) if os.path.exists(search_region): os.remove(search_region) c.write(c.down, 2) # Tried inserting button presses and sleeps to make sure icons and text doesn't get in the way # of screenshot. Unfortunately nothing works. Probably just have to save off custom thumbnail to look # for instead. Don't need to be able to see specific loot items in image anyways, since we aren't # performing image based classification pyautogui.screenshot(search_region, region=(1060, 400, 585, 215)) found = list( pyautogui.locateAll(hand_img, search_region, confidence=.45)) loot_screen_found = True if len(found) > 0 else False print(f'loot_screen_found: {loot_screen_found}') if loot_screen_found: loot_treasure(raid_info, inventory, areas, area_name, party, tile_number, dungeon_path, debug) print('Activate Curio Complete!') # check for mission complete if (raid_info['raid_instance']['type'] == 'inventory_activate' and provision in Items and Items[provision]['type'] == 'quest' and sum(item.type == 'quest' for item in inventory.items) == 1): time.sleep(1.5) c.write(c.b, 2)
def loot_treasure(raid_info, inventory, areas, area_name, party, tile_number, dungeon_path, debug): c = Controller(debug) print('Looting treasure!') dungeon_name = raid_info['raid_instance']['dungeon'] queued_loot = raid_info['loot']['queue_items']['items'] battle_reward = raid_info['loot']['result']['inventory_system'][ 'items'] if 'result' in raid_info['loot'] else [] c.write( c.left_stick_down ) # in case didn't exit from battle and accidentally grabbed item, let it go c.write(c.left_stick_up, 2) # check how many empty slots we have in our inventory item_data = {} for i in inventory.items: total = sum(item.quantity for item in inventory.items if item.name == i.name) stacks = math.ceil(total / i.full_stack) if i.name not in item_data: item_data.update({i.name: {'total': total, 'stacks': stacks}}) total_stacks = sum(item['stacks'] for item in item_data.values()) empty_slots = 16 - total_stacks print(f'number of empty slots in inventory: {empty_slots}') # combine item stacks in inventory if necessary items = inventory.items.copy() for i in items: items_to_combine = [ item for item in inventory.items if item.name == i.name and item.quantity != item.full_stack ] for index, item in enumerate(items_to_combine): if index != len(items_to_combine) - 1: combine_items(item.item_slot, items_to_combine[index + 1].item_slot, debug) new_item = items_to_combine[index + 1] leftover = item.quantity + new_item.quantity - item.full_stack if leftover <= 0: new_item.quantity += item.quantity inventory.empty_slots.append(item.item_slot) inventory.items.remove(item) else: new_item.quantity = item.full_stack item.quantity = leftover item.value = Items[item.name][ 'value'] * item.quantity if item.value is not None else item.value new_item.value = Items[new_item.name]['value'] * new_item.quantity if new_item.value is not None \ else new_item.value # if the loot is contained in the save file, parse it and sort it by value if len(queued_loot) > 0 or len(battle_reward) > 0: loot = [ Item(item['id'], item['type'], item['amount'], item_slot) for item_slot, item in (queued_loot.items( ) if len(queued_loot) > 0 else battle_reward.items()) ] print('Specific loot contained in save file!') print(queued_loot if len(queued_loot) > 0 else battle_reward) # combine loot items with items in our inventory if possible # - it is assumed that items in inventory are already combined such that there can only be 1 incomplete # stack per item for i in loot.copy(): if i.quantity != i.full_stack: item = next((item for item in inventory.items if i.name == item.name and item.quantity != item.full_stack), None) if item is not None: take_item(i.item_slot, debug) leftover = i.quantity + item.quantity - item.full_stack if leftover <= 0: item.quantity += i.quantity for j in loot: if j.item_slot > i.item_slot: j.item_slot -= 1 loot.remove(i) elif leftover > 0 >= empty_slots: item.quantity = item.full_stack i.quantity = leftover i.value = Items[i.name][ 'value'] * i.quantity if i.value is not None else i.value c.write(c.left_stick_down) c.write(c.left_stick_up, 2) elif leftover > 0 and empty_slots > 0: item.quantity = item.full_stack empty_slots -= 1 for j in loot: if j.item_slot > i.item_slot: j.item_slot -= 1 loot.remove(i) for index in range(16): if not any(j.item_slot == index for j in inventory.items): inventory.items.append( Item(i.name, i.type, quantity=leftover, item_slot=index)) if index in inventory.empty_slots: inventory.empty_slots.remove(index) break item.value = Items[item.name][ 'value'] * item.quantity if item.value is not None else item.value print('Combining loot items with inventory items finished!') # sort loot in order of priority # future - add priority for stacks of heirlooms and sort loot_to_take = [item for item in loot if item.value is None] other_loot = [item for item in loot if item.value is not None] other_loot.sort(key=lambda k: k.value, reverse=True) loot_to_take.extend(other_loot) loot = loot_to_take.copy() # take items until no more empty slots print(f'number of empty slots in inventory: {empty_slots}') for index, i in enumerate(loot_to_take): if index < empty_slots: take_item(i.item_slot, debug) for j in loot_to_take: if j.item_slot > i.item_slot: j.item_slot -= 1 for j in range(16): if not any(item.item_slot == j for item in inventory.items): inventory.items.append( Item(i.name, i.type, i.quantity, item_slot=j)) if j in inventory.empty_slots: inventory.empty_slots.remove(j) break loot.remove(i) # compare highest value loot with the lowest value item in our inventory # to determine whether or not we should switch them loot_to_take = loot.copy() droppable_items = get_droppable_items(raid_info, areas, inventory, dungeon_name, party, dungeon_path) drop_count = 0 for item in loot_to_take: if not ((item.type == 'trinket' and item.rating >= 6) or item.value is None or item.value > droppable_items[drop_count].value): break # check again first, and only drop item if it won't combine # if not any(i for i in inventory.items # if i.name == item.name and i.quantity + item.quantity <= i.full_stack): drop_item(droppable_items[drop_count].item_slot, debug) drop_count += 1 take_item(item.item_slot, debug) for i in loot_to_take: if i.item_slot > item.item_slot: i.item_slot -= 1 loot.remove(item) if len(loot) > 0: c.write(c.b) else: # the loot only exists in the game client, can't determine what the items are using the save file reader # future - use pattern recognition to perform classification of items, for now just drop the lowest value # item one at a time and take items randomly # loot = identify_lootscreen_items(search_region, dungeon_name, party) # - Currently runs pretty fast but accuracy is terrible. Game icons are most likely being scaled # and overlayed in the game in such a way that they can't recognized. Either need to create custom # thumbnails instead of using game assests, or need to use a better library for image recognition print('Random loot, not contained in save file!') area_tiles = areas[area_name]['tiles'] curio_prop = area_tiles[f'tile{tile_number}']['curio_prop'] # rearrange inventory to fill in empty slots (must do this step!!!) for empty_slot in inventory.empty_slots.copy(): print(f'filling in empty inventory slot {empty_slot}') last_item_slot = max(i.item_slot for i in inventory.items) last_item = next(item for item in inventory.items if item.item_slot == last_item_slot) last_slot_trinket = Items[ last_item.name] if last_item.type == 'trinket' else None selected_hero_class = next( hero.heroClass for hero in party if hero.rank == get_selected_hero_rank()) combine_items(last_item.item_slot, empty_slot, debug, last_slot_trinket, selected_hero_class) last_item.item_slot = empty_slot inventory.empty_slots.remove(empty_slot) # attempt to take all items using space print('attempting to take all items with space ...') c.write(c.right) # needed before space command, don't remove pydirectinput.press( c.space, interval=.05 ) # need to use pydirectinput.press for 'space'/'enter' key pydirectinput.press( c.space, interval=.05 ) # repeat command in case input doesn't work (don't remove!) time.sleep(.3) pydirectinput.press(c.enter, interval=.05) # close inventory full notification time.sleep(.3) sfr.decrypt_save_info('persist.map.json') f = open(Path(f'{sfr.save_editor_path()}/persist.map.json')) map_info = json.load(f)['base_root'] f.close() areas = map_info['map']['static_dynamic']['areas'] area_tile = areas[area_name]['tiles'][f'tile{tile_number}'] # if that doesn't work, drop the lowest value item and take items until there are none left if area_tile['content'] != 0: droppable_items = get_droppable_items(raid_info, areas, inventory, dungeon_name, party, dungeon_path) while area_tile['content'] != 0: # stop if don't want to drop lowest value item if ((droppable_items[0].value >= 800 and droppable_items[0].name != 'gold') or (droppable_items[0].value >= 500 and droppable_items[0].name == 'gold')) \ and not area_name.startswith('sec') and 'quest' not in Curios[curio_prop]['reward']: c.write(c.b) break drop_item(droppable_items[0].item_slot, debug) # don't add gaps created to inventory.empty slots since they will always be getting filled in inventory.items.remove(droppable_items[0]) droppable_items.pop(0) print('attempting to take all items with space ...') pydirectinput.press( c.space, interval=.05 ) # need to use pydirectinput.press for 'space' key pydirectinput.press( c.space, interval=.05 ) # repeat command in case doesn't work (don't remove!) time.sleep(.3) pydirectinput.press( c.enter, interval=.05) # close inventory full notification pydirectinput.press( c.enter, interval=.05 ) # repeat command in case doesn't work (don't remove!) time.sleep(.3) sfr.decrypt_save_info('persist.map.json') f = open(Path(f'{sfr.save_editor_path()}/persist.map.json')) map_info = json.load(f)['base_root'] f.close() areas = map_info['map']['static_dynamic']['areas'] area_tile = areas[area_name]['tiles'][f'tile{tile_number}'] c.write(c.b, 2) # important, make sure inventory is no longer selected time.sleep(.3) print('Looting Complete!')
def main(save_editor_path, game_install_location, profile_number, battle_speed='safe', debug=False, test_lootscreen=False): global provisions_bought c = Controller(debug) # Import Settings if Applicable filepath = Path(f'{os.getcwd()}/settings.json') if os.path.exists(filepath): f = open(filepath) settings = json.load(f)['settings'] save_editor_path = settings['save_editor_path'] game_install_location = settings['game_install_location'] profile_number = settings['save_profile_number'] battle_speed = settings['battle_speed'] f.close() # Initialize Save File Reader sfr = SaveFileReader(save_editor_path, game_install_location, profile_number) if not debug: # Make Darkest Dungeon the active window dd_window = win32gui.FindWindowEx(0, 0, 0, "Darkest Dungeon") win32gui.SetForegroundWindow(dd_window) pydirectinput.doubleClick(x=1050, y=500) pydirectinput.doubleClick(x=1050, y=500) while True: print('Beginning main loop ...') sfr.decrypt_save_info('persist.game.json') f = open(Path(f'{sfr.SaveEditorPath}/persist.game.json')) info = json.load(f)['base_root'] f.close() # In Dungeon if info['inraid']: # if ModsCheck is False: # ModsCheck = True # if 'applied_ugcs_1_0' in info: # installed_mods = info['applied_ugcs_1_0'] # battle_speed = 'fast' \ # if any(mod['name'] == '885957080' for mod in installed_mods.values()) else 'safe' sfr.decrypt_save_info('persist.raid.json') f = open(Path(f'{sfr.SaveEditorPath}/persist.raid.json')) raid_info = json.load(f)['base_root'] f.close() inventory = Inventory(raid_info) # Get party info sfr.decrypt_save_info('persist.roster.json') f = open(Path(f'{sfr.save_editor_path()}/persist.roster.json')) roster_info = json.load(f)['base_root'] f.close() party_order = raid_info['party']['heroes'] # [front - back] party_info = Party(roster_info, party_order) # Separate utility for testing pattern recognition with saved lootscreen images # - problem with screen grab where it doesn't capture all windows including the loot window. Need to # deselect and reselect darkest dungeon window in order to capture curio/loot window if not already # open when starting the program (see activate_curio()) # - second problem with pattern recognition accuracy not being good enough to classify items # (not even close, see loot_treasure() for more details) if test_lootscreen: print('Testing loot screen capture!') search_region = rf'{sfr.save_editor_path()}\search_region.png' loot_img = rf'{sfr.game_install_location()}\scrolls\byhand.png' # use_item_img = rf'{sfr.game_install_location()}\scrolls\use_inventory.png' if not debug: # Make Darkest Dungeon the active window dd_window = win32gui.FindWindowEx(0, 0, 0, "Darkest Dungeon") win32gui.SetForegroundWindow(dd_window) pydirectinput.doubleClick(x=1050, y=500) pydirectinput.doubleClick(x=1050, y=500) if os.path.exists(search_region): os.remove(search_region) # pyautogui.screenshot(search_region, region=(1300, 425, 100, 150)) pyautogui.screenshot(search_region, region=(1060, 400, 585, 215)) # found = list(pyautogui.locateAll(use_item_img, search_region, confidence=.8)) found = list(pyautogui.locateAll(loot_img, search_region, confidence=.45)) loot_screen_found = True if len(found) > 0 else False print(f'Found loot screen = {loot_screen_found}') if loot_screen_found: dungeon_name = raid_info['raid_instance']['dungeon'] loot = identify_lootscreen_items(search_region, dungeon_name, party=party_info.heroes) for item in loot: print(f'item: {item.name}, quantity: {item.quantity}, slot: {item.item_slot}') return # Take action in Dungeon if raid_info['inbattle']: battle(inventory, battle_speed, debug) else: # Determine Dungeon location map_file = 'persist.map.json' sfr.decrypt_save_info(map_file) # map_file = 'map.json' # can provide an alternate map file for debugging DungeonPath f = open(Path(f'{sfr.save_editor_path()}/{map_file}')) map_info = json.load(f)['base_root'] f.close() areas = map_info['map']['static_dynamic']['areas'] static_areas = map_info['map']['static_dynamic']['static_save']['base_root']['areas'] location_number = raid_info['in_area'] # 1111584611 location = next(index for index, area in areas.items() if static_areas[index]['id'] == location_number) # Used to debug droppable_items # dungeon_name = raid_info['raid_instance']['dungeon'] # droppable_items = get_droppable_items(raid_info, areas, inventory, dungeon_name, party_info.heroes) # return # Check for loot screen queued_loot = raid_info['loot']['queue_items']['items'] battle_reward = raid_info['loot']['result']['inventory_system']['items'] \ if 'result' in raid_info['loot'] else [] if len(queued_loot) > 0 or len(battle_reward) > 0: areas = map_info['map']['static_dynamic']['areas'] if location.startswith('co'): static_tiles = static_areas[location]['tiles'] hallway_length = len(static_tiles) - 1 last_room_number = raid_info['last_room_id'] # 1111584611 reverse = last_room_number != static_tiles['tile0']['door_to']['area_to'] party_tile = get_party_tile(raid_info, hallway_length, reverse) else: party_tile = 0 dungeon_path, _ = get_dungeon_path(raid_info, static_areas, location) loot_treasure(raid_info, inventory, areas, location, party_info.heroes, tile_number=party_tile, dungeon_path=dungeon_path, debug=debug) sfr.decrypt_save_info('persist.raid.json') f = open(Path(f'{sfr.SaveEditorPath}/persist.raid.json')) raid_info = json.load(f)['base_root'] f.close() inventory = Inventory(raid_info) # important, need to check inventory again after looting time.sleep(.5) # give enough time for loot/curio screen to close and mission complete to open c.write(c.b, 4) # close out of menu (e.g. mission complete) time.sleep(.2) # give enough time for mission complete screen to close navigate_dungeon(raid_info, areas, static_areas, inventory, party_info, location, debug) # In Town elif not info['inraid'] and not provisions_bought: # buy_provisions(dungeon_name, length, difficulty, debug) # provisions_bought = True # elif not info['inraid'] and provisions_bought: break print('DD bot finished!')