@sjtdev/koishi-plugin-dota2tracker 2.2.3 → 2.3.1

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.
Files changed (105) hide show
  1. package/changelog.md +28 -0
  2. package/lib/index.js +200 -162
  3. package/{queries → lib/queries}/MatchInfo.graphql +13 -0
  4. package/lib/templates/common/components/building_icons.ejs +20 -0
  5. package/lib/templates/common/styles/normalize.min.css +1 -0
  6. package/lib/templates/hero/hero_1.ejs +69 -0
  7. package/lib/templates/images/7.38_simple_minimap.png +0 -0
  8. package/lib/templates/item/item/recipe.ejs +9 -0
  9. package/lib/templates/item/item/style.css +1 -0
  10. package/lib/templates/item/item.ejs +52 -0
  11. package/lib/templates/item/itemlist.ejs +11 -0
  12. package/lib/templates/match/match_1/base.css +1 -0
  13. package/lib/templates/match/match_1/item.ejs +1 -0
  14. package/lib/templates/match/match_1/main.ejs +8 -0
  15. package/lib/templates/match/match_1/player.ejs +1 -0
  16. package/lib/templates/match/match_1/style.css +1 -0
  17. package/lib/templates/match/match_1.ejs +18 -0
  18. package/lib/templates/match/match_2/original.css +1 -0
  19. package/lib/templates/match/match_2/original.ejs +10 -0
  20. package/lib/templates/match/match_2+/charts.ejs +1 -0
  21. package/lib/templates/match/match_2+/extra.css +1 -0
  22. package/lib/templates/match/match_2+/lane_outcome.ejs +56 -0
  23. package/lib/templates/match/match_2+/map.ejs +160 -0
  24. package/lib/templates/match/match_2+.ejs +1 -0
  25. package/lib/templates/match/match_2.ejs +1 -0
  26. package/lib/templates/player/player_1/base.css +1 -0
  27. package/lib/templates/player/player_1/private.ejs +1 -0
  28. package/lib/templates/player/player_1.ejs +78 -0
  29. package/lib/templates/rank/rank_fun.ejs +1 -0
  30. package/lib/templates/report/daily/base.css +1 -0
  31. package/lib/templates/report/daily.ejs +29 -0
  32. package/package.json +2 -2
  33. package/template/hero/hero_1.ejs +0 -900
  34. package/template/item/item/recipe.ejs +0 -51
  35. package/template/item/item/style.css +0 -244
  36. package/template/item/item.ejs +0 -140
  37. package/template/item/itemlist.ejs +0 -99
  38. package/template/match/match_1/item.ejs +0 -11
  39. package/template/match/match_1/main.ejs +0 -37
  40. package/template/match/match_1/player.ejs +0 -154
  41. package/template/match/match_1/style.css +0 -764
  42. package/template/match/match_1.ejs +0 -56
  43. package/template/match/match_2/original.css +0 -463
  44. package/template/match/match_2/original.ejs +0 -192
  45. package/template/match/match_2+/charts.ejs +0 -261
  46. package/template/match/match_2+/extra.css +0 -143
  47. package/template/match/match_2+/lane_outcome.ejs +0 -157
  48. package/template/match/match_2+.ejs +0 -27
  49. package/template/match/match_2.ejs +0 -18
  50. package/template/player/player_1/private.ejs +0 -5
  51. package/template/player/player_1.ejs +0 -654
  52. package/template/rank/rank_fun.ejs +0 -131
  53. package/template/report/daily.ejs +0 -191
  54. /package/{queries → lib/queries}/Constants.graphql +0 -0
  55. /package/{queries → lib/queries}/GetWeeklyMetaByPosition.graphql +0 -0
  56. /package/{queries → lib/queries}/PlayerExtraInfo.graphql +0 -0
  57. /package/{queries → lib/queries}/PlayerInfoWith25Matches.graphql +0 -0
  58. /package/{queries → lib/queries}/PlayerPerformanceForHeroRecommendation.graphql +0 -0
  59. /package/{queries → lib/queries}/PlayersInfoWith10MatchesForGuild.graphql +0 -0
  60. /package/{queries → lib/queries}/PlayersLastmatchRankinfo.graphql +0 -0
  61. /package/{queries → lib/queries}/PlayersMatchesForDaily.graphql +0 -0
  62. /package/{queries → lib/queries}/RequestMatchDataAnalysis.graphql +0 -0
  63. /package/{queries → lib/queries}/VerifyingPlayer.graphql +0 -0
  64. /package/{template → lib/templates}/images/bei.jpg +0 -0
  65. /package/{template → lib/templates}/images/disconnected.png +0 -0
  66. /package/{template → lib/templates}/images/flag_dire.png +0 -0
  67. /package/{template → lib/templates}/images/flag_radiant.png +0 -0
  68. /package/{template → lib/templates}/images/hero_badge_1.png +0 -0
  69. /package/{template → lib/templates}/images/hero_badge_2.png +0 -0
  70. /package/{template → lib/templates}/images/hero_badge_3.png +0 -0
  71. /package/{template → lib/templates}/images/hero_badge_4.png +0 -0
  72. /package/{template → lib/templates}/images/hero_badge_5.png +0 -0
  73. /package/{template → lib/templates}/images/hero_badge_6.png +0 -0
  74. /package/{template → lib/templates}/images/lane_fail.svg +0 -0
  75. /package/{template → lib/templates}/images/lane_jungle.svg +0 -0
  76. /package/{template → lib/templates}/images/lane_stomp.svg +0 -0
  77. /package/{template → lib/templates}/images/lane_stomped.svg +0 -0
  78. /package/{template → lib/templates}/images/lane_tie.svg +0 -0
  79. /package/{template → lib/templates}/images/lane_victory.svg +0 -0
  80. /package/{template → lib/templates}/images/logo_dire.png +0 -0
  81. /package/{template → lib/templates}/images/logo_radiant.png +0 -0
  82. /package/{template → lib/templates}/images/medal_0.png +0 -0
  83. /package/{template → lib/templates}/images/medal_1.png +0 -0
  84. /package/{template → lib/templates}/images/medal_2.png +0 -0
  85. /package/{template → lib/templates}/images/medal_3.png +0 -0
  86. /package/{template → lib/templates}/images/medal_4.png +0 -0
  87. /package/{template → lib/templates}/images/medal_5.png +0 -0
  88. /package/{template → lib/templates}/images/medal_6.png +0 -0
  89. /package/{template → lib/templates}/images/medal_7.png +0 -0
  90. /package/{template → lib/templates}/images/medal_8.png +0 -0
  91. /package/{template → lib/templates}/images/medal_8b.png +0 -0
  92. /package/{template → lib/templates}/images/medal_8c.png +0 -0
  93. /package/{template → lib/templates}/images/scepter.png +0 -0
  94. /package/{template → lib/templates}/images/scepter_0.png +0 -0
  95. /package/{template → lib/templates}/images/scepter_1.png +0 -0
  96. /package/{template → lib/templates}/images/shard.png +0 -0
  97. /package/{template → lib/templates}/images/shard_0.png +0 -0
  98. /package/{template → lib/templates}/images/shard_1.png +0 -0
  99. /package/{template → lib/templates}/images/star_0.png +0 -0
  100. /package/{template → lib/templates}/images/star_1.png +0 -0
  101. /package/{template → lib/templates}/images/star_2.png +0 -0
  102. /package/{template → lib/templates}/images/star_3.png +0 -0
  103. /package/{template → lib/templates}/images/star_4.png +0 -0
  104. /package/{template → lib/templates}/images/star_5.png +0 -0
  105. /package/{template → lib/templates}/images/xi.jpg +0 -0
