diff --git a/repo/js/AutoHoeingOneDragon/README.md b/repo/js/AutoHoeingOneDragon/README.md index 1857ad3d2..367ba8aa0 100644 --- a/repo/js/AutoHoeingOneDragon/README.md +++ b/repo/js/AutoHoeingOneDragon/README.md @@ -38,8 +38,8 @@ - 切换队伍:❌ 关闭(配队请在脚本自定义配置中填写) - 战斗策略:推荐使用万能战斗策略(萌新推荐),不在万能战斗策略(萌新推荐)中的角色不建议使用。不推荐使用脚本仓库中任何除了万能战斗策略(萌新推荐)之外的策略,即使没有注明副本后缀或声称适合用于锄地,也不适用于锄地,使用劣质策略后果自负。(判断劣质策略的一些简单方法:出现任何直接的q或burst,较长的attack如attack(8),多次出现keypress(q)后没有使用attack或ready等待动画结束) - 扫描掉落物光柱:❌ 强烈建议关闭(易卡死、拖慢速度) - - 自动检测战斗结束:✅ 开启,延时设为 0.35-0.5秒(默认1.5秒太慢) - - 战斗结束后执行万叶长E:✅ 强烈建议开启(大幅提升狗粮收益),如果你觉得慢,你打280精英然后把捡的狗粮摧毁,和打400不捡狗粮的收益时一样的,如果没有万叶,只有琴,带上琴,效率略低,不过有奶也不错 + - 自动检测战斗结束:✅ 开启,延时设为 0.35-0.5秒(默认1.5秒太慢),如果和万能战斗策略中的配置要求冲突,优先参考万能战斗策略的 + - 战斗结束后执行万叶长E:✅ 强烈建议开启(大幅提升狗粮收益),如果你觉得慢,你打280精英然后把捡的狗粮摧毁,和打400不捡狗粮的收益是一样的,如果没有万叶,只有琴,带上琴,效率略低,不过有奶也不错 - 战斗超时:小怪路线建议 ≤60秒;练度低可适当延长 - 不在某时执行:⚠️ 无法中断运行,不要依赖此功能定时停止 - 生存位、行走位:生存位不建议开启,行走位建议根据设备情况决定,设备情况良好可以使用高移速体型或角色提速,如成男或哥伦比娅,设备较差经常跑过头等时,建议使用移速较慢的体型或角色,与此同时,选择生命上限较高的角色和韧性较高的角色(如大剑>单手剑)有助于提高稳定性。 @@ -54,22 +54,25 @@ - - 拾取模式:✅ 推荐“模板匹配拾取”根据需求选择拾取狗粮或狗粮与怪物材料;❌ 不推荐“BGI原版拾取”或“不拾取” - - 泥头车模式:可提前开E的角色:芙宁娜、爱可菲(泥头车模式请填写角色在队伍里对应序号);❌ 不建议盾角或纳塔角色 - - 🔹 第二部分:路线选择(仅路径组1生效!) + - 🔹 第二部分:路线选择与分组配置(仅路径组1生效!) - - 请正确设置路径组1的设置,其他组会自动读取组1的配置,无需重复设置❗❗❗ + - - 路径组一填写的第二部分是总目标,不是单组的目标 - - 账户名称:多账号用户填写不同名字以区分记录 - - 标签系统(中文逗号分隔): - - 蕈兽:建议排除(雷火会转化,白占名额) - - - 小怪:适合练级队,且无需携带万叶拾取狗粮 - - - 水免 / 次数盾 / 高危 / 传奇:根据队伍强度选择是否排除 - - - ⚠️路径一排除关键词仅针对路径一(如需在所有路径排除,请下滑到“排除关键词”并正确填写) + - - 小怪:适合练级队,且无需携带万叶拾取狗粮,含义为路线是纯小怪,没有精英怪 + - - 水免 / 次数盾 / 高危 / 传奇:根据队伍强度和特性选择是否排除或分组,水免建议分组运行,否则剩余路线可能不足或质量差 + - - 强水免:包括水法师,水使徒,浊水等较强力的水免怪,不包括普通水免怪如水史莱姆 + - - 分组逻辑:选好路线后,将路线按照后续组标签分配到其他组去,剩下的留在路径组1 - - 目标数量:精英怪建议 400,小怪建议 2000(均为游戏上限),或小怪填写1650-1700,剩余数量由好感盗宝团补足 - - 优先关键词:如 辖域守护者 用于优先选择对应怪物以获取材料 - - ⚠️优先关键词≠锄地顺序优先,优先关键词代表无视该路线效率,优先加入。 - - 排除关键词:如 纳塔,雪山(跳过不想打的区域,亦可输入不想打的怪物名称) + - - 忽略比例:默认-1,表示不启用,填写大于等于0的数值后启用,作用是将小怪数量/精英数量大于该比例的路线视为纯小怪路线,用于提高小怪组的小怪数量,提高经验收益 - - - ❗❗❗关于路径组,可使用一个路径组打完所有怪物,亦可拆分,以不同队伍应对不同的怪物❗❗❗ + - - ❗❗❗关于路径组,不设标签可使用一个路径组打完所有怪物,亦可拆分,以不同队伍应对不同的怪物❗❗❗ - - ⚠️请注意:若拆分多个路径组,后续路径组均从前面路径组筛选后的路径中筛选 - **极简版,以怪物材料为目的**: - - 执行模式:选 “仅指定怪物模式” @@ -77,13 +80,10 @@ - - 自定义配置拉到最下面,按要求填写需要的怪物名称 4. 分组运行示例: - - 路径组1排除标签:传奇,小怪,蕈兽(路径组一由于排除传奇与小怪,会执行精英路线,但精英路线会包含一部分小怪且无法避免) - - 路径组2选择标签:旧日之海龙蜥(由于该怪物带有传奇标签,被路径一排除,所以路径二可以选择) - - 路径组3选择标签:传奇(由于路径二已经筛选了旧日之海龙蜥,所以路径三将执行除该传奇以外的传奇路径) - - 路径组4选择标签:小怪(执行剩余小怪路线) + - 路径组1为剩余的路线 - ❗❗❗所有关键词均根据自己需求填写,然后执行一次路径调试输出,查看锄地一条龙目录下“调试结果”验证是否正确。 - ❗❗❗若拆分路径需要添加对应的配置组,在每个配置组中的JS中选择需要运行的路径组。(在一条龙中执行顺序无要求,确保运行过路径一配置组生成正确的配置文件) @@ -145,56 +145,25 @@ - - ❌ 叶洛亚/班尼特等刚需q启动,不开q就完全没有作用的角色,打不到怪的时候没球给你充能,怪在10m外看着你跳舞 - 脚本内置配队校验,若无法通过校验可启用跳过验证,视为同意免责声明,若跳过警告后出现任何问题,责任自负。 -### 五、具体自定义配置说明 -1. 第一部分:路径组运行配置:该部分将决定当前路径组的具体执行参数 - - **执行模式:** - - - 默认选择 **运行锄地路线** ,选择该模式会按照后续设置选择并运行相应路线 - - - 选项 **输出地图追踪文件** ,会将选择的路线读取并分组输出到js文件夹下pathingOut文件夹 - - - 选项 **强制刷新所有运行记录** ,用于清除js记录的运行历史 - - - 选项 **启用仅指定怪物模式** ,适用于轻度、以特定怪物材料为目的的用户,选择该模式后拉到自定义配置最下面,按要求填写需要的怪物 - - **选择执行第几个路径组:** 本js支持分组运行地图追踪,分组方式详见后续选项,需要分组运行时请确保精英目标数量,小怪目标数量,各个路径组的标签等信息【完全相同】,复制配置组时未知原因无法正确复制配置,请不要使用 - - 如果你需要分组执行,请先建立和组数对应的配置组,分别添加本js,路径组一要【排除】的标签填写需要完全禁用的标签,如蕈兽,路径组二要【选择】的标签填写需要分配到路径组二的路线的标签,如小怪,不同配置组的js中选择对应的配队和路径组编号,其他配置保持默认的情况下即可实现精英和小怪分队伍和配置组锄地,更多路径组数量以此类推,需要分组运行的可以参考b站官号视频https://www.bilibili.com/video/BV1JYGVzHEmD/?spm_id_from=333.1387.collection.video_card.click - - **本路径组使用配队名称:** 填写该路径组使用的配队名称,js会自动切换 - - **拾取模式:** 需要注意,沙暴路线只在模板匹配模式下可用 - - - 模板匹配拾取:推荐使用,速度最快,性能消耗最低 - - - bgi原版拾取:使用bgi自带的拾取 - - - 不拾取:不拾取任何物品 - - **拾取时间参数:** 建议先不要调节,观察到拾取异常时再根据自定义配置提示调节 - - **组内路线排序模式:** 效率降序用于当你的时间不足以刷完所有路线时使用,先刷效率最高的(可配合优先关键词使用,这些路线效率会被视为最高);高收益优先选项将会将包含高倍率怪物(如600,400,81,飞萤)的路线排在前面,避免因为超出上限损失这部分收益 - - **输入不运行的时间或时间段的小时数** 当你需要让js在特定的时间终止运行时,按描述填写,js会在距离目标时间小于五分钟时终止运行并等待到目标时间 - - **泥头车模式:** 接近战斗地点(距离5-30)时,提前让指定序号的角色开e,建议以下角色开启:芙宁娜,爱可菲,雷电将军,不建议各种盾位或纳塔角色开启。 -2. 第二部分:路线选择与分组配置:该部分将决定路线的选择与分配,js将尝试按照以下配置选择路线以达到目标数量的怪物,并分配到不同的路径组,在第一部分中选择不同的路径组以执行对应的路线 - - **账户名称:** 本js支持多用户,不同账户的记录分开存储,当你需要使用多用户时,请在这里填写不同的文本来区分不同账号的记录,如果你只使用一个账号,请不要修改该选项 - - **路径组x标签:** 本js使用不同的标签来禁用或分组路线,多个标签之间使用中文逗号分隔,目前支持的标签如下 - - - 水免:表明路线含有水元素伤害免疫的怪物,使用以水元素伤害为主的队伍处理该路线时可能较为麻烦 - - - 次数盾:表明路线含有带有次数盾的怪物,常规配队破盾效率较慢 - - - 高危 :表明路线战斗强度较高,可能存在生存压力 - - - 传奇 :表明路线含有地方传奇,战斗强度通常极高,请评估后选择是否排除 - - - 蕈兽 :表明路线含有蕈兽,蕈兽遇到雷火元素时会发生转化,转化后占据精英怪物的名额却只掉落少量摩拉,通常建议禁用 - - - 小怪 :表明路线只含小怪,战斗强度低,且无需携带万叶来拾取可能掉落的狗粮,可以适当携带等级较低或不上场的角色来获取经验收益 - - - 狂猎 :标明路线含义挪德卡莱的狂猎类型怪物,需要先破血条后打灰条,难度较高 - - - 狭窄地形 :表明路线存在部分非常狭窄,任何位移技能都容易导致致命后果 - - - 环境伤害 :路线处于雪山或挪德卡莱苦壑崖区域,环境伤害会持续扣血 - - - 分组逻辑:不含路径组1排除标签和任何其他组标签的路径会进入路径组1,剩余路径若含有路径组x的标签之一,则会进入路径组x - - - 新增支持自定义标签,将会尝试将未知的标签通过文件路径,description匹配,含有对应关键词的路线即视为含有这些标签 - - **摩拉/耗时权衡因数:** 影响js评估路线价值,含义为为了多赚1摩拉愿意多花多少秒 - - **自动优化:** js将根据运行记录调整每条路线的预期运行时间,具体逻辑为,至多7条记录,去除一个最大值、一个最小值后,每条记录占据20%的权重,剩余权重由默认数据填充。如果你不想要这个功能,请禁用。 - - **目标数量:** 选取路线目标达到的精英怪数量,默认为400,同理小怪数量默认为2000 - - **优先关键词:** 含有关键词的路线会被视为拥有最高效率,例如填写600来让所有600怪物优先考虑,填写骗骗花来优先考虑骗骗花 - - **排除关键词:** 含有关键词的路线会被排除,例如填写纳塔来排除所有纳塔路线,同样使用中文逗号分隔 +### 五、其他配置项说明 -3. **只建议在原神中设置1080p、全低画质、60帧使用,其他分辨率或帧率出现任何问题都是正常现象** -4. **不建议使用12小时制时间(虽然也能跑)** -5. **注意,仅选择路径组一的配置组的第二部分路线与分组配置有效,选择路径组一的配置组的本js运行后将保存配置,供其他路径组读取,其他路径组的自定义配置中第二部分完全无效,该设定是为了确保各个配置组中同账户的路线分配完全相同** -6. **个性化编辑**:允许一定程度的个性化配置。 - - 只需要拾取狗粮和部分材料时,可以将所需材料的图片复制到assets/targetItems/其他/保留文件夹,这个文件夹内的图片不会被更新覆盖(至多三层子文件夹,且只保留png),此时选择只拾取狗粮模式就会同时拾取狗粮和你添加的材料,选择拾取狗粮和怪物材料则会拾取狗粮,你添加的材料,怪物材料 - - 虽然仓库中“敌人与魔物”路径普遍质量较差,本js仍然允许用户自行添加部分路径,建议放在pathing/0-保留文件夹下,这个文件夹下的地图追踪文件不会被更新覆盖(至多三层子文件夹,且只保留json),按照本js规则正确标注了预计用时与怪物信息的路线可以正常参与路线分配和怪物数量计算,没有进行标注的路径则默认60秒,0怪物数量,0收益,通常仅在启用仅指定怪物模式时使用。使用自行添加路线造成的卡死、被击败、怪物数量异常等问题,本js概不负责。**强烈建议只使用本js原装的,经过优选和验证的路线** +1. **执行模式说明:** + - **运行锄地路线**(默认):按配置选择路线并自动执行锄地,是最常用的模式 + - **调试路线分配**:将选择的路线按组输出到 `调试结果/group1...group10/`,并生成 `调试结果/路线分配结果.txt`,用于验证路线配置是否正确 + - **强制刷新所有运行记录**:清除 js 记录的所有运行历史数据,重置自动优化的时间记录 + - **启用仅指定怪物模式**:适用于轻度、以特定怪物材料为目的的用户,选择该模式后需在自定义配置最下方填写需要的怪物名称 - - **js内置校验,用于在配队等存在问题时进行警告,如果无视警告执意使用,或勾选跳过校验阶段,则任意问题均由用户配置导致** +2. **复制配置组注意事项:** + - 复制配置组时,**需要先关闭 BGI 重新打开**,复制才能正常生效,否则部分配置项会异常 + +3. **组内路线排序模式:** + - **原文件顺序**:按路线文件在 pathing 文件夹中的原始顺序执行,不进行效率排序 + - **效率降序**:当时间不足以刷完所有路线时使用,先刷效率最高的(可配合优先关键词使用,这些路线效率会被视为最高) + - **高收益优先**:将会将包含高倍率怪物(如 600,400,81,飞萤)的路线排在前面,避免因为超出上限损失这部分收益 ### 六、其他 - **想要测作者怎么办** :来q群1057307824测测莫酱(有其他问题也行) - - **茶包版小广告**:茶包版bgi具有许多公版bgi没有的功能(如火神赶路),能相当程度提高锄地效率与稳定性,想要测测茶包也可以加上面的群聊 + - **茶包版小广告**:茶包版bgi具有许多公版bgi没有的功能(如火神赶路,更丰富的战斗结束检测机制),能相当程度提高锄地效率与稳定性,想要测测茶包也可以加上面的群聊 - **莫酱全家桶(部分)**: - 日常使用 - 锄地一条龙:一站式解决自动化锄地,支持只拾取狗粮 diff --git a/repo/js/AutoHoeingOneDragon/main.js b/repo/js/AutoHoeingOneDragon/main.js index e9fe8d501..906229837 100644 --- a/repo/js/AutoHoeingOneDragon/main.js +++ b/repo/js/AutoHoeingOneDragon/main.js @@ -15,6 +15,8 @@ let operationMode; let efficiencyIndex; let targetEliteNum; let targetMonsterNum; +let curiosityFactor; +let ignoreRate; let partyName; let groupSettings; let groupTags; @@ -91,9 +93,9 @@ const gameRegionManager = { } else { //预处理路线并建立对象 - pathings = await processPathings(groupTags); + pathings = await processPathings(); //按照用户配置标记路线 - await markPathings(pathings, groupTags, priorityTags, excludeTags); + await markPathings(pathings, priorityTags, excludeTags); //找出最优组合 await findBestRouteGroups(pathings, efficiencyIndex, targetEliteNum, targetMonsterNum); //分配到不同路径组 @@ -141,24 +143,23 @@ async function loadConfig() { /* -------- 1. 构造 10 个分组标签 + 其它字段的默认值 -------- */ const buildCfgObj = () => ({ - tagsForGroup1: settings.tagsForGroup1 || '', - tagsForGroup2: settings.tagsForGroup2 || '', - tagsForGroup3: settings.tagsForGroup3 || '', - tagsForGroup4: settings.tagsForGroup4 || '', - tagsForGroup5: settings.tagsForGroup5 || '', - tagsForGroup6: settings.tagsForGroup6 || '', - tagsForGroup7: settings.tagsForGroup7 || '', - tagsForGroup8: settings.tagsForGroup8 || '', - tagsForGroup9: settings.tagsForGroup9 || '', - tagsForGroup10: settings.tagsForGroup10 || '', + tagsForGroup2: settings.tagsForGroup2 ?? '', + tagsForGroup3: settings.tagsForGroup3 ?? '', + tagsForGroup4: settings.tagsForGroup4 ?? '', + tagsForGroup5: settings.tagsForGroup5 ?? '', + tagsForGroup6: settings.tagsForGroup6 ?? '', + tagsForGroup7: settings.tagsForGroup7 ?? '', + tagsForGroup8: settings.tagsForGroup8 ?? '', + tagsForGroup9: settings.tagsForGroup9 ?? '', + tagsForGroup10: settings.tagsForGroup10 ?? '', disableSelfOptimization: settings.disableSelfOptimization ?? false, efficiencyIndex: settings.efficiencyIndex ?? 0.25, curiosityFactor: settings.curiosityFactor ?? '0', - ignoreRate: settings.ignoreRate ?? 0, + ignoreRate: settings.ignoreRate ?? -1, targetEliteNum: settings.targetEliteNum ?? 400, targetMonsterNum: settings.targetMonsterNum ?? 2000, priorityTags: settings.priorityTags ?? '', - excludeTags: settings.excludeTags ?? '' + excludeTags: settings.excludeTags ?? '蕈兽,传奇,狭窄地形' }); /* -------- 2. 关键词黑名单检查 -------- */ @@ -204,36 +205,35 @@ async function loadConfig() { ? settings.activeDumperMode.split(',').map(Number).filter(num => [1, 2, 3, 4].includes(num)) : []; - findFInterval = Math.max(16, Math.min(200, +settings.findFInterval || 100)); + findFInterval = Math.max(16, Math.min(200, parseNumericSetting(settings.findFInterval, 100))); checkDelay = Math.round(findFInterval / 2); - rollingDelay = (+settings.rollingDelay || 32); - pickupDelay = (+settings.pickupDelay || 100); - timeMove = (+settings.timeMove || 1000); + rollingDelay = parseNumericSetting(settings.rollingDelay, 32); + pickupDelay = parseNumericSetting(settings.pickupDelay, 100); + timeMove = parseNumericSetting(settings.timeMove, 1000); timeMoveUp = Math.round(timeMove * 0.45); timeMoveDown = Math.round(timeMove * 0.55); - priorityTags = (settings.priorityTags || "").split(",").map(tag => tag.trim()).filter(tag => tag.length > 0); - excludeTags = (settings.excludeTags || "").split(",").map(tag => tag.trim()).filter(tag => tag.length > 0); + priorityTags = (settings.priorityTags ?? '').split(",").map(tag => tag.trim()).filter(tag => tag.length > 0); + excludeTags = (settings.excludeTags ?? '').split(",").map(tag => tag.trim()).filter(tag => tag.length > 0); if (!pickup_Mode.includes("模板匹配")) { excludeTags.push("沙暴"); log.warn("拾取模式不是模板匹配,无法处理沙暴路线,自动排除所有沙暴路线"); } - efficiencyIndex = settings.efficiencyIndex === undefined ? 0.25 : - isNaN(Number(settings.efficiencyIndex)) || - String(Number(settings.efficiencyIndex)) !== String(settings.efficiencyIndex) ? 0.25 : - Number(settings.efficiencyIndex) < 0 ? 0 : - Number(settings.efficiencyIndex); + efficiencyIndex = parseNumericSetting(settings.efficiencyIndex, 0.25); - targetEliteNum = Math.max(0, +settings.targetEliteNum || 400) + 5; // 预留漏怪 - targetMonsterNum = Math.max(0, +(settings.targetMonsterNum ?? 2000)) + 25; // 预留漏怪 + let parsedElite = parseNumericSetting(settings.targetEliteNum, 400); + if (parsedElite === 0) parsedElite = 400; + targetEliteNum = Math.max(0, parsedElite) + 5; // 预留漏怪 + targetMonsterNum = Math.max(0, parseNumericSetting(settings.targetMonsterNum, 2000)) + 25; // 预留漏怪 + curiosityFactor = parseNumericSetting(settings.curiosityFactor, 0); + ignoreRate = parseNumericSetting(settings.ignoreRate, -1); - partyName = settings.partyName || ""; + partyName = settings.partyName ?? ""; groupSettings = Array.from({ length: 10 }, (_, i) => - settings[`tagsForGroup${i + 1}`] || (i === 0 ? '蕈兽' : '') + settings[`tagsForGroup${i + 1}`] ?? '' ); groupTags = groupSettings.map(str => str.split(',').filter(Boolean)); - groupTags[0] = [...new Set(groupTags.flat())]; } /** @@ -282,12 +282,12 @@ async function rotateWarnIfAccountEmpty() { * 1. 读取 assets/monsterInfo.json 建立怪物-收益映射表 * 2. 扫描 pathing/ 目录下所有 *.json 路线文件,反序列化 info.description * 提取「预计用时」与「怪物清单」并计算普通/精英怪数量及对应摩拉收益 - * 3. 根据 settings.ignoreRate 过滤高小怪占比路线;按 groupTags[0] 反查补 tag + * 3. 根据 settings.ignoreRate 过滤高小怪占比路线 * 4. 若开启自我优化且存在历史运行时长,则对「预计用时」做削峰填谷取均值 * 返回已附加 {t, m, e, mora_m, mora_e, tags, map_name, ...} 的完整路径对象数组 * 依赖全局:settings、accountName、file、initializeCdTime、readFolder */ -async function processPathings(groupTags) { +async function processPathings() { // 读取怪物信息 const monsterInfoContent = await file.readText("assets/monsterInfo.json"); monsterInfoObject = JSON.parse(monsterInfoContent); @@ -378,25 +378,22 @@ async function processPathings(groupTags) { } } - // ===== 根据 settings.ignoreRate 过滤 ===== - const ignoreRate = Number(settings.ignoreRate) || 100; - if (Number.isInteger(ignoreRate) && ignoreRate > 0) { + // ===== 根据 ignoreRate 过滤 ===== + if (ignoreRate >= 0) { const protectTags = ['精英高收益', '高危', '传奇']; const hasProtectTag = protectTags.some(tag => pathing.tags.includes(tag)); if (!hasProtectTag && pathing.e > 0) { // ① 先保证有精英 const ratio = pathing.m / pathing.e; // ② 再计算比例(e 已 > 0) - if (ratio >= ignoreRate) { // ③ 比例达标才清零 + if (ratio > ignoreRate) { // ③ 比例达标才清零 pathing.e = 0; pathing.mora_e = 0; } } } - const allTags = groupTags[0]; // 已经是 [...new Set(...)] 的结果 - // 2. 待匹配文本:路径名 + 描述 + const allTags = [...new Set(groupTags.slice(1).flat())]; const textToMatch = (pathing.fullPath + " " + (description || "")); - // 3. 反查补 tag allTags.forEach(tag => { if (textToMatch.includes(tag)) { pathing.tags.push(tag); @@ -411,12 +408,10 @@ async function processPathings(groupTags) { for (const pathing of pathings) { if (!settings.disableSelfOptimization && pathing.records) { - // 1. 安全解析 + 边界校验 - let cf = 0; // 默认 - if (settings?.curiosityFactor != null) { - const parsed = parseFloat(String(settings.curiosityFactor)); - if (!Number.isNaN(parsed) && parsed >= 0 && parsed <= 1) cf = parsed; - } + // 好奇系数:全局已解析,此处仅做 0-1 边界限制 + let cf = curiosityFactor; + if (cf < 0) cf = 0; + if (cf > 1) cf = 1; // 2. 构造 7 条内部样本 const raw = Array.isArray(pathing.records) ? pathing.records.filter(v => v > 0) : []; @@ -444,43 +439,32 @@ async function processPathings(groupTags) { /** * 路线打标与过滤 - * 1. 将「仅第 0 组独有」的标签视为互斥标签:路线一旦包含则直接置 unavailable - * 2. 若路线文件名、已有标签或所含怪物名命中 excludeTags,同样置 unavailable - * 3. 命中 priorityTags 的路线打上 prioritized 标记,后续选路时会被优先保留 - * 4. 最终给每条路线新增: + * 1. 若路线文件名、已有标签或所含怪物名命中 excludeTags,置 unavailable + * 2. 命中 priorityTags 的路线打上 prioritized 标记,后续选路时会被优先保留 + * 3. 最终给每条路线新增: * available(bool)- 是否可参与后续选路 * prioritized(bool)- 是否优先保留 - * 依赖:pathings、groupTags、priorityTags、excludeTags + * 依赖:pathings、priorityTags、excludeTags */ -async function markPathings(pathings, groupTags, priorityTags, excludeTags) { - // 取出第 0 组并剔除与其他 9 组重复的标签 - const uniqueTags = groupTags[0].filter(tag => - !groupTags.slice(1).some(arr => arr.includes(tag)) - ); - +async function markPathings(pathings, priorityTags, excludeTags) { pathings.forEach(pathing => { pathing.tags = pathing.tags || []; pathing.monsterInfo = pathing.monsterInfo || {}; pathing.prioritized = false; - const containsUniqueTag = uniqueTags.some(uniqueTag => pathing.tags.includes(uniqueTag)); - - const containsExcludeTag = excludeTags.some(excludeTag => { + pathing.available = !excludeTags.some(excludeTag => { const fullPathContainsExcludeTag = pathing.fullPath && pathing.fullPath.includes(excludeTag); const tagsContainExcludeTag = pathing.tags.some(tag => tag.includes(excludeTag)); const monsterInfoContainsExcludeTag = Object.keys(pathing.monsterInfo).some(monsterName => monsterName.includes(excludeTag)); return fullPathContainsExcludeTag || tagsContainExcludeTag || monsterInfoContainsExcludeTag; }); - const containsPriorityTag = priorityTags.some(priorityTag => { + pathing.prioritized = priorityTags.some(priorityTag => { const fullPathContainsPriorityTag = pathing.fullPath && pathing.fullPath.includes(priorityTag); const tagsContainPriorityTag = pathing.tags.some(tag => tag.includes(priorityTag)); const monsterInfoContainsPriorityTag = Object.keys(pathing.monsterInfo).some(monsterName => monsterName.includes(priorityTag)); return fullPathContainsPriorityTag || tagsContainPriorityTag || monsterInfoContainsPriorityTag; }); - - pathing.available = !(containsUniqueTag || containsExcludeTag); - pathing.prioritized = containsPriorityTag; }); } @@ -654,7 +638,7 @@ async function findBestRouteGroups(pathings, efficiencyIndex, targetEliteNum, ta log.info("路线组合结果如下:"); log.info(`总精英怪数量: ${totalSelectedElites.toFixed(0)}`); log.info(`总普通怪数量: ${totalSelectedMonsters.toFixed(0)}`); - log.info(`总收益: ${totalGainCombined.toFixed(0)} 摩拉`); + log.info(`总收益: ${(totalGainCombined - 5 * 200 - 25 * 40.5).toFixed(0)} 摩拉`); const h = Math.floor(totalTimeCombined / 3600); const m = Math.floor((totalTimeCombined % 3600) / 60); const s = totalTimeCombined % 60; @@ -668,27 +652,20 @@ async function findBestRouteGroups(pathings, efficiencyIndex, targetEliteNum, ta /** * 把已选路线分配到 10 个用户分组 * 规则: - * 1. 只处理 selected 的路线,其余保持 group=0 - * 2. 若路线不含第 0 组任何标签 → 直接分到组 1 - * 3. 否则按 groupTags[1]...groupTags[9] 顺序匹配,命中即分到对应组(2-10) + * 1. 只处理 selected 的路线 + * 2. 按 groupTags[1]~groupTags[9] 顺序匹配,命中即分到对应组(2-10) + * 3. 若均不匹配则默认分到组 1(groupTags[0] 作为兜底) * 结果:pathing.group = 1..10,后续按组批量执行 * 依赖:pathings(已有 selected & tags)、groupTags */ async function assignGroups(pathings, groupTags) { - // 遍历 pathings 数组 pathings.forEach(pathing => { if (pathing.selected) { - pathing.group = 0; - - if (!groupTags[0].some(tag => pathing.tags.includes(tag))) { - pathing.group = 1; - } else { - // 依次判断 groupTags[1] ~ groupTags[9] - for (let i = 1; i <= 9; i++) { - if (groupTags[i].some(tag => pathing.tags.includes(tag))) { - pathing.group = i + 1; - break; - } + pathing.group = 1; + for (let i = 1; i <= 9; i++) { + if (groupTags[i].some(tag => pathing.tags.includes(tag))) { + pathing.group = i + 1; + break; } } } @@ -713,7 +690,7 @@ async function filterPathingsByTargetMonsters() { } // 2. 拆分成数组 - const targetMonsters = (settings.targetMonsters || "") + const targetMonsters = (settings.targetMonsters ?? "") .split(",") // 中文逗号 .map(s => s.trim()) .filter(Boolean); @@ -725,7 +702,7 @@ async function filterPathingsByTargetMonsters() { const fakeGroupTags = Array.from({ length: 10 }, () => []); // 5. 预处理拿到完整路线 - pathings = await processPathings(fakeGroupTags); + pathings = await processPathings(); // 6. 逐路线匹配 description 与文件名 for (const p of pathings) { @@ -903,17 +880,17 @@ async function printGroupSummary() { resultText += ` 总精英怪: ${totalElites.toFixed(0)}\n`; resultText += ` 被忽视精英怪数: ${totalIgnoredElites.toFixed(0)}\n`; resultText += ` 总小怪数: ${totalMonsters.toFixed(0)}\n`; - resultText += ` 总收益 : ${totalGain.toFixed(0)} 摩拉\n`; + resultText += ` 总收益 : ${(totalGain - 5 * 200 - 25 * 40.5).toFixed(0)} 摩拉\n`; resultText += ` 总用时 : ${totalH} 时 ${totalM} 分 ${totalS.toFixed(0)} 秒\n`; resultText += "=".repeat(50) + "\n\n"; // 其他配置信息 resultText += "配置参数:\n"; - resultText += ` 摩拉/耗时权衡因数: ${settings.efficiencyIndex || 0.25}\n`; - resultText += ` 好奇系数: ${settings.curiosityFactor || 0}\n`; - resultText += ` 忽略比例: ${settings.ignoreRate || 0}\n`; - resultText += ` 目标精英数: ${settings.targetEliteNum || 400}\n`; - resultText += ` 目标小怪数: ${settings.targetMonsterNum ?? 2000}\n`; + resultText += ` 摩拉/耗时权衡因数: ${settings.efficiencyIndex ?? ''}\n`; + resultText += ` 好奇系数: ${settings.curiosityFactor ?? ''}\n`; + resultText += ` 忽略比例: ${settings.ignoreRate ?? ''}\n`; + resultText += ` 目标精英数: ${settings.targetEliteNum ?? ''}\n`; + resultText += ` 目标小怪数: ${settings.targetMonsterNum ?? ''}\n`; resultText += ` 优先级标签: ${settings.priorityTags || ''}\n`; resultText += ` 排除标签: ${settings.excludeTags || ''}\n\n`; @@ -2026,7 +2003,22 @@ async function loadBlacklist(merge = false) { * 供以上各模块随时调用 * =========================================================== */ -//切换队伍 +/** + * 解析数值类型配置项 + * @param {*} value 配置原始值(可能是字符串、数字、undefined、null) + * @param {number} defaultVal 默认值 + * @returns {number} 若 value 为空字符串/undefined/null 或转换后为 NaN,返回 defaultVal;否则返回转换后的数值(包含 0) + */ +function parseNumericSetting(value, defaultVal) { + if (value === undefined || value === null || value === '') return defaultVal; + const n = Number(value); + return isNaN(n) ? defaultVal : n; +} + +/** + * 切换队伍 + * @param {string} partyName 目标队伍名称,若为空则仅返回主界面 + */ async function switchPartyIfNeeded(partyName) { if (!partyName) { await genshin.returnMainUi(); @@ -2048,7 +2040,8 @@ async function switchPartyIfNeeded(partyName) { /** * 判断当前是否位于主界面 - * @param {number} maxDuration 最大允许耗时(毫秒) + * @param {number} [maxDuration=10] 最大允许耗时(毫秒) + * @returns {Promise} 是否在主界面 */ async function isMainUI(maxDuration = 10) { const start = Date.now(); @@ -2068,7 +2061,8 @@ async function isMainUI(maxDuration = 10) { /** * 判断当前是否存在拾取滚轮图标 - * @param {number} maxDuration 最大允许耗时(毫秒) + * @param {number} [maxDuration=10] 最大允许耗时(毫秒) + * @returns {Promise} 是否存在滚轮图标 */ async function hasScroll(maxDuration = 10) { const start = Date.now(); @@ -2086,7 +2080,11 @@ async function hasScroll(maxDuration = 10) { return false; } -// 加载拾取物图片 +/** + * 加载拾取物模板图片 + * 根据 pickup_Mode 决定加载的目录:完整狗粮+材料 / 仅狗粮 / 其他 + * @returns {Promise} 模板对象数组,加载失败返回 null + */ async function loadTargetItems() { let targetItemPath; if (pickup_Mode === "模板匹配拾取,拾取狗粮和怪物材料") { @@ -2126,6 +2124,11 @@ async function loadTargetItems() { return items; } +/** + * OCR 识别指定区域文本 + * @param {number} centerYF 屏幕 Y 中心坐标(像素) + * @returns {Promise} 识别到的最长文本,失败返回 null + */ async function performOcr(centerYF) { const TEXT_X = 1210, TEXT_W = 250; // 1210 ~ 1460 const TEXT_Y = centerYF - 30, TEXT_H = 60; diff --git a/repo/js/AutoHoeingOneDragon/manifest.json b/repo/js/AutoHoeingOneDragon/manifest.json index 8cbb31195..afba56a72 100644 --- a/repo/js/AutoHoeingOneDragon/manifest.json +++ b/repo/js/AutoHoeingOneDragon/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 1, "name": "锄地一条龙", - "version": "2.8.2", + "version": "2.9.0", "description": "一站式解决自动化锄地,支持只拾取狗粮,请仔细阅读README.md后使用", "authors": [ { diff --git a/repo/js/AutoHoeingOneDragon/settings.json b/repo/js/AutoHoeingOneDragon/settings.json index 3ee8aa367..829dc4c3e 100644 --- a/repo/js/AutoHoeingOneDragon/settings.json +++ b/repo/js/AutoHoeingOneDragon/settings.json @@ -146,16 +146,10 @@ "label": "#############################################\n\n第二部分:路线选择与分组配置\n仅对于选择路径组一的配置组生效,其他配置组将读取同账户名路径组一的配置组的相关配置,其他路径组的以下配置无效\n仅正常锄地模式生效,指定怪物模式下无效\n#############################################\n\n账户名称\n用于多用户运行时区分不同账户的记录\n单用户请勿修改", "default": "默认账户" }, - { - "name": "tagsForGroup1", - "type": "input-text", - "label": "允许使用的标签:\n水免,次数盾,高危,传奇,蕈兽,小怪,沙暴,狭窄地形,环境伤害\n允许使用自定义标签,文件路径或描述包含时将会视为路线含有该标签\n多个标签使用【中文逗号】分隔\n\n路径组一要【排除】的标签", - "default": "蕈兽,传奇,狭窄地形" - }, { "name": "tagsForGroup2", "type": "input-text", - "label": "路径组二要【选择】的标签" + "label": "分组标签,允许使用的标签:\n水免 次数盾 高危 传奇 蕈兽 小怪 沙暴 狭窄地形 环境伤害\n允许使用自定义标签,文件路径包含时将会视为路线含有该标签\n多个标签使用【中文逗号】分隔\n路径组一作为兜底组,不在后续任意组中出现的路线将分到组一\n\n路径组二要【选择】的标签" }, { "name": "tagsForGroup3", @@ -187,30 +181,31 @@ { "name": "ignoreRate", "type": "input-text", - "label": "小怪数量/精英数量大于该值的路线将被视为纯小怪路线,忽略其中包含的精英\n用于将含有极少量精英和大量小怪的路线当做小怪路线打\n【注意】修改该选项可能导致总用时大幅增加", - "default": "100" + "label": "忽略比例\n小怪数量/精英数量大于该值的路线将被视为纯小怪路线,忽略其中包含的精英\n用于将含有极少量精英和大量小怪的路线当做小怪路线打,提高纯小怪路线的小怪数量\n填写值大于等于0时启用\n【注意】修改该选项可能导致总用时大幅增加", + "default": "-1" }, { "name": "targetEliteNum", "type": "input-text", - "label": "目标精英数量", + "label": "总目标精英数量\n注意填写两组分别400-0和0-2000是错误写法", "default": "400" }, { "name": "targetMonsterNum", "type": "input-text", - "label": "目标小怪数量", + "label": "总目标小怪数量\n注意填写两组分别400-0和0-2000是错误写法", "default": "2000" }, { "name": "priorityTags", "type": "input-text", - "label": "优先关键词,含关键词的路线会被视为最高效率\n不同关键词使用【中文逗号】分隔\n例如填骗骗花,可以优先选择含有骗骗花的路线\n建议使用怪物图鉴中的名字\n仅优先选择,不影响路线排序" + "label": "优先关键词,含关键词的路线会被视为最高效率\n范围包括文件路径、包含怪物、标签\n不同关键词使用【中文逗号】分隔\n例如填骗骗花,可以优先选择含有骗骗花的路线\n建议使用怪物图鉴中的名字\n仅优先选择,不影响路线排序" }, { "name": "excludeTags", "type": "input-text", - "label": "排除关键词,含关键词的路线会被完全排除\n不同关键词使用【中文逗号】分隔\n例如填火斧丘丘暴徒,可以排除所有含有火斧丘丘暴徒的路线\n建议使用怪物图鉴中的名字" + "label": "排除关键词,含关键词的路线会被完全排除\n范围包括文件路径、包含怪物、标签\n不同关键词使用【中文逗号】分隔\n例如填火斧丘丘暴徒,可以排除所有含有火斧丘丘暴徒的路线\n建议使用怪物图鉴中的名字", + "default": "蕈兽,传奇,狭窄地形" }, { "name": "tagsForGroup5",