fortnite-replay-analysis 1.0.6 → 1.0.8
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/README.md +89 -28
- package/index.js +48 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,56 +1,117 @@
|
|
|
1
1
|
# Fortnite Replay Analysis
|
|
2
2
|
|
|
3
|
-
Fortnite
|
|
3
|
+
Fortniteのリプレイファイルを解析して、プレイヤーデータを取得・集計・ソートできるNode.jsのモジュールです。
|
|
4
4
|
|
|
5
5
|
## 特徴
|
|
6
6
|
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* OS判定してC#でビルドされた自己完結バイナリを呼び出すから、高速解析できる
|
|
8
|
+
* botプレイヤーの除外や順位ソートなどオプション対応
|
|
9
|
+
* 複数マッチのスコアをパーティ単位でマージして集計可能
|
|
10
|
+
* 公式準拠でのスコアのソートも可能
|
|
11
11
|
|
|
12
12
|
## インストール
|
|
13
13
|
|
|
14
|
-
```
|
|
15
|
-
npm install fortnite-replay-analysis
|
|
14
|
+
```
|
|
15
|
+
npm install fortnite-replay-analysis@latest
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## 使い方
|
|
19
19
|
|
|
20
20
|
```js
|
|
21
|
-
const { ReplayAnalysis, sortScores,
|
|
21
|
+
const { ReplayAnalysis, mergeScores, sortScores, calculateScore } = require('fortnite-replay-analysis');
|
|
22
|
+
|
|
23
|
+
(async () => {
|
|
24
|
+
try {
|
|
25
|
+
// 1試合分のリプレイ解析(返り値はJSON形式)
|
|
26
|
+
// rawPlayerData: 元の解析結果、生データ
|
|
27
|
+
// processedPlayerInfo: bot除外や順位ソート済みのプレイヤーデータ
|
|
28
|
+
const { rawPlayerData, processedPlayerInfo } = await ReplayAnalysis('./path/to/replayDir', { bot: false, sort: true });
|
|
29
|
+
|
|
30
|
+
console.log('Raw Data:', rawPlayerData);
|
|
31
|
+
console.log('Processed Player Info:', processedPlayerInfo);
|
|
32
|
+
|
|
33
|
+
// 解析結果のスコア配列を公式準拠のルールでソートも可能
|
|
34
|
+
const sortedScores = sortScores(processedPlayerInfo);
|
|
35
|
+
|
|
36
|
+
// 複数マッチの解析結果をまとめたいときは、
|
|
37
|
+
// sortScoresでソート済みの配列を複数用意して
|
|
38
|
+
// mergeScoresに配列の配列として渡す
|
|
39
|
+
const mergedScores = mergeScores([
|
|
40
|
+
sortedScores, // 1試合目の結果
|
|
41
|
+
sortedScores2, // 2試合目の結果
|
|
42
|
+
// ...
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
// マージ後の結果もsortScoresで再ソート可能
|
|
46
|
+
const finalSorted = sortScores(mergedScores);
|
|
47
|
+
|
|
48
|
+
console.log('Merged and Sorted:', finalSorted);
|
|
49
|
+
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error(e);
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## calculateScoreの使い方
|
|
57
|
+
|
|
58
|
+
リプレイ解析済みの`ReplayAnalysis` の `result.processedPlayerInfo` を保存した JSON ファイル(ファイル名は任意でOK)から、大会形式のスコアを計算したいときに使える。
|
|
22
59
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
60
|
+
```js
|
|
61
|
+
const { calculateScore } = require('fortnite-replay-analysis');
|
|
62
|
+
|
|
63
|
+
const score = await calculateScore({
|
|
64
|
+
matchDataPath: './output/matchA1/playerInfo.json',
|
|
65
|
+
points: {
|
|
66
|
+
1: 11, 2: 6, 3: 5, 4: 4, 5: 3,
|
|
67
|
+
6: 2, 7: 1, 8: 1, 9: 1, 10: 1
|
|
68
|
+
},
|
|
69
|
+
killPointMultiplier: 1,
|
|
70
|
+
killCountUpperLimit: 10
|
|
26
71
|
});
|
|
72
|
+
|
|
73
|
+
console.log(score);
|
|
27
74
|
```
|
|
28
75
|
|
|
29
|
-
##
|
|
76
|
+
## API
|
|
77
|
+
|
|
78
|
+
### `ReplayAnalysis(replayFileDir, options)`
|
|
30
79
|
|
|
31
|
-
|
|
80
|
+
* `replayFileDir`:リプレイファイルが入ったディレクトリのパス
|
|
81
|
+
* `options`:
|
|
32
82
|
|
|
33
|
-
*
|
|
34
|
-
* `
|
|
35
|
-
*
|
|
83
|
+
* `bot`(boolean):botプレイヤーを結果に含めるか(デフォルトfalse)
|
|
84
|
+
* `sort`(boolean):順位でソートするか(デフォルトtrue)
|
|
85
|
+
* 返り値はPromiseで、`rawPlayerData`と`processedPlayerInfo`を含むオブジェクトを返す
|
|
36
86
|
|
|
37
|
-
### `mergeScores(scoreArrays
|
|
87
|
+
### `mergeScores(scoreArrays)`
|
|
38
88
|
|
|
39
|
-
*
|
|
89
|
+
* 複数マッチのスコア配列をパーティ単位でマージする
|
|
90
|
+
* 返り値はマージされたスコア配列
|
|
40
91
|
|
|
41
|
-
### `sortScores(
|
|
92
|
+
### `sortScores(scoreArray)`
|
|
42
93
|
|
|
43
|
-
*
|
|
94
|
+
* 公式準拠のルールでスコアをソートする
|
|
95
|
+
* 引数はマージ済みのスコア配列
|
|
44
96
|
|
|
45
|
-
|
|
97
|
+
### `calculateScore({ matchDataPath, points, killCountUpperLimit, killPointMultiplier })`
|
|
46
98
|
|
|
47
|
-
*
|
|
48
|
-
*
|
|
99
|
+
* `matchDataPath`:`ReplayAnalysis` の `result.processedPlayerInfo` を保存した JSON ファイルのパス(ファイル名は任意でOK)
|
|
100
|
+
* `points`:順位に対するポイント設定(例:{ 1: 11, 2: 6, ... })
|
|
101
|
+
* `killCountUpperLimit`:キル数制限(nullで無制限)
|
|
102
|
+
* `killPointMultiplier`:キル数倍率(例:1なら1キル1ポイント、2なら1キル2ポイント)
|
|
49
103
|
|
|
50
|
-
##
|
|
104
|
+
## 動作環境
|
|
51
105
|
|
|
52
|
-
|
|
106
|
+
* Node.js v22以上
|
|
107
|
+
* Windows / Linux対応(Macは未対応)
|
|
108
|
+
* C#で作られた自己完結バイナリが`CSproj/bin/Release/net8.0/`配下に同補されていること
|
|
53
109
|
|
|
54
|
-
##
|
|
110
|
+
## 注意事項
|
|
55
111
|
|
|
56
|
-
|
|
112
|
+
* リプレイファイルはディレクトリに1つ以上`.replay`ファイルが必要
|
|
113
|
+
* ディレクトリ内に複数ファイルある場合は現状最初の1つのみ処理される
|
|
114
|
+
* 何か問題起こっても俺は責任追わない
|
|
115
|
+
* このリポジトリをフォークする際は、GitHubの「Fork」ボタンからフォークしてください。
|
|
116
|
+
git cloneして新しく別リポジトリを作るのではなく、GitHub上のフォーク機能を使っていただけると、変更履歴を正しく追えます。
|
|
117
|
+
ご協力よろしくお願いします!
|
package/index.js
CHANGED
|
@@ -103,6 +103,53 @@ function ReplayAnalysis(replayFileDir, { bot = false, sort = true } = {}) { // F
|
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
async function calculateScore({ matchDataPath, points, killCountUpperLimit = null, killPointMultiplier = 1 } = {}) {
|
|
107
|
+
if (!matchDataPath || !fs.existsSync(matchDataPath)) {
|
|
108
|
+
throw new Error(`Match data file not found: ${matchDataPath}`);
|
|
109
|
+
}
|
|
110
|
+
if (!points || typeof points !== 'object' || Object.keys(points).length === 0) {
|
|
111
|
+
throw new Error('Points configuration is required and must be a non-empty object.');
|
|
112
|
+
}
|
|
113
|
+
if (killCountUpperLimit !== null && (typeof killCountUpperLimit !== 'number' || killCountUpperLimit < 0)) {
|
|
114
|
+
throw new Error('killCountUpperLimit must be a non-negative number or null.');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const playerInfo = JSON.parse(fs.readFileSync(path.join(matchDataPath), 'utf8'));
|
|
118
|
+
const partyScore = playerInfo.reduce((acc, player) => {
|
|
119
|
+
if (!acc[player.partyNumber]) {
|
|
120
|
+
const limitedKills = killCountUpperLimit == null
|
|
121
|
+
? (player.TeamKills || 0)
|
|
122
|
+
: Math.min(player.TeamKills || 0, killCountUpperLimit);
|
|
123
|
+
acc[player.partyNumber] = {
|
|
124
|
+
partyPlacement: player.Placement,
|
|
125
|
+
partyNumber: player.partyNumber,
|
|
126
|
+
partyKills: limitedKills,
|
|
127
|
+
partyKillsNoLimit: player.TeamKills || 0,
|
|
128
|
+
partyScore: (points[player.Placement] ?? 0) + ((limitedKills) * killPointMultiplier),
|
|
129
|
+
partyPoint: points[player.Placement] ?? 0,
|
|
130
|
+
partyVictoryRoyale: player.Placement === 1,
|
|
131
|
+
partyKillsList: [],
|
|
132
|
+
partyAliveTimeList: [],
|
|
133
|
+
partyMemberList: [],
|
|
134
|
+
partyMemberIdList: [],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// キル数の加算
|
|
139
|
+
acc[player.partyNumber].partyKillsList.push(player.Kills || 0);
|
|
140
|
+
acc[player.partyNumber].partyAliveTimeList.push(player.aliveTime || 0);
|
|
141
|
+
acc[player.partyNumber].partyMemberList.push(player.PlayerName);
|
|
142
|
+
acc[player.partyNumber].partyMemberIdList.push(player.EpicId);
|
|
143
|
+
|
|
144
|
+
return acc;
|
|
145
|
+
}, {});
|
|
146
|
+
|
|
147
|
+
let result = Object.values(partyScore);
|
|
148
|
+
result = sortScores(result);
|
|
149
|
+
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
106
153
|
function mergeScores(scoreArrays) { // 複数マッチの結果をマージしてパーティごとに集計
|
|
107
154
|
const map = new Map();
|
|
108
155
|
scoreArrays.forEach(scores =>
|
|
@@ -227,6 +274,7 @@ function sumMaxAliveTime(partyAliveTimeList, partyAliveTimeByMatch) {
|
|
|
227
274
|
|
|
228
275
|
module.exports = {
|
|
229
276
|
ReplayAnalysis,
|
|
277
|
+
calculateScore,
|
|
230
278
|
sortScores,
|
|
231
279
|
mergeScores
|
|
232
280
|
};
|