Skip to content

Commit 5332a38

Browse files
authored
Remove GameQ and use "own" implementation for queries (#94)
* remove gameq and use "own" implementation for queries * update readme * remove gameq from workflow * better error handling * fix minecraft hostname * pint * more minecraft fixes * fix player mapping for minecraft query * fix player list on bedrock * better check for allocation ip * add new packages to workflow * fix phpstan * better array checks * use timeout instead of connectTimeout * better null checks
1 parent 9ea98eb commit 5332a38

18 files changed

Lines changed: 493 additions & 169 deletions

.github/workflows/lint.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
phpstan:
3434
name: PHPStan
3535
runs-on: ubuntu-latest
36-
36+
3737
strategy:
3838
fail-fast: true
3939
matrix:
@@ -61,7 +61,7 @@ jobs:
6161
- name: Install plugin dependencies
6262
run: |
6363
cd pelican
64-
composer require "stripe/stripe-php:^18.0" "kovah/laravel-socialite-oidc:^0.6" "krymosoftware/gameq:^4.0" "socialiteproviders/pocketid:^5.0"
64+
composer require "stripe/stripe-php:^18.0" "kovah/laravel-socialite-oidc:^0.6" "socialiteproviders/pocketid:^5.0" "xpaw/php-minecraft-query:^5.0.0" "xpaw/php-source-query-class:^5.0.0"
6565
6666
- name: Setup .env file
6767
run: cp pelican/.env.example pelican/.env
@@ -76,4 +76,3 @@ jobs:
7676
run: |
7777
cd pelican
7878
vendor/bin/phpstan analyse --memory-limit=-1 --error-format=github
79-

player-counter/README.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,14 @@ Show the amount of connected players to game servers with real-time querying cap
44

55
## Setup
66

7-
**IMPORTANT**: You need to have the bz2 php extension and zip/7zip installed!
7+
Make sure your server has an allocation with a public ip. Alternatively, if you use local ips you can put the public ip in the allocation alias and enable "Use allocation alias?" in the plugin settings.
88

9-
Make sure your server has an allocation with a public ip.
10-
11-
For Minecraft servers you need to set `enable-query` to true and the `query-port` to your server port! (in `server.properties`)
12-
Game query for FiveM/RedM is currently not available due to a [bug with GameQ](https://github.com/pelican-dev/plugins/issues/48).
9+
Minecraft servers will first try the query (which requires you to set `enable-query` to true and `query-port` to your server port in `server.properties`) and will fallback to ping. It is recommended to enable query.
1310

1411
## Features
1512

1613
- Real-time player count display for game servers
17-
- Support for multiple game query protocols via [GameQ](https://github.com/krymosoftware/gameq)
14+
- Support for multiple game query protocols
1815
- Link query protocols to specific eggs
1916
- Dashboard widget showing connected players
2017
- Dedicated players page for detailed information

player-counter/database/Seeders/PlayerCounterSeeder.php

Lines changed: 79 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,101 @@
55
use App\Models\Egg;
66
use Boy132\PlayerCounter\Models\EggGameQuery;
77
use Boy132\PlayerCounter\Models\GameQuery;
8+
use Exception;
89
use Illuminate\Database\Seeder;
910

1011
class PlayerCounterSeeder extends Seeder
1112
{
13+
public const MAPPINGS = [
14+
[
15+
'names' => 'Squad',
16+
'query_type' => 'source',
17+
'query_port_offset' => 19378,
18+
],
19+
[
20+
'names' => 'Barotrauma',
21+
'query_type' => 'source',
22+
'query_port_offset' => 1,
23+
],
24+
[
25+
'names' => 'Valheim',
26+
'query_type' => 'source',
27+
'query_port_offset' => 1,
28+
],
29+
[
30+
'names' => ['V Rising', 'V-Rising', 'VRising'],
31+
'query_type' => 'source',
32+
'query_port_offset' => 1,
33+
],
34+
[
35+
'names' => ['The Forrest', 'TheForrest'],
36+
'query_type' => 'source',
37+
'query_port_offset' => 1,
38+
],
39+
[
40+
'names' => ['Arma 3', 'Arma3'],
41+
'query_type' => 'source',
42+
'query_port_offset' => 1,
43+
],
44+
[
45+
'names' => ['ARK: Survival Evolved', 'ARK: SurvivalEvolved', 'ARK Survival Evolved', 'ARK SurvivalEvolved', 'ARKSurvivalEvolved'],
46+
'query_type' => 'source',
47+
'query_port_offset' => 19238,
48+
],
49+
[
50+
'names' => 'Unturned',
51+
'query_type' => 'source',
52+
'query_port_offset' => 1,
53+
],
54+
[
55+
'names' => ['Insurgency: Sandstorm', 'Insurgency Sandstorm', 'InsurgencySandstorm'],
56+
'query_type' => 'source',
57+
'query_port_offset' => 29,
58+
],
59+
[
60+
'tag' => 'bedrock',
61+
'query_type' => 'minecraft_bedrock',
62+
'query_port_offset' => null,
63+
],
64+
[
65+
'tag' => 'minecraft',
66+
'query_type' => 'minecraft_java',
67+
'query_port_offset' => null,
68+
],
69+
[
70+
'tag' => 'source',
71+
'query_type' => 'source',
72+
'query_port_offset' => null,
73+
],
74+
];
75+
1276
public function run(): void
1377
{
14-
$minecraftQuery = GameQuery::firstOrCreate(['query_type' => 'minecraft']);
15-
$sourceQuery = GameQuery::firstOrCreate(['query_type' => 'source']);
16-
1778
foreach (Egg::all() as $egg) {
1879
$tags = $egg->tags ?? [];
1980

20-
if (in_array('minecraft', $tags)) {
21-
EggGameQuery::firstOrCreate([
22-
'egg_id' => $egg->id,
23-
], [
24-
'game_query_id' => $minecraftQuery->id,
25-
]);
26-
} elseif (in_array('source', $tags)) {
27-
if ($egg->name === 'Rust') {
28-
$rustQuery = GameQuery::firstOrCreate(['query_type' => 'rust']);
81+
foreach (self::MAPPINGS as $mapping) {
82+
if ((array_key_exists('names', $mapping) && in_array($egg->name, array_wrap($mapping['names']))) || (array_key_exists('tag', $mapping) && in_array($mapping['tag'], $tags))) {
83+
try {
84+
$query = GameQuery::firstOrCreate([
85+
'query_type' => $mapping['query_type'],
86+
'query_port_offset' => $mapping['query_port_offset'],
87+
]);
2988

30-
EggGameQuery::firstOrCreate([
31-
'egg_id' => $egg->id,
32-
], [
33-
'game_query_id' => $rustQuery->id,
34-
]);
35-
} else {
36-
EggGameQuery::firstOrCreate([
37-
'egg_id' => $egg->id,
38-
], [
39-
'game_query_id' => $sourceQuery->id,
40-
]);
89+
EggGameQuery::firstOrCreate([
90+
'egg_id' => $egg->id,
91+
], [
92+
'game_query_id' => $query->id,
93+
]);
94+
} catch (Exception) {
95+
}
4196
}
4297
}
4398
}
4499

45100
// @phpstan-ignore if.alwaysTrue
46101
if ($this->command) {
47-
$this->command->info('Created game query types for minecraft and source');
102+
$this->command->info('Created game query types for existing eggs');
48103
}
49104
}
50105
}

player-counter/plugin.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
],
1616
"panel_version": null,
1717
"composer_packages": {
18-
"krymosoftware/gameq": "^4.0"
18+
"xpaw/php-minecraft-query": "^5.0.0",
19+
"xpaw/php-source-query-class": "^5.0.0"
1920
}
2021
}

player-counter/src/Enums/GameQueryType.php

Lines changed: 0 additions & 58 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Boy132\PlayerCounter\Extensions\Query;
4+
5+
interface QueryTypeSchemaInterface
6+
{
7+
public function getId(): string;
8+
9+
public function getName(): string;
10+
11+
/** @return ?array{hostname: string, map: string, current_players: int, max_players: int, players: ?array<array{id: string, name: string}>} */
12+
public function process(string $ip, int $port): ?array;
13+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Boy132\PlayerCounter\Extensions\Query;
4+
5+
class QueryTypeService
6+
{
7+
/** @var QueryTypeSchemaInterface[] */
8+
private array $schemas = [];
9+
10+
public function get(string $id): ?QueryTypeSchemaInterface
11+
{
12+
return array_get($this->schemas, $id);
13+
}
14+
15+
public function register(QueryTypeSchemaInterface $schema): void
16+
{
17+
if (array_key_exists($schema->getId(), $this->schemas)) {
18+
return;
19+
}
20+
21+
$this->schemas[$schema->getId()] = $schema;
22+
}
23+
24+
/** @return array<string, string> */
25+
public function getMappings(): array
26+
{
27+
return collect($this->schemas)->mapWithKeys(fn ($schema) => [$schema->getId() => $schema->getName()])->all();
28+
}
29+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace Boy132\PlayerCounter\Extensions\Query\Schemas;
4+
5+
use Boy132\PlayerCounter\Extensions\Query\QueryTypeSchemaInterface;
6+
use Exception;
7+
use Illuminate\Support\Facades\Http;
8+
9+
class CitizenFXQueryTypeSchema implements QueryTypeSchemaInterface
10+
{
11+
public function getId(): string
12+
{
13+
return 'cfx';
14+
}
15+
16+
public function getName(): string
17+
{
18+
return 'CitizenFX';
19+
}
20+
21+
/** @return ?array{hostname: string, map: string, current_players: int, max_players: int, players: array<array{id: string, name: string}>} */
22+
public function process(string $ip, int $port): ?array
23+
{
24+
try {
25+
$this->resolveSRV($ip, $port);
26+
27+
$http = Http::acceptJson()
28+
->timeout(5)
29+
->throw()
30+
->baseUrl("http://$ip:$port/");
31+
32+
$info = $http->get('dynamic.json')->json();
33+
$players = $http->get('players.json')->json();
34+
35+
if (!is_array($info) || !is_array($players)) {
36+
return null;
37+
}
38+
39+
return [
40+
'hostname' => $info['hostname'],
41+
'map' => $info['mapname'],
42+
'current_players' => $info['clients'],
43+
'max_players' => $info['sv_maxclients'],
44+
'players' => array_map(fn ($player) => ['id' => (string) $player['id'], 'name' => (string) $player['name']], $players),
45+
];
46+
} catch (Exception $exception) {
47+
report($exception);
48+
}
49+
50+
return null;
51+
}
52+
53+
private function resolveSRV(string &$ip, int &$port): void
54+
{
55+
if (is_ip($ip)) {
56+
return;
57+
}
58+
59+
$record = dns_get_record('_cfx._udp.' . $ip, DNS_SRV);
60+
61+
if (!$record) {
62+
return;
63+
}
64+
65+
if ($record[0]['target']) {
66+
$ip = $record[0]['target'];
67+
}
68+
69+
if ($record[0]['port']) {
70+
$port = (int) $record[0]['port'];
71+
}
72+
}
73+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Boy132\PlayerCounter\Extensions\Query\Schemas;
4+
5+
use xPaw\SourceQuery\SourceQuery;
6+
7+
class GoldSourceQueryTypeSchema extends SourceQueryTypeSchema
8+
{
9+
public function getId(): string
10+
{
11+
return 'goldsrc';
12+
}
13+
14+
public function getName(): string
15+
{
16+
return 'GoldSrc';
17+
}
18+
19+
/** @return ?array{hostname: string, map: string, current_players: int, max_players: int, players: array<array{id: string, name: string}>} */
20+
public function process(string $ip, int $port): ?array
21+
{
22+
return $this->run($ip, $port, SourceQuery::GOLDSOURCE);
23+
}
24+
}

0 commit comments

Comments
 (0)