Ejemplo n.º 1
0
    def _make_widgets(self):
        bfr1 = tkd.LabelFrame(self, height=28)
        bfr1.propagate(False)
        bfr1.pack(expand=False, fill=tk.X, padx=1, pady=1)

        tkd.Button(bfr1, text='Sync', width=6, command=self.sync_local_grail, relief=tk.RIDGE, borderwidth=1, tooltip='Updates your local grail to include items logged either\nin your profiles or on herokuapp').pack(side=tk.LEFT, padx=[1, 15], pady=1)
        tkd.Checkbutton(bfr1, text='Profiles', variable=self.sync_drops).pack(side=tk.LEFT)
        tkd.Checkbutton(bfr1, text='Herokuapp', variable=self.sync_herokuapp).pack(side=tk.LEFT)

        bfr2 = tkd.Frame(self)
        bfr2.pack(pady=12, expand=True, fill=tk.X)
        tkd.Button(bfr2, text='Upload grail to herokuapp', command=self.upload_to_herokuapp, borderwidth=3, tooltip='This will not delete already found items on herokuapp if they are not\nin your local grail, but only add new items').pack(padx=8, side=tk.LEFT, fill=tk.X, expand=True)

        bfr3 = tkd.Frame(self)
        bfr3.pack(side=tk.BOTTOM, expand=tk.YES, fill=tk.X)
        tkd.Button(bfr3, text='Item table', borderwidth=3, command=self.open_grail_table, width=1).pack(side=tk.LEFT, fill=tk.X, padx=[1, 0], pady=1, expand=tk.YES)
        tkd.Button(bfr3, text='Grail controller', borderwidth=3, command=self.open_grail_controller, width=1).pack(side=tk.LEFT, fill=tk.X, padx=1, pady=1, expand=tk.YES)

        descr = tkd.ListboxFrame(self)
        descr.propagate(False)
        tk.Grid.columnconfigure(descr, 0, weight=1)
        descr.pack(side=tk.BOTTOM, fill=tk.X, expand=False)
        for i, l in enumerate(['', 'Exist', 'Owned', 'Left', '%']):
            tkd.ListboxLabel(descr, text=l).grid(row=0, column=i)
        ttk.Separator(descr, orient=tk.HORIZONTAL).grid(row=1, column=0, columnspan=5, sticky='ew')
        self._make_row(descr, 2, 'Uniq Armor')
        self._make_row(descr, 3, 'Uniq Weapons')
        self._make_row(descr, 4, 'Uniq Other')
        self._make_row(descr, 5, 'Sets')
        self._make_row(descr, 6, 'Runes')
        self._make_row(descr, 7, 'Total')
Ejemplo n.º 2
0
    def run_table(self, laps):
        run_table_fr = tkd.Frame(self.tabcontrol)
        self.tabcontrol.add(run_table_fr, text='Run table')

        cols = [
            "Run", "Run time", "Real time", "Name", "MF", "Players X", "Level",
            "XP Gained", "Uniques kills", "Champions kills", "Minion kills",
            "Total kills", "Session", "Map seed"
        ]
        tree_frame = tkd.Frame(run_table_fr)
        btn_frame2 = tkd.Frame(run_table_fr)
        btn_frame2.pack(side=tk.BOTTOM)

        vscroll_tree = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL)
        hscroll_tree = ttk.Scrollbar(run_table_fr, orient=tk.HORIZONTAL)
        tree = tkd.Treeview(tree_frame,
                            selectmode=tk.BROWSE,
                            yscrollcommand=vscroll_tree.set,
                            xscrollcommand=hscroll_tree.set,
                            show='headings',
                            columns=cols,
                            alternate_colour=True)
        hscroll_tree.config(command=tree.xview)
        vscroll_tree.config(command=tree.yview)
        tkd.Button(btn_frame2,
                   text='Save as .csv',
                   command=lambda: self.save_to_csv(tree)).pack(side=tk.LEFT,
                                                                fill=tk.X)

        vscroll_tree.pack(side=tk.RIGHT, fill=tk.Y)
        tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        hscroll_tree.pack(side=tk.BOTTOM, fill=tk.X)
        tree_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        renamed_cols = [
            c.replace('Uniques', 'Unique').replace('Champions', 'Champion')
            for c in cols
        ]
        tree['columns'] = renamed_cols
        widths = [35, 60, 115, 60, 42, 58, 45, 75, 71, 89, 71, 59, 80, 70]
        for i, col in enumerate(renamed_cols):
            tree.column(col, stretch=tk.NO, minwidth=0, width=widths[i])
            if col in [
                    'Run', 'XP Gained', 'Champion kills', 'Unique kills',
                    'Minion kills', 'Total kills'
            ]:
                sort_by = 'num'
            else:
                sort_by = 'name'
            tree.heading(col, text=col, sort_by=sort_by)

        for n, lap in enumerate(laps, 1):
            tmp_lap = dict(lap)
            tmp_lap['Run time'] = other_utils.build_time_str(
                tmp_lap['Run time'])
            tmp_lap['Run'] = n
            tree.insert('',
                        tk.END,
                        values=[tmp_lap.get(col, '') for col in cols])
