@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.
- package/changelog.md +28 -0
- package/lib/index.js +200 -162
- package/{queries → lib/queries}/MatchInfo.graphql +13 -0
- package/lib/templates/common/components/building_icons.ejs +20 -0
- package/lib/templates/common/styles/normalize.min.css +1 -0
- package/lib/templates/hero/hero_1.ejs +69 -0
- package/lib/templates/images/7.38_simple_minimap.png +0 -0
- package/lib/templates/item/item/recipe.ejs +9 -0
- package/lib/templates/item/item/style.css +1 -0
- package/lib/templates/item/item.ejs +52 -0
- package/lib/templates/item/itemlist.ejs +11 -0
- package/lib/templates/match/match_1/base.css +1 -0
- package/lib/templates/match/match_1/item.ejs +1 -0
- package/lib/templates/match/match_1/main.ejs +8 -0
- package/lib/templates/match/match_1/player.ejs +1 -0
- package/lib/templates/match/match_1/style.css +1 -0
- package/lib/templates/match/match_1.ejs +18 -0
- package/lib/templates/match/match_2/original.css +1 -0
- package/lib/templates/match/match_2/original.ejs +10 -0
- package/lib/templates/match/match_2+/charts.ejs +1 -0
- package/lib/templates/match/match_2+/extra.css +1 -0
- package/lib/templates/match/match_2+/lane_outcome.ejs +56 -0
- package/lib/templates/match/match_2+/map.ejs +160 -0
- package/lib/templates/match/match_2+.ejs +1 -0
- package/lib/templates/match/match_2.ejs +1 -0
- package/lib/templates/player/player_1/base.css +1 -0
- package/lib/templates/player/player_1/private.ejs +1 -0
- package/lib/templates/player/player_1.ejs +78 -0
- package/lib/templates/rank/rank_fun.ejs +1 -0
- package/lib/templates/report/daily/base.css +1 -0
- package/lib/templates/report/daily.ejs +29 -0
- package/package.json +2 -2
- package/template/hero/hero_1.ejs +0 -900
- package/template/item/item/recipe.ejs +0 -51
- package/template/item/item/style.css +0 -244
- package/template/item/item.ejs +0 -140
- package/template/item/itemlist.ejs +0 -99
- package/template/match/match_1/item.ejs +0 -11
- package/template/match/match_1/main.ejs +0 -37
- package/template/match/match_1/player.ejs +0 -154
- package/template/match/match_1/style.css +0 -764
- package/template/match/match_1.ejs +0 -56
- package/template/match/match_2/original.css +0 -463
- package/template/match/match_2/original.ejs +0 -192
- package/template/match/match_2+/charts.ejs +0 -261
- package/template/match/match_2+/extra.css +0 -143
- package/template/match/match_2+/lane_outcome.ejs +0 -157
- package/template/match/match_2+.ejs +0 -27
- package/template/match/match_2.ejs +0 -18
- package/template/player/player_1/private.ejs +0 -5
- package/template/player/player_1.ejs +0 -654
- package/template/rank/rank_fun.ejs +0 -131
- package/template/report/daily.ejs +0 -191
- /package/{queries → lib/queries}/Constants.graphql +0 -0
- /package/{queries → lib/queries}/GetWeeklyMetaByPosition.graphql +0 -0
- /package/{queries → lib/queries}/PlayerExtraInfo.graphql +0 -0
- /package/{queries → lib/queries}/PlayerInfoWith25Matches.graphql +0 -0
- /package/{queries → lib/queries}/PlayerPerformanceForHeroRecommendation.graphql +0 -0
- /package/{queries → lib/queries}/PlayersInfoWith10MatchesForGuild.graphql +0 -0
- /package/{queries → lib/queries}/PlayersLastmatchRankinfo.graphql +0 -0
- /package/{queries → lib/queries}/PlayersMatchesForDaily.graphql +0 -0
- /package/{queries → lib/queries}/RequestMatchDataAnalysis.graphql +0 -0
- /package/{queries → lib/queries}/VerifyingPlayer.graphql +0 -0
- /package/{template → lib/templates}/images/bei.jpg +0 -0
- /package/{template → lib/templates}/images/disconnected.png +0 -0
- /package/{template → lib/templates}/images/flag_dire.png +0 -0
- /package/{template → lib/templates}/images/flag_radiant.png +0 -0
- /package/{template → lib/templates}/images/hero_badge_1.png +0 -0
- /package/{template → lib/templates}/images/hero_badge_2.png +0 -0
- /package/{template → lib/templates}/images/hero_badge_3.png +0 -0
- /package/{template → lib/templates}/images/hero_badge_4.png +0 -0
- /package/{template → lib/templates}/images/hero_badge_5.png +0 -0
- /package/{template → lib/templates}/images/hero_badge_6.png +0 -0
- /package/{template → lib/templates}/images/lane_fail.svg +0 -0
- /package/{template → lib/templates}/images/lane_jungle.svg +0 -0
- /package/{template → lib/templates}/images/lane_stomp.svg +0 -0
- /package/{template → lib/templates}/images/lane_stomped.svg +0 -0
- /package/{template → lib/templates}/images/lane_tie.svg +0 -0
- /package/{template → lib/templates}/images/lane_victory.svg +0 -0
- /package/{template → lib/templates}/images/logo_dire.png +0 -0
- /package/{template → lib/templates}/images/logo_radiant.png +0 -0
- /package/{template → lib/templates}/images/medal_0.png +0 -0
- /package/{template → lib/templates}/images/medal_1.png +0 -0
- /package/{template → lib/templates}/images/medal_2.png +0 -0
- /package/{template → lib/templates}/images/medal_3.png +0 -0
- /package/{template → lib/templates}/images/medal_4.png +0 -0
- /package/{template → lib/templates}/images/medal_5.png +0 -0
- /package/{template → lib/templates}/images/medal_6.png +0 -0
- /package/{template → lib/templates}/images/medal_7.png +0 -0
- /package/{template → lib/templates}/images/medal_8.png +0 -0
- /package/{template → lib/templates}/images/medal_8b.png +0 -0
- /package/{template → lib/templates}/images/medal_8c.png +0 -0
- /package/{template → lib/templates}/images/scepter.png +0 -0
- /package/{template → lib/templates}/images/scepter_0.png +0 -0
- /package/{template → lib/templates}/images/scepter_1.png +0 -0
- /package/{template → lib/templates}/images/shard.png +0 -0
- /package/{template → lib/templates}/images/shard_0.png +0 -0
- /package/{template → lib/templates}/images/shard_1.png +0 -0
- /package/{template → lib/templates}/images/star_0.png +0 -0
- /package/{template → lib/templates}/images/star_1.png +0 -0
- /package/{template → lib/templates}/images/star_2.png +0 -0
- /package/{template → lib/templates}/images/star_3.png +0 -0
- /package/{template → lib/templates}/images/star_4.png +0 -0
- /package/{template → lib/templates}/images/star_5.png +0 -0
- /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>
|