@sjtdev/koishi-plugin-dota2tracker 1.1.10-hotfix → 1.2.0-hotfix

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -43,12 +43,16 @@ var import_koishi = require("koishi");
43
43
  var utils_exports = {};
44
44
  __export(utils_exports, {
45
45
  CONFIGS: () => CONFIGS,
46
+ HeroDescType: () => HeroDescType,
47
+ ImageFormat: () => ImageFormat,
46
48
  ImageType: () => ImageType,
49
+ formatHeroDesc: () => formatHeroDesc,
47
50
  formatNumber: () => formatNumber,
48
51
  getFormattedMatchData: () => getFormattedMatchData,
49
52
  getImageUrl: () => getImageUrl,
50
53
  playerisValid: () => playerisValid,
51
54
  query: () => query,
55
+ queryHeroFromValve: () => queryHeroFromValve,
52
56
  readDirectoryFilesSync: () => readDirectoryFilesSync,
53
57
  roundToDecimalPlaces: () => roundToDecimalPlaces,
54
58
  sec2time: () => sec2time,
@@ -513,6 +517,17 @@ async function query(query_str) {
513
517
  });
514
518
  }
515
519
  __name(query, "query");
520
+ async function queryHeroFromValve(heroId) {
521
+ return (await http.get(`https://www.dota2.com/datafeed/herodata?language=schinese&hero_id=${heroId}`)).result.data.heroes[0];
522
+ }
523
+ __name(queryHeroFromValve, "queryHeroFromValve");
524
+ var HeroDescType = /* @__PURE__ */ ((HeroDescType2) => {
525
+ HeroDescType2["Normal"] = "normal";
526
+ HeroDescType2["Facet"] = "facet";
527
+ HeroDescType2["Scepter"] = "scepter";
528
+ HeroDescType2["Shard"] = "shard";
529
+ return HeroDescType2;
530
+ })(HeroDescType || {});
516
531
  var ImageType = /* @__PURE__ */ ((ImageType2) => {
517
532
  ImageType2["Icons"] = "icons";
518
533
  ImageType2["IconsFacets"] = "icons/facets";
@@ -523,7 +538,12 @@ var ImageType = /* @__PURE__ */ ((ImageType2) => {
523
538
  ImageType2["Local"] = "local";
524
539
  return ImageType2;
525
540
  })(ImageType || {});
526
- function getImageUrl(image, type = "local" /* Local */) {
541
+ var ImageFormat = /* @__PURE__ */ ((ImageFormat2) => {
542
+ ImageFormat2["png"] = "png";
543
+ ImageFormat2["svg"] = "svg";
544
+ return ImageFormat2;
545
+ })(ImageFormat || {});
546
+ function getImageUrl(image, type = "local" /* Local */, format = "png" /* png */) {
527
547
  if (type === "local" /* Local */) {
528
548
  try {
529
549
  const imageData = import_fs.default.readFileSync(`./node_modules/@sjtdev/koishi-plugin-dota2tracker/template/images/${image}.png`);
@@ -534,7 +554,7 @@ function getImageUrl(image, type = "local" /* Local */) {
534
554
  return "";
535
555
  }
536
556
  } else
537
- return `https://cdn.cloudflare.steamstatic.com/apps/dota2/images/dota_react/${type}/${image}.png`;
557
+ return `https://cdn.cloudflare.steamstatic.com/apps/dota2/images/dota_react/${type}/${image}.${format}`;
538
558
  }
539
559
  __name(getImageUrl, "getImageUrl");
540
560
  function getFormattedMatchData(match) {
@@ -600,7 +620,7 @@ function getFormattedMatchData(match) {
600
620
  if (!match.party[player.partyId])
601
621
  match.party[player.partyId] = party_mark[party_index++];
602
622
  }
603
- if (match.parsedDateTime) {
623
+ if (player.stats.matchPlayerBuffEvent) {
604
624
  const maxStackCountsByAbilityOrItem = player.stats.matchPlayerBuffEvent.reduce((acc, event) => {
605
625
  const key = event.abilityId !== null ? `ability-${event.abilityId}` : `item-${event.itemId}`;
606
626
  if (!acc[key] || event.stackCount > acc[key].stackCount) {
@@ -843,6 +863,34 @@ function roundToDecimalPlaces(number, decimalPlaces) {
843
863
  return Math.round(number * factor) / factor;
844
864
  }
845
865
  __name(roundToDecimalPlaces, "roundToDecimalPlaces");
866
+ function formatHeroDesc(template, special_values, type = "normal" /* Normal */) {
867
+ return template.replace(/%%|%([^%]+)%/g, (match, p1) => {
868
+ if (match === "%%") {
869
+ return "%";
870
+ } else {
871
+ const specialValue = special_values.find((sv) => {
872
+ const match2 = /bonus_(.*)/.exec(p1);
873
+ return sv.name === p1 || sv.name === match2?.[1];
874
+ });
875
+ if (specialValue) {
876
+ let valuesToUse = "";
877
+ if (type == "facet" /* Facet */) {
878
+ valuesToUse = specialValue.facet_bonus.name ? specialValue.facet_bonus.values.join(" / ") : specialValue.values_float.join(" / ");
879
+ } else if (type == "scepter" /* Scepter */) {
880
+ valuesToUse = specialValue.values_scepter.length ? specialValue.values_scepter.join(" / ") : specialValue.values_float.join(" / ");
881
+ } else if (type == "shard" /* Shard */) {
882
+ valuesToUse = specialValue.values_shard.length ? specialValue.values_shard.join(" / ") : specialValue.values_float.join(" / ");
883
+ } else {
884
+ valuesToUse = specialValue.values_float.join(" / ");
885
+ }
886
+ return `<span class="value">${valuesToUse}</span>`;
887
+ } else {
888
+ return match;
889
+ }
890
+ }
891
+ });
892
+ }
893
+ __name(formatHeroDesc, "formatHeroDesc");
846
894
 
847
895
  // src/index.ts
848
896
  var import_fs2 = __toESM(require("fs"));
@@ -954,8 +1002,8 @@ var rank = {
954
1002
  "7": "超凡入圣",
955
1003
  "8": "冠绝一世"
956
1004
  };
957
- var roles = { CARRY: "核心", ESCAPE: "逃生", NUKER: "爆发", INITIATOR: "先手", DURABLE: "耐久", DISABLER: "控制", JUNGLER: "打野", SUPPORT: "辅助", PUSHER: "推进" };
958
- var primary_attrs = { all: "hero_universal", str: "hero_strength", agi: "hero_agility", int: "hero_intelligence" };
1005
+ var roles = ["核心", "辅助", "爆发", "控制", "打野", "耐久", "逃生", "推进", "先手"];
1006
+ var primary_attrs = { "3": "hero_universal", "0": "hero_strength", "1": "hero_agility", "2": "hero_intelligence" };
959
1007
  var behavior = {
960
1008
  "Unit Target": "单位目标",
961
1009
  Channeled: "持续施法",
@@ -1402,29 +1450,103 @@ async function apply(ctx, config) {
1402
1450
  }
1403
1451
  }
1404
1452
  });
1405
- ctx.command("查询英雄 <input_data>", "查询英雄技能/面板信息").usage("查询英雄的技能说明与各项数据,生成图片发布。\n参数可输入英雄ID、英雄名、英雄常用别名").example("-查询英雄 15").example("-查询英雄 雷泽").example("-查询英雄 电魂").action(async ({ session }, input_data) => {
1453
+ ctx.command("查询英雄 <input_data>", "查询英雄技能/面板信息").usage("查询英雄的技能说明与各项数据,生成图片发布。\n参数可输入英雄ID、英雄名、英雄常用别名").option("random", "-r 随机选择英雄").option("refresh", "-f 忽略缓存刷新数据").example("-查询英雄 15").example("-查询英雄 雷泽").example("-查询英雄 电魂").action(async ({ session, options }, input_data) => {
1454
+ if (options.random)
1455
+ input_data = random.pick(Object.keys(HEROES_CHINESE));
1406
1456
  if (input_data) {
1407
- let fhero = findingHero(input_data);
1408
- if (!fhero) {
1457
+ let hero = findingHero(input_data);
1458
+ if (!hero) {
1409
1459
  session.send("未找到输入的英雄,请确认后重新输入。");
1410
1460
  return;
1411
1461
  }
1412
1462
  try {
1413
- let AbilitiesConstantsCN;
1414
- let queryConstants = (await query(CURRENT_GAMEVERSION())).data.constants;
1415
- AbilitiesConstantsCN = (await ctx.database.get("dt_constants_abilities_cn", [1]))[0];
1416
- if (!AbilitiesConstantsCN || AbilitiesConstantsCN.gameVersionId < queryConstants.gameVersions[0].id) {
1417
- session.send("初次使用或版本更新,正在更新英雄技能数据中……");
1418
- AbilitiesConstantsCN = { data: (await query(ALL_ABILITIES_CHINESE_NAME())).data.constants };
1419
- await ctx.database.upsert("dt_constants_abilities_cn", (row) => [{ id: 1, data: AbilitiesConstantsCN, gameVersionId: queryConstants.gameVersions[0].id, gameVersionName: queryConstants.gameVersions[0].name }]);
1463
+ const tempHero = await ctx.database.get("dt_hero_data_cache", hero.id);
1464
+ if (tempHero.length && !options.refresh) {
1465
+ const gameVersionId = (await query(CURRENT_GAMEVERSION())).data.constants.gameVersions[0].id;
1466
+ if (tempHero[0].gameVersionId >= gameVersionId) {
1467
+ hero = tempHero[0].hero;
1468
+ }
1469
+ } else {
1470
+ const queryHero = await queryHeroFromValve(hero.id);
1471
+ Object.assign(hero, queryHero);
1472
+ hero.facet_abilities.forEach((fa, i) => {
1473
+ if (fa.abilities.length) {
1474
+ fa.abilities.forEach((ab) => {
1475
+ if (!hero.facets[i].abilities)
1476
+ hero.facets[i].abilities = [];
1477
+ if (hero.facets[i].description_loc !== ab.desc_loc)
1478
+ hero.facets[i].abilities.push({ id: ab.id, name: ab.name, name_loc: ab.name_loc, description_ability_loc: formatHeroDesc(ab.desc_loc, ab.special_values, "facet" /* Facet */) });
1479
+ else
1480
+ hero.facets[i].description_loc = formatHeroDesc(hero.facets[i].description_loc, ab.special_values, "facet" /* Facet */);
1481
+ hero.abilities.push(ab);
1482
+ });
1483
+ }
1484
+ });
1485
+ const all_special_values = [...hero.abilities.flatMap((ab) => ab.special_values), ...hero.facet_abilities.flatMap((fas) => fas.abilities.flatMap((fa) => fa.special_values))];
1486
+ hero.abilities.forEach((ab) => {
1487
+ ab.facets_loc.forEach((facet, i) => {
1488
+ if (facet) {
1489
+ if (!hero.facets[i].abilities)
1490
+ hero.facets[i].abilities = [];
1491
+ hero.facets[i].abilities.push({ id: ab.id, name: ab.name, name_loc: ab.name_loc, description_ability_loc: formatHeroDesc(facet, all_special_values, "facet" /* Facet */), attributes: [] });
1492
+ }
1493
+ });
1494
+ hero.facets.forEach((facet) => {
1495
+ const svs = ab.special_values.filter((sv) => sv.facet_bonus.name === facet.name);
1496
+ svs.forEach((sv) => {
1497
+ if (sv.heading_loc) {
1498
+ facet.abilities.find((ability) => ab.id == ability.id)?.attributes.push({ heading_loc: sv.heading_loc, values: [...sv.facet_bonus.values] });
1499
+ }
1500
+ });
1501
+ });
1502
+ ab.desc_loc = formatHeroDesc(ab.desc_loc, all_special_values);
1503
+ ab.notes_loc = ab.notes_loc.map((note) => formatHeroDesc(note, all_special_values));
1504
+ if (ab.ability_has_scepter)
1505
+ ab.scepter_loc = formatHeroDesc(ab.scepter_loc, ab.special_values, "scepter" /* Scepter */);
1506
+ if (ab.ability_has_shard)
1507
+ ab.shard_loc = formatHeroDesc(ab.shard_loc, ab.special_values, "shard" /* Shard */);
1508
+ });
1509
+ hero.talents.forEach((talent) => {
1510
+ const regex = /\{s:(.*?)\}/g;
1511
+ let match;
1512
+ while ((match = regex.exec(talent.name_loc)) !== null) {
1513
+ const specialValueName = match[1];
1514
+ const target = talent.special_values?.find((sv) => sv.name === specialValueName);
1515
+ if (target) {
1516
+ talent.name_loc = talent.name_loc.replace(match[0], target.values_float.join("/"));
1517
+ } else {
1518
+ const ability = hero.abilities.find((ability2) => ability2.special_values.some((specialValue) => specialValue.bonuses.some((bonus) => bonus.name === talent.name)));
1519
+ if (ability) {
1520
+ const specialValues = ability.special_values.filter((specialValue) => specialValue.bonuses.some((bonus) => bonus.name === talent.name));
1521
+ const regex2 = /{s:bonus_(.*?)}/g;
1522
+ let match2;
1523
+ const replacements = [];
1524
+ while ((match2 = regex2.exec(talent.name_loc)) !== null) {
1525
+ const specialValue = specialValues.find((sv) => sv.name === String(match2[1]));
1526
+ const replacement = specialValue?.bonuses.find((bonus) => bonus.name === talent.name)?.value;
1527
+ if (replacement !== void 0) {
1528
+ replacements.push({ original: match2[0], replacement });
1529
+ }
1530
+ }
1531
+ replacements.forEach(({ original, replacement }) => {
1532
+ talent.name_loc = talent.name_loc.replace(original, replacement);
1533
+ });
1534
+ }
1535
+ }
1536
+ }
1537
+ });
1538
+ try {
1539
+ const gameVersionId = (await query(CURRENT_GAMEVERSION())).data.constants.gameVersions[0].id;
1540
+ await ctx.database.upsert("dt_hero_data_cache", (row) => [{ id: hero.id, hero, gameVersionId }]);
1541
+ } catch (error) {
1542
+ ctx.logger.error(error);
1543
+ await session.send("数据缓存失败。");
1544
+ }
1420
1545
  }
1421
- let hero = (await query(HERO_INFO(fhero.id))).data.constants.hero;
1422
- hero.talents.forEach((talent) => talent.name_cn = AbilitiesConstantsCN.data.abilities?.find((item) => item.id == talent.abilityId)?.language?.displayName);
1423
1546
  await session.send(await ctx.puppeteer.render(genImageHTML(hero, config.template_hero, "hero" /* Hero */)));
1424
1547
  } catch (error) {
1425
1548
  ctx.logger.error(error);
1426
- session.send("获取数据失败");
1427
- return;
1549
+ await session.send("获取数据失败");
1428
1550
  }
1429
1551
  } else {
1430
1552
  session.send("请输入参数。");
@@ -1466,8 +1588,7 @@ async function apply(ctx, config) {
1466
1588
  let dc_heroes = Object.values(dotaconstants3.heroes).map((hero) => ({
1467
1589
  id: hero["id"],
1468
1590
  name: hero["name"],
1469
- shortName: hero["name"].match(/^npc_dota_hero_(.+)$/)[1],
1470
- localized_name: hero["localized_name"].toLowerCase().replace(/\s+/g, "")
1591
+ shortName: hero["name"].match(/^npc_dota_hero_(.+)$/)[1]
1471
1592
  }));
1472
1593
  let cn_heroes = Object.keys(HEROES_CHINESE).map((key) => ({
1473
1594
  id: parseInt(key),
@@ -1487,157 +1608,6 @@ async function apply(ctx, config) {
1487
1608
  return heroes3.find((hero) => hero.names_cn.some((cn) => cn.toLowerCase() == input.toLowerCase()) || hero.shortName === input.toLowerCase() || hero.id == input);
1488
1609
  }
1489
1610
  __name(findingHero, "findingHero");
1490
- ctx.command("7.36 <input_data>", "查询7.36改动").option("refresh", "-r 重新获取数据").usage("可查询英雄改动并生成图片返回").example("7.36 小松许").action(async ({ session, options }, input_data) => {
1491
- if (!("dt_7_36" in ctx.database.tables))
1492
- await ctx.model.extend("dt_7_36", { id: "integer", data: "string" });
1493
- const tem = await ctx.database.get("dt_7_36", void 0, ["id"]);
1494
- if (!tem.length || options.refresh) {
1495
- try {
1496
- session.send((!tem.length ? "初次使用," : "") + "正在获取数据……");
1497
- await ctx.model.extend("dt_7_36", { id: "integer", data: "string" });
1498
- const page = await ctx.puppeteer.page();
1499
- await page.setExtraHTTPHeaders({
1500
- "Accept-Language": "zh-CN,zh;q=0.9"
1501
- });
1502
- await page.goto("https://www.dota2.com/patches/7.36");
1503
- await page.waitForSelector("body > div:nth-of-type(2) > div:first-of-type > div:nth-of-type(2) > div:nth-of-type(3) > div:nth-of-type(5) > div:nth-of-type(2) > div:nth-of-type(1)");
1504
- await page.evaluate(() => {
1505
- const scripts = document.querySelectorAll("script");
1506
- scripts.forEach((script) => script.remove());
1507
- });
1508
- const result = await page.evaluate(() => {
1509
- try {
1510
- const divs = document.querySelectorAll("body > div:nth-of-type(2) > div:first-of-type > div:nth-of-type(2) > div:nth-of-type(3) > div:nth-of-type(5) > div:nth-of-type(2) > div");
1511
- const divArray = [];
1512
- divs.forEach((div) => {
1513
- const subDiv = div.querySelector("a > div");
1514
- console.log(subDiv);
1515
- const match = subDiv?.style.backgroundImage.match(/\/apps\/dota2\/images\/dota_react\/heroes\/([^"]+)\.png"\)/);
1516
- console.log(match);
1517
- divArray.push({ heroName: match[1], div: div.outerHTML });
1518
- });
1519
- document.querySelectorAll("body > div:nth-of-type(2) > div:first-of-type > div:nth-of-type(2) > div:nth-of-type(3) > div:nth-of-type(5) > div:nth-of-type(2) > div:not(:first-of-type)").forEach((node) => node.remove());
1520
- document.querySelector("body > div:nth-of-type(2) > div:first-of-type > div:nth-of-type(2) > div:nth-of-type(3) > div:nth-of-type(5) > div:nth-of-type(2) > div").classList.add("placeholder");
1521
- const prepareToRemovesNodes = [
1522
- document.querySelector("body > div:first-of-type"),
1523
- document.querySelector("body > div:nth-of-type(2) > div:first-of-type > div:first-of-type"),
1524
- document.querySelector("body > div:nth-of-type(2) > div:first-of-type > div:nth-of-type(2) > div:nth-of-type(1)"),
1525
- document.querySelector("body > div:nth-of-type(2) > div:first-of-type > div:nth-of-type(2) > div:nth-of-type(2)"),
1526
- document.querySelector("body > div:nth-of-type(2) > div:first-of-type > div:nth-of-type(2) > div:nth-of-type(3) > div:nth-of-type(5) > div:nth-of-type(1)"),
1527
- ...document.querySelectorAll("body > div:nth-of-type(2) > div:first-of-type > div:nth-of-type(2) > div:nth-of-type(3) > div:not(:last-of-type)")
1528
- ];
1529
- prepareToRemovesNodes.forEach((node) => node?.remove());
1530
- const remainingContent = document.documentElement.outerHTML;
1531
- return {
1532
- divArray,
1533
- remainingContent
1534
- };
1535
- } catch (error) {
1536
- console.error(error);
1537
- }
1538
- });
1539
- page.close();
1540
- const heroes3 = [];
1541
- result.divArray.forEach((hero) => {
1542
- const res = Object.values(dotaconstants3.heroes).find((Chero) => Chero.name.match(/^npc_dota_hero_(.+)$/)[1] == hero.heroName);
1543
- heroes3.push({ id: res.id, data: hero.div });
1544
- });
1545
- heroes3.push({ id: 0, data: result.remainingContent });
1546
- await ctx.database.upsert("dt_7_36", (row) => heroes3);
1547
- await session.send("数据获取完成。");
1548
- } catch (error) {
1549
- ctx.logger.error(error);
1550
- session.send("数据获取失败。");
1551
- return;
1552
- }
1553
- }
1554
- if (input_data) {
1555
- try {
1556
- const hero = findingHero(input_data);
1557
- if (!hero) {
1558
- session.send("英雄参数输入有误,请检查后重试。");
1559
- return;
1560
- }
1561
- session.send("正在查询,请耐心等待……");
1562
- const page = await ctx.puppeteer.page();
1563
- await page.setRequestInterception(false);
1564
- const [wrapperHTML, newHeroHTML] = (await ctx.database.get("dt_7_36", [0, hero.id])).map((data) => data.data);
1565
- await page.setContent(wrapperHTML);
1566
- await page.waitForSelector("div.placeholder");
1567
- const placeholder = await page.$("div.placeholder");
1568
- await page.waitForSelector("div.placeholder");
1569
- await page.evaluate(
1570
- (element, html) => {
1571
- element.outerHTML = html;
1572
- },
1573
- placeholder,
1574
- newHeroHTML
1575
- );
1576
- await page.evaluate(async () => {
1577
- const images = Array.from(document.querySelectorAll("img"));
1578
- const backgroundImages = Array.from(document.querySelectorAll("*")).filter((element) => {
1579
- const bg = window.getComputedStyle(element).backgroundImage;
1580
- return bg && bg !== "none";
1581
- });
1582
- await Promise.all([
1583
- ...images.map((img) => {
1584
- if (img.complete)
1585
- return Promise.resolve();
1586
- else {
1587
- return new Promise((resolve) => {
1588
- img.onload = resolve;
1589
- img.onerror = () => {
1590
- const placeholderSrc = "https://cdn.cloudflare.steamstatic.com/apps/dota2/images/dota_react/icons/innate_icon.png";
1591
- img.src = placeholderSrc;
1592
- img.onload = resolve;
1593
- img.onerror = resolve;
1594
- };
1595
- });
1596
- }
1597
- }),
1598
- ...backgroundImages.map((element) => {
1599
- const bg = window.getComputedStyle(element).backgroundImage;
1600
- const urlMatch = bg.match(/url\(["']?([^"')]+)["']?\)/);
1601
- if (urlMatch && urlMatch[1]) {
1602
- const src = urlMatch[1];
1603
- return new Promise((resolve) => {
1604
- const img = new Image();
1605
- img.onload = resolve;
1606
- img.onerror = () => {
1607
- const placeholderSrc = "https://cdn.cloudflare.steamstatic.com/apps/dota2/images/dota_react/icons/innate_icon.png";
1608
- img.src = placeholderSrc;
1609
- img.onload = resolve;
1610
- img.onerror = resolve;
1611
- };
1612
- img.src = src;
1613
- });
1614
- } else
1615
- return Promise.resolve();
1616
- })
1617
- ]);
1618
- await new Promise((resolve) => setTimeout(resolve, 500));
1619
- });
1620
- const testE = await page.$("body > div > div > div > div > div > div > div");
1621
- const res = await testE.screenshot();
1622
- const base64String = Buffer.from(res).toString("base64");
1623
- const imgTag = `<img src="data:image/png;base64,${base64String}" alt="Image" />`;
1624
- if (process.env.NODE_ENV === "development")
1625
- import_fs2.default.writeFileSync("./node_modules/@sjtdev/koishi-plugin-dota2tracker/temp.png", res);
1626
- if (process.env.NODE_ENV === "development")
1627
- import_fs2.default.writeFileSync("./node_modules/@sjtdev/koishi-plugin-dota2tracker/temp.html", await page.content());
1628
- session.send(imgTag);
1629
- page.close();
1630
- } catch (error) {
1631
- ctx.logger.error(error);
1632
- session.send("查询改动失败。");
1633
- }
1634
- } else
1635
- session.send("https://www.dota2.com/patches/7.36");
1636
- });
1637
- ctx.command("test <input_data>").option("a", "a").action(async ({ session, options }, input_data) => {
1638
- console.log(session);
1639
- ctx.broadcast(["kook:9510442027074966"], "test");
1640
- });
1641
1611
  ctx.on("ready", async () => {
1642
1612
  const tables = await ctx.database.tables;
1643
1613
  if (!("dt_subscribed_guilds" in tables)) {
@@ -1652,8 +1622,8 @@ async function apply(ctx, config) {
1652
1622
  if (!("dt_previous_query_results" in tables)) {
1653
1623
  ctx.model.extend("dt_previous_query_results", { matchId: "unsigned", data: "json", queryTime: "timestamp" }, { primary: "matchId" });
1654
1624
  }
1655
- if (!("dt_constants_abilities_cn" in tables)) {
1656
- ctx.model.extend("dt_constants_abilities_cn", { id: "unsigned", data: "json", gameVersionId: "unsigned", gameVersionName: "string" }, { primary: "id" });
1625
+ if (!("dt_hero_data_cache" in tables)) {
1626
+ ctx.model.extend("dt_hero_data_cache", { id: "unsigned", gameVersionId: "unsigned", hero: "json" });
1657
1627
  }
1658
1628
  ctx.cron("0 */6 * * *", () => {
1659
1629
  const oneMonthAgo = (0, import_moment.default)().subtract(1, "months").toDate();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sjtdev/koishi-plugin-dota2tracker",
3
3
  "description": "koishi插件-追踪群友的DOTA2对局",
4
- "version": "1.1.10-hotfix",
4
+ "version": "1.2.0-hotfix",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -26,7 +26,7 @@
26
26
  "dota2"
27
27
  ],
28
28
  "dependencies": {
29
- "dotaconstants": "^8.7.0",
29
+ "dotaconstants": "^8.8.0",
30
30
  "ejs": "^3.1.10",
31
31
  "moment": "^2.30.1"
32
32
  },
@@ -131,21 +131,42 @@
131
131
  line-height: 1.25;
132
132
  }
133
133
 
134
+ .details .hype .npe {
135
+ color: #a5e0f3;
136
+ font-weight: bold;
137
+ line-height: 2;
138
+ }
139
+
134
140
  .talents {
135
- display: flex; /* 启用Flex布局 */
136
- flex-wrap: wrap; /* 允许元素换行 */
137
- justify-content: center; /* 水平居中整体内容 */
141
+ display: grid;
142
+ grid-template-rows: repeat(4, 35px);
143
+ gap: 10px;
138
144
  border: #444 10px solid;
139
- /* background-color: ; */
140
- /* padding: 10px; */
141
145
  box-sizing: border-box;
146
+ position: relative; /* 添加相对定位 */
147
+ }
148
+
149
+ .talents::before {
150
+ content: "";
151
+ position: absolute;
152
+ top: 0;
153
+ left: 0;
154
+ right: 0;
155
+ bottom: 0;
156
+ background: #444;
157
+ pointer-events: none; /* 使伪元素不可点击 */
158
+ box-sizing: border-box;
159
+ width: 395px;
160
+ transform: translate(-10px, -10px); /* 调整伪元素的位置 */
142
161
  }
143
162
 
144
163
  .talents .talent {
145
164
  width: 375px;
146
- line-height: 35px;
147
165
  text-align: center;
148
166
  display: flex;
167
+ align-items: center;
168
+ background: #000; /* 设置背景颜色以覆盖伪元素的背景 */
169
+ position: relative; /* 添加相对定位 */
149
170
  }
150
171
 
151
172
  .talents .talent .left,
@@ -154,13 +175,9 @@
154
175
  font-size: 12px;
155
176
  }
156
177
 
157
- .talents .talent:not(:last-child) {
158
- border-bottom: 10px solid #444;
159
- }
160
-
161
178
  .talents .talent .level {
162
- flex: 0 0 auto; /* 第二个元素不伸缩,保持自身宽度 */
163
- width: 35px; /* 给中间元素一个固定宽度 */
179
+ flex: 0 0 auto;
180
+ width: 35px;
164
181
  height: 35px;
165
182
  font-size: 18px;
166
183
  line-height: 35px;
@@ -170,8 +187,6 @@
170
187
  color: #e7d292;
171
188
  text-shadow: 0px 0px 8px #ff531c;
172
189
  background-color: #444;
173
- /* font-family: Reaver, serif;
174
- font-weight: bold; */
175
190
  }
176
191
 
177
192
  .details .list {
@@ -186,6 +201,128 @@
186
201
  background-color: #333;
187
202
  }
188
203
 
204
+ .facets {
205
+ display: flex;
206
+ flex-wrap: wrap;
207
+ gap: 10px; /* 可选:设置项目之间的间距 */
208
+ }
209
+
210
+ .facet {
211
+ flex: 1 1 calc(50% - 10px); /* 每行两个项目 */
212
+ box-sizing: border-box; /* 包含padding和border在宽度和高度的计算中 */
213
+ background-color: #181f24;
214
+ position: relative;
215
+ border: 1px solid #2b2f33;
216
+ }
217
+
218
+ .facet:nth-child(odd):last-child {
219
+ flex-basis: 100%; /* 最后一个奇数项目占据整行 */
220
+ }
221
+ .facet > .name_back {
222
+ position: absolute;
223
+ height: 50px;
224
+ width: 100%;
225
+ }
226
+ .facet > .name_back.type_0 {
227
+ background: linear-gradient(to right, #9f3c3c, #4a2026);
228
+ }
229
+ .facet > .name_line.type_0 {
230
+ filter: invert(22%) sepia(100%) saturate(100%) hue-rotate(316deg) brightness(98%) contrast(100%);
231
+ }
232
+ .facet > .name_back.type_1 {
233
+ background: linear-gradient(to right, #c8a45c, #6f3d21);
234
+ }
235
+ .facet > .name_line.type_1 {
236
+ filter: invert(54%) sepia(99%) saturate(100%) hue-rotate(0deg) brightness(97%) contrast(100%);
237
+ }
238
+ .facet > .name_back.type_2 {
239
+ background: linear-gradient(to right, #a2b23e, #2d5a18);
240
+ }
241
+ .facet > .name_line.type_2 {
242
+ filter: invert(57%) sepia(100%) saturate(100%) hue-rotate(32deg) brightness(93%) contrast(100%);
243
+ }
244
+ .facet > .name_back.type_3 {
245
+ background: linear-gradient(to right, #547ea6, #2a385e);
246
+ }
247
+ .facet > .name_line.type_3 {
248
+ filter: invert(39%) sepia(100%) saturate(99%) hue-rotate(167deg) brightness(99%) contrast(100%);
249
+ }
250
+ .facet > .name_back.type_4 {
251
+ background: linear-gradient(to right, #675cae, #261c44);
252
+ }
253
+ .facet > .name_line.type_4 {
254
+ filter: invert(33%) sepia(100%) saturate(100%) hue-rotate(207deg) brightness(99%) contrast(100%);
255
+ }
256
+ .facet > .name_back.type_5 {
257
+ background: linear-gradient(to right, #adb6be, #4e5557);
258
+ }
259
+ .facet > .name_line.type_5 {
260
+ filter: invert(73%) sepia(23%) saturate(99%) hue-rotate(166deg) brightness(93%) contrast(94%);
261
+ }
262
+ .facet > .name_line {
263
+ position: absolute;
264
+ background-size: cover;
265
+ height: 50px;
266
+ width: 100%;
267
+ background-image: url("https://cdn.akamai.steamstatic.com/apps/dota2/images/dota_react/icons/facets/ripple_texture.png");
268
+ }
269
+ .facet > .name {
270
+ height: 50px;
271
+ line-height: 50px;
272
+ z-index: 1;
273
+ position: relative;
274
+ display: flex;
275
+ }
276
+ .facet > .name > img {
277
+ width: 24px;
278
+ padding: 13px;
279
+ background-color: #0003;
280
+ }
281
+ .facet > .name > span {
282
+ margin-left: 16px;
283
+ letter-spacing: 2px;
284
+ text-shadow: 2px 2px 3px rgba(0, 0, 0, 0.3), 4px 4px 6px rgba(0, 0, 0, 0.2), 6px 6px 9px rgba(0, 0, 0, 0.1);
285
+ }
286
+ .facet > .content {
287
+ padding: 12px;
288
+
289
+ display: flex;
290
+ flex-direction: column;
291
+ gap: 12px;
292
+ }
293
+ .facet > .content > .ability {
294
+ display: flex;
295
+ flex-direction: column;
296
+ gap: 12px;
297
+ }
298
+ .facet > .content > .ability > .name {
299
+ background: linear-gradient(to right, #9bcdff17 0%, #9bcdff09 30%, #d0e8ff00 100%);
300
+ line-height: 1;
301
+ }
302
+ .facet > .content > .ability > .name > img {
303
+ width: 30px;
304
+ }
305
+ .facet > .content > .ability > .name > span {
306
+ margin-left: 10px;
307
+ font-size: 14px;
308
+ /* font-weight: bold; */
309
+ }
310
+ .facet > .content .description {
311
+ color: #9ab0cd;
312
+ }
313
+ .facet > .content > .ability > .attributes {
314
+ font-size: 12px;
315
+ display: flex;
316
+ flex-direction: column;
317
+ gap: 5px;
318
+ }
319
+ .facet > .content > .ability > .attributes .item {
320
+ color: #737373;
321
+ }
322
+ .facet > .content .value {
323
+ color: #fff;
324
+ }
325
+
189
326
  .skills {
190
327
  width: 800px;
191
328
  display: flex;
@@ -209,8 +346,19 @@
209
346
  background-color: #1f272b;
210
347
  padding: 8px;
211
348
  font-weight: 100;
349
+ }
350
+ .skill > .title > .name {
212
351
  font-family: "KaiTi", "楷体", "楷体_GB2312", "STKaiti", serif;
213
352
  }
353
+ .skill > .title > .is_innate {
354
+ font-size: 14px;
355
+ line-height: 18px;
356
+ width: auto;
357
+ display: inline;
358
+ padding: 2px 8px;
359
+ box-sizing: content-box;
360
+ background-color: #5b93d1;
361
+ }
214
362
 
215
363
  .skill img.scepter,
216
364
  .skill img.shard {
@@ -272,6 +420,14 @@
272
420
  margin-bottom: 32px;
273
421
  }
274
422
 
423
+ .skill .facet {
424
+ padding-left: 0;
425
+ }
426
+
427
+ .skill .value{
428
+ color: #fff;
429
+ }
430
+
275
431
  .skill .aghanim_description {
276
432
  padding-left: 0;
277
433
  color: #9bb1ce;
@@ -293,7 +449,7 @@
293
449
  display: block;
294
450
  }
295
451
 
296
- .skill .aghanim_description img{
452
+ .skill .aghanim_description img {
297
453
  position: unset;
298
454
  transform: none;
299
455
  }
@@ -317,6 +473,10 @@
317
473
  color: #4b525d;
318
474
  }
319
475
 
476
+ .skill .attributes .values > img {
477
+ width: 16px;
478
+ }
479
+
320
480
  .skill .attributes {
321
481
  margin-bottom: 12px;
322
482
  }
@@ -365,45 +525,46 @@
365
525
  <div class="wrapper">
366
526
  <%- `
367
527
  <div class="hero" id="${hero.id}">
368
- <img src="${utils.getImageUrl(hero.shortName, ImageType.Heroes)}" alt="" />
369
- <img class="pri_attr" src="${utils.getImageUrl(d2a.primary_attrs[dotaconstants.heroes[hero.id].primary_attr], ImageType.Icons)}" alt="" />
528
+ <img src="${utils.getImageUrl(hero.shortName, ImageType.Heroes)}"/>
529
+ <img class="pri_attr" src="${utils.getImageUrl(d2a.primary_attrs[hero.primary_attr], ImageType.Icons)}"/>
370
530
  <div class="info">
371
- <p class="name">${hero.language.displayName}</p>
531
+ <p class="name">${hero.name_loc}</p>
372
532
  <p class="roles">
373
- ${hero.roles.map((item) => `<span class="role level${item.level}">${d2a.roles[item.roleId]}</span>`).join("")}
533
+ ${hero.role_levels.map((item, index) => item>0?`<span class="role level${item}">${d2a.roles[index]}</span>`:"").join("")}
374
534
  </p>
375
535
  <p class="attrs">
376
- <span class="str">${dotaconstants.heroes[hero.id].base_str} <span class="gain">+${dotaconstants.heroes[hero.id].str_gain.toFixed(1)}</span></span>
377
- <span class="agi">${dotaconstants.heroes[hero.id].base_agi} <span class="gain">+${dotaconstants.heroes[hero.id].agi_gain.toFixed(1)}</span></span>
378
- <span class="int">${dotaconstants.heroes[hero.id].base_int} <span class="gain">+${dotaconstants.heroes[hero.id].int_gain.toFixed(1)}</span></span>
536
+ <span class="str">${hero.str_base} <span class="gain">+${hero.str_gain.toFixed(1)}</span></span>
537
+ <span class="agi">${hero.agi_base} <span class="gain">+${hero.agi_gain.toFixed(1)}</span></span>
538
+ <span class="int">${hero.int_base} <span class="gain">+${hero.int_gain.toFixed(1)}</span></span>
379
539
  </p>
380
540
  </div>
381
541
  </div>
382
542
  <div class="details">
383
543
  <div class="hype_talents">
384
544
  <div class="hype">
385
- ${hero.language.hype}
545
+ <p class="npe">${hero.npe_desc_loc}</p>
546
+ ${hero.hype_loc}
386
547
  </div>
387
548
  <div class="talents">
388
549
  <div class="talent">
389
- <div class="left">${hero.talents[7].name_cn}</div>
550
+ <div class="left">${hero.talents[7].name_loc}</div>
390
551
  <div class="level">25</div>
391
- <div class="right">${hero.talents[6].name_cn}</div>
552
+ <div class="right">${hero.talents[6].name_loc}</div>
392
553
  </div>
393
554
  <div class="talent">
394
- <div class="left">${hero.talents[5].name_cn}</div>
555
+ <div class="left">${hero.talents[5].name_loc}</div>
395
556
  <div class="level">20</div>
396
- <div class="right">${hero.talents[4].name_cn}</div>
557
+ <div class="right">${hero.talents[4].name_loc}</div>
397
558
  </div>
398
559
  <div class="talent">
399
- <div class="left">${hero.talents[3].name_cn}</div>
560
+ <div class="left">${hero.talents[3].name_loc}</div>
400
561
  <div class="level">15</div>
401
- <div class="right">${hero.talents[2].name_cn}</div>
562
+ <div class="right">${hero.talents[2].name_loc}</div>
402
563
  </div>
403
564
  <div class="talent">
404
- <div class="left">${hero.talents[1].name_cn}</div>
565
+ <div class="left">${hero.talents[1].name_loc}</div>
405
566
  <div class="level">10</div>
406
- <div class="right">${hero.talents[0].name_cn}</div>
567
+ <div class="right">${hero.talents[0].name_loc}</div>
407
568
  </div>
408
569
  </div>
409
570
  </div>
@@ -411,36 +572,27 @@
411
572
  <tbody>
412
573
  <tr>
413
574
  <td>初始生命值</td>
414
- <td>${dotaconstants.heroes[hero.id].base_health + dotaconstants.heroes[hero.id].base_str * 22}</td>
575
+ <td>${hero.max_health}</td>
415
576
  </tr>
416
577
  <tr>
417
578
  <td>初始生命回复</td>
418
- <td>${dotaconstants.heroes[hero.id].base_health_regen}</td>
579
+ <td>${hero.health_regen.toFixed(2)}</td>
419
580
  </tr>
420
581
  <tr>
421
582
  <td>初始魔法值</td>
422
- <td>${dotaconstants.heroes[hero.id].base_mana + dotaconstants.heroes[hero.id].base_int * 12}</td>
583
+ <td>${hero.max_mana}</td>
423
584
  </tr>
424
585
  <tr>
425
586
  <td>初始魔法回复</td>
426
- <td>${dotaconstants.heroes[hero.id].base_mana_regen}</td>
587
+ <td>${hero.mana_regen.toFixed(2)}</td>
427
588
  </tr>
428
589
  <tr>
429
590
  <td>初始攻击力</td>
430
- <td>${dotaconstants.heroes[hero.id].base_mr + Math.round(dotaconstants.heroes[hero.id].primary_attr == "all" ?
431
- (dotaconstants.heroes[hero.id].base_str + dotaconstants.heroes[hero.id].base_agi + dotaconstants.heroes[hero.id].base_int) * 0.7
432
- : dotaconstants.heroes[hero.id]["base_" + dotaconstants.heroes[hero.id].primary_attr])}
433
- (${dotaconstants.heroes[hero.id].base_attack_min + Math.round(dotaconstants.heroes[hero.id].primary_attr == "all" ?
434
- (dotaconstants.heroes[hero.id].base_str + dotaconstants.heroes[hero.id].base_agi + dotaconstants.heroes[hero.id].base_int) * 0.7
435
- : dotaconstants.heroes[hero.id]["base_" + dotaconstants.heroes[hero.id].primary_attr])}
436
- ~${dotaconstants.heroes[hero.id].base_attack_max + Math.round(dotaconstants.heroes[hero.id].primary_attr == "all" ?
437
- (dotaconstants.heroes[hero.id].base_str + dotaconstants.heroes[hero.id].base_agi + dotaconstants.heroes[hero.id].base_int) * 0.7
438
- : dotaconstants.heroes[hero.id]["base_" + dotaconstants.heroes[hero.id].primary_attr])})
439
- </td>
591
+ <td>${hero.damage_min}~${hero.damage_max}</td>
440
592
  </tr>
441
593
  <tr>
442
594
  <td>基础攻击间隔</td>
443
- <td>${dotaconstants.heroes[hero.id].attack_rate.toFixed(1)}</td>
595
+ <td>${hero.attack_capability.toFixed(1)}</td>
444
596
  </tr>
445
597
  <tr>
446
598
  <td>基础攻击前摇</td>
@@ -448,96 +600,163 @@
448
600
  </tr>
449
601
  <tr>
450
602
  <td>攻击范围</td>
451
- <td>${dotaconstants.heroes[hero.id].attack_range}</td>
603
+ <td>${hero.attack_range}</td>
452
604
  </tr>
453
605
  <tr>
454
606
  <td>护甲</td>
455
- <td>${(Math.round((dotaconstants.heroes[hero.id].base_armor + dotaconstants.heroes[hero.id].base_agi * 0.167) * 10) / 10).toFixed(1)}</td>
607
+ <td>${hero.armor.toFixed(1)}</td>
456
608
  </tr>
457
609
  <tr>
458
610
  <td>移动速度</td>
459
- <td>${dotaconstants.heroes[hero.id].move_speed}</td>
611
+ <td>${hero.movement_speed}</td>
460
612
  </tr>
461
613
  <tr>
462
614
  <td>视野范围</td>
463
- <td>${dotaconstants.heroes[hero.id].day_vision}(${dotaconstants.heroes[hero.id].night_vision})</td>
615
+ <td>${hero.sight_range_day}(${hero.sight_range_night})</td>
464
616
  </tr>
465
617
  </tbody>
466
618
  </table>
467
619
  </div>
620
+ <div class="facets">
621
+ ${hero.facets.map(facet=>`
622
+ <div class="facet">
623
+ <div class="name_back type_${facet.color}"></div>
624
+ <div class="name_line type_${facet.color}"></div>
625
+ <p class="name">
626
+ <img src="${utils.getImageUrl(facet.icon, ImageType.IconsFacets)}"/>
627
+ <span>${facet.title_loc}</span>
628
+ </p>
629
+ <div class="content">
630
+ ${facet.description_loc ?`<p class="description">${facet.description_loc}</p>`:""}
631
+ ${facet.abilities?facet.abilities.map(ability=>
632
+ `<div class="ability">
633
+ <div class="name">
634
+ <img src="${utils.getImageUrl(ability.name, ImageType.Abilities)}" onerror="this.onerror=null; this.src='${utils.getImageUrl(`innate_icon`,ImageType.Icons)}';"/>
635
+ <span>${ability.name_loc}</span>
636
+ </div>
637
+ ${ability.description_ability_loc?`<div class="description">${ability.description_ability_loc}</div>`:""}
638
+ ${ability.attributes&&ability.attributes?.length ? ability.attributes.map(attr=>
639
+ `<div class="attributes">
640
+ <p><span class="item">${attr.heading_loc}</span><span class="values">${attr.values.join(" / ")}</span></p>
641
+ </div>`).join(""):""}
642
+ </div>`).join("")
643
+ :""}
644
+ </div>
645
+ </div>
646
+ `).join("")}
647
+ </div>
468
648
  <div class="skills">
469
- ${hero.abilities.filter((item) => dotaconstants.abilities[item.ability.name].behavior != "Hidden")
649
+ ${hero.abilities//.filter((item) => dotaconstants.abilities[item.name].behavior != "Hidden")
470
650
  .map((item) => `
471
- <div class="skill">
651
+ <div class="skill" data-ability="${item.ability_is_innate}">
472
652
  <p class="title">
473
- <span>${item.ability.language.displayName}</span>
474
- ${item.ability.stat.isGrantedByScepter ?`<img src="${utils.getImageUrl("scepter")}" alt="" class="scepter"></p>`:""}
475
- ${item.ability.stat.isGrantedByShard ?`<img src="${utils.getImageUrl("shard")}" alt="" class="shard"></p>`:""}
653
+ <span class="name">${item.name_loc}</span>
654
+ ${item.ability_is_innate?`<span class="is_innate">先天技能</span>`:""}
655
+ ${item.ability_is_granted_by_scepter ?`<img src="${utils.getImageUrl("scepter")}" class="scepter">`:""}
656
+ ${item.ability_is_granted_by_shard ?`<img src="${utils.getImageUrl("shard")}" class="shard">`:""}
476
657
  </p>
477
- <div class="img_stats">
478
- <img src="${utils.getImageUrl(item.ability.name, ImageType.Abilities)}" alt="" />
479
- <div class="stats">
480
- <p class="behavior">技能:${(Array.isArray(dotaconstants.abilities[item.ability.name].behavior) ? dotaconstants.abilities[item.ability.name].behavior : [dotaconstants.abilities[item.ability.name].behavior])
481
- .filter((beh) => beh !== "Hidden" || !(item.ability.stat.isGrantedByShard || item.ability.stat.isGrantedByScepter))
482
- .map((beh) => d2a.behavior[beh])
483
- .join("/")}</p>
484
- ${dotaconstants.abilities[item.ability.name].target_team
485
- ? `<p class="target_team">影响:${(Array.isArray(dotaconstants.abilities[item.ability.name].target_team)
486
- ? dotaconstants.abilities[item.ability.name].target_team
487
- : [dotaconstants.abilities[item.ability.name].target_team])
488
- .map((tt) => d2a.target_team[tt])
489
- .join("/")}</p>`
490
- : ""}
491
- ${!Array.isArray(dotaconstants.abilities[item.ability.name].dmg_type) && dotaconstants.abilities[item.ability.name].dmg_type
492
- ? `<p class="dmg_type ${dotaconstants.abilities[item.ability.name].dmg_type}">伤害类型:</p>`
493
- : ""}
494
- ${dotaconstants.abilities[item.ability.name].dispellable
495
- ? `<p class="dispellable ${dotaconstants.abilities[item.ability.name].dispellable == "Strong Dispels Only" ? "Strong" : dotaconstants.abilities[item.ability.name].dispellable}">能否驱散:</p>`
496
- : ""}
497
- ${!Array.isArray(dotaconstants.abilities[item.ability.name].bkbpierce) && dotaconstants.abilities[item.ability.name].bkbpierce
498
- ? `<p class="bkbpierce">无视减益免疫: ${dotaconstants.abilities[item.ability.name].bkbpierce == "Yes" ? "是" : "否"}</p>`
499
- : ""}
658
+ <div class="img_stats">
659
+ <img src="${utils.getImageUrl(item.name, ImageType.Abilities)}" onerror="this.onerror=null; this.src='${utils.getImageUrl(`innate_icon`,ImageType.Icons)}';"/>
660
+ <div class="stats">
661
+ <p class="behavior">技能:${(Array.isArray(dotaconstants.abilities[item.name].behavior) ? dotaconstants.abilities[item.name].behavior : [dotaconstants.abilities[item.name].behavior])
662
+ .filter((beh) => beh !== "Hidden" || !(item.ability_is_granted_by_shard || item.ability_is_granted_by_scepter))
663
+ .map((beh) => d2a.behavior[beh])
664
+ .join("/")}</p>
665
+ ${dotaconstants.abilities[item.name].target_team
666
+ ? `<p class="target_team">影响:${(Array.isArray(dotaconstants.abilities[item.name].target_team)
667
+ ? dotaconstants.abilities[item.name].target_team
668
+ : [dotaconstants.abilities[item.name].target_team])
669
+ .map((tt) => d2a.target_team[tt])
670
+ .join("/")}</p>`
671
+ : ""}
672
+ ${!Array.isArray(dotaconstants.abilities[item.name].dmg_type) && dotaconstants.abilities[item.name].dmg_type
673
+ ? `<p class="dmg_type ${dotaconstants.abilities[item.name].dmg_type}">伤害类型:</p>`
674
+ : ""}
675
+ ${dotaconstants.abilities[item.name].dispellable
676
+ ? `<p class="dispellable ${dotaconstants.abilities[item.name].dispellable == "Strong Dispels Only" ? "Strong" : dotaconstants.abilities[item.name].dispellable}">能否驱散:</p>`
677
+ : ""}
678
+ ${!Array.isArray(dotaconstants.abilities[item.name].bkbpierce) && dotaconstants.abilities[item.name].bkbpierce
679
+ ? `<p class="bkbpierce">无视减益免疫: ${dotaconstants.abilities[item.name].bkbpierce == "Yes" ? "是" : "否"}</p>`
680
+ : ""}
681
+ </div>
500
682
  </div>
501
- </div>
502
- ${item.ability.language.description.map((desc) => `<p class="description">${desc}</p>`).join("")}
503
- ${item.ability.language.aghanimDescription
504
- ? `<p class="aghanim_description">
505
- <span class="title"><img src="${utils.getImageUrl("scepter")}" alt="" class="scepter">阿哈利姆神杖</span>
506
- <span class="desc">${item.ability.language.aghanimDescription}</span>
507
- </p>` : ""}
508
- ${item.ability.language.shardDescription
509
- ? `<p class="aghanim_description">
510
- <span class="title"><img src="${utils.getImageUrl("shard")}" alt="" class="shard">阿哈利姆魔晶</span>
511
- <span class="desc">${item.ability.language.shardDescription}</span>
512
- </p>` : ""}
513
- <div class="notes"${!item.ability.language.notes.length ? ` style="display:none;"` : ""}>
514
- ${item.ability.language.notes.map((note) => `<p>${note}</p>`).join("")}
515
- </div>
516
- <div class="attributes">
517
- ${item.ability.language.attributes
518
- .map((attr) => {
519
- const parts = attr.split("");
520
- return `<p><span class="item">${parts[0]}</span><span class="values">${parts[1]}</span></p>`;
521
- })
522
- .join("")}
523
- </div>
524
- <p>
525
- ${dotaconstants.abilities[item.ability.name].cd ?
526
- `<span class="cooldown"> ${(Array.isArray(dotaconstants.abilities[item.ability.name].cd)
527
- ? dotaconstants.abilities[item.ability.name].cd
528
- : [dotaconstants.abilities[item.ability.name].cd]).join(" / ")}
529
- </span>` : ""}
530
- ${dotaconstants.abilities[item.ability.name].mc ?
531
- `<span class="mana_cost"> ${(Array.isArray(dotaconstants.abilities[item.ability.name].mc)
532
- ? dotaconstants.abilities[item.ability.name].mc
533
- : [dotaconstants.abilities[item.ability.name].mc]).join(" / ")}
534
- </span>` : ""}
535
- </p>
536
- <p class="lore"${!item.ability.language.lore ? ` style="display:none;"` : ""}>${item.ability.language.lore}</p>
537
- </div>`).join("")}
683
+ <p class="description">${item.desc_loc}</p>
684
+ ${item.facets_loc.map((facet_loc,index)=>(facet_loc!=""?`
685
+ <div class="facet">
686
+ <div class="name_back type_${hero.facets[index].color}"></div>
687
+ <div class="name_line type_${hero.facets[index].color}"></div>
688
+ <p class="name">
689
+ <img src="${utils.getImageUrl(hero.facets[index].icon, ImageType.IconsFacets)}" />
690
+ <span>${hero.facets[index].title_loc}</span>
691
+ </p>
692
+ <div class="content">
693
+ <div class="ability">
694
+ <div class="description">${hero.facets[index].abilities.find(ab=>ab.id==item.id)?.description_ability_loc}</div>
695
+ </div>
696
+ </div>
697
+ </div>
698
+ `:"")).join("")}
699
+ ${item.ability_has_scepter&&!item.ability_is_granted_by_scepter
700
+ ? `<p class="aghanim_description">
701
+ <span class="title"><img src="${utils.getImageUrl("scepter")}"class="scepter">阿哈利姆神杖</span>
702
+ <span class="desc">${item.scepter_loc}</span>
703
+ </p>` : ""}
704
+ ${item.ability_has_shard&&!item.ability_is_granted_by_shard
705
+ ? `<p class="aghanim_description">
706
+ <span class="title"><img src="${utils.getImageUrl("shard")}"class="shard">阿哈利姆魔晶</span>
707
+ <span class="desc">${item.shard_loc}</span>
708
+ </p>` : ""}
709
+ <div class="notes"${!item.notes_loc.length ? ` style="display:none;"` : ""}>
710
+ ${item.notes_loc.map((note) => `<p>${note}</p>`).join("")}
711
+ </div>
712
+ <div class="attributes">
713
+ ${item.special_values
714
+ .filter(sv => sv.heading_loc)
715
+ .map((sv) => `<p><span class="item">${sv.heading_loc}</span><span class="values">${sv.values_float.map(value=>value+(sv.is_percentage?"%":"")).join(" / ")}${sv.bonuses.map(bonus=>` (<img src="${utils.getImageUrl("talents","icons","svg")}"/>${(bonus.value>0?"+":"")+bonus.value+(sv.is_percentage?"%":"")})`).join(" ")}</span></p>`)
716
+ .join("")}
717
+ </div>
718
+ <p>
719
+ ${item.special_values.find(sv=>sv.name=="AbilityCooldown").values_float.length && !(item.special_values.find(sv=>sv.name=="AbilityCooldown").values_float.length === 1 && item.special_values.find(sv=>sv.name=="AbilityCooldown").values_float[0] === 0) ?
720
+ `<span class="cooldown"> ${item.special_values.find(sv=>sv.name=="AbilityCooldown").values_float.join(" / ")}
721
+ </span>` : ""}
722
+ ${item.special_values.find(sv=>sv.name=="AbilityManaCost").values_float.length && !(item.special_values.find(sv=>sv.name=="AbilityManaCost").values_float.length === 1 && item.special_values.find(sv=>sv.name=="AbilityManaCost").values_float[0] === 0) ?
723
+ `<span class="mana_cost"> ${item.special_values.find(sv=>sv.name=="AbilityManaCost").values_float.join(" / ")}
724
+ </span>` : ""}
725
+ </p>
726
+ <p class="lore"${!item.lore_loc ? ` style="display:none;"` : ""}>${item.lore_loc}</p>
727
+ </div>`).join("")}
728
+ </div>
729
+ <div class="lore">
730
+ ${hero.bio_loc}
731
+ </div>` %>
538
732
  </div>
539
- <div class="lore">
540
- ${hero.language.lore}
541
- </div>` %>
542
733
  </body>
734
+ <script>
735
+ document.addEventListener('DOMContentLoaded', function() {
736
+ const items = document.querySelectorAll('.skills > .skill');
737
+ items.forEach(item => {
738
+ // const name = item.getAttribute('data-name');
739
+ const abilityIsInnate = item.getAttribute('data-ability') === 'true';
740
+ const img = item.querySelector('.img_stats > img');
741
+ const imageUrl = img.src;
742
+
743
+ // Check if image exists
744
+ const image = new Image();
745
+ image.src = imageUrl;
746
+ image.onload = function() {
747
+ // Image exists, do nothing
748
+ };
749
+ image.onerror = function() {
750
+ // Image doesn't exist
751
+ if (abilityIsInnate) {
752
+ item.style.order = -1;
753
+ item.style.flexBasis = "100%";
754
+ img.src = '<%- utils.getImageUrl("innate_icon",ImageType.Icons) %>'; // Set backup image URL
755
+ // item.querySelector(".cooldown").style.display = "none";
756
+ // item.querySelector(".mana_cost").style.display = "none";
757
+ }
758
+ };
759
+ });
760
+ });
761
+ </script>
543
762
  </html>