Skip to content

Commit 32618cf

Browse files
committed
trophy support
1 parent b907b9e commit 32618cf

5 files changed

Lines changed: 89579 additions & 37 deletions

File tree

public_html/lib/css/debug.css

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,42 @@
674674
line-height:22px;
675675
display: inline-block;
676676
}
677+
/* Trophies Tab */
678+
.rpcn-trophy-list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
679+
.rpcn-trophy-item {
680+
display: flex; align-items: center; background: var(--rpcn-surface);
681+
border: 1px solid var(--rpcn-border); border-radius: var(--rpcn-radius-sm);
682+
padding: 14px 16px; gap: 16px; transition: background .2s;
683+
}
684+
.rpcn-trophy-item:hover { background: var(--rpcn-accent-bg); }
685+
.rpcn-trophy-icon {
686+
width: 56px; height: 56px; flex-shrink: 0; border-radius: 4px;
687+
background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.1);
688+
box-shadow: 0 2px 6px rgba(0,0,0,0.5);
689+
}
690+
.rpcn-trophy-info { flex: 1; min-width: 0; }
691+
.rpcn-trophy-name { font-size: 1.05rem; font-weight: 700; color: var(--rpcn-text-strong); margin-bottom: 2px; display: flex; align-items: center; gap: 8px; }
692+
.rpcn-trophy-desc { font-size: 0.85rem; color: var(--rpcn-subtle); margin-bottom: 6px; }
693+
.rpcn-trophy-stats { display: flex; align-items: center; gap: 12px; font-size: 0.8rem; font-weight: 600; }
694+
.rpcn-trophy-hidden { font-size: 0.65rem; background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 4px; text-transform: uppercase; font-weight: bold; }
677695

