Skip to content

Commit 2940798

Browse files
authored
Merge pull request #151 from counterstrikesharp-panel/codex/create-top-time-rank-listing
Add playtime leaderboard
2 parents 8e4e473 + c6c8bc7 commit 2940798

File tree

8 files changed

+264
-0
lines changed

8 files changed

+264
-0
lines changed

app/Http/Controllers/K4Ranks/RanksController.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ public function index(Request $request)
2929
$servers = ModuleServerSetting::all();
3030
return view('k4Ranks.list', compact('servers'));
3131
}
32+
33+
public function playtime(Request $request)
34+
{
35+
$serverId = $request->query('server_id');
36+
ModuleHelper::useConnection('Ranks', $serverId);
37+
$servers = ModuleServerSetting::all();
38+
return view('k4Ranks.playtime', compact('servers'));
39+
}
3240
public function getPlayersList(Request $request)
3341
{
3442
ModuleHelper::useConnection('Ranks');
@@ -171,6 +179,105 @@ public function getPlayersList(Request $request)
171179
]);
172180
}
173181

182+
public function getPlaytimeList(Request $request)
183+
{
184+
ModuleHelper::useConnection('Ranks');
185+
186+
$start = $request->input('start');
187+
$length = $request->input('length');
188+
$searchValue = $request->input('search.value');
189+
$orderColumn = $request->input('order.0.column');
190+
$orderDirection = $request->input('order.0.dir');
191+
192+
$query = ZenithPlayerStorage::selectRaw('*, (SELECT COUNT(*) + 1 FROM zenith_player_storage AS zps WHERE CAST(JSON_EXTRACT(zps.`K4-Zenith-TimeStats.storage`, "$.TotalPlaytime") AS UNSIGNED) > CAST(JSON_EXTRACT(zenith_player_storage.`K4-Zenith-TimeStats.storage`, "$.TotalPlaytime") AS UNSIGNED)) AS `position`');
193+
194+
if (!empty($searchValue)) {
195+
$query->where('steam_id', 'like', '%' . $searchValue . '%')
196+
->orWhere('name', 'like', '%' . $searchValue . '%');
197+
}
198+
199+
if ($orderColumn !== null) {
200+
$columnName = $request->input('columns.' . $orderColumn . '.data');
201+
switch ($columnName) {
202+
case 'playtime':
203+
$query->orderByRaw('CAST(JSON_UNQUOTE(JSON_EXTRACT(`K4-Zenith-TimeStats.storage`, "$.TotalPlaytime")) AS UNSIGNED) ' . $orderDirection);
204+
break;
205+
case 'kills':
206+
$query->orderByRaw('CAST(JSON_UNQUOTE(JSON_EXTRACT(`K4-Zenith-Stats.storage`, "$.Kills")) AS UNSIGNED) ' . $orderDirection);
207+
break;
208+
case 'deaths':
209+
$query->orderByRaw('CAST(JSON_UNQUOTE(JSON_EXTRACT(`K4-Zenith-Stats.storage`, "$.Deaths")) AS UNSIGNED) ' . $orderDirection);
210+
break;
211+
case 'assists':
212+
$query->orderByRaw('CAST(JSON_UNQUOTE(JSON_EXTRACT(`K4-Zenith-Stats.storage`, "$.Assists")) AS UNSIGNED) ' . $orderDirection);
213+
break;
214+
case 'headshots':
215+
$query->orderByRaw('CAST(JSON_UNQUOTE(JSON_EXTRACT(`K4-Zenith-Stats.storage`, "$.Headshots")) AS UNSIGNED) ' . $orderDirection);
216+
break;
217+
case 'rounds_ct':
218+
$query->orderByRaw('CAST(JSON_UNQUOTE(JSON_EXTRACT(`K4-Zenith-Stats.storage`, "$.RoundsCT")) AS UNSIGNED) ' . $orderDirection);
219+
break;
220+
case 'rounds_t':
221+
$query->orderByRaw('CAST(JSON_UNQUOTE(JSON_EXTRACT(`K4-Zenith-Stats.storage`, "$.RoundsT")) AS UNSIGNED) ' . $orderDirection);
222+
break;
223+
case 'rounds_overall':
224+
$query->orderByRaw('(CAST(JSON_UNQUOTE(JSON_EXTRACT(`K4-Zenith-Stats.storage`, "$.RoundsCT")) AS UNSIGNED) + CAST(JSON_UNQUOTE(JSON_EXTRACT(`K4-Zenith-Stats.storage`, "$.RoundsT")) AS UNSIGNED)) ' . $orderDirection);
225+
break;
226+
case 'games_won':
227+
$query->orderByRaw('CAST(JSON_UNQUOTE(JSON_EXTRACT(`K4-Zenith-Stats.storage`, "$.GameWin")) AS UNSIGNED) ' . $orderDirection);
228+
break;
229+
case 'games_lost':
230+
$query->orderByRaw('CAST(JSON_UNQUOTE(JSON_EXTRACT(`K4-Zenith-Stats.storage`, "$.GameLose")) AS UNSIGNED) ' . $orderDirection);
231+
break;
232+
case 'position':
233+
$query->orderBy('position', $orderDirection);
234+
break;
235+
default:
236+
$query->orderBy($columnName, $orderDirection);
237+
break;
238+
}
239+
} else {
240+
$query->orderByRaw('CAST(JSON_UNQUOTE(JSON_EXTRACT(`K4-Zenith-TimeStats.storage`, "$.TotalPlaytime")) AS UNSIGNED) DESC');
241+
}
242+
243+
$players = $query->offset($start)->limit($length)->get();
244+
245+
$formattedData = [];
246+
foreach ($players as $player) {
247+
$playerData = $player['K4-Zenith-Stats.storage'];
248+
$playerRank = $player['K4-Zenith-Ranks.storage'];
249+
$playerTime = $player['K4-Zenith-TimeStats.storage'];
250+
$player->player_steamid = $player->steam_id;
251+
$response = CommonHelper::steamProfile($player);
252+
$serverId = Crypt::encrypt(Session::get('Ranks_server'));
253+
$formattedData[] = [
254+
"profile" => env('VITE_SITE_DIR')."/ranks/profile/$player->player_steamid/$serverId",
255+
"position" => $player->position,
256+
"name" => !empty($response['response']['players'][0]['personaname']) ? $response['response']['players'][0]['personaname'] : 'Profile',
257+
"player_steamid" => $player->steam_id,
258+
"playtime" => number_format(CarbonInterval::minutes($playerTime['TotalPlaytime'] ?? 0)->totalHours, 2),
259+
"kills" => $playerData['Kills'] ?? 0,
260+
"deaths" => $playerData['Deaths'] ?? 0,
261+
"assists" => $playerData['Assists'] ?? 0,
262+
"headshots" => $playerData['Headshots'] ?? 0,
263+
"rounds_ct" => $playerData['RoundsCT'] ?? 0,
264+
"rounds_t" => $playerData['RoundsT'] ?? 0,
265+
"rounds_overall" => ($playerData['RoundsCT'] ?? 0) + ($playerData['RoundsT'] ?? 0),
266+
"games_won" => $playerData['GameWin'] ?? 0,
267+
"games_lost" => $playerData['GameLose'] ?? 0,
268+
"avatar" => !empty($response['response']['players'][0]['avatar']) ? $response['response']['players'][0]['avatar'] : 'https://mdbootstrap.com/img/Photos/Avatars/img(32).jpg',
269+
"last_seen" => Carbon::parse($player->last_online ?? now())->diffForHumans(),
270+
];
271+
}
272+
273+
return response()->json([
274+
'draw' => $request->input('draw'),
275+
"recordsTotal" => ZenithPlayerStorage::count(),
276+
"recordsFiltered" => !empty($searchValue) ? count($formattedData) : ZenithPlayerStorage::count(),
277+
"data" => $formattedData
278+
]);
279+
}
280+
174281
public function viewProfile(Request $request, $steam_id, $server_id) {
175282
ModuleHelper::useConnection('Ranks',$server_id);
176283
$player = ZenithPlayerStorage::where('steam_id', $steam_id)->firstOrFail();

lang/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@
232232
"dashboard.bans": "Bans",
233233
"dashboard.connect": "Connect",
234234
"dashboard.csRating": "CS Rating",
235+
"dashboard.playTime": "Play Time",
235236
"dashboard.deaths": "Deaths",
236237
"dashboard.expired": "Expired",
237238
"dashboard.expires": "Expires",

lang/en/dashboard.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
'players' => 'Players',
1717
'player' => 'Player',
1818
'csRating' => 'CS Rating',
19+
'playTime' => 'Play Time',
1920
'rank' => 'Rank',
2021
'kills' => 'Kills',
2122
'deaths' => 'Deaths',

resources/js/ranks/playtime.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import DataTable from 'datatables.net-dt';
2+
import 'datatables.net-responsive';
3+
let dataTable = null;
4+
function loadPlayTime() {
5+
dataTable = new DataTable("#playTimeList", {
6+
"processing": true,
7+
"serverSide": true,
8+
"responsive": true,
9+
pageLength: 25,
10+
"ajax": {
11+
"url": playTimeListUrl,
12+
"headers": {
13+
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content')
14+
},
15+
"type": "POST",
16+
"dataType": "json"
17+
},
18+
"language": {
19+
"search": window.translations.searchByPlayernameAndSteamid,
20+
'processing': '<div class="spinner"></div>'
21+
},
22+
order: [[2, 'desc']],
23+
"columns": [
24+
{"data": "position"},
25+
{
26+
"data": "name", "render": function (data, type, row, meta) {
27+
const truncatedName = truncatePlayerName(data);
28+
return `<div class="ranksList"><span class="list-profile"><a target="_blank" href="${row.profile}"><i class="fas fa-external-link-alt rank-profile"></i></a><img src="${row.avatar}" /><a href="https://steamcommunity.com/profiles/${row.player_steamid}">${truncatedName}</a></span><p class="text-muted mb-0">${window.translations.lastSeen}: <span class="badge badge-light-info rounded-pill d-inline">${row.last_seen}</span></p></div>`;
29+
}
30+
},
31+
{"data": "playtime"},
32+
{"data": "kills"},
33+
{"data": "deaths"},
34+
{"data": "assists"},
35+
{"data": "headshots"},
36+
{"data": "rounds_ct"},
37+
{"data": "rounds_t"},
38+
{"data": "rounds_overall"},
39+
{"data": "games_won"},
40+
{"data": "games_lost"}
41+
]
42+
});
43+
}
44+
loadPlayTime();
45+
46+
function truncatePlayerName(playerName: string): string {
47+
if (playerName === null) {
48+
return "Unknown";
49+
} else if (playerName.length > 15) {
50+
return playerName.substring(0, 12) + '...';
51+
} else {
52+
return playerName;
53+
}
54+
}

resources/views/components/menu/vertical-menu.blade.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,13 @@ class="feather feather-chevron-right">
230230
</div>
231231
</a>
232232
</li>
233+
<li class="{{ Request::is('*list/playtime*') ? 'active' : '' }}">
234+
<a href="{{getAppSubDirectoryPath()}}/list/playtime" class="dropdown-toggle">
235+
<div class="">
236+
<i class="fas fa-clock fa-fw me-3"></i><span>{{ __('dashboard.playTime') }}</span>
237+
</div>
238+
</a>
239+
</li>
233240
@endif
234241
@if(env('VIP') == 'Enabled')
235242
<li class="{{ Request::is('*vip*') ? 'active' : '' }}">
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
@php use Illuminate\Support\Facades\Crypt;use Illuminate\Support\Facades\Session; @endphp
2+
<x-base-layout :scrollspy="false">
3+
<x-slot:pageTitle>
4+
{{ __('dashboard.playTime') }} - CSS-BANS
5+
</x-slot>
6+
@vite(['resources/scss/dark/assets/components/datatable.scss'])
7+
<!-- BEGIN GLOBAL MANDATORY STYLES -->
8+
<x-slot:headerFiles>
9+
<link rel="stylesheet" href="{{asset('plugins/notification/snackbar/snackbar.min.css')}}">
10+
@vite(['resources/scss/light/plugins/notification/snackbar/custom-snackbar.scss'])
11+
</x-slot>
12+
@if (session('success'))
13+
<x-alert type="success" :message="session('success')"/>
14+
@endif
15+
@if (session('error'))
16+
<x-alert type="danger" :message="session('error')"/>
17+
@endif
18+
@if ($errors->any())
19+
<div class="alert alert-danger">
20+
<ul>
21+
@foreach ($errors->all() as $error)
22+
<li>{{ $error }}</li>
23+
@endforeach
24+
</ul>
25+
</div>
26+
@endif
27+
<section class="mb-12">
28+
<div class="card">
29+
<div class="card-header text-center py-3">
30+
<h5 class="mb-0 text-center">
31+
<strong>{{ __('dashboard.playTime') }}</strong>
32+
</h5>
33+
</div>
34+
<div class="card-body">
35+
<div class="rank-servers">
36+
<select class="form-select" id="serverSelect">
37+
@foreach ($servers as $server)
38+
<option
39+
{{$server->id == Session::get('Ranks_server') ? 'selected': ''}} value="{{ Crypt::encrypt($server->id) }}">
40+
{{ $server->name }}
41+
</option>
42+
@endforeach
43+
</select>
44+
<label for="serverSelect"
45+
class="serverSelectLabel form-label">{{ __('admins.selectServers') }}</label>
46+
</div>
47+
<div class="table-responsive">
48+
<table class="table table-hover table-borderless" id="playTimeList" style="width:100%">
49+
<thead>
50+
<tr>
51+
<th>{{ __('dashboard.position') }}</th>
52+
<th>{{ __('dashboard.player') }}</th>
53+
<th>{{ __('dashboard.playTime') }}</th>
54+
<th>{{ __('dashboard.kills') }} <i class="fas fa-skull-crossbones"></i></th>
55+
<th>{{ __('dashboard.deaths') }} <i class="fas fa-skull"></i></th>
56+
<th>{{ __('admins.assists') }} <i class="fas fa-hands-helping"></i></th>
57+
<th>{{ __('admins.headhost') }} <i class="fas fa-bullseye"></i></th>
58+
<th>{{ __('admins.ct') }} <i class="fas fa-trophy"></i></th>
59+
<th>{{ __('admins.t') }} <i class="fas fa-trophy"></i></th>
60+
<th>{{ __('admins.overall') }} <i class="fas fa-trophy"></i></th>
61+
<th>{{ __('admins.gameswon') }} <i class="fas fa-trophy"></i></th>
62+
<th>{{ __('admins.gameslost') }} <i class="fas fa-times-circle"></i></th>
63+
</tr>
64+
</thead>
65+
<tbody>
66+
67+
</tbody>
68+
</table>
69+
</div>
70+
</div>
71+
</div>
72+
</section>
73+
<x-slot:footerFiles>
74+
@vite(['resources/js/ranks/playtime.ts'])
75+
<script>
76+
const playTimeListUrl = '{!! env('VITE_SITE_DIR') !!}/list/playtime';
77+
$(document).ready(function () {
78+
$('#serverSelect').change(function () {
79+
const serverId = $(this).val();
80+
window.location.href = '{{ url()->current() }}' + '?server_id=' + serverId;
81+
});
82+
});
83+
window.translations = {
84+
searchByPlayernameAndSteamid: "{{ __('admins.searchByPlayernameAndSteamid') }}",
85+
lastSeen: "{{ __('dashboard.lastSeen') }}"
86+
};
87+
</script>
88+
</x-slot>
89+
</x-base-layout>
90+
91+

routes/web.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@
9393
Route::group(['prefix' => 'list'], function () {
9494
Route::get('/ranks', [RanksController::class, 'index']);
9595
Route::post('/ranks', [RanksController::class, 'getPlayersList']);
96+
Route::get('/playtime', [RanksController::class, 'playtime']);
97+
Route::post('/playtime', [RanksController::class, 'getPlaytimeList']);
9698
});
9799
Route::get('/ranks/profile/{steam_id}/{server_id}', [RanksController::class, 'viewProfile'])->name('ranks.profile');
98100
});

vite.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ export default defineConfig({
205205
'resources/js/groups/groups.ts',
206206
'resources/js/groups/edit.ts',
207207
'resources/js/ranks/ranks.ts',
208+
'resources/js/ranks/playtime.ts',
208209
'resources/js/admin/delete.ts',
209210
'resources/js/nav.js',
210211
'resources/js/vip/list.ts',

0 commit comments

Comments
 (0)