Ejemplo n.º 3
0
    def map_evaluation(self, laps):
        map_eval_fr = tkd.Frame(self.tabcontrol)
        self.tabcontrol.add(map_eval_fr, text='Map evaluation')

        cols = [
            "Map", "Map seed", "Run count", "Avg run time", "Avg MF",
            "Avg players X", "Avg packs (55% hork)",
            "Avg secs/pack (55% hork)", "Adjeff (55% hork)"
        ]
        tree_frame = tkd.Frame(map_eval_fr)
        btn_frame2 = tkd.Frame(map_eval_fr)
        btn_frame2.pack(side=tk.BOTTOM)

        vscroll_tree = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL)
        hscroll_tree = ttk.Scrollbar(map_eval_fr, orient=tk.HORIZONTAL)
        tree = tkd.Treeview(tree_frame,
                            selectmode=tk.BROWSE,
                            yscrollcommand=vscroll_tree.set,
                            xscrollcommand=hscroll_tree.set,
                            show='headings',
                            columns=cols,
                            alternate_colour=True)
        hscroll_tree.config(command=tree.xview)
        vscroll_tree.config(command=tree.yview)
        tkd.Button(btn_frame2,
                   text='Save as .csv',
                   command=lambda: self.save_to_csv(tree)).pack(side=tk.LEFT,
                                                                fill=tk.X)

        vscroll_tree.pack(side=tk.RIGHT, fill=tk.Y)
        tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        hscroll_tree.pack(side=tk.BOTTOM, fill=tk.X)
        tree_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        tree['columns'] = cols
        widths = [35, 70, 70, 80, 55, 85, 130, 150, 105]
        for i, col in enumerate(cols):
            tree.column(col, stretch=tk.NO, minwidth=0, width=widths[i])
            if col in [
                    "Avg packs (55% hork)", "Avg secs/pack (55% hork)",
                    "Adjeff (55% hork)"
            ]:
                sort_by = 'name'
            else:
                sort_by = 'num'

            tree.heading(col, text=col, sort_by=sort_by)

        grouped = self.group_laps(laps=laps)

        for n, smap in enumerate(grouped, 1):
            tmp_lap = dict(smap)
            tmp_lap['Map'] = n
            tree.insert('',
                        tk.END,
                        values=[tmp_lap.get(col, '') for col in cols])
Ejemplo n.º 4
0
    def _make_widgets(self):
        tkd.Label(self, text='Select active profile', justify=tk.LEFT).pack(anchor=tk.W)

        profile_dropdown_frame = tkd.Frame(self, height=28, width=238, pady=2, padx=2)
        profile_dropdown_frame.propagate(False)
        profile_dropdown_frame.pack()

        self.active_profile.set(self.main_frame.active_profile)
        self.profile_dropdown = tkd.Combobox(profile_dropdown_frame, textvariable=self.active_profile, state='readonly', values=self.main_frame.profiles)
        self.profile_dropdown.bind("<<ComboboxSelected>>", lambda e: self._change_active_profile())
        self.profile_dropdown.bind("<FocusOut>", lambda e: self.profile_dropdown.selection_clear())
        self.profile_dropdown.pack(side=tk.LEFT, expand=True, fill=tk.X)

        tkd.Button(profile_dropdown_frame, text='New...', command=self._add_new_profile).pack(side=tk.LEFT)
        tkd.Button(profile_dropdown_frame, text='Delete', command=self._delete_profile).pack(side=tk.LEFT)

        self.run_type = tk.StringVar(self, value=self.extra_data.get('Run type', ''))
        self.game_mode = tk.StringVar(self, value=self.extra_data.get('Game mode', 'Single Player'))
        self.char_name = tk.StringVar(self, value=self.extra_data.get('Character name', ''))
        self._extra_info_label('Run type', self.run_type)
        # self._extra_info_label('Game mode', self.game_mode)
        self._extra_info_label('Character name', self.char_name)

        tkd.Label(self, text='Select an archived run for this profile', justify=tk.LEFT).pack(anchor=tk.W, pady=(6, 0))
        sel_frame = tkd.Frame(self, height=28, width=238, pady=2, padx=2)
        sel_frame.propagate(False)
        sel_frame.pack()
        self.archive_dropdown = tkd.Combobox(sel_frame, textvariable=self.selected_archive, state='readonly', values=self.available_archive)
        self.archive_dropdown.bind("<<ComboboxSelected>>", lambda e: self.update_descriptive_statistics())
        self.archive_dropdown.bind("<FocusOut>", lambda e: self.archive_dropdown.selection_clear())
        self.archive_dropdown.pack(side=tk.LEFT, expand=True, fill=tk.X)

        tkd.Button(sel_frame, text='Open', command=lambda: archive_browser.ArchiveBrowser(self.main_frame)).pack(side=tk.LEFT)
        tkd.Button(sel_frame, text='Delete', command=self.delete_archived_session).pack(side=tk.LEFT)

        self.descr = tkd.Listbox(self, selectmode=tk.EXTENDED, height=8, activestyle='none', font=('courier', 8))
        self.descr.bind('<FocusOut>', lambda e: self.descr.selection_clear(0, tk.END))
        self.descr.pack(side=tk.BOTTOM, fill=tk.X, expand=1, anchor=tk.S)