696+
.rpcn-trophy-type {
697+
width: 44px; height: 44px; flex-shrink: 0; display: flex; align-items: center; justify-content: center;
698+
border-radius: 50%; box-shadow: 0 2px 6px rgba(0,0,0,0.3);
699+
}
700+
.rpcn-trophy-type svg { width: 22px; height: 22px; }
701+
.rpcn-type-platinum { background: linear-gradient(135deg, #e6e9f0, #eef1f5); border: 2px solid #aeb5cc; }
702+
.rpcn-type-platinum svg { fill: #7b88a8; }
703+
.rpcn-type-gold { background: linear-gradient(135deg, #ffe066, #f5b041); border: 2px solid #b8860b; }
704+
.rpcn-type-gold svg { fill: #8a6300; }
705+
.rpcn-type-silver { background: linear-gradient(135deg, #e0e0e0, #bdc3c7); border: 2px solid #95a5a6; }
706+
.rpcn-type-silver svg { fill: #556061; }
707+
.rpcn-type-bronze { background: linear-gradient(135deg, #e09f67, #cd7f32); border: 2px solid #8b4513; }
708+
.rpcn-type-bronze svg { fill: #5c3016; }
709+
#tab-trophies:checked ~ .rpcn-tabs-nav label[for="tab-trophies"] {
710+
color: var(--rpcn-accent); border-bottom-color: var(--rpcn-accent);
711+
}
712+
#tab-trophies:checked ~ .rpcn-tab-panels #panel-trophies { display: block; }
678713

679714

680715

public_html/lib/module/rpcn/games.json

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@
182182
"id": []
183183
},
184184
"NPWR00077_00": {
185-
"title": ["Unknown Game"],
185+
"title": ["skate."],
186186
"id": []
187187
},
188188
"NPWR00082_00": {
@@ -1246,7 +1246,7 @@
12461246
},
12471247
"NPWR00574_00": {
12481248
"title": ["ゴミ箱 -GOMIBAKO-"],
1249-
"id": ["NPUA80247"]
1249+
"id": ["NPUA80247", "NPHA80058"]
12501250
},
12511251
"NPWR00575_00": {
12521252
"title": ["真・三國無双5 Empires"],
@@ -2105,11 +2105,11 @@
21052105
},
21062106
"NPWR00875_00": {
21072107
"title": ["Vandal Hearts: Flames of Judgment"],
2108-
"id": []
2108+
"id": ["NPUB30075"]
21092109
},
21102110
"NPWR00876_00": {
21112111
"title": ["Vandal Hearts: Flames of Judgment"],
2112-
"id": []
2112+
"id": ["NPJB00038"]
21132113
},
21142114
"NPWR00877_00": {
21152115
"title": ["Vandal Hearts: Flames of Judgment"],
@@ -2445,7 +2445,7 @@
24452445
},
24462446
"NPWR01014_00": {
24472447
"title": ["Tumble"],
2448-
"id": ["BCJS30068", "NPUA80470", "NPEA00239", "NPHA80114"],
2448+
"id": ["BCJS30068", "NPUA80470", "NPEA00239", "NPHA80114", "NPJA00064"],
24492449
"collection-id": ["BCES01334", "BCET00025", "BCUS98368"]
24502450
},
24512451
"NPWR01016_00": {
@@ -3816,7 +3816,8 @@
38163816
},
38173817
"NPWR01506_00": {
38183818
"title": ["Prince of Persia: The Sands of Time"],
3819-
"id": ["BLES01092", "BLUS30754", "NPEB00341", "NPUB30303"]
3819+
"id": ["NPEB00341", "NPUB30303"],
3820+
"collection-id": ["BLES01092", "BLUS30754"]
38203821
},
38213822
"NPWR01507_00": {
38223823
"title": ["Space Channel 5 Part 2"],
@@ -3881,7 +3882,8 @@
38813882
},
38823883
"NPWR01526_00": {
38833884
"title": ["Prince of Persia: Warrior Within"],
3884-
"id": ["BLES01092", "BLUS30754", "NPUB30388", "NPEB00389"]
3885+
"id": [ "NPUB30388", "NPEB00389"],
3886+
"collection-id": ["BLES01092", "BLUS30754"]
38853887
},
38863888
"NPWR01527_00": {
38873889
"title": ["Funky Lab Rat"],
@@ -3953,7 +3955,8 @@
39533955
},
39543956
"NPWR01551_00": {
39553957
"title": ["Prince of Persia: The Two Thrones"],
3956-
"id": ["BLES01092", "BLUS30754", "NPUB30387", "NPEB00390"]
3958+
"id": ["NPUB30387", "NPEB00390"],
3959+
"collection-id": ["BLES01092", "BLUS30754"]
39573960
},
39583961
"NPWR01552_00": {
39593962
"title": ["Alice: Madness Returns"],
@@ -6990,15 +6993,15 @@
69906993
"id": ["BLAS50498", "BLKS20375"]
69916994
},
69926995
"NPWR03724_00": {
6993-
"title": ["マクロス30~銀河を繋ぐ歌声~"],
6996+
"title": ["Macross 30 Ginga o Tsunagu Utagoe"],
69946997
"id": ["BLJS10184"]
69956998
},
69966999
"NPWR03725_00": {
69977000
"title": ["The Testament of Sherlock Holmes"],
69987001
"id": ["BLUS31046"]
69997002
},
70007003
"NPWR03726_00": {
7001-
"title": ["激アツ!! パチゲー魂MAX ヱヴァンゲリヲン “7” × “生命の鼓動”"],
7004+
"title": ["Gekiatsu!! Pachige-Damashi Max Evangelion 7 x Seimei no kodou"],
70027005
"id": ["BLJM60509"]
70037006
},
70047007
"NPWR03729_00": {
@@ -7018,23 +7021,23 @@
70187021
"id": ["BLES01715", "BLJM60505", "BLUS31027", "NPEB01124", "NPUB30930"]
70197022
},
70207023
"NPWR03740_00": {
7021-
"title": ["三國志12"],
7024+
"title": ["Sangokushi 12"],
70227025
"id": ["BLJM60550", "BLJM61078", "NPJB00253"]
70237026
},
70247027
"NPWR03742_00": {
7025-
"title": ["ドリームクラブZERO Special Edipyon!"],
7028+
"title": ["Dream C Club Zero: Special Edipyon!"],
70267029
"id": ["BLJS10208"]
70277030
},
70287031
"NPWR03745_00": {
7029-
"title": ["真・三國無双7"],
7032+
"title": ["Dynasty Warriors 8"],
70307033
"id": ["BLJM60586", "NPJB00325"]
70317034
},
70327035
"NPWR03752_00": {
7033-
"title": ["みんなのGOLF 6"],
7036+
"title": ["Everybody's Golf 6"],
70347037
"id": ["BCJS30089", "NPJA00086"]
70357038
},
70367039
"NPWR03754_00": {
7037-
"title": ["ガンダムブレイカー"],
7040+
"title": ["Gundam Breaker"],
70387041
"id": ["BLJS10197", "NPJB00357"]
70397042
},
70407043
"NPWR03755_00": {
@@ -7254,7 +7257,7 @@
72547257
"id": ["BLES01752", "BLUS31077", "NPUB31062"]
72557258
},
72567259
"NPWR03959_00": {
7257-
"title": ["Brothers"],
7260+
"title": ["Brothers: A Tale of Two Sons"],
72587261
"id": ["NPEB01201"]
72597262
},
72607263
"NPWR03961_00": {
@@ -8021,7 +8024,7 @@
80218024
},
80228025
"NPWR04643_00": {
80238026
"title": ["Giana Sisters: Twisted Dreams"],
8024-
"id": ["NPEB01338"]
8027+
"id": ["NPEB01338", "NPUB31272"]
80258028
},
80268029
"NPWR04645_00": {
80278030
"title": ["TEKKEN REVOLUTION"],
@@ -8512,7 +8515,7 @@
85128515
},
85138516
"NPWR05140_00": {
85148517
"title": ["麻雀覇王 段級バトル3"],
8515-
"id": ["BLJM61030"]
8518+
"id": ["BLJM61030", "NPJB00522"]
85168519
},
85178520
"NPWR05149_00": {
85188521
"title": ["戦国BASARA4"],
@@ -8958,8 +8961,8 @@
89588961
"id": ["BLES01998", "BLUS31392", "NPEB01954", "NPUB31429"]
89598962
},
89608963
"NPWR05896_00": {
8961-
"title": ["ブラザーズ 2人の息子の物語"],
8962-
"id": []
8964+
"title": ["Brothers: A Tale of Two Sons"],
8965+
"id": ["NPJB00568"]
89638966
},
89648967
"NPWR05897_00": {
89658968
"title": ["Jet Car Stunts"],

public_html/lib/module/rpcn/inc-rpcn-game.php

Lines changed: 117 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ class RPCNGame
1414
private string $defaultIcon;
1515
private string $pic1JsonPath;
1616
private string $pic1BasePath;
17+
private string $trophiesJsonPath;
18+
private string $trophiesIconBasePath;
19+
private string $trophiesSetsPath;
20+
private int $trophiesCacheTime;
21+
private array $raritySettings;
1722

1823
public bool $has_error = false;
1924
public string $gameTitle = 'Unknown Game';
@@ -30,6 +35,9 @@ class RPCNGame
3035
public array $chartDataHourly = [];
3136
public array $chartDataDaily = [];
3237
public array $chartDataAllTime = [];
38+
public bool $hasTrophies = false;
39+
public int $totalTrophies = 0;
40+
public array $trophies = [];
3341

3442
public function __construct(
3543
string $cacheDir,
@@ -44,21 +52,31 @@ public function __construct(
4452
string $iconBasePath,
4553
string $defaultIcon,
4654
string $pic1JsonPath = '',
47-
string $pic1BasePath = ''
55+
string $pic1BasePath = '',
56+
string $trophiesJsonPath = '',
57+
string $trophiesIconBasePath = '',
58+
string $trophiesSetsPath = '',
59+
int $trophiesCacheTime = 3600,
60+
array $raritySettings = []
4861
) {
49-
$this->cacheDir = rtrim($cacheDir, '/') . '/';
50-
$this->cacheTime = $cacheTime;
51-
$this->maxDisplayRows = $maxDisplayRows;
52-
$this->badwordsFile = $badwordsFile;
53-
$this->blacklistFile = $blacklistFile;
54-
$this->violationLog = $violationLog;
55-
$this->apiBase = rtrim($apiBase, '/');
56-
$this->parsersPath = rtrim($parsersPath, '/') . '/';
57-
$this->logFile = $logFile;
58-
$this->iconBasePath = rtrim($iconBasePath, '/') . '/';
59-
$this->defaultIcon = $defaultIcon;
60-
$this->pic1JsonPath = $pic1JsonPath;
61-
$this->pic1BasePath = rtrim($pic1BasePath, '/'). '/';
62+
$this->cacheDir = rtrim($cacheDir, '/') . '/';
63+
$this->cacheTime = $cacheTime;
64+
$this->maxDisplayRows = $maxDisplayRows;
65+
$this->badwordsFile = $badwordsFile;
66+
$this->blacklistFile = $blacklistFile;
67+
$this->violationLog = $violationLog;
68+
$this->apiBase = rtrim($apiBase, '/');
69+
$this->parsersPath = rtrim($parsersPath, '/') . '/';
70+
$this->logFile = $logFile;
71+
$this->iconBasePath = rtrim($iconBasePath, '/') . '/';
72+
$this->defaultIcon = $defaultIcon;
73+
$this->pic1JsonPath = $pic1JsonPath;
74+
$this->pic1BasePath = rtrim($pic1BasePath, '/') . '/';
75+
$this->trophiesJsonPath = $trophiesJsonPath;
76+
$this->trophiesIconBasePath = rtrim($trophiesIconBasePath, '/') . '/';
77+
$this->trophiesSetsPath = rtrim($trophiesSetsPath, '/') . '/';
78+
$this->trophiesCacheTime = $trophiesCacheTime;
79+
$this->raritySettings = $raritySettings;
6280
}
6381

6482
private function log_error(string $message): void
@@ -154,6 +172,83 @@ private function fetch_api(string $url, string $cacheFile): string
154172
return '';
155173
}
156174

175+
private function loadTrophies(string $commId): void
176+
{
177+
$cacheFile = $this->cacheDir . "trophies_{$commId}.json";
178+
$url = $this->apiBase . "/trophy/" . rawurlencode($commId);
179+
180+
$json = $this->fetch_api($url, $cacheFile);
181+
if ($json === '') return;
182+
183+
$apiData = json_decode($json, true);
184+
if (!is_array($apiData)) return;
185+
186+
$localFile = $this->trophiesSetsPath . $commId . '.json';
187+
if (!file_exists($localFile)) return;
188+
189+
$localData = json_decode(@file_get_contents($localFile), true);
190+
if (!is_array($localData) || empty($localData['trophies'])) return;
191+
192+
$this->hasTrophies = true;
193+
$this->totalTrophies = (int)($localData['totalItemCount'] ?? count($localData['trophies']));
194+
195+
$uniquePlayers = (int)($apiData['uniquePlayers'] ?? 0);
196+
$earnerMap = [];
197+
foreach ($apiData['trophies'] ?? [] as $t)
198+
{
199+
$earnerMap[(int)$t['trophyId']] = (int)$t['earnerCount'];
200+
}
201+
202+
$iconMap = [];
203+
if ($this->trophiesJsonPath !== '' && file_exists($this->trophiesJsonPath))
204+
{
205+
$mapData = json_decode(@file_get_contents($this->trophiesJsonPath), true);
206+
if (isset($mapData[$commId]))
207+
{
208+
$iconMap = $mapData[$commId];
209+
}
210+
}
211+
212+
foreach ($localData['trophies'] as $t)
213+
{
214+
$trophyId = (int)$t['trophyId'];
215+
$tId = (string)$trophyId;
216+
217+
$iconHash = $iconMap[$tId] ?? '';
218+
$iconUrl = $iconHash !== '' ? $this->trophiesIconBasePath . $iconHash . '.png' : $this->defaultIcon;
219+
220+
$earnerCount = $earnerMap[$trophyId] ?? 0;
221+
$pct = ($uniquePlayers > 0)
222+
? round($earnerCount / $uniquePlayers * 100, 2)
223+
: 0.0;
224+
225+
$rarity = 'Common';
226+
$rarityColor = '#a0aec0';
227+
foreach ($this->raritySettings as $setting)
228+
{
229+
if (($pct == 0.0 && $setting['max_pct'] == 0.0) || ($pct > 0.0 && $pct <= $setting['max_pct']))
230+
{
231+
$rarity = $setting['name'];
232+
$rarityColor = $setting['color'];
233+
break;
234+
}
235+
}
236+
237+
$this->trophies[] = [
238+
'id' => $trophyId,
239+
'hidden' => (bool)($t['trophyHidden'] ?? false),
240+
'type' => $t['trophyType'] ?? 'unknown',
241+
'name' => $t['trophyName'] ?? 'Unknown',
242+
'detail' => $t['trophyDetail'] ?? '',
243+
'earnerCount' => $earnerCount,
244+
'percentage' => $pct,
245+
'rarity' => $rarity,
246+
'rarityColor' => $rarityColor,
247+
'icon' => $iconUrl,
248+
];
249+
}
250+
}
251+
157252
// Leaderboard ajax
158253
public function handle_ajax(string $commId, ?string $boardIdParam): void
159254
{
@@ -383,6 +478,8 @@ public function load_page_data(string $commId, RPCNStats $stats, ?mysqli $db): v
383478
$this->boards = is_array($loaded) ? ($loaded['config']['names'] ?? []) : [];
384479
}
385480

481+
$this->loadTrophies($commId);
482+
386483
// Database stats
387484
if ($db === null) return;
388485

@@ -729,7 +826,12 @@ private function loadDbStats(mysqli $db, string $commId, RPCNStats $stats): void
729826
$icon_base_path,
730827
$default_icon,
731828
$pic1_json,
732-
$pic1_base_path
829+
$pic1_base_path,
830+
$trophies_json ?? '',
831+
$trophies_icon_base_path ?? '',
832+
$trophies_sets_path ?? '',
833+
(int)($trophies_cache_time ?? 3600),
834+
$trophies_rarity_settings ?? []
733835
);
734836

735837
if ($isAjax)

0 commit comments

Comments
 (0)