def load_settings(settings=None): if settings is None: settings = default_settings global g_setting g_setting = {} for setting in settings: with open(setting["path"], "r", encoding="utf-8") as setting_file: try: g_setting[setting["name"]] = yaml.load(setting_file, Loader=yaml.FullLoader) except Exception as error: notify_error( logger, "配置表={}的格式有问题,具体问题请看下面的报错中的line $行数$ column $列数$来定位\n错误信息:{}\n" .format(setting["name"], error)) exit(0) ok, msg = check_settings(g_setting) if not ok: notify_error(logger, "配置表填写有误:\n{}".format(msg)) exit(0) logger.info("setting loaded") logger.debug("setting={}".format(g_setting))
def report_bugsnag_in_worker(current_process, error, processed_count, args, show_error_messagebox=True): traceback_info = traceback.format_exc() # 打印错误日志 logger.info( "work thread {} unhandled exception={} when processing {}th work\nargs={}\n{}" .format(current_process, error, processed_count, args, traceback_info)) # 弹出错误框 if show_error_messagebox: notify_error( logger, "工作线程{}在处理第{}个计算项的搜索搭配过程中出现了未处理的异常\n{}".format( current_process, processed_count, traceback_info)) # 上报bugsnag cpu_name, physical_cpu_cores, manufacturer = get_hardward_info() meta_data = { "worker_task_info": { "args": args, }, "stacktrace_brief": { "info": traceback_info, }, "config": config(), "settings": all_settings(), "app": { "releaseStage": RUN_ENV, "version": now_version, "release_time": ver_time, }, "device": { "uuid": uuid.getnode(), "node": platform.node(), "osName": platform.system(), "osVersion": platform.version(), "release": platform.release(), "architecture": platform.machine(), "processor": platform.processor(), "logical_cpu_num": multiprocessing.cpu_count(), "physical_cpu_num": physical_cpu_cores, "cpu_name": cpu_name, "manufacturer": manufacturer, "time": time.strftime("%Y-%m-%d %H:%M:%S"), "timezone": time.strftime("%z", time.gmtime()), }, } bugsnag.notify( exception=error, context="worker", meta_data=meta_data, user={ "id": platform.node(), "uuid": uuid.getnode(), }, )
def producer(*args): # if exit_calc.value == 1: # return producer_data.work_queue.put((producer_data.calc_index, args)) producer_data.produced_count += 1 logger.info("producer put %3dth work into work queue", producer_data.produced_count)
def on_config_update(self, raw_config: dict): consoleHandler.setLevel(self.log_level_map[self.log_level]) if multiprocessing.current_process().name == "MainProcess": logger.info("config loaded") logging.info("log level change to %s", self.log_level) logging.info("max thread is set to %d", self.multi_threading.max_thread) logger.debug("raw_config={}".format(raw_config)) logger.debug("config={}".format(g_config))
def get_update_info() -> UpdateInfo: update_info = UpdateInfo() # 获取github本项目的readme页面内容 readme_html_text = requests.get(config().readme_page).text # 从更新日志中提取所有版本信息 versions = re.findall("(?<=[vV])[0-9.]+(?=\s+\d+\.\d+\.\d+)", readme_html_text) # 找出其中最新的那个版本号 update_info.latest_version = version_int_list_to_version( max(version_to_version_int_list(ver) for ver in versions)) # 从readme中提取最新网盘信息 netdisk_address_matches = re.findall( '链接: <a[\s\S]+?rel="nofollow">(?P<link>.+?)<\/a> 提取码: (?P<passcode>[a-zA-Z0-9]+)', readme_html_text, re.MULTILINE) # 先选取首个网盘链接作为默认值 netdisk_link = netdisk_address_matches[0][0] netdisk_passcode = netdisk_address_matches[0][1] # 然后随机从仍有效的网盘链接中随机一个作为最终结果 random.seed(datetime.now()) random.shuffle(netdisk_address_matches) for match in netdisk_address_matches: if not is_shared_content_blocked(match[0]): update_info.netdisk_link = match[0] update_info.netdisk_passcode = match[1] break # 尝试提取更新信息 update_message_list_match_groupdict = re.search( "(?<=更新公告</h1>)\s*<ol>(?P<update_message_list>(\s|\S)+?)</ol>", readme_html_text, re.MULTILINE).groupdict() if "update_message_list" in update_message_list_match_groupdict: update_message_list_str = update_message_list_match_groupdict[ "update_message_list"] update_messages = re.findall("<li>(?P<update_message>.+?)</li>", update_message_list_str, re.MULTILINE) update_info.update_message = "\n".join( "{}. {}".format(idx + 1, message) for idx, message in enumerate(update_messages)) logger.info("netdisk_address_matches={}, selected=({}, {})".format( netdisk_address_matches, update_info.netdisk_link, update_info.netdisk_passcode)) return update_info
def check_update_on_start(): try: if not config().check_update_on_start: logger.warning("启动时检查更新被禁用,若需启用请在config.toml中设置") return ui = get_update_info() if need_update(now_version, ui.latest_version): logger.info("当前版本为{},已有最新版本{},更新内容为{}".format( now_version, ui.latest_version, ui.update_message)) ask_update = tkinter.messagebox.askquestion( '更新', ("当前版本为{},已有最新版本{}. 你需要更新吗?\n" "{}".format(now_version, ui.latest_version, ui.update_message))) if ask_update == 'yes': if not is_shared_content_blocked(ui.netdisk_link): webbrowser.open(ui.netdisk_link) tkinter.messagebox.showinfo( "蓝奏云网盘提取码", "蓝奏云网盘提取码为: {}".format(ui.netdisk_passcode)) else: # 如果分享的网盘链接被系统屏蔽了,写日志并弹窗提示 logger.warning("网盘链接={}又被系统干掉了=-=".format(ui.netdisk_link)) webbrowser.open( "https://github.com/fzls/dnf_calc/releases") tkinter.messagebox.showerror( "不好啦", ("分享的网盘地址好像又被系统给抽掉了呢=。=先暂时使用github的release页面下载吧0-0\n" "请稍作等待~ 风之凌殇看到这个报错后会尽快更新网盘链接的呢\n" "届时再启动程序将自动获取到最新的网盘地址呢~")) else: tkinter.messagebox.showinfo( "取消启动时自动检查更新方法", "如果想停留在当前版本,不想每次启动都弹出前面这个提醒更新的框框,可以前往config.toml,将check_update_on_start的值设为false即可" ) else: logger.info("当前版本{}已是最新版本,无需更新".format(now_version)) except Exception as err: logger.error("更新版本失败, 错误为{}".format(err))
def load_settings(settings=None): if settings is None: settings = default_settings global g_setting g_setting = {} for setting in settings: try: with open(setting["path"], "r", encoding="utf-8") as setting_file: g_setting[setting["name"]] = yaml.load(setting_file, Loader=yaml.FullLoader) except FileNotFoundError as error: notify_error( logger, "没找到配置表={},你是否直接在压缩包中打开了?\n错误信息:{}\n".format( setting["name"], error)) sys.exit(0) except UnicodeDecodeError as error: notify_error( logger, "配置表={}的编码格式有问题,应为utf-8,如果使用系统自带记事本的话,请下载vscode或notepad++等文本编辑器\n错误信息:{}\n" .format(setting["name"], error)) sys.exit(0) except Exception as error: notify_error( logger, "配置表={}的格式有问题,具体问题请看下面的报错中的line $行数$ column $列数$来定位\n错误信息:{}\n" .format(setting["name"], error)) sys.exit(0) ok, msg = check_settings(g_setting) if not ok: notify_error(logger, "配置表填写有误:\n{}".format(msg)) sys.exit(0) if multiprocessing.current_process().name == "MainProcess": logger.info("setting loaded") logger.debug("setting={}".format(g_setting))
def consumer(work_queue, exit_calc, work_func): """ @type work_queue: multiprocessing.JoinableQueue """ current_process = multiprocessing.current_process() # 为工作线程配置bugsnag信息 configure_bugsnag() # 启动时先读取config和setting load_config() load_settings() logger.info( "work thread={} started, configure_bugsnag done, ready to work".format( current_process)) current_calc_index = 0 processed_count = 0 continue_wrok = True while continue_wrok: try: calc_index, args = work_queue.get() if calc_index != current_calc_index: current_calc_index = calc_index processed_count = 0 processed_count += 1 logger.info("work thread {} processing {}th work".format( current_process, processed_count)) work_func(*args) except BrokenPipeError as error: # 这个一般是程序退出的时候发生的,这种情况直接退出 logger.warning("work thread={} BrokenPipeError quit job".format( current_process)) continue_wrok = False except Exception as error: args_list = [] if 'args' in locals(): args_list = [arg.__dict__ for arg in args] report_bugsnag_in_worker(current_process, error, processed_count, args_list) finally: work_queue.task_done() logger.info("work thread ={} stopped, processed_count={}".format( current_process, processed_count))
def add_bonus_attributes_to_base_array(job_type, base_array, style, creature, save_name, equip_fixup, equip_index_to_realname, huanzhuang_slot_fixup): cfg = config() original_base_array = base_array.copy() guofu_teses = [ { "name": "称号", "setting_name": "styles", "selected": style }, { "name": "宠物", "setting_name": "creatures", "selected": creature }, { "name": "其余特色", "setting_name": "account_other_bonus_attributes", "selected": "所有账号共用" }, { "name": "其余特色", "setting_name": "account_other_bonus_attributes", "selected": save_name }, ] for tese in guofu_teses: # 处理每一种特色 setting = get_setting(tese["setting_name"], tese["selected"]) if setting is None or setting["entries"] is None: continue # 增加当前选择的特色的各个词条对应的该类型职业的属性 logger.info("应用国服特色:{}({})".format(tese["selected"], tese["name"])) for entry in setting["entries"]: for name, value in entry.items(): entry_indexes = entry_name_to_indexes[name] entry_value = eval(str(value)) entry_writen = False if job_type == "deal": # 处理输出职业的对应属性 if "deal" not in entry_indexes: continue for entry_index in entry_indexes["deal"]: if entry_index in deal_multiply_entry_indexes: # 需要乘算 base_array[entry_index] = multiply_entry( base_array[entry_index], entry_value) elif entry_index in deal_use_max_entry_indexes: # 输出词条取最高值的词条,如黄字和爆伤,最终效果为所有该词条中最大的那个值 base_array[entry_index] = use_max_entry( base_array[entry_index], entry_value) else: # 其余加算 if name == "extra_all_job_all_active_skill_lv_1_30" and entry_index == index_deal_extra_active_skill_lv_1_45: # 由于词条[所有职业Lv1~30全部主动技能Lv+X(特性技能除外)]不能直接对应输出职业的1-45主动技能,需要打个折,可以自行配置折扣率 base_array[ entry_index] += entry_value * cfg.data_fixup.extra_all_job_all_active_skill_lv_1_30_deal_1_45_rate else: # 正常情况 base_array[entry_index] += entry_value if not entry_writen: logger.info("\t词条:{} {}".format( entry_name_to_name[name], entry_value)) entry_writen = True logger.info("\t\t{} => {}".format( deal_entry_index_to_name[entry_index], entry_value)) else: # 处理奶系职业的对应属性 if "buf" not in entry_indexes: continue for entry_index in entry_indexes["buf"]: if entry_index in buf_multiply_entry_indexes: # 需要乘算 base_array[entry_index] = multiply_entry( base_array[entry_index], entry_value) else: # 全部加算 base_array[entry_index] += entry_value if not entry_writen: logger.info("\t词条:{} => {}".format( entry_name_to_name[name], entry_value)) entry_writen = True logger.info("\t\t{} => {}".format( buf_entry_index_to_name[entry_index], entry_value)) diff_base_array = base_array - original_base_array logger.info("最终特色加成属性如下:\n{}".format( format_base_array(job_type, diff_base_array))) save_setting = get_setting("account_other_bonus_attributes", save_name) if save_setting is not None: # 读取当前存档的装备补正信息 logger.info("尝试查找装备补正信息") fixup_cfg = { "deal": ("deal_equip_fixup", deal_entry_index_to_name), "buf": ("buf_equip_fixup", buf_entry_index_to_name) }[job_type] if fixup_cfg[0] in save_setting: for equip_index, entries in save_setting[fixup_cfg[0]].items(): equip_index = str(equip_index) for entry in entries: for name, value in entry.items(): entry_index = eval(name) entry_value = eval(str(value)) if entry_value < 0 and not cfg.misc.use_negative_equip_fixup_setting: # 如果这个补正数据是用来矫正与满属性装备的差距的,且设置了不使用负数的修正数据,则跳过 continue if equip_index not in equip_fixup: equip_fixup[equip_index] = np.array( [0.0 for idx in range(len(fixup_cfg[1]))]) equip_fixup[equip_index][entry_index] += entry_value logger.info("最终装备补正数据为:\n") for equip_index, ba in equip_fixup.items(): logger.info("{}-{}\n{}".format( equip_index, equip_index_to_realname[equip_index], format_base_array(job_type, ba))) if cfg.misc.use_huanzhuang_slot_fixup: # 读取当前存档的buff换装槽位补正信息 logger.info("尝试查找buff换装槽位补正信息") if "huanzhuang_slot_fixup" in save_setting: for slot_index, entries in save_setting[ "huanzhuang_slot_fixup"].items(): slot_index = str(slot_index) for entry in entries: for name, value in entry.items(): entry_index = eval(name) entry_value = eval(str(value)) if slot_index not in huanzhuang_slot_fixup: huanzhuang_slot_fixup[slot_index] = np.array([ 0.0 for idx in range( len(buf_entry_index_to_name)) ]) huanzhuang_slot_fixup[slot_index][ entry_index] += entry_value logger.info("最终换装槽位补正数据为:\n") for slot_index, ba in huanzhuang_slot_fixup.items(): logger.info("{}-{}\n{}".format( slot_index, slot_index_to_realname[slot_index], format_base_array(job_type, ba)))
logging.info("log level change to %s", self.log_level) logging.info("max thread is set to %d", self.multi_threading.max_thread) logger.debug("raw_config={}".format(raw_config)) logger.debug("config={}".format(g_config)) g_config = Config() # 读取程序config def load_config(config_path="config.toml"): global g_config try: raw_config = toml.load(config_path) g_config.auto_update_config(raw_config) except UnicodeDecodeError as error: notify_error(logger, "{}的编码格式有问题,应为utf-8,如果使用系统自带记事本的话,请下载vscode或notepad++等文本编辑器\n错误信息:{}\n".format(config_path, error)) sys.exit(0) except Exception as error: notify_error(logger, "读取{}文件出错,是否直接在压缩包中打开了?\n具体出错为:".format(config_path, error)) sys.exit(-1) def config(): return g_config if __name__ == '__main__': load_config("../config.toml") logger.info("ui cfg={}".format(g_config.ui))
def add_bonus_attributes_to_base_array(job_type, base_array, style, creature, save_name): cfg = config() original_base_array = base_array.copy() guofu_teses = [ { "name": "称号", "setting_name": "styles", "selected": style }, { "name": "宠物", "setting_name": "creatures", "selected": creature }, { "name": "其余特色", "setting_name": "account_other_bonus_attributes", "selected": "所有账号共用" }, { "name": "其余特色", "setting_name": "account_other_bonus_attributes", "selected": save_name }, ] for tese in guofu_teses: # 处理每一种特色 setting = get_setting(tese["setting_name"], tese["selected"]) if setting is None or setting["entries"] is None: continue # 增加当前选择的特色的各个词条对应的该类型职业的属性 logger.info("应用国服特色:{}({})".format(tese["selected"], tese["name"])) for entry in setting["entries"]: for name, value in entry.items(): entry_indexes = entry_name_to_indexes[name] entry_value = eval(str(value)) entry_writen = False if job_type == "deal": # 处理输出职业的对应属性 if "deal" not in entry_indexes: continue for entry_index in entry_indexes["deal"]: if entry_index in deal_multiply_entry_indexes: # 需要乘算 base_array[entry_index] = multiply_entry( base_array[entry_index], entry_value) else: # 其余加算 if name == "extra_all_job_all_active_skill_lv_1_30" and entry_index == index_deal_extra_active_skill_lv_1_45: # 由于词条[所有职业Lv1~30全部主动技能Lv+X(特性技能除外)]不能直接对应输出职业的1-45主动技能,需要打个折,可以自行配置折扣率 base_array[ entry_index] += entry_value * cfg.data_fixup.extra_all_job_all_active_skill_lv_1_30_deal_1_45_rate else: # 正常情况 base_array[entry_index] += entry_value if not entry_writen: logger.info("\t词条:{} {}".format( entry_name_to_name[name], entry_value)) entry_writen = True logger.info("\t\t{} => {}".format( deal_entry_index_to_name[entry_index], entry_value)) else: # 处理奶系职业的对应属性 if "buf" not in entry_indexes: continue for entry_index in entry_indexes["buf"]: if entry_index in buf_multiply_entry_indexes: # 需要乘算 base_array[entry_index] = multiply_entry( base_array[entry_index], entry_value) else: # 全部加算 base_array[entry_index] += entry_value if not entry_writen: logger.info("\t词条:{} => {}".format( entry_name_to_name[name], entry_value)) entry_writen = True logger.info("\t\t{} => {}".format( buf_entry_index_to_name[entry_index], entry_value)) all_tese_strs = [] diff_base_array = base_array - original_base_array index_info = job_to_base_array_index_range_and_index_to_name_dict[job_type] index_to_name_dict = index_info["index_to_name_dict"] for index in range(index_info["index_begin"], index_info["index_end"] + 1): name = index_to_name_dict[index] if diff_base_array[index] == 0: # 跳过没有实际加成的特色词条 continue all_tese_strs.append("{} => {}".format(name, diff_base_array[index])) logger.info("最终特色加成属性如下:\n{}".format("\n".join(all_tese_strs)))
ui.update_message))) if ask_update == 'yes': if not is_shared_content_blocked(ui.netdisk_link): webbrowser.open(ui.netdisk_link) tkinter.messagebox.showinfo( "蓝奏云网盘提取码", "蓝奏云网盘提取码为: {}".format(ui.netdisk_passcode)) else: # 如果分享的网盘链接被系统屏蔽了,写日志并弹窗提示 logger.warning("网盘链接={}又被系统干掉了=-=".format(ui.netdisk_link)) webbrowser.open( "https://github.com/fzls/dnf_calc/releases") tkinter.messagebox.showerror( "不好啦", ("分享的网盘地址好像又被系统给抽掉了呢=。=先暂时使用github的release页面下载吧0-0\n" "请稍作等待~ 风之凌殇看到这个报错后会尽快更新网盘链接的呢\n" "届时再启动程序将自动获取到最新的网盘地址呢~")) else: tkinter.messagebox.showinfo( "取消启动时自动检查更新方法", "如果想停留在当前版本,不想每次启动都弹出前面这个提醒更新的框框,可以前往config.toml,将check_update_on_start的值设为false即可" ) else: logger.info("当前版本{}已是最新版本,无需更新".format(now_version)) except Exception as err: logger.error("更新版本失败, 错误为{}".format(err)) if __name__ == '__main__': logger.info(get_update_info().update_message)