Ejemplo n.º 5
0
    def statistics(self, laps, drops, session_time):
        statistics_fr = tkd.Frame(self.tabcontrol)
        self.tabcontrol.add(statistics_fr, text='Statistics')

        sum_laps = sum(x['Run time'] if isinstance(x, dict) else x
                       for x in laps)
        avg_lap = sum_laps / len(laps) if laps else 0
        pct = sum_laps * 100 / session_time if session_time > 0 else 0

        # Kill averages
        list_uniques = [
            int(x.get('Uniques kills', '')) for x in laps
            if isinstance(x, dict) and x.get('Uniques kills', '')
        ]
        list_champs = [
            int(x.get('Champions kills', '')) for x in laps
            if isinstance(x, dict) and x.get('Uniques kills', '')
        ]
        avg_uniques = sum(list_uniques) / len(
            list_uniques) if list_uniques else 0
        avg_champs = sum(list_champs) / len(list_champs) if list_champs else 0
        avg_packs = avg_uniques + avg_champs / 2.534567
        seconds_per_pack = avg_lap / avg_packs if avg_packs > 0 else 0

        # Configure the list frame with scrollbars which displays the archive of the chosen session
        list_win = tkd.Frame(statistics_fr)
        list_frame = tkd.Frame(list_win)
        vscroll = ttk.Scrollbar(list_frame, orient=tk.VERTICAL)
        hscroll = ttk.Scrollbar(list_win, orient=tk.HORIZONTAL)
        txt_list = tkd.Text(list_frame,
                            yscrollcommand=vscroll.set,
                            xscrollcommand=hscroll.set,
                            font='courier 10',
                            wrap=tk.WORD,
                            state=tk.NORMAL,
                            cursor='',
                            exportselection=1,
                            name='archivebrowser')
        vscroll.pack(side=tk.RIGHT, fill=tk.Y)
        txt_list.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
        txt_list.tag_configure("HEADER",
                               font=font.Font(family='courier',
                                              size=12,
                                              weight='bold',
                                              underline=True))
        hscroll.config(command=txt_list.xview)
        vscroll.config(command=txt_list.yview)

        # Build header for output file with information and descriptive statistics
        txt_list.insert(tk.END, 'Statistics', tag='HEADER')
        txt_list.insert(
            tk.END, '\nCharacter name: %s' %
            self.main_frame.profile_tab.extra_data.get('Character name', ''))
        txt_list.insert(
            tk.END, '\nRun type:       %s' %
            self.main_frame.profile_tab.extra_data.get('Run type', ''))
        txt_list.insert(
            tk.END, '\nGame mode:      %s' %
            self.main_frame.profile_tab.extra_data.get('Game mode',
                                                       'Single Player'))

        txt_list.insert(
            tk.END, '\n\nTotal session time:   %s' %
            other_utils.build_time_str(session_time))
        txt_list.insert(
            tk.END, '\nTotal run time:       %s' %
            other_utils.build_time_str(sum_laps))
        txt_list.insert(
            tk.END,
            '\nAverage run time:     %s' % other_utils.build_time_str(avg_lap))
        txt_list.insert(
            tk.END, '\nFastest run time:     %s' % other_utils.build_time_str(
                min([
                    x['Run time'] if isinstance(x, dict) else x for x in laps
                ],
                    default=0)))
        txt_list.insert(tk.END, '\nNumber of runs:       %s' % str(len(laps)))
        txt_list.insert(tk.END,
                        '\nTime spent in runs:   %s%%' % str(round(pct, 2)))

        txt_list.insert(
            tk.END,
            '\n\nAvg unique kills:     %s' % str(round(avg_uniques, 2)))
        txt_list.insert(
            tk.END, '\nAvg champion kills:   %s' % str(round(avg_champs, 2)))
        txt_list.insert(
            tk.END, '\nAvg pack kills:       %s' % str(round(avg_packs, 2)))
        txt_list.insert(
            tk.END,
            '\nAvg seconds/pack:     %s' % str(round(seconds_per_pack, 2)))

        # List all drops collected
        if drops:
            if any(drop for drop in drops.values()):
                txt_list.insert(tk.END, '\n\nCollected drops', tag='HEADER')
            for run_no, drop in drops.items():
                if drop:
                    str_n = ' ' * max(
                        len(str(len(laps))) - len(str(run_no)),
                        0) + str(run_no)
                    txt_list.insert(
                        tk.END, '\nRun ' + str_n + ' - ' +
                        ', '.join(x['input'].strip() for x in drop))

        if laps:
            txt_list.insert(tk.END, '\n\nRun times', tag='HEADER')

        # Loop through all runs and add run times and drops for each run
        for n, lap in enumerate(laps, 1):
            run_time = lap['Run time'] if isinstance(lap, dict) else lap
            str_n = ' ' * max(len(str(len(laps))) - len(str(n)), 0) + str(n)
            droplst = drops.get(str(n), [])
            tmp = '\nRun ' + str_n + ': ' + other_utils.build_time_str(
                run_time)
            if droplst:
                tmp += ' - ' + ', '.join([d['input'].strip() for d in droplst])
            txt_list.insert(tk.END, tmp)

        # Add bold tags
        # txt_list.tag_add("BOLD", "1.0", "1.15")
        # txt_list.tag_add("BOLD", "2.0", "2.9")
        # txt_list.tag_add("BOLD", "3.0", "3.10")
        # txt_list.tag_add("BOLD", "5.0", "5.19")
        # txt_list.tag_add("BOLD", "6.0", "6.15")
        # txt_list.tag_add("BOLD", "7.0", "7.17")
        # txt_list.tag_add("BOLD", "8.0", "8.17")
        # txt_list.tag_add("BOLD", "9.0", "9.15")
        # txt_list.tag_add("BOLD", "10.0", "10.19")
        # txt_list.tag_add("BOLD", "1.16", "1.0 lineend")
        # txt_list.tag_add("BOLD", "2.16", "2.0 lineend")
        # txt_list.tag_add("BOLD", "3.16", "3.0 lineend")
        # txt_list.tag_add("BOLD", "5.20", "5.0 lineend")
        # txt_list.tag_add("BOLD", "6.20", "6.0 lineend")
        # txt_list.tag_add("BOLD", "7.20", "7.0 lineend")
        # txt_list.tag_add("BOLD", "8.20", "8.0 lineend")
        # txt_list.tag_add("BOLD", "9.20", "9.0 lineend")
        # txt_list.tag_add("BOLD", "10.20", "10.0 lineend")
        # txt_list.tag_add("HEADER", "12.0", "12.0 lineend")
        txt_list.config(state=tk.DISABLED)

        btn_frame1 = tkd.Frame(statistics_fr)
        tkd.Button(btn_frame1,
                   text='Copy to clipboard',
                   command=lambda: self.copy_to_clipboard(
                       txt_list.get(1.0, tk.END))).pack(side=tk.LEFT,
                                                        fill=tk.X)
        tkd.Button(
            btn_frame1,
            text='Save as .txt',
            command=lambda: self.save_to_txt(txt_list.get(1.0, tk.END))).pack(
                side=tk.LEFT, fill=tk.X)

        # Packs all the buttons and UI in the archive browser. Packing order is very important:
        # TOP: Title first (furthest up), then list frame
        # BOTTOM: Buttons first (furthest down) and then horizontal scrollbar
        list_win.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        list_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        hscroll.pack(side=tk.BOTTOM, fill=tk.X)
        btn_frame1.pack(side=tk.BOTTOM)
