Ejemplo n.º 1
0
 def get_server_twitch_client_id(self):
     try:
         url = self.entries['trackerserver_url'].get() + "/tracker/api/twitchclientid/"
         return urllib.request.urlopen(url).read()
     except Exception:
         log_error("Couldn't get twitch client id from tracker server\n" + traceback.format_exc())
         return None
Ejemplo n.º 2
0
    def reset_options(self):
        """ Reset state variables affected by options """
        opt = Options()
        font_size = int(16 * opt.size_multiplier)

        # Anything that gets calculated and cached based on something in options
        # now needs to be flushed
        self.text_margin_size = font_size
        self.show_floors = opt.show_floors and (not self.state or self.state.game_version != "Antibirth")
        try:
            self.font = pygame.font.SysFont(
                opt.show_font,
                font_size,
                bold=opt.bold_font
            )
        except Exception:
            log_error("ERROR: Couldn't load font \"" + opt.show_font +"\", falling back to Arial\n" + traceback.format_exc())
            self.font = pygame.font.SysFont(
                "arial",
                font_size,
                bold=opt.bold_font
            )

        self._image_library = {}
        self.roll_icon = self.get_scaled_icon(self.numeric_id_to_image_path("284"), font_size * 2)
        self.blind_icon = self.get_scaled_icon("questionmark.png", font_size * 2)
        if opt.show_description or opt.show_status_message:
            self.text_height = self.write_message(" ")
        else:
            self.text_height = 0
Ejemplo n.º 3
0
    def __init__(self):
        self.options = Options()
        self.root = Tk()
        self.root.destroy()
        # Our 'safe' list of fonts that should work in pygame
        self.fonts = ['Andalus', 'Angsana New', 'AngsanaUPC', 'Arial', 'Arial Black', 'Browallia New', 'BrowalliaUPC',
                      'Comic Sans MS', 'Cordia New', 'CordiaUPC', 'Courier New', 'DFKai-SB', 'David', 'DilleniaUPC',
                      'Estrangelo Edessa', 'FrankRuehl', 'Franklin Gothic Medium', 'Gautami', 'Georgia', 'Impact',
                      'IrisUPC', 'JasmineUPC', 'KodchiangUPC', 'Latha', 'LilyUPC', 'Lucida Console', 'MV Boli',
                      'Mangal', 'Microsoft Sans Serif', 'Miriam', 'Miriam Fixed', 'Narkisim', 'Raavi', 'Rod', 'Shruti',
                      'SimHei', 'Simplified Arabic', 'Simplified Arabic Fixed', 'Sylfaen', 'Tahoma', 'Times New Roman',
                      'Traditional Arabic', 'Trebuchet MS', 'Tunga', 'Verdana']
        self.game_versions = ['Rebirth', 'Afterbirth', 'Afterbirth+', 'Antibirth']
        self.network_queue = Queue()

        # Check if the system has the fonts installed, and remove them from the list if it doesn't
        try:
            valid_pygame_fonts = [str.lower(x.replace(" ", "")) for x in self.fonts]
            system_fonts = pygame.sysfont.get_fonts()
            to_delete = []
            for index, font in enumerate(valid_pygame_fonts):
                if font not in system_fonts:
                    to_delete += [index]
            for index in to_delete[::-1]:
                del self.fonts[index]
        except:
            log_error("There may have been an error detecting system fonts.\n" + traceback.print_exc())
