text: import keyboard
  from char.sorceress import Sorceress
  from utils.custom_mouse import mouse
  from logger import Logger
  from utils.misc import wait, rotate_vec, unit_vector
  import random
  from pather import Location
  import numpy as np
  
  
  class BlizzSorc(Sorceress):
      def __init__(self, *args, **kwargs):
          Logger.info("Setting up Blizz Sorc")
          super().__init__(*args, **kwargs)
  
      def _ice_blast(self, cast_pos_abs: tuple[float, float], delay: tuple[float, float] = (0.16, 0.23), spray: float = 10):
          keyboard.send(self._char_config["stand_still"], do_release=False)
          if self._skill_hotkeys["ice_blast"]:
              keyboard.send(self._skill_hotkeys["ice_blast"])
          for _ in range(5):
              x = cast_pos_abs[0] + (random.random() * 2*spray - spray)
              y = cast_pos_abs[1] + (random.random() * 2*spray - spray)
              cast_pos_monitor = self._screen.convert_abs_to_monitor((x, y))
              mouse.move(*cast_pos_monitor)
              mouse.press(button="left")
              wait(delay[0], delay[1])
              mouse.release(button="left")
          keyboard.send(self._char_config["stand_still"], do_press=False)
  
      def _blizzard(self, cast_pos_abs: tuple[float, float], spray: float = 10):
          if not self._skill_hotkeys["blizzard"]:
              raise ValueError("You did not set a hotkey for blizzard!")
          keyboard.send(self._skill_hotkeys["blizzard"])
          x = cast_pos_abs[0] + (random.random() * 2 * spray - spray)
          y = cast_pos_abs[1] + (random.random() * 2 * spray - spray)
          cast_pos_monitor = self._screen.convert_abs_to_monitor((x, y))
          mouse.move(*cast_pos_monitor)
          click_tries = random.randint(2, 4)
          for _ in range(click_tries):
              mouse.press(button="right")
              wait(0.09, 0.12)
              mouse.release(button="right")
  
      def kill_pindle(self) -> bool:
          pindle_pos_abs = self._screen.convert_screen_to_abs(self._config.path["pindle_end"][0])
          cast_pos_abs = [pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9]
          for _ in range(int(self._char_config["atk_len_pindle"])):
              self._blizzard(cast_pos_abs, spray=11)
              self._ice_blast(cast_pos_abs, spray=11)
          # Move to items
          wait(self._cast_duration, self._cast_duration + 0.2)
          self._pather.traverse_nodes_fixed("pindle_end", self)
          return True
  
      def kill_eldritch(self) -> bool:
          #move up
          pos_m = self._screen.convert_abs_to_monitor((0, -175))
          self.pre_move()
          self.move(pos_m, force_move=True)
          self._blizzard((-50, -50), spray=10)
          self._cast_static()
          #move down
          pos_m = self._screen.convert_abs_to_monitor((0, 85))
          self.pre_move()
          self.move(pos_m, force_move=True)
          wait(0.70)
          self._blizzard((-170, -350), spray=10)
          self._cast_static()
          #move down
          pos_m = self._screen.convert_abs_to_monitor((0, 75))
          self.pre_move()
          self.move(pos_m, force_move=True)
          self._blizzard((100, -300), spray=10)
          self._cast_static()
          pos_m = self._screen.convert_abs_to_monitor((0, 55))
          self.pre_move()
          self.move(pos_m, force_move=True)
          wait(1.0)
          self._blizzard((-50, -130), spray=10)
          self._cast_static()
          wait(3.0)
          self._pather.traverse_nodes_fixed("eldritch_end", self)
          return True
  
      def kill_shenk(self) -> bool:
          pos_m = self._screen.convert_abs_to_monitor((100, 170))
          self.pre_move()
          self.move(pos_m, force_move=True)
          #lower left posistion
          self._pather.traverse_nodes([151], self, time_out=2.5, force_tp=False)
          self._cast_static()
          self._blizzard((-250, 100), spray=10)
          self._ice_blast((60, 70), spray=60)
          self._blizzard((400, 200), spray=10)
          self._cast_static()
          self._ice_blast((-300, 100), spray=60)
          self._blizzard((185, 200), spray=10)
          pos_m = self._screen.convert_abs_to_monitor((-10, 10))
          self.pre_move()
          self.move(pos_m, force_move=True)
          self._cast_static()
          self._blizzard((-300, -270), spray=10)
          self._ice_blast((-20, 30), spray=60)
          wait(1.0)
          #teledance 2
          pos_m = self._screen.convert_abs_to_monitor((150, -240))
          self.pre_move()
          self.move(pos_m, force_move=True)
          #teledance attack 2
          self._cast_static()
          self._blizzard((450, -250), spray=10)
          self._ice_blast((150, -100), spray=60)
          self._blizzard((0, -250), spray=10)
          wait(0.3)
          #Shenk Kill
          self._cast_static()
          self._blizzard((100, -50), spray=10)
          # Move to items
          self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.4, force_tp=True)
          return True
  
      def kill_council(self) -> bool:
          # Move inside to the right
          self._pather.traverse_nodes_fixed([(1110, 120)], self)
          self._pather.offset_node(300, (80, -110))
          self._pather.traverse_nodes([300], self, time_out=5.5, force_tp=True)
          self._pather.offset_node(300, (-80, 110))
          # Attack to the left
          self._blizzard((-150, 10), spray=80)
          self._ice_blast((-300, 50), spray=40)
          # Tele back and attack
          pos_m = self._screen.convert_abs_to_monitor((-50, 200))
          self.pre_move()
          self.move(pos_m, force_move=True)
          self._blizzard((-235, -230), spray=80)
          wait(1.0)
          pos_m = self._screen.convert_abs_to_monitor((-285, -320))
          self.pre_move()
          self.move(pos_m, force_move=True)
          wait(0.5)
          # Move to far left
          self._pather.offset_node(301, (-80, -50))
          self._pather.traverse_nodes([301], self, time_out=2.5, force_tp=True)
          self._pather.offset_node(301, (80, 50))
          # Attack to RIGHT
          self._blizzard((100, 150), spray=80)
          self._ice_blast((230, 230), spray=20)
          wait(0.5)
          self._blizzard((310, 260), spray=80)
          wait(1.0)
          # Move to bottom of stairs
          self.pre_move()
          for p in [(450, 100), (-190, 200)]:
              pos_m = self._screen.convert_abs_to_monitor(p)
              self.move(pos_m, force_move=True)
          self._pather.traverse_nodes([304], self, time_out=2.5, force_tp=True)
          # Attack to center of stairs
          self._blizzard((-175, -200), spray=30)
          self._ice_blast((30, -60), spray=30)
          wait(0.5)
          self._blizzard((175, -270), spray=30)
          wait(1.0)
          # Move back inside
          self._pather.traverse_nodes_fixed([(1110, 15)], self)
          self._pather.traverse_nodes([300], self, time_out=2.5, force_tp=False)
          # Attack to center
          self._blizzard((-100, 0), spray=10)
          self._cast_static()
          self._ice_blast((-300, 30), spray=50)
          self._blizzard((-175, 50), spray=10)
          wait(1.0)
          # Move back outside and attack
          pos_m = self._screen.convert_abs_to_monitor((-430, 230))
          self.pre_move()
          self.move(pos_m, force_move=True)
          self._blizzard((-50, -150), spray=30)
          self._cast_static()
          wait(0.5)
          # Move back inside and attack
          pos_m = self._screen.convert_abs_to_monitor((150, -350))
          self.pre_move()
          self.move(pos_m, force_move=True)
          # Attack sequence center
          self._blizzard((-100, 35), spray=30)
          self._cast_static()
          self._blizzard((-150, 20), spray=30)
          wait(1.0)
          # Move inside
          pos_m = self._screen.convert_abs_to_monitor((100, -30))
          self.pre_move()
          self.move(pos_m, force_move=True)
          # Attack sequence to center
          self._blizzard((-50, 50), spray=30)
          self._cast_static()
          self._ice_blast((-30, 50), spray=10)
          # Move outside since the trav.py expects to start searching for items there if char can teleport
          self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
          return True
  
      def kill_nihlatak(self, end_nodes: list[int]) -> bool:
          # Find nilhlatak position
          atk_sequences = max(2, int(self._char_config["atk_len_nihlatak"]) - 1)
          for i in range(atk_sequences):
              nihlatak_pos_abs = self._pather.find_abs_node_pos(end_nodes[-1], self._screen.grab())
              if nihlatak_pos_abs is not None:
                  cast_pos_abs = np.array([nihlatak_pos_abs[0] * 0.9, nihlatak_pos_abs[1] * 0.9])
                  self._blizzard(cast_pos_abs, spray=90)
                  self._cast_static()
                  # Do some tele "dancing" after each sequence
                  if i < atk_sequences - 1:
                      rot_deg = random.randint(-10, 10) if i % 2 == 0 else random.randint(170, 190)
                      tele_pos_abs = unit_vector(rotate_vec(cast_pos_abs, rot_deg)) * 100
                      pos_m = self._screen.convert_abs_to_monitor(tele_pos_abs)
                      self.pre_move()
                      self.move(pos_m)
          # Move to items
          self._pather.traverse_nodes(end_nodes, self, time_out=0.8)
          self._blizzard((0, 0), spray=10)
          return True
  
      def kill_summoner(self) -> bool:
          # Attack
          cast_pos_abs = np.array([0, 0])
          pos_m = self._screen.convert_abs_to_monitor((-20, 20))
          mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
          for _ in range(int(self._char_config["atk_len_arc"])):
              self._blizzard(cast_pos_abs, spray=11)
              self._ice_blast(cast_pos_abs, spray=11)
          wait(self._cast_duration, self._cast_duration + 0.2)
          return True
      
      
  if __name__ == "__main__":
      import os
      import keyboard
      from screen import Screen
      from template_finder import TemplateFinder
      from pather import Pather
      keyboard.add_hotkey('f12', lambda: Logger.info('Force Exit (f12)') or os._exit(1))
      keyboard.wait("f11")
      from config import Config
      from ui import UiManager
      config = Config()
      screen = Screen(config.general["monitor"])
      t_finder = TemplateFinder(screen)
      pather = Pather(screen, t_finder)
      ui_manager = UiManager(screen, t_finder)
      char = BlizzSorc(config.blizz_sorc, config.char, screen, t_finder, ui_manager, pather)
      char.kill_council()
  