Ejemplo n.º 6
0
    def drop_table(self, drops):
        drop_table_fr = tkd.Frame(self.tabcontrol)
        self.tabcontrol.add(drop_table_fr, text='Drop table')

        cols = [
            "Run", "Item name", "Extra input", "Real time", "TC", "QLVL",
            "Item Class", "Grailer", "Eth Grailer"
        ]
        tree_frame = tkd.Frame(drop_table_fr)
        btn_frame2 = tkd.Frame(drop_table_fr)
        btn_frame2.pack(side=tk.BOTTOM)

        vscroll_tree = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL)
        hscroll_tree = ttk.Scrollbar(drop_table_fr, orient=tk.HORIZONTAL)
        tree = tkd.Treeview(tree_frame,
                            selectmode=tk.BROWSE,
                            yscrollcommand=vscroll_tree.set,
                            xscrollcommand=hscroll_tree.set,
                            show='headings',
                            columns=cols,
                            alternate_colour=True)
        hscroll_tree.config(command=tree.xview)
        vscroll_tree.config(command=tree.yview)
        tkd.Button(btn_frame2,
                   text='Save as .csv',
                   command=lambda: self.save_to_csv(tree)).pack(side=tk.LEFT,
                                                                fill=tk.X)

        vscroll_tree.pack(side=tk.RIGHT, fill=tk.Y)
        tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        hscroll_tree.pack(side=tk.BOTTOM, fill=tk.X)
        tree_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        tree['columns'] = cols
        widths = [35, 190, 140, 120, 35, 35, 100, 47, 58]
        tree.tag_configure('Grail', background='#e6ffe6')
        for i, col in enumerate(cols):
            tree.column(col, stretch=tk.NO, minwidth=0, width=widths[i])
            if col in ['Run', 'TC', 'QLVL']:
                sort_by = 'num'
            else:
                sort_by = 'name'
            tree.heading(col, text=col, sort_by=sort_by)

        for n, drop_list in drops.items():
            for drop in drop_list:
                tmp_drop = dict(drop)
                tmp_drop['Run'] = n
                if drop.get("item_name", ''):
                    tmp_drop["Item name"] = drop["item_name"]
                    tmp_drop["Extra input"] = drop["extra"]
                else:
                    tmp_drop["Item name"] = tmp_drop["input"]
                    tmp_drop["Extra input"] = ""

                if drop.get('Grailer', False) == 'True' or drop.get(
                        'Eth Grailer', False) == 'True':
                    tree.insert('',
                                tk.END,
                                values=[tmp_drop.get(col, '') for col in cols],
                                tag='Grail')
                else:
                    tree.insert('',
                                tk.END,
                                values=[tmp_drop.get(col, '') for col in cols])
Ejemplo n.º 7
0
    def drop_table(self, drops):
        flat_drops = [{
            **drop, 'Run': n,
            'Item name': drop.get('item_name', drop.get('input', '')),
            'Extra input': drop.get('extra', '')
        } for n, drop_list in drops.items() for drop in drop_list]

        def select_drops_from_filters(event=None):
            tree.delete(*tree.get_children())

            # The filtering function breaks if column name has underscore in it - potential issue that could be fixed..
            all_filter = lambda x: all(
                str(x.get(f.split('_')[-1], '')) == getattr(self, f).get() or
                getattr(self, f).get() == '' for f in filters)
            for drop in flat_drops:
                if all_filter(drop):
                    if drop.get('Grailer', False) == 'True':
                        tree.insert('',
                                    tk.END,
                                    values=[drop.get(col, '') for col in cols],
                                    tag='Grail')
                    elif drop.get('Eth Grailer', False) == 'True':
                        tree.insert('',
                                    tk.END,
                                    values=[drop.get(col, '') for col in cols],
                                    tag='EthGrail')
                    else:
                        tree.insert('',
                                    tk.END,
                                    values=[drop.get(col, '') for col in cols])

        drop_table_fr = tkd.Frame(self.tabcontrol)
        self.tabcontrol.add(drop_table_fr, text='Drop table')

        cols = [
            "Run", "Item name", "Extra input", "Real time", "TC", "QLVL",
            "Item Class", "Grailer", "Eth Grailer", "Session", "Rarity"
        ]
        tree_frame = tkd.Frame(drop_table_fr)
        btn_frame2 = tkd.Frame(drop_table_fr)
        btn_frame2.pack(side=tk.BOTTOM)

        vscroll_tree = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL)
        hscroll_tree = ttk.Scrollbar(drop_table_fr, orient=tk.HORIZONTAL)
        tree = tkd.Treeview(tree_frame,
                            selectmode=tk.BROWSE,
                            yscrollcommand=vscroll_tree.set,
                            xscrollcommand=hscroll_tree.set,
                            show='headings',
                            columns=cols,
                            alternate_colour=True)
        hscroll_tree.config(command=tree.xview)
        vscroll_tree.config(command=tree.yview)
        tkd.Button(btn_frame2,
                   text='Save as .csv',
                   command=lambda: self.save_to_csv(tree)).pack(side=tk.LEFT,
                                                                fill=tk.X)
        combofr = tkd.Frame(tree_frame)

        vscroll_tree.pack(side=tk.RIGHT, fill=tk.Y)
        combofr.pack(fill=tk.X)
        tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        hscroll_tree.pack(side=tk.BOTTOM, fill=tk.X)
        tree_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        tree['columns'] = cols
        widths = [35, 200, 140, 120, 35, 38, 100, 47, 65, 80, 80]
        cb_width = [3, 30, 20, 16, 3, 3, 13, 5, 7, 10, 10]
        tree.tag_configure('Grail', background='#e6ffe6')
        tree.tag_configure('EthGrail', background='light goldenrod yellow')

        filters = []
        for i, col in enumerate(cols):
            tree.column(col, stretch=tk.NO, minwidth=0, width=widths[i])
            if col in ['Run', 'TC', 'QLVL']:
                sort_by = 'num'
                sort_key = lambda x: float('-inf') if x == '' else float(
                    x.replace('%', ''))
            else:
                sort_by = 'name'
                sort_key = lambda x: x
            tree.heading(col, text=col, sort_by=sort_by)

            name = 'combofilter_' + col
            filters.append(name)
            setattr(
                self, name,
                tkd.Combobox(combofr,
                             values=sorted(set(
                                 str(x.get(col, ''))
                                 for x in flat_drops).union({''}),
                                           key=sort_key),
                             state="readonly",
                             width=cb_width[i]))
            getattr(self, name).pack(side=tk.LEFT)
            getattr(self, name).bind('<<ComboboxSelected>>',
                                     select_drops_from_filters)

        select_drops_from_filters()