Ejemplo n.º 4
0
    def __init__(self):
        self.options = Options()
        # Our 'safe' list of fonts that should work in pygame
        self.fonts = ['Andalus', 'Angsana New', 'AngsanaUPC', 'Arial', 'Arial Black', 'Browallia New', 'BrowalliaUPC',
                      'Comic Sans MS', 'Cordia New', 'CordiaUPC', 'Courier New', 'DFKai-SB', 'David', 'DilleniaUPC',
                      'Estrangelo Edessa', 'FrankRuehl', 'Franklin Gothic Medium', 'Gautami', 'Georgia', 'Impact',
                      'IrisUPC', 'JasmineUPC', 'KodchiangUPC', 'Latha', 'LilyUPC', 'Lucida Console', 'MV Boli',
                      'Mangal', 'Microsoft Sans Serif', 'Miriam', 'Miriam Fixed', 'Narkisim', 'Raavi', 'Rod', 'Shruti',
                      'SimHei', 'Simplified Arabic', 'Simplified Arabic Fixed', 'Sylfaen', 'Tahoma', 'Times New Roman',
                      'Traditional Arabic', 'Trebuchet MS', 'Tunga', 'Verdana']
        self.game_versions = ['Rebirth', 'Afterbirth', 'Afterbirth+', 'Antibirth']
        self.network_queue = Queue()

        # Check if the system has the fonts installed, and remove them from the list if it doesn't
        try:
            valid_pygame_fonts = [lower(x.replace(" ", "")) for x in self.fonts]
            system_fonts = pygame.sysfont.get_fonts()
            to_delete = []
            for index, font in enumerate(valid_pygame_fonts):
                if font not in system_fonts:
                    to_delete += [index]
            for index in to_delete[::-1]:
                del self.fonts[index]
        except:
            log_error("There may have been an error detecting system fonts.\n" + traceback.print_exc())
Ejemplo n.º 5
0
 def get_server_twitch_client_id(self):
     try:
         url = self.entries['trackerserver_url'].get() + "/tracker/api/twitchclientid/"
         return urllib2.urlopen(url).read()
     except Exception:
         log_error("Couldn't get twitch client id from tracker server\n" + traceback.format_exc())
         return None
Ejemplo n.º 6
0
    def reset_options(self):
        """ Reset state variables affected by options """
        opt = Options()
        font_size = int(16 * opt.size_multiplier)

        # Anything that gets calculated and cached based on something in options
        # now needs to be flushed
        self.text_margin_size = font_size
        self.show_floors = opt.show_floors and (
            not self.state or self.state.game_version != "Antibirth")
        try:
            self.font = pygame.font.SysFont(opt.show_font,
                                            font_size,
                                            bold=opt.bold_font)
        except Exception:
            log_error("ERROR: Couldn't load font \"" + opt.show_font +
                      "\", falling back to Arial\n" + traceback.format_exc())
            self.font = pygame.font.SysFont("arial",
                                            font_size,
                                            bold=opt.bold_font)

        self._image_library = {}
        self.roll_icon = self.get_scaled_icon(
            self.numeric_id_to_image_path("284"), font_size * 2)
        self.blind_icon = self.get_scaled_icon("questionmark.png",
                                               font_size * 2)
        self.jacob_icon = self.get_scaled_icon("JacobHead.png", font_size * 2)
        self.esau_icon = self.get_scaled_icon("EsauHead.png", font_size * 2)
        if opt.show_description or opt.show_status_message:
            self.text_height = self.write_message(" ")
        else:
            self.text_height = 0
Ejemplo n.º 7
0
def main():
    """ Main """
    try:
        # Pass "logging.DEBUG" in debug mode
        rt = IsaacTracker()
        rt.run()
    except Exception:
        log_error(traceback.format_exc())
Ejemplo n.º 8
0
def main():
    """ Main """
    try:
        # Pass "logging.DEBUG" in debug mode
        rt = IsaacTracker()
        rt.run()
    except Exception:
        log_error(traceback.format_exc())
Ejemplo n.º 9
0
def main():
    """ Main """
    try:
        # Pass "logging.DEBUG" in debug mode
        rt = IsaacTracker()
        rt.run()
    except Exception:
        excepthook = IsaacTracker.filter_excepthook(IsaacTracker)
        log_error(excepthook)
