Skip to content

Commit

Permalink
Add playerlist command
Browse files Browse the repository at this point in the history
  • Loading branch information
D4isDAVID committed Oct 18, 2024
1 parent 69c9bad commit b8d537d
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 0 deletions.
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": {
"@discordjs/collection": "1.5.2",
"@discordjs/core": "1.0.0",
"@discordjs/formatters": "0.3.1",
"@discordjs/rest": "2.0.0",
"@discordjs/util": "1.0.0",
"@discordjs/ws": "1.0.0"
Expand Down
2 changes: 2 additions & 0 deletions server/components/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { client, gateway, rest, zdiscordBridge } from '../utils/env.js';
import { isStatefulInteraction } from '../utils/stateful.js';
import core from './core/index.js';
import ping from './ping/index.js';
import standalone from './standalone/index.js';
import {
ApplicationCommand,
Component,
Expand Down Expand Up @@ -86,6 +87,7 @@ function loadComponent({
export function loadComponents() {
loadComponent(core);
loadComponent(ping);
loadComponent(standalone);
if (zdiscordBridge) {
loadComponent(zdiscord);
console.log('zdiscord bridge enabled');
Expand Down
8 changes: 8 additions & 0 deletions server/components/standalone/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Component } from '../types.js';
import { playerlistButtonBase } from './playerlist/action-row.js';
import { playerlistCommand } from './playerlist/command.js';

export default {
commands: [playerlistCommand],
messageComponents: [playerlistButtonBase],
} satisfies Component;
114 changes: 114 additions & 0 deletions server/components/standalone/playerlist/action-row.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {
APIActionRowComponent,
APIButtonComponentWithCustomId,
APIMessageActionRowComponent,
ButtonStyle,
ComponentType,
} from '@discordjs/core';
import { createStatefulInteraction } from '../../../utils/stateful.js';
import { Button } from '../../types.js';
import { playerlistEmbed, playerlistPageCount } from './embed.js';

export enum PlayerlistButtonType {
First,
Previous,
Reload,
Next,
Last,
}

const typeData: Record<
PlayerlistButtonType,
{
emoji: string;
state: (page: number) => string;
}
> = {
[PlayerlistButtonType.First]: {
emoji: '⏪',
state: () => 'first',
},
[PlayerlistButtonType.Previous]: {
emoji: '⬅️',
state: (page) => `${page - 1}`,
},
[PlayerlistButtonType.Reload]: {
emoji: '🔃',
state: (page) => `${page}`,
},
[PlayerlistButtonType.Next]: {
emoji: '➡️',
state: (page) => `${page + 1}`,
},
[PlayerlistButtonType.Last]: {
emoji: '⏩',
state: () => 'last',
},
};

export const playerlistButtonBase = createStatefulInteraction<Button>({
data: {
type: ComponentType.Button,
style: ButtonStyle.Secondary,
custom_id: 'playerlist_button',
},
async execute({ data: interaction, api, state }) {
const pageCount = playerlistPageCount();
let page = Math.min(
{
first: 1,
last: pageCount,
}[state] ?? parseInt(state),
pageCount,
);

await api.interactions.updateMessage(
interaction.id,
interaction.token,
{
embeds: [playerlistEmbed(page)],
components: [playerlistActionRow(page)],
},
);
},
});

function playerlistButton(
type: PlayerlistButtonType,
page: number = 1,
): APIButtonComponentWithCustomId {
const data = typeData[type];
const buttonData = playerlistButtonBase.stateful(data.state(page));
buttonData.emoji = { name: data.emoji };

if (
(page === 1 &&
[
PlayerlistButtonType.First,
PlayerlistButtonType.Previous,
].includes(type)) ||
(page === playerlistPageCount() &&
[PlayerlistButtonType.Next, PlayerlistButtonType.Last].includes(
type,
))
) {
buttonData.disabled = true;
}

return buttonData;
}

export function playerlistActionRow(
page: number = 1,
): APIActionRowComponent<APIMessageActionRowComponent> {
return {
type: ComponentType.ActionRow,
components: [
playerlistButton(PlayerlistButtonType.First, page),
playerlistButton(PlayerlistButtonType.Previous, page),
playerlistButton(PlayerlistButtonType.Reload, page),
playerlistButton(PlayerlistButtonType.Next, page),
playerlistButton(PlayerlistButtonType.Last, page),
],
};
}
27 changes: 27 additions & 0 deletions server/components/standalone/playerlist/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
ApplicationCommandType,
ApplicationIntegrationType,
InteractionContextType,
MessageFlags,
} from '@discordjs/core';
import { ChatInputCommand } from '../../types.js';
import { playerlistActionRow } from './action-row.js';
import { playerlistEmbed } from './embed.js';

export const playerlistCommand = {
data: {
type: ApplicationCommandType.ChatInput,
name: 'playerlist',
description: 'Player list',
default_member_permissions: '0',
integration_types: [ApplicationIntegrationType.GuildInstall],
contexts: [InteractionContextType.Guild],
},
async execute({ data: interaction, api }) {
await api.interactions.reply(interaction.id, interaction.token, {
embeds: [playerlistEmbed()],
components: [playerlistActionRow()],
flags: MessageFlags.Ephemeral,
});
},
} satisfies ChatInputCommand;
49 changes: 49 additions & 0 deletions server/components/standalone/playerlist/embed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { APIEmbed } from '@discordjs/core';
import { escapeMarkdown } from '@discordjs/formatters';

const NAME_MAX_LENGTH = 32 + 5 + 5; // max steam name length + max id length + extra chars
const PLAYERS_PER_PAGE = Math.floor(4096 / NAME_MAX_LENGTH) || 1; // max embed length
export function playerlistPageCount() {
return Math.ceil(GetNumPlayerIndices() / PLAYERS_PER_PAGE) || 1;
}

export function playerlistEmbed(page: number = 1) {
const playerCount = GetNumPlayerIndices();
const maxPlayers = GetConvarInt('sv_maxClients', 32);
const embed: APIEmbed = {
title: `Online Players (${playerCount}/${maxPlayers})`,
footer: {
text: `Page ${page}/${playerlistPageCount()}`,
},
timestamp: new Date().toISOString(),
};

if (playerCount === 0) {
embed.description = 'There are no players online.';
return embed;
}

let startingIndex;
do {
startingIndex = PLAYERS_PER_PAGE * --page;
} while (startingIndex > playerCount);

const lines: string[] = [];
for (
let i = startingIndex;
i < playerCount && i < startingIndex + PLAYERS_PER_PAGE;
i++
) {
const playerId = GetPlayerFromIndex(i);
const playerName = GetPlayerName(playerId);
lines.push(
`\`${playerId}\` - ${escapeMarkdown(playerName)}`.substring(
0,
NAME_MAX_LENGTH + 1,
),
);
}

embed.description = lines.join('\n');
return embed;
}

0 comments on commit b8d537d

Please sign in to comment.