def __init__(self, tibia_wid: int, tibia_window_spec: TibiaWindowSpec): super().__init__(tibia_wid=tibia_wid) self.tibia_window_spec = tibia_window_spec self.item_repository = ItemRepositoryContainer( tibia_window_spec.item_repository ) self.task_loop = TaskLoop()
def test_add_future(self): # given target = TaskLoop() future_time = target.add_future(time.time) main_time = time.time() # when target.start() try: # then self.assertGreater(future_time.get(), main_time) finally: target.stop()
def test_add_futures_sequential_exec(self): # given target = TaskLoop() future_time_1 = target.add_future(time.time) future_time_2 = target.add_future(time.time) future_time_3 = target.add_future(time.time) main_time = time.time() # when target.start() try: # then self.assertGreater(future_time_1.get(), main_time) self.assertGreater(future_time_2.get(), future_time_1.get()) self.assertGreater(future_time_3.get(), future_time_2.get()) finally: target.stop()
def test_add_task(self): # given ready_event = Event() task_time = None def task(): nonlocal task_time task_time = time.time() ready_event.set() target = TaskLoop() target.add_task(task) main_time = time.time() # when target.start() try: # then ready_event.wait() self.assertGreater(task_time, main_time) finally: target.stop()
def test_cancel_pending_tasks(self): # given task_done = Event() task_wait = Event() task_exec = False def task(): nonlocal task_exec task_exec = True task_done.set() # the test will cancel tasks right before allowing # execution. task_wait.wait() cancelled_task_exec = False def cancelled_task(): # this should not execute, due to cancel_pending_tasks nonlocal cancelled_task_exec cancelled_task_exec = True target = TaskLoop() target.add_task(task) target.add_task(cancelled_task) target.start() try: task_done.wait() # when target.cancel_pending_tasks() # then task_wait.set() # this future should execute after without # the cancelled task ever executing future = target.add_future(lambda: True) future.get() self.assertTrue(task_exec) self.assertFalse(cancelled_task_exec) finally: target.stop()
def __init__(self): super().__init__() self.task_loop = TaskLoop()
class EquipmentReader(ScreenReader): def __init__(self): super().__init__() self.task_loop = TaskLoop() def open(self): super().open() self.task_loop.start() def stop(self): super().close() self.task_loop.stop() def cancel_pending_futures(self): """Cancels pending future values for equipment status""" self.task_loop.cancel_pending_tasks() def _compare_screen_coords(self, coords: Tuple[int, int], color_spec: Tuple[str, ...]): return ScreenReader.matches_screen(self, coords, color_spec) def matches_screen(self, coords, specs): if type(specs) == list or type(specs) == tuple: for animation_spec in specs: if self._compare_screen_coords(coords, animation_spec.colors): return True return False else: return self._compare_screen_coords(coords, specs.colors) def get_equipment_status( self, emergency_action_amulet_cb: Callable[[str], None] = NOOP, emergency_action_ring_cb: Callable[[str], None] = NOOP, equipped_ring_cb: Callable[[str], None] = NOOP, equipped_amulet_cb: Callable[[str], None] = NOOP, magic_shield_status_cb: Callable[[str], None] = NOOP) -> EquipmentStatus: return FutureEquipmentStatus({ 'equipped_amulet': self.task_loop.add_future( self.get_equipped_amulet_name, equipped_amulet_cb, lambda e: equipped_amulet_cb('ERROR, check logs')), 'equipped_ring': self.task_loop.add_future( self.get_equipped_ring_name, equipped_ring_cb, lambda e: equipped_ring_cb('ERROR, check logs')), 'magic_shield_status': self.task_loop.add_future( self.get_magic_shield_status, magic_shield_status_cb, lambda e: magic_shield_status_cb('ERROR, check logs')), 'emergency_action_amulet': self.task_loop.add_future( self.get_emergency_action_bar_amulet_name, emergency_action_amulet_cb, lambda e: emergency_action_amulet_cb('ERROR, check logs')), 'emergency_action_ring': self.task_loop.add_future( self.get_emergency_action_bar_ring_name, emergency_action_ring_cb, lambda e: emergency_action_ring_cb('ERROR, check logs')), }) def get_emergency_action_bar_amulet_name(self) -> AmuletName: color_spec = spec(*self.get_pixels(EMERGENCY_ACTION_BAR_AMULET_COORDS)) return AMULET_REPOSITORY.get_action_name(color_spec) def get_equipped_amulet_name(self) -> AmuletName: color_spec = spec(*self.get_pixels(EQUIPPED_AMULET_COORDS)) return AMULET_REPOSITORY.get_equipment_name(color_spec) def get_emergency_action_bar_ring_name(self) -> RingName: color_spec = spec(*self.get_pixels(EMERGENCY_ACTION_BAR_RING_COORDS)) return RING_REPOSITORY.get_action_name(color_spec) def get_equipped_ring_name(self) -> RingName: color_spec = spec(*self.get_pixels(EQUIPPED_RING_COORDS)) return RING_REPOSITORY.get_equipment_name(color_spec) def is_emergency_action_bar_amulet(self, name: ItemName): amulet = AMULET_REPOSITORY.get(name) return self.matches_screen(EMERGENCY_ACTION_BAR_AMULET_COORDS, amulet.action_color_specs) def is_emergency_action_bar_ring(self, name: ItemName): ring = RING_REPOSITORY.get(name) return self.matches_screen(EMERGENCY_ACTION_BAR_RING_COORDS, ring.action_color_specs) def is_amulet(self, name: ItemName): spec = AMULET_REPOSITORY.get(name).eq_color_specs return self.matches_screen(EQUIPPED_AMULET_COORDS, spec) def is_amulet_empty(self): return self.is_amulet(AmuletName.EMPTY) def is_ring(self, name: ItemName): spec = RING_REPOSITORY.get(name).eq_color_specs return self.matches_screen(EQUIPPED_RING_COORDS, spec) def is_ring_empty(self): return self.is_ring(RingName.EMPTY) def get_magic_shield_status(self) -> str: for name in MAGIC_SHIELD_SPEC: specs = [] if isinstance(MAGIC_SHIELD_SPEC[name][0], list): specs = [spec(*_spec) for _spec in MAGIC_SHIELD_SPEC[name]] else: specs = spec(*MAGIC_SHIELD_SPEC[name]) if self.matches_screen(MAGIC_SHIELD_COORDS, specs): return name # There are only 3 possible states: recently cast, off cooldown and # on cooldown. return MagicShieldStatus.ON_COOLDOWN
class EquipmentReader(ScreenReader): def __init__(self, tibia_wid: int, tibia_window_spec: TibiaWindowSpec): super().__init__(tibia_wid=tibia_wid) self.tibia_window_spec = tibia_window_spec self.item_repository = ItemRepositoryContainer( tibia_window_spec.item_repository ) self.task_loop = TaskLoop() def __enter__(self, *args, **kwargs) -> 'EquipmentReader': self.open() return self def __exit__(self, *args, **kwargs): self.close() def open(self): super().open() self.task_loop.start() def close(self): super().close() self.task_loop.stop() def cancel_pending_futures(self): """Cancels pending future values for equipment status""" self.task_loop.cancel_pending_tasks() def get_equipment_status( self, emergency_action_amulet_cb: Callable[[str], None] = NOOP, emergency_action_ring_cb: Callable[[str], None] = NOOP, tank_action_amulet_cb: Callable[[str], None] = NOOP, tank_action_ring_cb: Callable[[str], None] = NOOP, equipped_ring_cb: Callable[[str], None] = NOOP, equipped_amulet_cb: Callable[[str], None] = NOOP, magic_shield_status_cb: Callable[[str], None] = NOOP, normal_action_amulet_cb: Callable[[str], None] = NOOP, normal_action_ring_cb: Callable[[str], None] = NOOP, ) -> EquipmentStatus: return FutureEquipmentStatus( { "equipped_amulet": self.task_loop.add_future( self.get_equipped_amulet_name, equipped_amulet_cb, lambda e: equipped_amulet_cb("ERROR, check logs"), ), "equipped_ring": self.task_loop.add_future( self.get_equipped_ring_name, equipped_ring_cb, lambda e: equipped_ring_cb("ERROR, check logs"), ), "magic_shield_status": self.task_loop.add_future( self.get_magic_shield_status, magic_shield_status_cb, lambda e: magic_shield_status_cb("ERROR, check logs"), ), "emergency_action_amulet": self.task_loop.add_future( self.get_emergency_action_bar_amulet_name, emergency_action_amulet_cb, lambda e: emergency_action_amulet_cb("ERROR, check logs"), ), "emergency_action_ring": self.task_loop.add_future( self.get_emergency_action_bar_ring_name, emergency_action_ring_cb, lambda e: emergency_action_ring_cb("ERROR, check logs"), ), "tank_action_amulet": self.task_loop.add_future( self.get_tank_action_bar_amulet_name, tank_action_amulet_cb, lambda e: tank_action_amulet_cb("ERROR, check logs"), ), "tank_action_ring": self.task_loop.add_future( self.get_tank_action_bar_ring_name, tank_action_ring_cb, lambda e: tank_action_ring_cb("ERROR, check logs"), ), "normal_action_amulet": self.task_loop.add_future( self.get_normal_action_bar_amulet_name, normal_action_amulet_cb, lambda e: normal_action_amulet_cb("ERROR, check logs"), ), "normal_action_ring": self.task_loop.add_future( self.get_normal_action_bar_ring_name, normal_action_ring_cb, lambda e: normal_action_ring_cb("ERROR, check logs"), ), } ) def read_equipment_colors(self, coords: EquipmentCoords) -> ItemColors: return ItemColors( north=self.get_coord_color(coords.north), south=self.get_coord_color(coords.south), left=self.get_coord_color(coords.left), right=self.get_coord_color(coords.right), ) def gen_square_coords(self, center: Coord, delta: int) -> EquipmentCoords: return EquipmentCoords( north=Coord(center.x, center.y - delta), south=Coord(center.x, center.y + delta), left=Coord(center.x - delta, center.y), right=Coord(center.x + delta, center.y), ) def matches_screen_item( self, coords: EquipmentCoords, color_specs: List[ItemColors] ) -> bool: actual_color_spec = self.read_equipment_colors(coords) for color_spec in color_specs: if actual_color_spec == color_spec: return True return False # start: item lookup methods def lookup_ring_by_name(self, name: Union[str, ItemName]) -> ItemEntry: return self.item_repository.lookup_ring_by_name(name) def lookup_amulet_by_name(self, name: Union[str, ItemName]) -> ItemEntry: return self.item_repository.lookup_amulet_by_name(name) def lookup_amulet_by_action_bar_colors(self, colors: ItemColors) -> ItemEntry: return self.item_repository.lookup_amulet_by_action_bar_colors(colors) def lookup_ring_by_action_bar_colors(self, colors: ItemColors) -> ItemEntry: return self.item_repository.lookup_ring_by_action_bar_colors(colors) def lookup_ring_by_equipped_colors(self, colors: ItemColors) -> ItemEntry: return self.item_repository.lookup_ring_by_equipped_colors(colors) def lookup_amulet_by_equipped_colors(self, colors: ItemColors) -> ItemEntry: return self.item_repository.lookup_amulet_by_equipped_colors(colors) # end: item lookup methods # start: read ring methods def gen_action_bar_ring_coords(self, center: Coord) -> EquipmentCoords: return self.gen_square_coords(center, 3) def get_normal_action_bar_ring_name(self) -> str: return self.lookup_ring_by_action_bar_colors( self.read_action_bar_normal_ring_colors() ).name def read_action_bar_normal_ring_colors(self) -> ItemColors: return self.read_equipment_colors( self.gen_action_bar_ring_coords( self.tibia_window_spec.action_bar.ring_center ) ) def get_emergency_action_bar_ring_name(self) -> str: return self.lookup_ring_by_action_bar_colors( self.read_action_bar_emergency_ring_colors() ).name def read_action_bar_emergency_ring_colors(self) -> ItemColors: return self.read_equipment_colors( self.gen_action_bar_ring_coords( self.tibia_window_spec.action_bar.emergency_ring_center ) ) def read_action_bar_tank_ring_colors(self) -> ItemColors: return self.read_equipment_colors( self.gen_action_bar_ring_coords( self.tibia_window_spec.action_bar.tank_ring_center ) ) def get_tank_action_bar_ring_name(self) -> str: if not self.tibia_window_spec.action_bar.tank_ring_center: return RingName.UNKNOWN.name return self.lookup_ring_by_action_bar_colors( self.read_action_bar_tank_ring_colors() ).name def read_equipped_ring_colors(self) -> ItemColors: return self.read_equipment_colors(self.tibia_window_spec.char_equipment.ring) def get_equipped_ring_name(self) -> str: return self.lookup_ring_by_equipped_colors( self.read_equipped_ring_colors() ).name def is_normal_action_bar_ring(self, name: ItemName): return self.matches_screen_item( self.gen_action_bar_ring_coords( self.tibia_window_spec.action_bar.ring_center ), self.lookup_ring_by_name(name).action_bar_colors, ) def is_emergency_action_bar_ring(self, name: ItemName): return self.matches_screen_item( self.gen_action_bar_ring_coords( self.tibia_window_spec.action_bar.emergency_ring_center ), self.lookup_ring_by_name(name).action_bar_colors, ) def is_tank_action_bar_ring(self, name: ItemName): if not self.tibia_window_spec.action_bar.tank_ring_center: return False return self.matches_screen_item( self.gen_action_bar_ring_coords( self.tibia_window_spec.action_bar.tank_ring_center ), self.lookup_ring_by_name(name).action_bar_colors, ) def is_ring(self, name: ItemName) -> bool: return self.matches_screen_item( self.tibia_window_spec.char_equipment.ring, self.lookup_ring_by_name(name).equipped_colors, ) def is_ring_empty(self): return self.is_ring(RingName.EMPTY) # end: read ring methods # start: read amulet methods def gen_action_bar_amulet_coords(self, center: Coord) -> EquipmentCoords: return self.gen_square_coords(center, 10) def read_action_bar_normal_amulet_colors(self) -> ItemColors: return self.read_equipment_colors( self.gen_action_bar_amulet_coords( self.tibia_window_spec.action_bar.amulet_center ) ) def get_normal_action_bar_amulet_name(self) -> str: return self.lookup_amulet_by_action_bar_colors( self.read_action_bar_normal_amulet_colors() ).name def read_action_bar_emergency_amulet_colors(self) -> ItemColors: return self.read_equipment_colors( self.gen_action_bar_amulet_coords( self.tibia_window_spec.action_bar.emergency_amulet_center ) ) def get_emergency_action_bar_amulet_name(self) -> str: return self.lookup_amulet_by_action_bar_colors( self.read_action_bar_emergency_amulet_colors() ).name def read_action_bar_tank_amulet_colors(self) -> ItemColors: if not self.tibia_window_spec.action_bar.tank_amulet_center: raise Exception("tibia_window_spec.action_bar.tank_amulet_center is not set") return self.read_equipment_colors( self.gen_action_bar_amulet_coords( self.tibia_window_spec.action_bar.tank_amulet_center ) ) def get_tank_action_bar_amulet_name(self) -> str: if not self.tibia_window_spec.action_bar.amulet_center: return AmuletName.UNKNOWN.name return self.lookup_amulet_by_action_bar_colors( self.read_action_bar_tank_amulet_colors() ).name def read_equipped_amulet_colors(self) -> ItemColors: return self.read_equipment_colors(self.tibia_window_spec.char_equipment.amulet) def get_equipped_amulet_name(self) -> str: return self.lookup_amulet_by_equipped_colors( self.read_equipped_amulet_colors() ).name def is_normal_action_bar_amulet(self, name: ItemName) -> bool: return self.matches_screen_item( self.gen_action_bar_amulet_coords( self.tibia_window_spec.action_bar.amulet_center ), self.lookup_amulet_by_name(name).action_bar_colors, ) def is_emergency_action_bar_amulet(self, name: ItemName) -> bool: return self.matches_screen_item( self.gen_action_bar_amulet_coords( self.tibia_window_spec.action_bar.emergency_amulet_center ), self.lookup_amulet_by_name(name).action_bar_colors, ) def is_tank_action_bar_amulet(self, name: ItemName) -> bool: if not self.tibia_window_spec.action_bar.tank_amulet_center: return False return self.matches_screen_item( self.gen_action_bar_amulet_coords( self.tibia_window_spec.action_bar.tank_amulet_center ), self.lookup_amulet_by_name(name).action_bar_colors, ) def is_amulet(self, name: ItemName): return self.matches_screen_item( self.tibia_window_spec.char_equipment.amulet, self.lookup_amulet_by_name(name).equipped_colors, ) def is_amulet_empty(self) -> bool: return self.is_amulet(AmuletName.EMPTY) # end: read amulet methods def get_magic_shield_status(self) -> str: magic_shield_spec = self.tibia_window_spec.action_bar.magic_shield color_str = self.get_coord_color(magic_shield_spec.coord) if color_str in magic_shield_spec.off_cooldown_color: return MagicShieldStatus.OFF_COOLDOWN if color_str in magic_shield_spec.recently_cast_color: return MagicShieldStatus.RECENTLY_CAST # There are only 3 possible states: recently cast, off cooldown and # on cooldown. return MagicShieldStatus.ON_COOLDOWN