Ejemplo n.º 10
0
 def from_valid_json(json_dic, *args):
     """ Create a Floor from a type-checked dic """
     floor_id = json_dic['floor_id']
     curse = json_dic['curse']
     if (floor_id not in Floor.__floor_id_to_label or curse < Curse.No_Curse
             or curse > Curse.Labyrinth):
         log_error("ERROR: Invalid floor_id or curse (" + floor_id + ", " +
                   curse + ")")
         return None
     return Floor(floor_id, curse)
Ejemplo n.º 11
0
 def get_server_userlist_and_enqueue(self):
     try:
         url = self.entries['trackerserver_url'].get() + "/tracker/api/userlist/"
         json_state = urllib2.urlopen(url).read()
         users = json.loads(json_state)
         success = True
     except Exception:
         log_error("Problem getting userlist from tracker server\n" + traceback.format_exc())
         users = []
         success = False
     network_result = {"users": users, "success": success}
     self.network_queue.put(network_result)
Ejemplo n.º 12
0
 def get_server_userlist_and_enqueue(self):
     try:
         url = self.entries['trackerserver_url'].get() + "/tracker/api/userlist/"
         json_state = urllib.request.urlopen(url).read()
         users = json.loads(json_state)
         success = True
     except Exception:
         log_error("Problem getting userlist from tracker server\n" + traceback.format_exc())
         users = []
         success = False
     network_result = {"users": users, "success": success}
     self.network_queue.put(network_result)
Ejemplo n.º 13
0
 def check_item_keys(items_dic, filename):
     """ 
     Check for unexpected keys in an item dict. if we find any, complain about them in the error log.
     This shouldn't actually stop the program though, because it just means some data won't be recognized,
     and that data is only of limited importance.
     """
     invalid_keys = set()
     for item_id in items_dic:
         for item_info_key in items_dic[item_id]:
             if item_info_key not in ItemInfo.valid_key_set:
                 invalid_keys.add(item_info_key)
     if len(invalid_keys) > 0:
         log_error("The file " + filename + " contains unexpected keys: " + ", ".join(invalid_keys))
Ejemplo n.º 14
0
 def check_item_keys(items_dic, filename):
     """ 
     Check for unexpected keys in an item dict. if we find any, complain about them in the error log.
     This shouldn't actually stop the program though, because it just means some data won't be recognized,
     and that data is only of limited importance.
     """
     invalid_keys = set()
     for item_id in items_dic:
         for item_info_key in items_dic[item_id]:
             if item_info_key not in ItemInfo.valid_key_set:
                 invalid_keys.add(item_info_key)
     if len(invalid_keys) > 0:
         log_error("The file " + filename + " contains unexpected keys: " + ", ".join(invalid_keys))
Ejemplo n.º 15
0
    def from_valid_json(json_dic, *args):
        """ Create an Item from a type-checked dic and a floor_list """
        floor_list = args[0]
        floor = next((f for f in floor_list if f.floor_id == json_dic['floor_id']),
                     None)
        if not floor:
            log_error("ERROR: Floor id %s is not found in state list", json_dic['floor_id'])
            return None

        item_id = json_dic['item_id']
        if not Item.contains_info(item_id):
            item_id = "NEW"

        flagstr = json_dic['flags']

        return Item(item_id, floor, flagstr=flagstr)
Ejemplo n.º 16
0
    def from_valid_json(json_dic, *args):
        """ Create an Item from a type-checked dic and a floor_list """
        floor_list = args[0]
        floor = next((f for f in floor_list if f.floor_id == json_dic['floor_id']),
                     None)
        if not floor:
            log_error("ERROR: Floor id %s is not found in state list", json_dic['floor_id'])
            return None

        item_id = json_dic['item_id']
        if not Item.contains_info(item_id):
            item_id = "NEW"

        flagstr = json_dic['flags']

        return Item(item_id, floor, flagstr=flagstr)