Ejemplo n.º 8
0
    def __init__(self):
        # Check if application is already open
        self.title = 'MF run counter'

        # Create error logger
        lh = logging.FileHandler(filename='mf_timer.log', mode='w', delay=True)
        logging.basicConfig(handlers=[lh],
                            format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
                            datefmt='%H:%M:%S',
                            level=logging.WARNING)

        # Check OS
        self.os_platform = platform.system()
        self.os_release = platform.release()
        if not self.os_platform == 'Windows':
            raise SystemError("MF Run Counter only supports windows")

        # Create root
        self.root = tkd.Tk()

        # Ensure errors are handled with an exception pop-up if encountered
        self.root.report_callback_exception = self.report_callback_exception

        # Build/load config file
        self.cfg = self.load_config_file()
        if hasattr(logging, self.cfg['DEFAULT']['logging_level']):
            logging.getLogger().setLevel(getattr(logging, self.cfg['DEFAULT']['logging_level']))
        self.SP_game_path = self.cfg['DEFAULT']['SP_game_path']
        self.MP_game_path = self.cfg['DEFAULT']['MP_game_path']
        self.herokuapp_username = self.cfg['DEFAULT']['herokuapp_username']
        self.herokuapp_password = base64.b64decode(self.cfg['DEFAULT']['herokuapp_password']).decode('utf-8')
        self.webproxies = other_utils.safe_eval(self.cfg['DEFAULT']['webproxies'])
        self.automode = other_utils.safe_eval(self.cfg['AUTOMODE']['automode'])
        self.end_run_in_menu = other_utils.safe_eval(self.cfg['AUTOMODE']['end_run_in_menu'])
        self.pause_on_esc_menu = other_utils.safe_eval(self.cfg['AUTOMODE']['pause_on_esc_menu'])
        self.always_on_top = other_utils.safe_eval(self.cfg['OPTIONS']['always_on_top'])
        self.tab_switch_keys_global = other_utils.safe_eval(self.cfg['OPTIONS']['tab_switch_keys_global'])
        self.check_for_new_version = other_utils.safe_eval(self.cfg['OPTIONS']['check_for_new_version'])
        self.enable_sound_effects = other_utils.safe_eval(self.cfg['OPTIONS']['enable_sound_effects'])
        self.start_run_delay_seconds = other_utils.safe_eval(self.cfg['OPTIONS']['start_run_delay_seconds'])
        self.show_drops_tab_below = other_utils.safe_eval(self.cfg['OPTIONS']['show_drops_tab_below'])
        self.active_theme = self.cfg['OPTIONS']['active_theme'].lower()
        self.auto_upload_herokuapp = other_utils.safe_eval(self.cfg['OPTIONS']['auto_upload_herokuapp'])
        self.auto_archive_hours = other_utils.safe_eval(self.cfg['OPTIONS']['auto_archive_hours'])
        self.autocompletion_unids = other_utils.safe_eval(self.cfg['OPTIONS']['autocompletion_unids'])
        self.add_to_last_run = other_utils.safe_eval(self.cfg['OPTIONS']['add_to_last_run'])
        self.disable_scaling = other_utils.safe_eval(self.cfg['OPTIONS']['disable_dpi_scaling'])

        # UI config
        self.show_buttons = other_utils.safe_eval(self.cfg['UI']['show_buttons'])
        self.show_drops_section = other_utils.safe_eval(self.cfg['UI']['show_drops_section'])
        self.show_advanced_tracker = other_utils.safe_eval(self.cfg['UI']['show_advanced_tracker'])
        self.show_xp_tracker = other_utils.safe_eval(self.cfg['UI']['show_xp_tracker'])

        # Initiate variables for memory reading
        self.is_user_admin = reader_utils.is_user_admin()
        self.advanced_error_thrown = False
        self.d2_reader = None

        # Load theme
        if self.active_theme not in available_themes:
            self.active_theme = 'vista'
        self.theme = Theme(used_theme=self.active_theme)

        # Create hotkey queue and initiate process for monitoring the queue
        self.queue = queue.Queue(maxsize=1)
        self.process_queue()

        # Check for version update
        if self.check_for_new_version:
            self.dl_count = github_releases.check_newest_version(release_repo)
        else:
            self.dl_count = ''

        # Load profile info
        self.make_profile_folder()
        self.profiles = [x[:-5] for x in os.listdir('Profiles') if x.endswith('.json') and not x == 'grail.json']
        self.active_profile = self.cfg['DEFAULT']['active_profile']
        if len(self.profiles) == 0:
            self.active_profile = ''
        elif len(self.profiles) > 0 and self.active_profile not in self.profiles:
            self.active_profile = self.profiles[0]

        self.profiles = self.sorted_profiles()

        # Modify root window
        self.root.title(self.title)
        self.clickable = True
        self.root.resizable(False, False)
        self.root.geometry('+%d+%d' % other_utils.safe_eval(self.cfg['DEFAULT']['window_start_position']))
        self.root.config(borderwidth=2, height=365, width=240, relief='raised')
        # self.root.wm_attributes("-transparentcolor", "purple")
        self.root.wm_attributes("-topmost", self.always_on_top)
        self.root.focus_get()
        self.root.protocol("WM_DELETE_WINDOW", self.Quit)
        self.root.iconbitmap(media_path + 'icon.ico')
        self.root.pack_propagate(False)

        # Build banner image and make window draggable on the banner
        d2banner = media_path + 'd2icon.png'
        img = tk.PhotoImage(file=d2banner)
        self.img_panel = tkd.Label(self.root, image=img, borderwidth=0)
        self.img_panel.pack()
        self.img_panel.bind("<ButtonPress-1>", self.root.start_move)
        self.img_panel.bind("<ButtonRelease-1>", self.root.stop_move)
        self.img_panel.bind("<B1-Motion>", self.root.on_motion)
        self.root.bind("<Delete>", self.delete_selection)
        self.root.bind("<Left>", self.root.moveleft)
        self.root.bind("<Right>", self.root.moveright)
        self.root.bind("<Up>", self.root.moveup)
        self.root.bind("<Down>", self.root.movedown)

        # Add buttons to main widget
        self.btn_frame = tkd.Frame(self.root)
        tkd.Button(self.btn_frame, text='Delete selection', command=self.delete_selection).pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=[2, 1], pady=1)
        tkd.Button(self.btn_frame, text='Archive session', command=self.ArchiveReset).pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=[0, 1], pady=1)

        # Build tabs
        self.caret_frame = tkd.Frame(self.root)
        self.drops_frame = tkd.Frame(self.caret_frame)
        self.adv_stats_frame = tkd.Frame(self.caret_frame)

        self.tabcontrol = tkd.Notebook(self.root)
        self.tabcontrol.pack(expand=False, fill=tk.BOTH)
        self.profile_tab = Profile(self, parent=self.tabcontrol)
        self.timer_tab = MFRunTimer(self, parent=self.tabcontrol)
        self.drops_tab = Drops(self, parent=self.drops_frame)
        self.options_tab = Options(self, self.timer_tab, self.drops_tab, parent=self.tabcontrol)
        self.grail_tab = Grail(self, parent=self.tabcontrol)
        self.about_tab = About(self, parent=self.tabcontrol)

        self.tabcontrol.add(self.timer_tab, text='Timer')
        self.tabcontrol.add(self.options_tab, text='Options')
        self.tabcontrol.add(self.profile_tab, text='Profile')
        self.tabcontrol.add(self.grail_tab, text='Grail')
        self.tabcontrol.add(self.about_tab, text='About')

        self.root.bind("<<NotebookTabChanged>>", lambda _e: self.notebook_tab_change())
        self.profile_tab.update_descriptive_statistics()

        self.toggle_drops_frame(show=self.show_drops_tab_below)
        self.drops_caret = tkd.CaretButton(self.drops_frame, active=self.show_drops_tab_below, command=self.toggle_drops_frame, text='Drops', compound=tk.RIGHT, height=13)
        self.drops_caret.propagate(False)
        self.drops_caret.pack(side=tk.BOTTOM, fill=tk.X, expand=True, padx=[2, 1], pady=[0, 1])

        tracker_is_active = other_utils.safe_eval(self.cfg['AUTOMODE']['advanced_tracker_open']) and self.automode == 2 and self.is_user_admin
        self.advanced_stats_tracker = StatsTracker(self, self.adv_stats_frame)
        self.advanced_stats_caret = tkd.CaretButton(self.adv_stats_frame, active=tracker_is_active, text='Advanced stats', compound=tk.RIGHT, height=13, command=self.toggle_advanced_stats_frame)
        self.advanced_stats_caret.propagate(False)
        self.advanced_stats_caret.pack(side=tk.BOTTOM, fill=tk.X, expand=True, padx=[2, 1], pady=[0, 1])

        # Register binds for changing tabs
        if self.tab_switch_keys_global:
            self.options_tab.tab2.hk.register(['control', 'shift', 'next'], callback=lambda event: self.queue.put(self.tabcontrol.next_tab))
            self.options_tab.tab2.hk.register(['control', 'shift', 'prior'], callback=lambda event: self.queue.put(self.tabcontrol.prev_tab))
        else:
            self.root.bind_all('<Control-Shift-Next>', lambda event: self.tabcontrol.next_tab())
            self.root.bind_all('<Control-Shift-Prior>', lambda event: self.tabcontrol.prev_tab())

        # Load save state and start autosave process
        active_state = self.load_state_file()
        self.LoadActiveState(active_state)
        self.root.after(30000, self._autosave_state)

        # Apply styling options
        self.theme.apply_theme_style()
        self.theme.update_colors()

        # Automode and advanced stats loop
        self.am_lab = tk.Text(self.root, height=1, width=13, wrap=tk.NONE, bg="black", font=('Segoe UI', 9), cursor='', borderwidth=0)
        self.am_lab.tag_configure("am", foreground="white", background="black")
        self.am_lab.tag_configure("on", foreground="lawn green", background="black")
        self.am_lab.tag_configure("off", foreground="red", background="black")
        self.am_lab.place(x=1, y=0.4)
        self.toggle_automode()
        self.toggle_advanced_stats_frame(show=tracker_is_active)

        # A trick to disable windows DPI scaling - the app doesnt work well with scaling, unfortunately
        if self.os_release == '10' and self.disable_scaling:
            ctypes.windll.shcore.SetProcessDpiAwareness(2)

        # Used if "auto archive session" is activated
        self.profile_tab.auto_reset_session()

        # Pressing ALT_L paused UI updates when in focus, disable (probably hooked to opening menus)
        self.root.unbind_all('<Alt_L>')

        # Start the program
        self.root.mainloop()