@@ -1,261 +0,0 @@
1
- <div id="charts">
2
- <% const match = data; %>
3
- <%
4
- const mainData = match.radiantNetworthLeads ?? [];
5
-
6
- const secondaryData = match.radiantExperienceLeads ?? [];
7
-
8
- const maxValue = Math.max(...mainData.map(Math.abs), ...secondaryData.map(Math.abs));
9
- const minValue = Math.min(...mainData);
10
-
11
- const svgWidth = 400;
12
- const svgHeight = 200;
13
- const padding = 50;
14
-
15
- // 以秒为步进单位,下方调用时需要×60,并且需要判断不满整数分钟时对durationSeconds取余补齐长度,
16
- // 即padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60))
17
- const xScale = (svgWidth - padding * 2) / (match.durationSeconds - 1);
18
- let yScale = (svgHeight - padding * 2) / (maxValue * 2);
19
-
20
- // 格式化数字为千分位
21
- const formatNumber = (num) => Math.floor(num).toLocaleString();
22
-
23
- // 生成时间标签
24
- const generateTimeLabels = (length) => {
25
- const labels = [];
26
- for (let i = 0; i < length; i += 10) {
27
- labels.push(`${i.toString().padStart(2, '0')}:00`);
28
- }
29
- return labels;
30
- };
31
-
32
- const timeLabels = generateTimeLabels(mainData.length - 1).slice(0,Math.floor(match.durationSeconds / 60) % 10 > 5 ? undefined : -1);
33
- const valueLabels = [maxValue, maxValue / 2, 0, maxValue / 2 * -1, maxValue * -1];
34
- %>
35
- <%
36
- // 处理主数据点,分割正负区域
37
- const mainPoints = mainData.map((value, index) => ({
38
- value,
39
- test: index * 60,
40
- x: padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60)),
41
- y: svgHeight / 2 - value * yScale
42
- }));
43
-
44
- let upperPoints = [];
45
- let lowerPoints = [];
46
-
47
- for (let i = 0; i < mainPoints.length; i++) {
48
- const current = mainPoints[i];
49
- const next = mainPoints[i + 1];
50
-
51
- // 处理当前点
52
- if (current.value >= 0) {
53
- upperPoints.push(`${current.x},${current.y}`);
54
- } else {
55
- lowerPoints.push(`${current.x},${current.y}`);
56
- }
57
-
58
- // 处理跨零点的线段
59
- if (next) {
60
- if ((current.value >= 0 && next.value < 0) || (current.value < 0 && next.value >= 0)) {
61
- // 计算线段与零轴的交点
62
- const t = Math.abs(current.value) / (Math.abs(current.value) + Math.abs(next.value));
63
- const x = current.x + t * (next.x - current.x);
64
- const y = svgHeight / 2;
65
- upperPoints.push(`${x},${y}`);
66
- lowerPoints.push(`${x},${y}`);
67
- }
68
- }
69
- }
70
- // 闭合多边形路径
71
- upperPoints.push(`${svgWidth - padding},${svgHeight / 2}`, `${padding},${svgHeight / 2}`);
72
- lowerPoints.push(`${svgWidth - padding},${svgHeight / 2}`, `${padding},${svgHeight / 2}`);
73
-
74
- // 添加胜率数据处理
75
- const winRateMax = 1;
76
- const winRateMin = 0;
77
- const winRateScale = (svgHeight - padding * 2) / (winRateMax - winRateMin);
78
- // 计算胜率折线的坐标
79
- const winRatePoints = match.winRates ? [match.winRates[0]].concat(match.winRates).map((value, index) => ({
80
- value,
81
- x: padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60)),
82
- y: svgHeight - padding - value * winRateScale
83
- })) : undefined;
84
- %>
85
- <svg width="<%= svgWidth %>" height="<%= svgHeight %>" xmlns="http://www.w3.org/2000/svg">
86
- <text x="50%" y="30" text-anchor="middle" font-size="16" font-weight="bold"><%= $t("dota2tracker.template.situation") %></text>
87
- <image x="<%= padding %>" y="<%= padding %>" width="20" height="20" href="<%= getImageUrl("logo_radiant") %>"/>
88
- <image x="<%= padding %>" y="<%= svgHeight - padding * 1.4 %>" width="20" height="20" href="<%= getImageUrl("logo_dire") %>"/>
89
- <text x="<%= padding + 20 %>" y="<%= padding + 10 %>" dominant-baseline="middle" fill="#3c9028"><%= $t("dota2tracker.template.radiant") %></text>
90
- <text x="<%= padding + 20 %>" y="<%= svgHeight - padding * 1.4 + 10 %>" dominant-baseline="middle" fill="#9c3628"><%= $t("dota2tracker.template.dire") %></text>
91
- <!-- 横轴线与标签 -->
92
- <% for (let i = 0; i < 5; i++) { %>
93
- <% const y = padding + i * ((svgHeight - padding * 2) / 4) %>
94
- <line x1="<%= padding %>" y1="<%= y %>" x2="<%= svgWidth - padding %>" y2="<%= y %>" stroke="gray" stroke-width="1" />
95
- <text x="<%= padding - 2 %>" y="<%= y %>" text-anchor="end" dominant-baseline="middle" fill="#333a"><%= formatNumber(valueLabels[i]) %></text>
96
- <% } %>
97
-
98
- <!-- 纵轴线与标签 -->
99
- <% for (let i = 0; i < mainData.length - 1; i += 10) { %>
100
- <% const x = padding + i * xScale * 60 %>
101
- <line x1="<%= x %>" y1="<%= padding %>" x2="<%= x %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1" />
102
- <text x="<%= x %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= timeLabels[i / 10] %></text>
103
- <% } %>
104
- <!-- 额外添加最后一个纵轴线与横轴时间标签 -->
105
- <line x1="<%= svgWidth - padding %>" y1="<%= padding %>" x2="<%= svgWidth - padding %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1" />
106
- <text x="<%= svgWidth - padding %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= `${Math.floor(match.durationSeconds / 60)}:${(match.durationSeconds % 60).toString().padStart(2, '0')}` %></text>
107
-
108
- <!-- 胜率折线 -->
109
- <% if (match.winRates) { %>
110
- <polyline points="<%= winRatePoints.map(point => `${point.x},${point.y}`).join(' ') %>" fill="none" stroke="lightgreen" stroke-width="3" />
111
- <% } %>
112
-
113
- <!-- 次要数据 -->
114
- <polyline points="<%= secondaryData.map((value, index) => `${padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60))},${svgHeight / 2 - value * yScale}`).join(' ') %>" fill="none" stroke="gray" stroke-width="1" />
115
- <polygon points="<%= secondaryData.map((value, index) => `${padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60))},${svgHeight / 2 - value * yScale}`).join(' ') %> <%= svgWidth - padding %>,<%= svgHeight / 2 %> <%= padding %>,<%= svgHeight / 2 %>" fill="rgba(128, 128, 128, 0.3)" />
116
-
117
- <!-- 主要数据 -->
118
- <polyline points="<%= mainData.map((value, index) => `${padding + (index * 60 > match.durationSeconds ? ((((index - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (index * xScale * 60))},${svgHeight / 2 - value * yScale}`).join(' ') %>" fill="none" stroke="rgba(100, 100, 100, 0.3)" stroke-width="2" />
119
-
120
- <!-- 正区域填充 -->
121
- <polygon points="<%= upperPoints.join(' ') %>" fill="rgba(0, 255, 0, 0.3)" />
122
-
123
- <!-- 负区域填充 -->
124
- <polygon points="<%= lowerPoints.join(' ') %>" fill="rgba(255, 0, 0, 0.3)" />
125
-
126
- <!-- 图例 -->
127
- <g transform="translate(<%= padding %>, <%= svgHeight - 20 %>)">
128
- <!-- 财产总和(折线1) -->
129
- <g transform="translate(0, 0)">
130
- <!-- 左半段(浅绿色) -->
131
- <path d="M0,5 Q5,0 10,5 Z"
132
- fill="rgba(0, 255, 0, 0.5)"/>
133
-
134
- <!-- 右半段(浅红色) -->
135
- <path d="M10,5 Q15,10 20,5 Z"
136
- fill="rgba(255, 0, 0, 0.5)"/>
137
-
138
- <!-- 灰色描边 -->
139
- <path d="M0,5 Q5,0 10,5 T20,5"
140
- fill="none"
141
- stroke="rgba(100, 100, 100, 0.3)"
142
- stroke-width="1"/>
143
- <% if (match.odParsed) { %>
144
- <text x="30" y="7" fill="#ccc" font-size="12"><%= $t("dota2tracker.template.opendota.gold_t") %></text>
145
- <% } else { %>
146
- <text x="30" y="7" font-size="12"><%= $t("dota2tracker.template.networth") %></text>
147
- <% } %>
148
- </g>
149
-
150
- <!-- 经验(折线2) -->
151
- <g transform="translate(120, 0)">
152
- <path d="M0,5 Q5,0 10,5 T20,5"
153
- fill="none"
154
- stroke="gray"
155
- stroke-width="1"/>
156
- <text x="30" y="7" font-size="12"><%= $t("dota2tracker.template.experience") %></text>
157
- </g>
158
-
159
- <!-- 胜率(折线3) -->
160
- <g transform="translate(220, 0)">
161
- <path d="M0,5 Q5,0 10,5 T20,5"
162
- fill="none"
163
- stroke="lightgreen"
164
- stroke-width="3"/>
165
- <text x="30" y="7" font-size="12"><%= $t("dota2tracker.template.winrate") %></text>
166
- </g>
167
- </g>
168
- </svg>
169
- <%
170
- // 计算每个玩家的财产总和数据
171
- const playerNetworthData = match.players.map(player => player.stats.networthPerMinute.concat(player.networth));
172
-
173
- // 找到所有玩家中财产总和的最大值
174
- const maxNetworth = Math.max(...playerNetworthData.flat().map(Math.abs));
175
-
176
- // 计算纵坐标轴的刻度
177
- const networthValueLabels = [maxNetworth, maxNetworth * 0.75, maxNetworth * 0.5, maxNetworth * 0.25, 0];
178
-
179
- // 获取颜色数据
180
- const playerColors = dotaconstants.player_colors;
181
-
182
- yScale = (svgHeight - padding * 2) / maxNetworth;
183
- %>
184
- <!-- 新增的经济图表 -->
185
- <svg width="<%= svgWidth %>" height="<%= svgHeight %>" xmlns="http://www.w3.org/2000/svg">
186
- <%# 检查数据源是否为 OpenDota %>
187
- <% if (match.odParsed) { %>
188
- <%# OpenDota 分支:显示灰色标题和居中提示 %>
189
- <text x="50%" y="30" text-anchor="middle" font-size="16" font-weight="bold" fill="#ccc">
190
- <%# 标题:保持原始位置,修改文本和颜色 %>
191
- <%= $t("dota2tracker.template.opendota.networth_unavailable") %>
192
- </text>
193
- <text x="50%" y="<%= svgHeight / 2 + 10 %>" text-anchor="middle" font-size="12" fill="#ccc">
194
- <%# 提示:水平居中,大致垂直居中,灰色 %>
195
- <%= $t("dota2tracker.template.opendota.networth_unavailable_reason") %>
196
- </text>
197
-
198
- <% } else { %>
199
-
200
- <%# 非 OpenDota 分支(例如 Stratz):渲染完整的图表 %>
201
- <text x="50%" y="30" text-anchor="middle" font-size="16" font-weight="bold">
202
- <%= $t("dota2tracker.template.networth") %> <%# 原始标题 %>
203
- </text>
204
-
205
- <%# --- 你现有的图表绘制代码开始 --- %>
206
- <% for (let i = 0; i < 5; i++) { %>
207
- <% const y = padding + i * ((svgHeight - padding * 2) / 4) %>
208
- <line x1="<%= padding %>" y1="<%= y %>" x2="<%= svgWidth - padding %>" y2="<%= y %>" stroke="gray" stroke-width="1" />
209
- <text x="<%= padding - 2 %>" y="<%= y %>" text-anchor="end" dominant-baseline="middle" fill="#333a"><%= formatNumber(networthValueLabels[i]) %></text>
210
- <% } %>
211
-
212
- <!-- 纵轴线与标签 -->
213
- <% for (let i = 0; i < mainData.length - 1; i += 10) { %>
214
- <% const x = padding + i * xScale * 60 %>
215
- <line x1="<%= x %>" y1="<%= padding %>" x2="<%= x %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1" />
216
- <text x="<%= x %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= timeLabels[i / 10] %></text>
217
- <% } %>
218
- <!-- 额外添加最后一个纵轴线与横轴时间标签 -->
219
- <line x1="<%= svgWidth - padding %>" y1="<%= padding %>" x2="<%= svgWidth - padding %>" y2="<%= svgHeight - padding %>" stroke="lightgray" stroke-width="1" />
220
- <text x="<%= svgWidth - padding %>" y="<%= svgHeight - padding + 15 %>" text-anchor="middle" fill="#333a"><%= `${Math.floor(match.durationSeconds / 60)}:${(match.durationSeconds % 60).toString().padStart(2, '0')}` %></text>
221
-
222
- <!-- 绘制每个玩家的财产总和折线 -->
223
- <% playerNetworthData.forEach((playerData, index) => { %>
224
- <% const playerSlot = match.players[index].playerSlot; %>
225
- <% const color = playerColors[playerSlot] || '#000000'; %>
226
- <% const points = playerData.map((value, i) => `${padding + (i * 60 > match.durationSeconds ? ((((i - 1) * 60) + (match.durationSeconds % 60)) * xScale) : (i * xScale * 60))},${svgHeight - padding - value * yScale}`); %>
227
- <polyline points="<%= points.join(' ') %>" fill="none" stroke="<%= color %>" stroke-width="2" />
228
- <% }) %>
229
-
230
- <!-- 在第二个svg的最后添加以下图例代码 -->
231
- <g transform="translate(<%= (svgWidth - (match.players.length * 30)) / 2 %>, <%= svgHeight - 27 %>)">
232
- <% match.players.sort((a, b) => a.networth - b.networth).forEach((player, index) => { %>
233
- <% const playerSlot = player.playerSlot; %>
234
- <% const color = playerColors[playerSlot] || '#000000'; %>
235
- <!-- 头像框 -->
236
- <rect
237
- x="<%= index * 30 %>"
238
- y="0"
239
- width="24"
240
- height="24"
241
- rx="6"
242
- ry="6"
243
- fill="#f0f0f0"
244
- stroke="<%= color %>"
245
- stroke-width="3"
246
- />
247
- <image
248
- x="<%= index * 30 %>"
249
- y="0"
250
- width="24"
251
- height="24"
252
- href="<%= getImageUrl(player.hero.shortName, ImageType.HeroIcons) %>"
253
- clip-path="inset(0 round 6)"
254
- />
255
- <% }) %>
256
- </g>
257
- <%# --- 你现有的图表绘制代码结束 --- %>
258
-
259
- <% } %>
260
- </svg>
261
- </div>
@@ -1,143 +0,0 @@
1
- html {
2
- overflow: visible;
3
- }
4
-
5
- body {
6
- /* width: 1200px; */
7
- display: flex;
8
- flex-direction: column;
9
- overflow: visible;
10
- }
11
-
12
- #regular {
13
- width: 800px;
14
- }
15
-
16
- #extra {
17
- display: flex;
18
- width: 800px;
19
- flex-direction: row;
20
- }
21
-
22
- #charts {
23
- display: flex;
24
- flex-direction: column;
25
- width: 50%;
26
- }
27
-
28
- #charts > svg {
29
- font-size: 10px;
30
- }
31
-
32
- #charts > .title {
33
- width: 100%;
34
- display: flex;
35
- justify-content: space-between;
36
- }
37
-
38
- #charts > .title > .logo {
39
- display: flex;
40
- padding: 0 25px;
41
- }
42
-
43
- #charts > .title > .logo > img {
44
- width: 20px;
45
- }
46
-
47
- #extra > .tip {
48
- width: 100%;
49
- line-height: 4;
50
- text-align: center;
51
- color: #ccc;
52
- }
53
-
54
- .lane_outcome {
55
- position: relative;
56
- display: flex;
57
- flex-direction: column;
58
- width: 386px;
59
- margin-right: 14px;
60
- }
61
-
62
- .lane_outcome > .title {
63
- width: 100%;
64
- text-align: center;
65
- padding: 0;
66
- margin: 0;
67
- line-height: 50px;
68
- }
69
-
70
- .lane_outcome .panel {
71
- display: flex;
72
- flex-direction: column;
73
- height: 100%;
74
- justify-content: space-evenly;
75
- }
76
-
77
- .lane_outcome .panel .lane {
78
- display: flex;
79
- flex-direction: column;
80
- width: 100%;
81
- }
82
-
83
- .lane_outcome .panel .lane > .title {
84
- width: 100%;
85
- text-align: center;
86
- height: 32px;
87
- }
88
-
89
- .lane_outcome .panel .lane > .title > p:nth-child(1) {
90
- font-size: 12px;
91
- }
92
-
93
- .lane_outcome .panel .lane > .title > p:nth-child(2) {
94
- font-size: 14px;
95
- }
96
-
97
- .lane_outcome .panel .details {
98
- display: grid;
99
- grid-template-columns: 24px 32px 44px auto 44px 32px 24px;
100
- grid-template-rows: 1fr;
101
- gap: 1px;
102
- font-size: 12px;
103
- height: 32px;
104
- line-height: 32px;
105
- align-items: center;
106
- }
107
-
108
- .lane_outcome .panel .lane img.hero {
109
- width: 24px;
110
- height: 24px;
111
- }
112
-
113
- .lane_outcome .panel .lane .kda {
114
- height: 100%;
115
- line-height: 32px;
116
- text-align: center;
117
- font-size: 10px;
118
- }
119
-
120
- .lane_outcome .panel .lane .graph {
121
- height: 32px;
122
- display: flex;
123
- align-items: center;
124
- }
125
-
126
- .graph svg {
127
- shape-rendering: crispEdges;
128
- }
129
- .graph text {
130
- dominant-baseline: middle; /* 垂直居中 */
131
- white-space: nowrap; /* 防止换行 */
132
- }
133
-
134
- .lane_outcome .subtitle {
135
- position: absolute;
136
- inset: 0;
137
- top: 36px;
138
- text-align: center;
139
- color: #ccc;
140
- font-size: 12px;
141
- margin: 0;
142
- z-index: 1;
143
- }
@@ -1,157 +0,0 @@
1
- <% const match = data; %>
2
- <%
3
- function generateLevelCircle(xp) {
4
- const XP_LEVELS = dotaconstants.xp_level;
5
- let level = XP_LEVELS.findIndex(l => xp < l) - 1;
6
- if (level < 0) level = XP_LEVELS.length - 1;
7
- const currentXP = xp - XP_LEVELS[level];
8
- const nextXP = XP_LEVELS[level+1] - XP_LEVELS[level];
9
- const percentage = currentXP / nextXP * 100;
10
- // return { level, progress: currentXP / nextXP };
11
- const config = {
12
- radius: 12,
13
- strokeColor: '#ffd700',
14
- bgColor: '#eee',
15
- strokeWidth: 2,
16
- svgSize: 32,
17
- showBackground: true,
18
- // 新增文字配置
19
- showText: true, // 是否显示文字
20
- textColor: '#333', // 文字颜色
21
- fontSize: 12 * 0.8,
22
- fontWeight: 'bold', // 字体粗细
23
- // ...options
24
- };
25
-
26
- const center = config.svgSize / 2;
27
- const circumference = 2 * Math.PI * config.radius;
28
- const progress = circumference * (percentage / 100);
29
- const gap = circumference - progress;
30
-
31
- return `
32
- <svg width="${config.svgSize}" height="${config.svgSize}" viewBox="0 0 ${config.svgSize} ${config.svgSize}">
33
- ${config.showBackground ? `
34
- <circle cx="${center}" cy="${center}" r="${config.radius}"
35
- fill="none" stroke="${config.bgColor}"
36
- stroke-width="${config.strokeWidth}"/>
37
- ` : ''}
38
-
39
- <circle cx="${center}" cy="${center}" r="${config.radius}"
40
- fill="none" stroke="${config.strokeColor}"
41
- stroke-width="${config.strokeWidth}"
42
- stroke-dasharray="${progress} ${gap}"
43
- stroke-linecap="round"
44
- transform="rotate(-90 ${center} ${center})"/>
45
-
46
- ${config.showText ? `
47
- <!-- 居中文字 -->
48
- <text x="${center}" y="${center}"
49
- text-anchor="middle" // 水平居中
50
- dominant-baseline="middle" // 垂直居中
51
- fill="${config.textColor}"
52
- font-size="${config.fontSize}px"
53
- font-weight="${config.fontWeight}"
54
- font-family="Arial, sans-serif">
55
- ${level}
56
- </text>
57
- ` : ''}
58
- </svg>`;
59
- }
60
-
61
- function generateDetails(rivals) {
62
- const svgHeight = 32;
63
- rivals.sort((a, b) => b.isRadiant - a.isRadiant);
64
-
65
- const gold = { radiant: rivals[0].stats.networthPerMinute.at(Math.min(10, rivals[0].stats.networthPerMinute.length - 1)), dire: rivals[1].stats.networthPerMinute.at(Math.min(10, rivals[1].stats.networthPerMinute.length - 1)) };
66
- let kills = { radiant: 0, dire: 0 };
67
- let deaths = { radiant: 0, dire: 0 };
68
- let assists = { radiant: 0, dire: 0 };
69
- let xp = { radiant: 0, dire: 0 };
70
- const totalGold = gold.radiant + gold.dire;
71
- const goldSplit = gold.radiant / (totalGold || 1);
72
- if (match.odParsed) {
73
- kills = { radiant: "-", dire: "-" };
74
- deaths = { radiant: "-", dire: "-" };
75
- assists = { radiant: "-", dire: "-" };
76
- } else {
77
- kills = { radiant: rivals[0].stats.killEvents?.filter(e => e.time <= 600).length || 0, dire: rivals[1].stats.killEvents?.filter(e => e.time <= 600).length || 0 }
78
- deaths = { radiant: rivals[0].stats.deathEvents?.filter(e => e.time <= 600).length || 0, dire: rivals[1].stats.deathEvents?.filter(e => e.time <= 600).length || 0 }
79
- assists = { radiant: rivals[0].stats.assistEvents?.filter(e => e.time <= 600).length || 0, dire: rivals[1].stats.assistEvents?.filter(e => e.time <= 600).length || 0 }
80
- }
81
- return `
82
- <div class="details">
83
- <img src="${getImageUrl(rivals[0].hero.shortName, ImageType.HeroIcons)}" class="hero radiant" />
84
- ${generateLevelCircle(rivals[0].stats.experiencePerMinute.slice(0, Math.min(11, rivals[0].stats.experiencePerMinute.length - 1)).reduce((a, b) => a + b, 0))}
85
- <p class="kda">${kills.radiant}/${deaths.radiant}/${assists.radiant}</p>
86
- <div class="graph">
87
- <svg width="100%" height="${svgHeight}" viewBox="0 0 180 ${svgHeight}">
88
-
89
- <!-- 经济差 -->
90
- <g transform="translate(0,0)">
91
- <rect y="9" width="180" height="14" fill="#9c3628"/>
92
- <rect y="9" width="${180 * goldSplit}" height="14" fill="#3c9028"/>
93
- <line x1="${180 * goldSplit}" y1="9" x2="${180 * goldSplit}" y2="25"
94
- stroke="#fff" stroke-width="1"/>
95
-
96
- <!-- 左侧数值(天辉) -->
97
- <text x="5" y="50%"
98
- dominant-baseline="middle"
99
- fill="#fff"
100
- font-size="10"
101
- text-anchor="start">${match.odParsed ? "~" : ""}${gold.radiant}</text>
102
-
103
- <!-- 右侧数值(夜魇) -->
104
- <text x="175" y="50%"
105
- dominant-baseline="middle"
106
- fill="#fff"
107
- font-size="10"
108
- text-anchor="end">${match.odParsed ? "~" : ""}${gold.dire}</text>
109
-
110
- <!-- 差值(上方) -->
111
- <text x="${180 * goldSplit}"
112
- y="5px"
113
- dominant-baseline="middle"
114
- fill="${gold.radiant > gold.dire ? "#3c9028" : "#9c3628"}"
115
- font-size="10"
116
- text-anchor="middle">+${Math.abs(gold.dire - gold.radiant)}</text>
117
- </g>
118
- </svg>
119
- </div>
120
- <p class="kda">${kills.dire}/${deaths.dire}/${assists.dire}</p>
121
- ${generateLevelCircle(rivals[1].stats.experiencePerMinute.slice(0, Math.min(11, rivals[1].stats.experiencePerMinute.length - 1)).reduce((a, b) => a + b, 0))}
122
- <img src="${getImageUrl(rivals[1].hero.shortName, ImageType.HeroIcons)}" class="hero dire" />
123
- </div>
124
- `
125
- }
126
- %>
127
- <div class="lane_outcome">
128
- <h4 class="title"><%= $t("dota2tracker.template.lane") %></h4>
129
- <% if (match.odParsed) { %>
130
- <p class="subtitle"><%= $t("dota2tracker.template.opendota.lane_outcome_tip") %></p>
131
- <% } %>
132
- <div class="panel">
133
- <div class="lane">
134
- <div class="title">
135
- <p><%= $t("dota2tracker.template.lane_top") %></p>
136
- <p class="<%= match.topLaneOutcome.split("_")[0].toLowerCase() %>"><%= $t("dota2tracker.template.OUTCOME_MAP."+match.topLaneOutcome) %></p>
137
- </div>
138
- <%- generateDetails(match.players.filter(p => (p.position.slice(-1) == 3 && p.isRadiant) || (p.position.slice(-1) == 1 && !p.isRadiant))) %>
139
- <%- generateDetails(match.players.filter(p => (p.position.slice(-1) == 4 && p.isRadiant) || (p.position.slice(-1) == 5 && !p.isRadiant))) %>
140
- </div>
141
- <div class="lane">
142
- <div class="title">
143
- <p><%= $t("dota2tracker.template.lane_mid") %></p>
144
- <p class="<%= match.midLaneOutcome.split("_")[0].toLowerCase() %>"><%= $t("dota2tracker.template.OUTCOME_MAP."+match.midLaneOutcome) %></p>
145
- </div>
146
- <%- generateDetails(match.players.filter(p => p.position.slice(-1) == 2)) %>
147
- </div>
148
- <div class="lane">
149
- <div class="title">
150
- <p><%= $t("dota2tracker.template.lane_bottom") %></p>
151
- <p class="<%= match.bottomLaneOutcome.split("_")[0].toLowerCase() %>"><%= $t("dota2tracker.template.OUTCOME_MAP."+match.bottomLaneOutcome) %></p>
152
- </div>
153
- <%- generateDetails(match.players.filter(p => (p.position.slice(-1) == 1 && p.isRadiant) || (p.position.slice(-1) == 3 && !p.isRadiant))) %>
154
- <%- generateDetails(match.players.filter(p => (p.position.slice(-1) == 5 && p.isRadiant) || (p.position.slice(-1) == 4 && !p.isRadiant))) %>
155
- </div>
156
- </div>
157
- </div>
@@ -1,27 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="<%= languageTag %>">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Document</title>
7
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" />
8
- <%- "<style>" %>
9
- <%- include("./match_2/original.css") %>
10
- <%- include("./match_2+/extra.css") %>
11
- <% if (fontFamily) { %>
12
- <%- `body { font-family: ${fontFamily}; }` %>
13
- <% } %>
14
- <%- "</style>" %>
15
- </head>
16
- <body>
17
- <section id="regular"><%- include('./match_2/original') %></section>
18
- <section id="extra">
19
- <% if (data.parsedDateTime && data.radiantNetworthLeads && data.radiantExperienceLeads) { %>
20
- <%- include('./match_2+/charts') %>
21
- <%- include('./match_2+/lane_outcome') %>
22
- <% } else { %>
23
- <div class="tip"><%= $t("dota2tracker.template.empty_extra_info") %></div>
24
- <% } %>
25
- </section>
26
- </body>
27
- </html>
@@ -1,18 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="<%= languageTag %>">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Document</title>
7
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" />
8
- <%- "<style>" %>
9
- <%- include("./match_2/original.css") %>
10
- <% if (fontFamily) { %>
11
- <%- `body { font-family: ${fontFamily}; }` %>
12
- <% } %>
13
- <%- "</style>" %>
14
- </head>
15
- <body>
16
- <%- include('./match_2/original') %>
17
- </body>
18
- </html>
@@ -1,5 +0,0 @@
1
- <div class="blur-overlay">
2
- <span class="lock-icon">🔒</span>
3
- <span class="lock-text"><%= $t("dota2tracker.template.anonymous_player_1") %></span>
4
- <span class="lock-sub"><%= $t("dota2tracker.template.anonymous_player_2",{player:player.steamAccount.name}) %></span>
5
- </div>