Ejemplo n.º 17
0
 def from_json(cls, json_dic, *args):
     """
     This function does some type checking on expected attributes,
     and then calls the derived factory method
     """
     log = logging.getLogger("tracker")
     if not isinstance(json_dic, dict):
         log_error("ERROR: json_dic is not a dictionary")
         return None
     # Basic type check
     for key, value_type in cls.serialize:
         if key not in json_dic:
             log_error("ERROR: key "+ key + " not found in dictionary")
             return None
         if not isinstance(json_dic[key], value_type):
             log_error("ERROR: key " + key + " is not a " + value_type.__name__ + " as expected")
             return None
     return cls.from_valid_json(json_dic, *args)
Ejemplo n.º 18
0
 def from_json(cls, json_dic, *args):
     """
     This function does some type checking on expected attributes,
     and then calls the derived factory method
     """
     log = logging.getLogger("tracker")
     if not isinstance(json_dic, dict):
         log_error("ERROR: json_dic is not a dictionary")
         return None
     # Basic type check
     for key, value_type in cls.serialize:
         if key not in json_dic:
             log_error("ERROR: key " + key + " not found in dictionary")
             return None
         if not isinstance(json_dic[key], value_type):
             log_error("ERROR: key " + key + " is not a " +
                       value_type.__name__ + " as expected")
             return None
     return cls.from_valid_json(json_dic, *args)