Ejemplo n.º 9
0
    def open_archive_browser(self):
        chosen = self.archive_dropdown.get()
        if chosen == '':
            # If nothing is selected the function returns
            return

        # We build the new tkinter window to be opened
        new_win = tkd.Toplevel()
        new_win.title('Archive browser')
        new_win.wm_attributes('-topmost', 1)

        disp_coords = tk_utils.get_displaced_geom(self.main_frame.root, 400, 460)
        new_win.geometry(disp_coords)
        new_win.focus_get()
        new_win.iconbitmap(os.path.join(getattr(sys, '_MEIPASS', os.path.abspath('.')), media_path + 'icon.ico'))
        new_win.minsize(400, 460)
        title = tkd.Label(new_win, text='Archive browser', font='Helvetica 14')

        # Handle how loading of session data should be treated in the 3 different cases
        if chosen == 'Active session':
            # Load directly from timer module
            session_time = self.main_frame.timer_tab.session_time
            laps = self.main_frame.timer_tab.laps
            drops = self.main_frame.drops_tab.drops
        elif chosen == 'Profile history':
            # Load everything from profile .json, and append data from timer module
            active = self.main_frame.load_state_file()
            laps = []
            session_time = 0
            drops = dict()
            # Concatenate information from each available session
            for key in [x for x in active.keys() if x not in ['active_state', 'extra_data']]:
                session_drops = active[key].get('drops', dict())
                for run_no, run_drop in session_drops.items():
                    drops[str(int(run_no)+len(laps))] = run_drop
                laps.extend(active[key].get('laps', []))
                session_time += active[key].get('session_time', 0)

            # Append data for active session from timer module
            for run_no, run_drop in self.main_frame.drops_tab.drops.items():
                drops[str(int(run_no) + len(laps))] = run_drop
            laps.extend(self.main_frame.timer_tab.laps)
            session_time += self.main_frame.timer_tab.session_time
        else:
            # Load selected session data from profile .json
            active = self.main_frame.load_state_file()
            chosen_archive = active.get(chosen, dict())
            session_time = chosen_archive.get('session_time', 0)
            laps = chosen_archive.get('laps', [])
            drops = chosen_archive.get('drops', dict())

        # Ensure no division by zero errors by defaulting to displaying 0
        avg_lap = sum(laps) / len(laps) if laps else 0
        pct = sum(laps) * 100 / session_time if session_time > 0 else 0

        # Configure the list frame with scrollbars which displays the archive of the chosen session
        list_win = tkd.Frame(new_win)
        list_frame = tkd.Frame(list_win)
        vscroll = ttk.Scrollbar(list_frame, orient=tk.VERTICAL)
        hscroll = ttk.Scrollbar(list_win, orient=tk.HORIZONTAL)
        txt_list = tkd.Text(list_frame, yscrollcommand=vscroll.set, xscrollcommand=hscroll.set, font='courier 10', wrap=tk.WORD, state=tk.NORMAL, cursor='', exportselection=1, name='archivebrowser')
        # txt_list.bind('<FocusOut>', lambda e: txt_list.tag_remove(tk.SEL, "1.0", tk.END))  # Lose selection when shifting focus
        vscroll.pack(side=tk.RIGHT, fill=tk.Y)
        txt_list.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
        txt_list.tag_configure("HEADER", font=tkFont(family='courier', size=12, weight='bold', underline=True))
        hscroll.config(command=txt_list.xview)
        vscroll.config(command=txt_list.yview)

        # Build header for output file with information and descriptive statistics
        output = [['Statistics'],
                  ['Character name: ', self.extra_data.get('Character name', '')],
                  ['Run type:       ', self.extra_data.get('Run type', '')],
                  ['Game mode:      ', self.extra_data.get('Game mode', 'Single Player')],
                  [''],
                  ['Total session time:   ', utils.other_utils.build_time_str(session_time)],
                  ['Total run time:       ', utils.other_utils.build_time_str(sum(laps))],
                  ['Average run time:     ', utils.other_utils.build_time_str(avg_lap)],
                  ['Fastest run time:     ', utils.other_utils.build_time_str(min(laps, default=0))],
                  ['Number of runs:       ', str(len(laps))],
                  ['Time spent in runs:   ', str(round(pct, 2)) + '%'],
                  ['']]

        # Backwards compatibility with old drop format
        for k, v in drops.items():
            for i in range(len(v)):
                if not isinstance(v[i], dict):
                    drops[k][i] = {'item_name': None, 'input': v[i], 'extra': ''}

        # List all drops collected
        if drops:
            if any(drop for drop in drops.values()):
                output.append(['Collected drops'])
            for run_no, drop in drops.items():
                if drop:
                    str_n = ' ' * max(len(str(len(laps))) - len(str(run_no)), 0) + str(run_no)
                    output.append(['Run ' + str_n, '', *[x['input'] for x in drop]])
            output.append([''])

        if laps:
            output.append(['Run times'])

        # Loop through all runs and add run times and drops for each run
        for n, lap in enumerate(laps, 1):
            str_n = ' ' * max(len(str(len(laps))) - len(str(n)), 0) + str(n)
            droplst = drops.get(str(n), [])
            tmp = ['Run ' + str_n + ': ', utils.other_utils.build_time_str(lap)]
            if droplst:
                tmp += [d['input'] for d in droplst]
            output.append(tmp)

        # Format string list to be shown in the archive browser
        for i, op in enumerate(output, 1):
            tmpstr = ''.join(op[:2])
            if len(op) > 2:
                tmpstr += ' --- ' + ', '.join(op[2:])
            if txt_list.get('1.0', tk.END) != '\n':
                tmpstr = '\n' + tmpstr
            txt_list.insert(tk.END, tmpstr)
            if op[0] in ['Statistics', 'Collected drops', 'Run times']:
                txt_list.tag_add("HEADER", str(i) + ".0", str(i) + ".0 lineend")

        # Add bold tags
        # txt_list.tag_add("BOLD", "1.0", "1.15")
        # txt_list.tag_add("BOLD", "2.0", "2.9")
        # txt_list.tag_add("BOLD", "3.0", "3.10")
        # txt_list.tag_add("BOLD", "5.0", "5.19")
        # txt_list.tag_add("BOLD", "6.0", "6.15")
        # txt_list.tag_add("BOLD", "7.0", "7.17")
        # txt_list.tag_add("BOLD", "8.0", "8.17")
        # txt_list.tag_add("BOLD", "9.0", "9.15")
        # txt_list.tag_add("BOLD", "10.0", "10.19")
        # txt_list.tag_add("BOLD", "1.16", "1.0 lineend")
        # txt_list.tag_add("BOLD", "2.16", "2.0 lineend")
        # txt_list.tag_add("BOLD", "3.16", "3.0 lineend")
        # txt_list.tag_add("BOLD", "5.20", "5.0 lineend")
        # txt_list.tag_add("BOLD", "6.20", "6.0 lineend")
        # txt_list.tag_add("BOLD", "7.20", "7.0 lineend")
        # txt_list.tag_add("BOLD", "8.20", "8.0 lineend")
        # txt_list.tag_add("BOLD", "9.20", "9.0 lineend")
        # txt_list.tag_add("BOLD", "10.20", "10.0 lineend")

        txt_list.tag_add("HEADER", "12.0", "12.0 lineend")

        txt_list.config(state=tk.DISABLED)

        button_frame = tkd.Frame(new_win)
        tkd.Button(button_frame, text='Copy to clipboard', command=lambda: self.copy_to_clipboard(new_win, txt_list.get(1.0, tk.END))).pack(side=tk.LEFT, fill=tk.X)
        tkd.Button(button_frame, text='Save as .txt', command=lambda: self.save_to_txt(txt_list.get(1.0, tk.END))).pack(side=tk.LEFT, fill=tk.X)
        tkd.Button(button_frame, text='Save as .csv', command=lambda: self.save_to_csv(output)).pack(side=tk.LEFT, fill=tk.X)

        # Packs all the buttons and UI in the archive browser. Packing order is very important:
        # TOP: Title first (furthest up), then list frame
        # BOTTOM: Buttons first (furthest down) and then horizontal scrollbar
        title.pack(side=tk.TOP)
        list_win.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        list_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        hscroll.pack(side=tk.BOTTOM, fill=tk.X)
        button_frame.pack(side=tk.BOTTOM)


        theme = Theme(self.main_frame.active_theme)
        theme.update_colors()
