Beispiel #1
0
    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
Beispiel #3
0
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!')
Beispiel #9
0
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!')