Ejemplo n.º 19
0
    def run(self):
        """ The main routine which controls everything """
        framecount = 0

        # Create drawing tool to use to draw everything - it'll create its own screen
        drawing_tool = DrawingTool(wdir_prefix)
        drawing_tool.set_window_title_info(update_notifier=(" v" + self.tracker_version))
        opt = Options()

        parser = LogParser(wdir_prefix, self.tracker_version, LogFinder())

        event_result = None
        state = None
        custom_title_enabled = opt.custom_title_enabled
        read_from_server = opt.read_from_server
        write_to_server = opt.write_to_server
        game_version = opt.game_version
        state_version = -1
        twitch_username = None
        new_states_queue = []
        screen_error_message = None
        retry_in = 0
        update_timer = opt.log_file_check_seconds
        last_game_version = None

        while event_result != Event.DONE:
            # Check for events and handle them
            event_result = drawing_tool.handle_events()

            # The user checked or unchecked the "Custom Title Enabled" checkbox
            if opt.custom_title_enabled != custom_title_enabled:
                custom_title_enabled = opt.custom_title_enabled
                drawing_tool.update_window_title()

            # The user started or stopped watching someone from the server (or they started watching a new person from the server)
            if opt.read_from_server != read_from_server or opt.twitch_name != twitch_username:
                twitch_username = opt.twitch_name
                read_from_server = opt.read_from_server
                new_states_queue = []
                # Also restart version count if we go back and forth from log.txt to server
                if read_from_server:
                    state_version = -1
                    state = None
                    # Change the delay for polling, as we probably don't want to fetch it every second
                    update_timer_override = 2
                    # Show who we are watching in the title bar
                    drawing_tool.set_window_title_info(watching=True, watching_player=twitch_username, updates_queued=len(new_states_queue))
                else:
                    drawing_tool.set_window_title_info(watching=False)
                    update_timer_override = 0

            # The user started or stopped broadcasting to the server
            if opt.write_to_server != write_to_server:
                write_to_server = opt.write_to_server
                drawing_tool.set_window_title_info(uploading=opt.write_to_server)

            if opt.game_version != game_version:
                parser.reset()
                game_version = opt.game_version

            # Force refresh state if we updated options or if we need to retry
            # to contact the server.
            if (event_result == Event.OPTIONS_UPDATE or
                (screen_error_message is not None and retry_in == 0)):
                # By setting the framecount to 0 we ensure we'll refresh the state right away
                framecount = 0
                screen_error_message = None
                retry_in = 0
                # Force updates after changing options
                if state is not None:
                    state.modified = True

            # normally we check for updates based on how the option is set
            # when doing network stuff, this can be overridden
            update_delay = opt.log_file_check_seconds
            if update_timer_override != 0:
                update_delay = update_timer_override
                
            # Now we re-process the log file to get anything that might have loaded;
            # do it every update_timer seconds (making sure to truncate to an integer
            # or else it might never mod to 0)
            frames_between_checks = int(Options().framerate_limit * update_delay)
            if frames_between_checks <= 0:
                frames_between_checks = 1
            
            if framecount % frames_between_checks == 0:
                if retry_in != 0:
                    retry_in -= 1
                # Let the parser do his thing and give us a state
                if opt.read_from_server:
                    base_url = opt.trackerserver_url + "/tracker/api/user/" + opt.twitch_name
                    json_dict = None
                    try:
                        json_version = urllib.request.urlopen(base_url + "/version").read()
                        if int(json_version) > state_version:
                            # FIXME better handling of 404 error ?
                            json_state = urllib.request.urlopen(base_url).read()
                            json_dict = json.loads(json_state, "utf-8")
                            new_state = TrackerState.from_json(json_dict)
                            if new_state is None:
                                raise Exception("server gave us empty state")
                            state_version = int(json_version)
                            new_states_queue.append((state_version, new_state))
                            drawing_tool.set_window_title_info(updates_queued=len(new_states_queue))
                    except Exception:
                        state = None
                        log_error("Couldn't load state from server\n" + traceback.format_exc())
                        if json_dict is not None:
                            if "tracker_version" in json_dict:
                                their_version = json_dict["tracker_version"]
                            else:
                                # This is the only version that can upload to the server but doesn't include a version string
                                their_version = "0.10-beta1"

                            if their_version != self.tracker_version:
                                screen_error_message = "They are using tracker version " + their_version + " but you have " + self.tracker_version
                else:
                    force_draw = state and state.modified
                    state = parser.parse()
                    if force_draw:
                        state.modified = True
                    if write_to_server and not opt.trackerserver_authkey:
                        screen_error_message = "Your authkey is blank. Get a new authkey in the options menu and paste it into the authkey text field."
                    if state is not None and write_to_server and state.modified and screen_error_message is None:
                        opener = urllib.request.build_opener(urllib.request.HTTPHandler)
                        put_url = opt.trackerserver_url + "/tracker/api/update/" + opt.trackerserver_authkey
                        json_string = json.dumps(state, cls=TrackerStateEncoder, sort_keys=True)
                        request = urllib.request.Request(put_url,
                                                  data=json_string)
                        request.add_header('Content-Type', 'application/json')
                        request.get_method = lambda: 'PUT'
                        try:
                            result = opener.open(request)
                            result_json = json.loads(result.read())
                            updated_user = result_json["updated_user"]
                            if updated_user is None:
                                screen_error_message = "The server didn't recognize you. Try getting a new authkey in the options menu."
                            else:
                                screen_error_message = None
                        except Exception as e:
                            log_error("ERROR: Couldn't send item info to server\n" + traceback.format_exc())
                            screen_error_message = "ERROR: Couldn't send item info to server, check tracker_log.txt"
                            # Retry to write the state in 10*update_timer (aka 10 sec in write mode)
                            retry_in = 10

            # Check the new state at the front of the queue to see if it's time to use it
            if len(new_states_queue) > 0:
                (state_timestamp, new_state) = new_states_queue[0]
                current_timestamp = int(time.time())
                if current_timestamp - state_timestamp >= opt.read_delay or opt.read_delay == 0 or state is None:
                    state = new_state
                    new_states_queue.pop(0)
                    drawing_tool.set_window_title_info(updates_queued=len(new_states_queue))

            if state is None and screen_error_message is None:
                if read_from_server:
                    screen_error_message = "Unable to read state from server. Please verify your options setup and tracker_log.txt"
                    # Retry to read the state in 5*update_timer (aka 10 sec in read mode)
                    retry_in = 5
                else:
                    screen_error_message = "log.txt for " + opt.game_version + " not found. Make sure you have the right game selected in the options."

            if screen_error_message is not None:
                drawing_tool.write_error_message(screen_error_message)
            else:
                # We got a state, now we draw it
                drawing_tool.draw_state(state)

            # if we're watching someone and they change their game version, it can require us to reset
            if state and last_game_version != state.game_version:
                drawing_tool.reset_options()
                last_game_version = state.game_version

            drawing_tool.tick()
            framecount += 1

        # Main loop finished; program is exiting
        drawing_tool.save_window_position()
        Options().save_options(wdir_prefix + "options.json")