Ejemplo n.º 10
0
    def make_widgets(self):
        self.gamemode_frame = tkd.LabelFrame(self,
                                             height=LAB_HEIGHT,
                                             width=LAB_WIDTH)
        self.gamemode_frame.propagate(False)

        self.gamemode_lab = tkd.Label(
            self.gamemode_frame,
            text='Game mode',
            tooltip=
            'If Multiplayer is selected, the .map file is used to check for updates.\n'
            'Thus, new runs begin every time you enter a new game (since your local .map files will be updated by this)\n\n'
            'If Single Player is selected the .d2s file is used to check for updates.\n'
            'Thus, a new run begins every time you leave a game (since your .d2s files are saved upon exit)'
        )

        self.game_mode = tk.StringVar()
        self.game_mode.set(self.main_frame.profile_tab.game_mode.get())
        self.gamemode_cb = ttk.Combobox(
            self.gamemode_frame,
            textvariable=self.game_mode,
            state='readonly',
            values=['Single Player', 'Multiplayer'])
        self.gamemode_cb.bind("<FocusOut>",
                              lambda e: self.gamemode_cb.selection_clear())
        self.gamemode_cb.config(width=11)
        self.game_mode.trace_add(
            'write', lambda name, index, mode: self.update_game_mode())

        self.charname_frame = tkd.LabelFrame(self,
                                             height=LAB_HEIGHT,
                                             width=LAB_WIDTH)
        self.charname_frame.propagate(False)

        self.char_var = tk.StringVar()
        self.char_var.set(self.main_frame.profile_tab.char_name.get())
        self.charname_text_lab = tkd.Label(
            self.charname_frame,
            text='Character name',
            tooltip='Your character name is inferred from the active profile.\n'
            'Make sure the character name in your profile is matching your in-game character name'
        )
        self.charname_val_lab = tkd.Label(self.charname_frame,
                                          textvariable=self.char_var)

        self.sp_path_lab = tkd.Label(self, text='Game path (Single Player)')
        self.SP_game_path = tk.StringVar()
        self.SP_game_path.set(self.main_frame.SP_game_path)
        self.sp_path_entry = tkd.Entry(self, textvariable=self.SP_game_path)

        self.sp_path_frame = tkd.Frame(self)
        self.sp_path_get = tkd.Button(
            self.sp_path_frame,
            text='Get',
            command=lambda: self.get_game_path(is_sp=True),
            tooltip=
            'The app tries to automatically find your game path for single player\n'
            'If nothing is returned you have to type it in manually')
        self.sp_path_apply = tkd.Button(
            self.sp_path_frame,
            text='Apply',
            command=self.apply_path_ch,
            tooltip='Apply the current specified path')

        self.mp_path_lab = tkd.Label(self, text='Game path (Multiplayer)')
        self.MP_game_path = tk.StringVar()
        self.MP_game_path.set(self.main_frame.MP_game_path)
        self.mp_path_entry = tkd.Entry(self, textvariable=self.MP_game_path)
        self.mp_path_frame = tkd.Frame(self)

        self.mp_path_get = tkd.Button(
            self.mp_path_frame,
            text='Get',
            command=lambda: self.get_game_path(is_sp=False),
            tooltip=
            'The app tries to automatically find your game path for multiplayer\n'
            'If nothing is returned you have to type it in manually')
        self.mp_path_apply = tkd.Button(
            self.mp_path_frame,
            text='Apply',
            command=self.apply_path_ch,
            tooltip='Apply the current specified path')

        # Stuff for advanced mode
        self.advanced_mode_stop = self.add_flag(
            flag_name='Stop when leaving',
            comment=
            'On: Stops the current run when you exit to menu.\nOff: The run counter will continue ticking until you enter a new game',
            pack=False,
            config_section='AUTOMODE')
        self.advanced_pause_on_esc_menu = self.add_flag(
            flag_name='Pause on ESC menu',
            comment=
            'When activated, the counter will be paused when ESC menu\nis open inside d2 (not working for 1.14b and 1.14c)',
            pack=False,
            config_section='AUTOMODE')
        self.advanced_automode_warning = tkd.Label(
            self,
            text='"Advanced automode" is highly \n'
            'discouraged when playing\n'
            'multiplayer, as it might result\n'
            'in a ban.\n'
            'Explanation: Advanced automode\n'
            'utilizes "memory reading" of the\n'
            'D2 process to discover information\n'
            'about the current game state,\n'
            'and this could be deemed cheating.',
            justify=tk.LEFT)

        self.toggle_automode_btn(first=True)