mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-19 03:59:51 +08:00
760 lines
32 KiB
JavaScript
760 lines
32 KiB
JavaScript
|
||
/************************ logout.js ************************/
|
||
|
||
let enableLog = settings.enableLog;
|
||
|
||
// 打印日志
|
||
function logout(text, filename) {
|
||
if (logout.text === void 0) logout.text = text;
|
||
else logout.text = logout.text + "\n" + text;
|
||
if (filename) {// 写文件仅支持格式 .txt, .json, .log, .csv, .xml, .html, .css
|
||
for (let i = 0; i < 100 && !file.writeTextSync(filename, logout.text); ++i);
|
||
if (logout.last != logout.text) {
|
||
if (enableLog) {
|
||
const info = logout.text.split("\n");
|
||
for (let i = 0; i < info.length; ++i) log.info(info[i]);
|
||
}
|
||
logout.last = logout.text;
|
||
}
|
||
logout.text = void 0;
|
||
}
|
||
}
|
||
|
||
/************************ relics.js ************************/
|
||
|
||
// 所有词条名称
|
||
const targetKeywords = [
|
||
"生命值", "攻击力", "防御力", "元素精通",// 0-3
|
||
"元素充能效率", "暴击率", "暴击伤害",// 4-6
|
||
"治疗加成", "物理伤害加成", "火元素伤害加成",// 7-9
|
||
"雷元素伤害加成", "水元素伤害加成", "草元素伤害加成",// 10-12
|
||
"风元素伤害加成", "岩元素伤害加成", "冰元素伤害加成" // 13-15
|
||
];
|
||
|
||
// 圣遗物部件名称
|
||
const targetPartNames = ["生之花", "死之羽", "时之沙", "空之杯", "理之冠"];
|
||
|
||
class Relics {
|
||
/* 同等级五星与四星圣遗物主属性之比 10/9, 同点数副属性之比 5/4 */
|
||
constructor() {
|
||
this.main = -1;// 主属性 index
|
||
this.level = -1;// 等级
|
||
this.quality = -1;// 星级
|
||
this.part = -1;// 部件 index
|
||
this.name = "";// 套装名称
|
||
this.subAttr = [];// 副属性 index
|
||
this.subValue = [];// 副属性值
|
||
this.subPoints = [];// 副属性点数
|
||
this.subCounts = [];// 副属性加强次数
|
||
}
|
||
set_main(str) {
|
||
for (let i = 0; i < targetKeywords.length; ++i) {
|
||
if (str.includes(targetKeywords[i])) {
|
||
this.main = i;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
set_level(lvl) {
|
||
this.level = lvl;
|
||
}
|
||
set_quality(qt) {
|
||
this.quality = qt;
|
||
}
|
||
set_part(str) {
|
||
for (let i = 0; i < targetPartNames.length; ++i) {
|
||
if (str.includes(targetPartNames[i])) {
|
||
this.part = i;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
set_name(str) {
|
||
this.name = str.replace(/[^\u4E00-\u9FA5]/g, '');
|
||
}
|
||
set_subAttr(...attrs) {
|
||
if (attrs.length > 4) throw new Error("副属性解析参数错误");
|
||
for (let i = 0; i < 4; ++i) {
|
||
if (!attrs[i]) {
|
||
this.subAttr[i] = -1;
|
||
this.subValue[i] = 0;
|
||
continue;
|
||
}
|
||
let id = 0;
|
||
for (; id < targetKeywords.length; ++id) {
|
||
if (attrs[i].includes(targetKeywords[id])) break;
|
||
}
|
||
if (id >= targetKeywords.length) {
|
||
this.subAttr[i] = -1;
|
||
this.subValue[i] = 0;
|
||
continue;
|
||
}
|
||
if (id < 3 && !attrs[i].includes("%")) id += targetKeywords.length;
|
||
this.subAttr[i] = id;
|
||
if (!attrs[i].includes("待激活")) {
|
||
const strval = attrs[i].match(/\d*\.?\d+/);
|
||
if (!strval) throw new Error("副属性数值解析错误");
|
||
this.subValue[i] = parseFloat(strval[0]);
|
||
}
|
||
else this.subValue[i] = 0;
|
||
}
|
||
}
|
||
get_mainAttr() {// 由于数值精度, 可能计算不准确
|
||
if (this.main == -1) return null;
|
||
let start, end;
|
||
if (this.part == 0) {// 生之花
|
||
start = (this.quality == 5 ? 717 : 645);
|
||
end = (this.quality == 5 ? 4780 : 4303);
|
||
return "生命值 " + Math.round(start + (end - start) / 20.0 * this.level);
|
||
}
|
||
if (this.part == 1) {// 死之羽
|
||
start = (this.quality == 5 ? 47 : 42);
|
||
end = (this.quality == 5 ? 311 : 280);
|
||
return "攻击力 " + Math.round(start + (end - start) / 20.0 * this.level);
|
||
}
|
||
switch (this.main) {
|
||
case 0:// 生命百分比
|
||
case 1:// 攻击百分比
|
||
case 9:// 火元素伤害加成
|
||
case 10:// 雷元素伤害加成
|
||
case 11:// 水元素伤害加成
|
||
case 12:// 草元素伤害加成
|
||
case 13:// 风元素伤害加成
|
||
case 14:// 岩元素伤害加成
|
||
case 15:// 冰元素伤害加成
|
||
start = (this.quality == 5 ? 7.0 : 6.3);
|
||
end = (this.quality == 5 ? 46.6 : 41.9);
|
||
break;
|
||
case 2:// 防御百分比
|
||
case 8:// 物理伤害加成
|
||
start = (this.quality == 5 ? 8.7 : 7.9);
|
||
end = (this.quality == 5 ? 58.3 : 52.4);
|
||
break;
|
||
case 3:// 元素精通
|
||
start = (this.quality == 5 ? 28 : 25.2);
|
||
end = (this.quality == 5 ? 186.5 : 167.8);
|
||
break;
|
||
case 4:// 元素充能效率
|
||
start = (this.quality == 5 ? 7.8 : 7.0);
|
||
end = (this.quality == 5 ? 51.8 : 46.6);
|
||
break;
|
||
case 5:// 暴击率
|
||
start = (this.quality == 5 ? 4.7 : 4.2);
|
||
end = (this.quality == 5 ? 31.1 : 28.0);
|
||
break;
|
||
case 6:// 暴击伤害
|
||
start = (this.quality == 5 ? 9.3 : 8.4);
|
||
end = (this.quality == 5 ? 62.2 : 55.9);
|
||
break;
|
||
case 7:// 治疗加成
|
||
start = (this.quality == 5 ? 5.4 : 4.8);
|
||
end = (this.quality == 5 ? 35.9 : 32.3);
|
||
break;
|
||
default:
|
||
throw new Error("圣遗物主属性错误");
|
||
}
|
||
const value = start + (end - start) / 20.0 * this.level;
|
||
if (this.main == 3) return "元素精通 " + Math.round(value);
|
||
return targetKeywords[this.main] + " " + value.toFixed(1) + "%";
|
||
}
|
||
|
||
make_point() {
|
||
// 计算所有副属性的点数
|
||
for (let i = 0; i < 4; ++i) {
|
||
let point, id = this.subAttr[i];
|
||
if (id == -1) {
|
||
this.subPoints[i] = 0;
|
||
continue;
|
||
}
|
||
switch (id) {
|
||
case (targetKeywords.length):// 生命值
|
||
point = 29.8444;// * 2.2533;
|
||
break;
|
||
case (targetKeywords.length + 1):// 攻击力
|
||
point = 1.9430;// * 2.4300;
|
||
break;
|
||
case (targetKeywords.length + 2):// 防御力
|
||
point = 2.3316;// * 2.2468;
|
||
break;
|
||
case 3:// 元素精通
|
||
point = 2.3316;
|
||
break;
|
||
case 0:// 生命百分比
|
||
case 1:// 攻击百分比
|
||
point = 0.5829;
|
||
break;
|
||
case 2:// 防御百分比
|
||
point = 0.7286;
|
||
break;
|
||
case 4:// 元素充能效率
|
||
point = 0.6477;
|
||
break;
|
||
case 5:// 暴击率
|
||
point = 0.3886;
|
||
break;
|
||
case 6:// 暴击伤害
|
||
point = 0.7772;
|
||
break;
|
||
default:
|
||
throw new Error("圣遗物副属性错误");
|
||
}
|
||
if (this.quality != 5) point *= 0.8;// 若四星圣遗物则都乘以 0.8
|
||
this.subPoints[i] = this.subValue[i] / point;
|
||
}
|
||
// 计算所有副属性的词条数(计算不可能完全准确因而总词条数可能比实际多一个)
|
||
let sum = 0, allcounts = Math.trunc(this.level / 4) + (this.quality - 1);
|
||
for (let i = 0; i < 4; ++i) {
|
||
let points = this.subPoints[i];
|
||
if (points == 0) {
|
||
this.subCounts[i] = 0;
|
||
continue;
|
||
}
|
||
this.subCounts[i] = -1;// 缺省未知词条数
|
||
if (points < 20.5) this.subCounts[i] = (points < 12 ? 1 : 2);
|
||
else if (points < 27.9) this.subCounts[i] = 3;
|
||
else if (this.quality == 5) {
|
||
if (points > 30.1 && points < 34.9) this.subCounts[i] = 4;
|
||
else if (points > 40.1 && points < 41.9) this.subCounts[i] = 5;
|
||
else if (points > 50.1) this.subCounts[i] = 6;
|
||
}
|
||
else if (points > 30.1) this.subCounts[i] = 4;
|
||
if (this.subCounts[i] != -1) sum += this.subCounts[i];
|
||
else sum |= 128;
|
||
}
|
||
if (sum > 128) {
|
||
sum = allcounts - (sum - 128);// 未确定的词条数
|
||
const id = this.subCounts.indexOf(-1);
|
||
if (this.quality == 5) {// 五星圣遗物需猜测词条数
|
||
const nid = this.subCounts.indexOf(-1, id + 1);
|
||
if (nid == -1) {// 仅需处理一个未确定词条
|
||
this.subCounts[id] = Math.min(sum, Math.trunc(this.subPoints[id] * 0.1434));
|
||
}
|
||
else {// 需处理两个未确定词条
|
||
this.subCounts[nid] = this.subCounts[id] = 3;
|
||
if (sum == 7) {
|
||
if (this.subPoints[id] >= this.subPoints[nid]) this.subCounts[id] = 4;
|
||
else this.subCounts[nid] = 4;
|
||
}
|
||
}
|
||
}
|
||
else {// 四星圣遗物基本可确定
|
||
this.subCounts[id] = sum;//(sum < 4 || this.subPoints[id] < 29 ? 3 : 4);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 获取副属性总点数 */
|
||
getAttrPoints() {
|
||
//if (this.subPoints.length == 0) this.make_point();
|
||
return this.subPoints[0] + this.subPoints[1] + this.subPoints[2] + this.subPoints[3];
|
||
}
|
||
|
||
/* 获取副属性总词条数(对于已强化的圣遗物可能不准确) */
|
||
getAttrCounts() {
|
||
//if (this.subCounts.length == 0) this.make_point();
|
||
return this.subCounts[0] + this.subCounts[1] + this.subCounts[2] + this.subCounts[3];
|
||
}
|
||
|
||
/* 判断是否为初始完全词条圣遗物(对于已强化的圣遗物可能不准确) */
|
||
isDominant() {
|
||
//if (this.subPoints.length == 0) this.make_point();
|
||
return this.getAttrCounts() >= Math.trunc(this.level / 4) + (this.quality - 1);
|
||
}
|
||
|
||
/* 打印圣遗物信息 */
|
||
print() {
|
||
if (!this.isDominant()) logout("==== 圣遗物 ====");
|
||
else logout("==== 完全圣遗物 ====");
|
||
logout(this.name + ":" + targetPartNames[this.part] + "," +
|
||
(this.quality == 5 ? "金" : "紫") + ",等级+" + this.level);
|
||
logout("总点数:" + this.getAttrPoints().toFixed(1) + "," + "主属性:" + this.get_mainAttr());
|
||
for (let i = 0; i < 4; ++i) {
|
||
if (this.subAttr[i] == -1) continue;
|
||
const val = this.subValue[i];
|
||
if (val == 0) continue;
|
||
let str, id = this.subAttr[i];
|
||
if (id < targetKeywords.length) str = targetKeywords[id];
|
||
else str = targetKeywords[id - targetKeywords.length];
|
||
if (id == 3 || id >= targetKeywords.length) str = str + "+" + Math.round(val);
|
||
else str = str + "+" + val.toFixed(1) + "%";
|
||
logout("副属性" + (i + 1) + ":+" + (this.subCounts[i] - 1) + "次," + str);
|
||
}
|
||
}
|
||
};
|
||
|
||
/************************ characters.js ************************/
|
||
|
||
// 词条名称简写
|
||
const abbrevKeywords = [
|
||
"生命", "攻击", "防御", "精通",// 0-3
|
||
"充能", "暴击", "暴伤",// 4-6
|
||
"治疗", "物伤", "火伤",// 7-9
|
||
"雷伤", "水伤", "草伤",// 10-12
|
||
"风伤", "岩伤", "冰伤" // 13-15
|
||
];
|
||
|
||
// 单个角色的套装
|
||
class Actor {
|
||
constructor(str) {
|
||
if (!str) return;// 支持无参构造
|
||
const actorName = str.match(/^\d+\. *(.*)$/m);// 匹配角色名字
|
||
const weaponRe = /^- 武器:(.*)$/m;
|
||
const relicsRe = /^- 圣遗物:(.*)$/m;
|
||
const mainAttrRe = /^- 圣遗物主词条:(.*)$/m;
|
||
const subAttrRe = /^- 圣遗物副词条:(.*)$/m;
|
||
let result, weapon, relics, mainAttr, subAttr, lvl = 0;
|
||
if ((/^.*;.*;.*$/m).test(str)) lvl = 2;
|
||
else if((/^.*;.*$/m).test(str)) lvl = 1;
|
||
/* 武器 */
|
||
result = str.match(weaponRe);
|
||
if (!result || !result[1]) weapon = ["", "", ""];
|
||
else {
|
||
weapon = result[1].split(";");
|
||
for (let i = weapon.length; i <= lvl; ++i) weapon[i] = weapon[0];
|
||
}
|
||
/* 圣遗物 */
|
||
result = str.match(relicsRe);
|
||
if (!result || !result[1]) relics = ["", "", ""];
|
||
else {
|
||
relics = result[1].split(";");
|
||
for (let i = relics.length; i <= lvl; ++i) relics[i] = relics[0];
|
||
}
|
||
/* 圣遗物主词条 */
|
||
result = str.match(mainAttrRe);
|
||
if (!result || !result[1]) mainAttr = ["", "", ""];
|
||
else {
|
||
mainAttr = result[1].split(";");
|
||
for (let i = mainAttr.length; i <= lvl; ++i) mainAttr[i] = mainAttr[0];
|
||
}
|
||
/* 圣遗物副词条 */
|
||
result = str.match(subAttrRe);
|
||
if (!result || !result[1]) subAttr = ["", "", ""];
|
||
else {
|
||
subAttr = result[1].split(";");
|
||
for (let i = subAttr.length; i <= lvl; ++i) subAttr[i] = subAttr[0];
|
||
}
|
||
// 子配置
|
||
let pos = this, objects = [this];
|
||
for (let i = 0; i < lvl; ++i) {
|
||
pos = pos.next = new Actor();
|
||
objects[i + 1] = pos;
|
||
}
|
||
pos.next = null;
|
||
// 解析配置
|
||
for (let id = 0; id <= lvl; ++id) {
|
||
objects[id].name = actorName[1];// 角色名字
|
||
objects[id].weaponName = weapon[id].split("、");// 武器名称
|
||
objects[id].relicsName = relics[id].split("、");// 圣遗物名称
|
||
objects[id].mainAttr = [(1 << 0), (1 << 1)];// 主属性采取位标志存储
|
||
objects[id].subAttr = [];// 副属性 index
|
||
const mainAttrName = mainAttr[id].split("、");
|
||
for (let i = mainAttrName.length; i < 3; ++i) mainAttrName[i] = mainAttrName[0];
|
||
for (let i = 2; i < 5; ++i) {
|
||
let val = 0, keywords = mainAttrName[i - 2].split("/");
|
||
for (let j = 0; j < keywords.length; ++j) {
|
||
val |= (1 << abbrevKeywords.indexOf(keywords[j]));
|
||
}
|
||
objects[id].mainAttr[i] = val;
|
||
}
|
||
const keywords = subAttr[id].split("、");
|
||
for (let i = 0; i < keywords.length; ++i) {
|
||
objects[id].subAttr[i] = abbrevKeywords.indexOf(keywords[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
print(index) {
|
||
logout((index + ".").padEnd(3) + " " + this.name);
|
||
logout("");
|
||
logout("- 武器:" + this.weaponName.join("、"));
|
||
logout("- 圣遗物:" + this.relicsName.join("、"));
|
||
const mainAttrName = [];
|
||
for (let i = 2; i < 5; ++i) {
|
||
const attrName = [];
|
||
for (let j = 0; j < abbrevKeywords.length; ++j) {
|
||
if ((1 << j) & this.mainAttr[i]) attrName.push(abbrevKeywords[j]);
|
||
}
|
||
mainAttrName.push(attrName.join("/"));
|
||
}
|
||
logout("- 圣遗物主词条:" + mainAttrName.join("、"));
|
||
const subAttrName = [];
|
||
for (let i = 0; i < this.subAttr.length; ++i) {
|
||
subAttrName.push(abbrevKeywords[this.subAttr[i]]);
|
||
}
|
||
logout("- 圣遗物副词条:" + subAttrName.join("、"));
|
||
logout("");
|
||
if (this.next) this.next.print(index);
|
||
}
|
||
|
||
/* 圣遗物是否适合当前角色, 返回适合的配置(Actor类型) */
|
||
getSuitable(relics) {
|
||
do {
|
||
// 套装名称必须匹配
|
||
if (this.relicsName[0] && !this.relicsName.includes(relics.name)) break;
|
||
// 主属性必须匹配
|
||
if (!((1 << relics.main) & this.mainAttr[relics.part])) break;
|
||
// 计算是否包含有效词条
|
||
const hasAttr = [];
|
||
for (let i = 0; i < this.subAttr.length; ++i) {
|
||
if (relics.part >= 2 && this.subAttr[i] == relics.main) continue;
|
||
hasAttr.push(relics.subAttr.indexOf(this.subAttr[i]) != -1);
|
||
}
|
||
// 若初始完全词条且含前两类
|
||
const dominant = relics.isDominant();
|
||
const hasFirst = (hasAttr[0] === void 0 || hasAttr[0]);
|
||
const hasSecond = (hasAttr[1] === void 0 || hasAttr[1]);
|
||
if (dominant && hasFirst && hasSecond) return this;
|
||
// 若有效词条类型多于三种(不占用主词条)或四种(占用主词条)
|
||
if (hasAttr.length > 3) {
|
||
const hasThird = (hasAttr[2] === void 0 || hasAttr[2]);
|
||
const hasFourth = (hasAttr[3] === void 0 || hasAttr[3]);
|
||
if (!dominant) {// 非初始完全词条
|
||
if (!hasFirst || !hasSecond) break;// 前两类必须都有
|
||
if (hasThird && hasFourth) return this;// 前四类都有
|
||
if (!hasThird && !hasFourth) break;// 前四类缺少两类
|
||
for (let i = 4; i < hasAttr.length; ++i) {
|
||
if (hasAttr[i]) return this;// 四有效
|
||
}
|
||
}
|
||
else {// 初始完全词条
|
||
if (!hasFirst && !hasSecond) break;// 前两类必须有一类
|
||
if (hasThird && hasFourth) return this;// 前四类有三类
|
||
if (!hasThird && !hasFourth) break;// 前四类缺少三类
|
||
for (let i = 4; i < hasAttr.length; ++i) {
|
||
if (hasAttr[i]) return this;// 三有效
|
||
}
|
||
}
|
||
}
|
||
} while(false);
|
||
if (!this.next) return null;
|
||
else return this.next.getSuitable(relics);
|
||
}
|
||
|
||
/* 圣遗物是否可用于当前角色, 返回适合的配置(Actor类型) */
|
||
getAffable(relics) {
|
||
do {
|
||
// 套装名称必须匹配
|
||
if (this.relicsName[0] && !this.relicsName.includes(relics.name)) break;
|
||
// 主属性必须匹配
|
||
if (!((1 << relics.main) & this.mainAttr[relics.part])) break;
|
||
// 计算是否包含有效词条
|
||
const hasAttr = [];
|
||
for (let i = 0; i < this.subAttr.length; ++i) {
|
||
if (relics.part >= 2 && this.subAttr[i] == relics.main) continue;
|
||
hasAttr.push(relics.subAttr.indexOf(this.subAttr[i]) != -1);
|
||
}
|
||
// 若含前两类词条
|
||
const hasFirst = (hasAttr[0] === void 0 || hasAttr[0]);
|
||
const hasSecond = (hasAttr[1] === void 0 || hasAttr[1]);
|
||
if (hasFirst && hasSecond) return this;
|
||
// 若有效词条类型多于三种(不占用主词条)或四种(占用主词条)
|
||
if (hasAttr.length > 3) {
|
||
const hasThird = (hasAttr[2] === void 0 || hasAttr[2]);
|
||
const hasFourth = (hasAttr[3] === void 0 || hasAttr[3]);
|
||
if (!hasFirst && !hasSecond) break;// 前两类必须有一类
|
||
if (hasThird && hasFourth) return this;// 前四类有三类
|
||
if (!hasThird && !hasFourth) break;// 前四类缺少三类
|
||
for (let i = 4; i < hasAttr.length; ++i) {
|
||
if (hasAttr[i]) return this;// 三有效
|
||
}
|
||
}
|
||
} while(false);
|
||
if (!this.next) return null;
|
||
else return this.next.getAffable(relics);
|
||
}
|
||
|
||
/* 获取圣遗物于当前角色的适用分数 */
|
||
getScore(relics) {
|
||
let ret = this.relicsName.indexOf(relics.name);
|
||
if (ret != -1) {
|
||
if (ret == 0) ret = 2;// 专属套装加两分
|
||
else if (ret > 1) ret = 0;// 非专属套装不加分
|
||
}
|
||
const indexAttr = [];
|
||
for (let i = 0; i < this.subAttr.length; ++i) {// 要排除与主属性相同的副词条
|
||
if (relics.part >= 2 && this.subAttr[i] == relics.main) continue;
|
||
indexAttr.push(relics.subAttr.indexOf(this.subAttr[i]));
|
||
}
|
||
for (let i = 0; i < indexAttr.length; ++i) {
|
||
const id = indexAttr[i];
|
||
if (id == -1) continue;
|
||
if (i < 2) ret += relics.subPoints[id];
|
||
else if (i < 4) ret += relics.subPoints[id] * 0.8;
|
||
else ret += relics.subPoints[id] * 0.56;
|
||
if (relics.subAttr[id] < 3) {// 计算数值生命值、攻击力、防御力
|
||
const nid = relics.subAttr.indexOf(this.subAttr[i] + targetKeywords.length);
|
||
if (nid == -1) continue;
|
||
// 统一将点数除以 2.37 以转为百分比属性点
|
||
if (i < 2) ret += relics.subPoints[nid] * (1.0 * 27 / 64);
|
||
else if (i < 4) ret += relics.subPoints[nid] * (0.8 * 27 / 64);
|
||
else ret += relics.subPoints[nid] * (0.56 * 27 / 64);
|
||
}
|
||
}
|
||
this.score = ret;
|
||
return ret;
|
||
}
|
||
};
|
||
|
||
// 所有角色的套装
|
||
class Characters {
|
||
constructor(str) {
|
||
this.actors = [];
|
||
const general = str.match(/1\. .*\n\n(?:- .*\n)+\n/g);// 默认模板
|
||
let pos = this.actors[0] = new Actor(general[0]);
|
||
for (let i = 1; i < general.length; ++i) {
|
||
pos = pos.next = new Actor(general[i]);
|
||
}
|
||
const specials = str.match(/\d+\..*\n\n(?:- .*\n)+\n/g);// 特定角色
|
||
for (let i = general.length; i < specials.length; ++i) {
|
||
this.actors.push(new Actor(specials[i]));
|
||
}
|
||
//enableLog = false;
|
||
//this.print();// 调试信息
|
||
//logout("", "characters.txt");
|
||
//enableLog = settings.enableLog;
|
||
}
|
||
|
||
print() {
|
||
for (let i = 0; i < this.actors.length; ++i) this.actors[i].print(i + 1);
|
||
}
|
||
|
||
/* 评价圣遗物 */
|
||
evaluate(relics) {
|
||
let text, objects = [];// 首先查找毕业配置
|
||
for (let i = 1; i < this.actors.length; ++i) {// 先跳过默认模板
|
||
const obj = this.actors[i].getSuitable(relics);
|
||
if (obj != null) {
|
||
obj.getScore(relics);
|
||
objects.push(obj);
|
||
}
|
||
}
|
||
objects.sort((x, y) => y.score - x.score);// 评分降序排列
|
||
let obj = this.actors[0].getSuitable(relics);
|
||
if (obj != null) {
|
||
obj.getScore(relics);
|
||
objects.push(obj);// 确保默认模板排在最後
|
||
}
|
||
if (objects.length == 0) {// 尝试查找可用配置
|
||
for (let i = 1; i < this.actors.length; ++i) {// 先跳过默认模板
|
||
const obj = this.actors[i].getAffable(relics);
|
||
if (obj != null) {
|
||
obj.getScore(relics);
|
||
objects.push(obj);
|
||
}
|
||
}
|
||
objects.sort((x, y) => y.score - x.score);// 评分降序排列
|
||
let obj = this.actors[0].getAffable(relics);
|
||
if (obj != null) {
|
||
obj.getScore(relics);
|
||
objects.push(obj);// 确保默认模板排在最後
|
||
}
|
||
if (objects.length == 0) {// 没有角色需要此圣遗物
|
||
logout("**这个圣遗物不堪大用啊**");
|
||
return;
|
||
}
|
||
else text = "可用:";
|
||
}
|
||
else text = "适配:";
|
||
/* 打印前六名适配角色 */
|
||
text = text + objects[0].name;
|
||
for (let i = 1; i < 6 && i < objects.length; ++i) {
|
||
text = text + "、" + objects[i].name;
|
||
}
|
||
logout(text);
|
||
/* 打印首个适配角色所需副词条和评分 */
|
||
obj = objects[0];
|
||
text = "词条点(" + obj.score.toFixed(1) + "):";
|
||
for (let i = 0; i < obj.subAttr.length; ++i) {
|
||
let midstr = "";
|
||
if (relics.subAttr.indexOf(obj.subAttr[i]) != -1) midstr = "*";
|
||
text = text + midstr + abbrevKeywords[obj.subAttr[i]] + midstr + "、";
|
||
}
|
||
logout(text.slice(0, -1));
|
||
}
|
||
};
|
||
|
||
/************************ genshin.js ************************/
|
||
|
||
// 辅助函数:检查圣遗物品质(使用模板匹配)
|
||
function checkQuality(image, templateObj, x, y, w, h) {
|
||
const crop = image.DeriveCrop(x, y, w, h);
|
||
const matchResult = crop.Find(templateObj);
|
||
const ret = !matchResult.IsEmpty();
|
||
matchResult.Dispose();
|
||
crop.Dispose();
|
||
return ret;
|
||
}
|
||
|
||
// 辅助函数:检查圣遗物等级(使用模板匹配,因为OCR识别不准确)
|
||
function checkLevel(image, templateObjs, x, y, w, h) {
|
||
const crop = image.DeriveCrop(x, y, w, h);
|
||
let ret = 0;
|
||
let res = crop.Find(templateObjs[ret]);
|
||
if (res.IsEmpty()) {// 尝试两遍
|
||
ret = 20;
|
||
let i = 0;
|
||
while (true) {
|
||
res.Dispose();
|
||
res = crop.Find(templateObjs[ret]);
|
||
if (!res.IsEmpty()) break;
|
||
if (--ret < 0) {
|
||
if (++i >= 2) break;
|
||
ret = 20;
|
||
}
|
||
}
|
||
}
|
||
res.Dispose();
|
||
crop.Dispose();
|
||
return ret;
|
||
}
|
||
|
||
// 辅助函数:读取指定区域的文本内容(使用OCR识别)
|
||
function readTextRegion(image, KeywordObj) {
|
||
const textResult = image.Find(KeywordObj);
|
||
const ret = textResult.Text;
|
||
textResult.Dispose();
|
||
return ret;
|
||
}
|
||
|
||
function autoZoom(x, y, w, h) {
|
||
/* 注意, BetterGI 截图强制转为 1080P, 这里不用缩放 */
|
||
const ratio = 1;//genshin.scaleTo1080PRatio;
|
||
return [x * ratio, y * ratio, w * ratio, h * ratio];
|
||
}
|
||
|
||
function readImageAutoZoom(path, width, height) {
|
||
const ratio = 1;//genshin.scaleTo1080PRatio;
|
||
return file.ReadImageMatWithResizeSync(path, width * ratio, height * ratio);
|
||
}
|
||
|
||
function readTemplate() {
|
||
const template = [];
|
||
for (let i = 0; i <= 20; ++i) {
|
||
template[i] = readImageAutoZoom("assets/" + i.toFixed(0) + ".png", 47, 21);
|
||
}
|
||
template[21] = readImageAutoZoom("assets/star.png", 28, 26);
|
||
return template;
|
||
}
|
||
|
||
function freeTemplate(template) {
|
||
for (let i = 0; i <= 21; ++i) template[i].Dispose();
|
||
}
|
||
|
||
// 入口函数
|
||
(async function () {
|
||
const characters = new Characters(file.readTextSync("角色一览.md"));
|
||
const template = readTemplate();
|
||
const templateQualityObj = RecognitionObject.TemplateMatch(template[21]);
|
||
const templateLevelObj = [];
|
||
for (let i = 0; i <= 20; ++i) {
|
||
let tempObj = RecognitionObject.TemplateMatch(template[i]);
|
||
tempObj.Threshold = 0.91;// 至少 0.91
|
||
//tempObj.InitTemplate();// 这个不需要
|
||
templateLevelObj[i] = tempObj;
|
||
}
|
||
|
||
const backpackPartNameObj = RecognitionObject.Ocr(...autoZoom(1320, 180, 90, 40));
|
||
const backpackMainAttrObj = RecognitionObject.Ocr(...autoZoom(1320, 268, 180, 32));
|
||
const backpackSuitNameObj = RecognitionObject.Ocr(...autoZoom(1320, 630, 240, 35));
|
||
const backpackSubAttrObj1 = RecognitionObject.Ocr(...autoZoom(1352, 470, 368, 40));
|
||
const backpackSubAttrObj2 = RecognitionObject.Ocr(...autoZoom(1352, 510, 368, 40));
|
||
const backpackSubAttrObj3 = RecognitionObject.Ocr(...autoZoom(1352, 550, 368, 40));
|
||
const backpackSubAttrObj4 = RecognitionObject.Ocr(...autoZoom(1352, 590, 368, 40));
|
||
// 祝圣之霜定义的圣遗物, 坐标轴需下移 38px
|
||
const ebackpackSuitNameObj = RecognitionObject.Ocr(...autoZoom(1320, 630 + 38, 240, 35));
|
||
const ebackpackSubAttrObj1 = RecognitionObject.Ocr(...autoZoom(1352, 470 + 38, 368, 40));
|
||
const ebackpackSubAttrObj2 = RecognitionObject.Ocr(...autoZoom(1352, 510 + 38, 368, 40));
|
||
const ebackpackSubAttrObj3 = RecognitionObject.Ocr(...autoZoom(1352, 550 + 38, 368, 40));
|
||
const ebackpackSubAttrObj4 = RecognitionObject.Ocr(...autoZoom(1352, 590 + 38, 368, 40));
|
||
|
||
const characterSuitNameObj = RecognitionObject.Ocr(...autoZoom(1458, 486, 242, 32));
|
||
const characterPartNameObj = RecognitionObject.Ocr(...autoZoom(1458, 172, 90, 30));
|
||
const characterMainAttrObj = RecognitionObject.Ocr(...autoZoom(1466, 214, 214, 38));
|
||
const characterSubAttrObj1 = RecognitionObject.Ocr(...autoZoom(1480, 352, 368, 27));
|
||
const characterSubAttrObj2 = RecognitionObject.Ocr(...autoZoom(1480, 385, 368, 27));
|
||
const characterSubAttrObj3 = RecognitionObject.Ocr(...autoZoom(1480, 419, 368, 27));
|
||
const characterSubAttrObj4 = RecognitionObject.Ocr(...autoZoom(1480, 453, 368, 27));
|
||
|
||
while (true) {
|
||
// 每次分析耗时约 100ms, 等待约 200ms
|
||
await sleep(200);
|
||
// 捕获游戏区域图像
|
||
const gameImage = captureGameRegion();
|
||
if (gameImage.IsEmpty()) {
|
||
log.error("无法捕获游戏画面");
|
||
break;
|
||
}
|
||
try {
|
||
// 尝试背包界面匹配主词条
|
||
let mainResult = gameImage.Find(backpackMainAttrObj);
|
||
const relics = new Relics();
|
||
if (!mainResult.IsEmpty()) {
|
||
relics.set_main(mainResult.Text);
|
||
if (relics.main != -1) {
|
||
// 背包界面属性识别
|
||
let name, part, quality, level, attr1, attr2, attr3, attr4;
|
||
part = readTextRegion(gameImage, backpackPartNameObj);
|
||
if (checkQuality(gameImage, templateQualityObj, ...autoZoom(1468, 356, 32, 32)))
|
||
quality = 5;
|
||
else quality = 4;
|
||
level = checkLevel(gameImage, templateLevelObj, ...autoZoom(1338, 429, 55, 27));
|
||
if (level != -1) {
|
||
name = readTextRegion(gameImage, backpackSuitNameObj);
|
||
attr1 = readTextRegion(gameImage, backpackSubAttrObj1);
|
||
attr2 = readTextRegion(gameImage, backpackSubAttrObj2);
|
||
attr3 = readTextRegion(gameImage, backpackSubAttrObj3);
|
||
attr4 = readTextRegion(gameImage, backpackSubAttrObj4);
|
||
}
|
||
else {
|
||
level = checkLevel(gameImage, templateLevelObj, ...autoZoom(1338, 429 + 38, 55, 27));
|
||
name = readTextRegion(gameImage, ebackpackSuitNameObj);
|
||
attr1 = readTextRegion(gameImage, ebackpackSubAttrObj1);
|
||
attr2 = readTextRegion(gameImage, ebackpackSubAttrObj2);
|
||
attr3 = readTextRegion(gameImage, ebackpackSubAttrObj3);
|
||
attr4 = readTextRegion(gameImage, ebackpackSubAttrObj4);
|
||
}
|
||
relics.set_name(name);
|
||
relics.set_part(part);
|
||
relics.set_quality(quality);
|
||
relics.set_level(level);
|
||
relics.set_subAttr(attr1, attr2, attr3, attr4);
|
||
}
|
||
} else {
|
||
mainResult.Dispose();
|
||
// 尝试角色界面匹配主词条
|
||
mainResult = gameImage.Find(characterMainAttrObj);
|
||
if (!mainResult.IsEmpty()) {
|
||
relics.set_main(mainResult.Text);
|
||
if (relics.main != -1) {
|
||
// 角色界面属性识别
|
||
relics.set_level(checkLevel(gameImage, templateLevelObj, ...autoZoom(1465, 310, 56, 28)));
|
||
relics.set_quality(checkQuality(gameImage, templateQualityObj, ...autoZoom(1596, 265, 32, 32)) ? 5 : 4);
|
||
relics.set_part(readTextRegion(gameImage, characterPartNameObj));
|
||
relics.set_name(readTextRegion(gameImage, characterSuitNameObj));
|
||
relics.set_subAttr(
|
||
readTextRegion(gameImage, characterSubAttrObj1),
|
||
readTextRegion(gameImage, characterSubAttrObj2),
|
||
readTextRegion(gameImage, characterSubAttrObj3),
|
||
readTextRegion(gameImage, characterSubAttrObj4)
|
||
);
|
||
}
|
||
}
|
||
}
|
||
mainResult.Dispose();
|
||
/* 分析结果 */
|
||
if (relics.main != -1) {
|
||
relics.make_point();// 计算副属性点数
|
||
relics.print();// 打印圣遗物信息
|
||
characters.evaluate(relics);// 评价圣遗物
|
||
logout("----------------", "overlay.txt");
|
||
}
|
||
} catch (error) {
|
||
log.error(`处理失败: ${error.message}`);
|
||
}
|
||
finally {
|
||
gameImage.Dispose();
|
||
}
|
||
}
|
||
freeTemplate(template);
|
||
})();
|