Ejemplo n.º 20
0
    def run(self):
        """ The main routine which controls everything """
        framecount = 0

        # Create drawing tool to use to draw everything - it'll create its own screen
        drawing_tool = DrawingTool(wdir_prefix)
        drawing_tool.set_window_title_info(
            update_notifier=(" v" + self.tracker_version))
        opt = Options()

        parser = LogParser(wdir_prefix, self.tracker_version, LogFinder())

        event_result = None
        state = None
        custom_title_enabled = opt.custom_title_enabled
        read_from_server = opt.read_from_server
        write_to_server = opt.write_to_server
        game_version = opt.game_version
        state_version = -1
        twitch_username = None
        new_states_queue = []
        screen_error_message = None
        retry_in = 0
        update_timer = opt.log_file_check_seconds
        last_game_version = None

        while event_result != Event.DONE:
            # Check for events and handle them
            event_result = drawing_tool.handle_events()

            # The user checked or unchecked the "Custom Title Enabled" checkbox
            if opt.custom_title_enabled != custom_title_enabled:
                custom_title_enabled = opt.custom_title_enabled
                drawing_tool.update_window_title()

            # The user started or stopped watching someone from the server (or they started watching a new person from the server)
            if opt.read_from_server != read_from_server or opt.twitch_name != twitch_username:
                twitch_username = opt.twitch_name
                read_from_server = opt.read_from_server
                new_states_queue = []
                # Also restart version count if we go back and forth from log.txt to server
                if read_from_server:
                    state_version = -1
                    state = None
                    # Change the delay for polling, as we probably don't want to fetch it every second
                    update_timer_override = 2
                    # Show who we are watching in the title bar
                    drawing_tool.set_window_title_info(
                        watching=True,
                        watching_player=twitch_username,
                        updates_queued=len(new_states_queue))
                else:
                    drawing_tool.set_window_title_info(watching=False)
                    update_timer_override = 0

            # The user started or stopped broadcasting to the server
            if opt.write_to_server != write_to_server:
                write_to_server = opt.write_to_server
                drawing_tool.set_window_title_info(
                    uploading=opt.write_to_server)

            if opt.game_version != game_version:
                parser.reset()
                game_version = opt.game_version

            # Force refresh state if we updated options or if we need to retry
            # to contact the server.
            if (event_result == Event.OPTIONS_UPDATE
                    or (screen_error_message is not None and retry_in == 0)):
                # By setting the framecount to 0 we ensure we'll refresh the state right away
                framecount = 0
                screen_error_message = None
                retry_in = 0
                # Force updates after changing options
                if state is not None:
                    state.modified = True

            # normally we check for updates based on how the option is set
            # when doing network stuff, this can be overridden
            update_delay = opt.log_file_check_seconds
            if update_timer_override != 0:
                update_delay = update_timer_override

            # Now we re-process the log file to get anything that might have loaded;
            # do it every update_timer seconds (making sure to truncate to an integer
            # or else it might never mod to 0)
            frames_between_checks = int(Options().framerate_limit *
                                        update_delay)
            if frames_between_checks <= 0:
                frames_between_checks = 1

            if framecount % frames_between_checks == 0:
                if retry_in != 0:
                    retry_in -= 1
                # Let the parser do his thing and give us a state
                if opt.read_from_server:
                    base_url = opt.trackerserver_url + "/tracker/api/user/" + opt.twitch_name
                    json_dict = None
                    try:
                        json_version = urllib.request.urlopen(
                            base_url + "/version").read()
                        if int(json_version) > state_version:
                            # FIXME better handling of 404 error ?
                            json_state = urllib.request.urlopen(
                                base_url).read()
                            json_dict = json.loads(json_state)
                            new_state = TrackerState.from_json(json_dict)
                            if new_state is None:
                                raise Exception("server gave us empty state")
                            state_version = int(json_version)
                            new_states_queue.append((state_version, new_state))
                            drawing_tool.set_window_title_info(
                                updates_queued=len(new_states_queue))
                    except Exception:
                        state = None
                        log_error("Couldn't load state from server\n" +
                                  traceback.format_exc())
                        if json_dict is not None:
                            if "tracker_version" in json_dict:
                                their_version = json_dict["tracker_version"]
                            else:
                                # This is the only version that can upload to the server but doesn't include a version string
                                their_version = "0.10-beta1"

                            if their_version != self.tracker_version:
                                screen_error_message = "They are using tracker version " + their_version + " but you have " + self.tracker_version
                else:
                    force_draw = state and state.modified
                    state = parser.parse()
                    if force_draw and state is not None:
                        state.modified = True
                    if write_to_server and not opt.trackerserver_authkey:
                        screen_error_message = "Your authkey is blank. Get a new authkey in the options menu and paste it into the authkey text field."
                    if state is not None and write_to_server and state.modified and screen_error_message is None:
                        opener = urllib.request.build_opener(
                            urllib.request.HTTPHandler)
                        put_url = opt.trackerserver_url + "/tracker/api/update/" + opt.trackerserver_authkey
                        json_string = json.dumps(
                            state, cls=TrackerStateEncoder,
                            sort_keys=True).encode("utf-8")
                        request = urllib.request.Request(put_url,
                                                         data=json_string)
                        request.add_header('Content-Type', 'application/json')
                        request.get_method = lambda: 'PUT'
                        try:
                            result = opener.open(request)
                            result_json = json.loads(result.read())
                            updated_user = result_json["updated_user"]
                            if updated_user is None:
                                screen_error_message = "The server didn't recognize you. Try getting a new authkey in the options menu."
                            else:
                                screen_error_message = None
                        except Exception as e:
                            log_error(
                                "ERROR: Couldn't send item info to server\n" +
                                traceback.format_exc())
                            screen_error_message = "ERROR: Couldn't send item info to server, check tracker_log.txt"
                            # Retry to write the state in 10*update_timer (aka 10 sec in write mode)
                            retry_in = 10

            # Check the new state at the front of the queue to see if it's time to use it
            if len(new_states_queue) > 0:
                (state_timestamp, new_state) = new_states_queue[0]
                current_timestamp = int(time.time())
                if current_timestamp - state_timestamp >= opt.read_delay or opt.read_delay == 0 or state is None:
                    state = new_state
                    new_states_queue.pop(0)
                    drawing_tool.set_window_title_info(
                        updates_queued=len(new_states_queue))

            if state is None and screen_error_message is None:
                if read_from_server:
                    screen_error_message = "Unable to read state from server. Please verify your options setup and tracker_log.txt"
                    # Retry to read the state in 5*update_timer (aka 10 sec in read mode)
                    retry_in = 5
                else:
                    screen_error_message = "log.txt for " + opt.game_version + " not found. Make sure you have the right game selected in the options."

            if screen_error_message is not None:
                drawing_tool.write_error_message(screen_error_message)
            else:
                # We got a state, now we draw it
                drawing_tool.draw_state(state, framecount)

            # if we're watching someone and they change their game version, it can require us to reset
            if state and last_game_version != state.game_version:
                drawing_tool.reset_options()
                last_game_version = state.game_version

            drawing_tool.tick()
            framecount += 1

        # Main loop finished; program is exiting
        drawing_tool.save_window_position()
        Options().save_options(wdir_prefix + "options.json")