Skip to content

Commit

Permalink
Injection
Browse files Browse the repository at this point in the history
  • Loading branch information
voruti committed Jul 22, 2023
1 parent d95793d commit 298c750
Show file tree
Hide file tree
Showing 11 changed files with 402 additions and 105 deletions.
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
package voruti.velocityplayerlistquery;

import com.google.inject.Inject;
import com.velocitypowered.api.event.EventTask;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.ServerPing;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.experimental.FieldDefaults;
import org.slf4j.Logger;
import voruti.velocityplayerlistquery.model.Config;
import voruti.velocityplayerlistquery.service.PersistenceService;
import voruti.velocityplayerlistquery.util.ServerListEntryBuilder;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import voruti.velocityplayerlistquery.model.exception.InvalidServerPingException;
import voruti.velocityplayerlistquery.service.ConfigService;
import voruti.velocityplayerlistquery.service.serverpingprocessor.ServerPingProcessor;

import javax.inject.Inject;
import java.util.Optional;
import java.util.Set;

@Plugin(
id = "velocityplayerlistquery",
Expand All @@ -42,90 +34,69 @@ public class VelocityPlayerListQuery {
Logger logger;

@Inject
ProxyServer server;
ConfigService configService;

@Inject
@DataDirectory
Path dataDirectory;

Config config;
ServerListEntryBuilder serverListEntryBuilder;
Set<ServerPingProcessor> serverPingProcessors;


@Subscribe
public void onProxyInitialization(ProxyInitializeEvent ignored) {
this.loadConfig();
this.configService.reloadConfig();

this.logger.info("Enabled");
}

@Subscribe
public void onVelocityReload(ProxyReloadEvent ignored) {
this.loadConfig();
this.configService.reloadConfig();
}

private void loadConfig() {
this.config = new PersistenceService(logger, dataDirectory)
.loadConfig();
this.serverListEntryBuilder = new ServerListEntryBuilder(logger, config);

this.logger.info("Loaded config");
}

@Subscribe
public EventTask onServerPing(final ProxyPingEvent event) {
this.logger.trace("Server ping event received, adding players to server list entry...");
this.logger.trace("Server ping event received");

return EventTask.async(() -> {
final ServerPing serverPing = event.getPing();
final boolean versionUnset = serverPing.getVersion() == null ||
serverPing.getVersion().getName().equals("Unknown") ||
serverPing.getVersion().getProtocol() == 0 ||
serverPing.getDescriptionComponent() == null;
final boolean setVersion = config.setVersion() || (config.onlySetUnsetVersion() && versionUnset);

// check if server ping is invalid:
if (versionUnset && !setVersion) {
// Version and description (aka. MOTD) are both required to create a new ServerPing instance.
// Version could be set later on by this plugin, but description can't.
if (serverPing.getDescriptionComponent() == null) {
this.logger.info("Server ping is invalid, skipping");
return;
}

// collect players:
final Collection<Player> players = this.server.getAllPlayers();
if (!players.isEmpty()) {
final Stream<ServerPing.SamplePlayer> playerStream = players.stream()
// format players:
.map(player -> new ServerPing.SamplePlayer(
this.serverListEntryBuilder.buildForPlayer(player),
player.getGameProfile().getId()
))
// sort alphabetically:
.sorted(Comparator.comparing(ServerPing.SamplePlayer::getName));

// limit number of players shown in list, if configured:
final List<ServerPing.SamplePlayer> samplePlayers;
if (this.config.maxListEntries() > 0) {
samplePlayers = playerStream
.limit(this.config.maxListEntries())
.collect(Collectors.toCollection(ArrayList::new));

final int numberOfLeftOutPlayers = players.size() - this.config.maxListEntries();
if (numberOfLeftOutPlayers > 0) {
samplePlayers.add(
new ServerPing.SamplePlayer(
String.format("...and %d more", numberOfLeftOutPlayers),
UUID.randomUUID()
)
);
}
} else {
samplePlayers = playerStream.collect(Collectors.toList());
}
final ServerPing.Builder ping = serverPing.asBuilder()
.clearSamplePlayers()
.samplePlayers(samplePlayers.toArray(new ServerPing.SamplePlayer[0]));
if (config.replaceMaxPlayerCount()) ping.maximumPlayers(this.server.getConfiguration().getShowMaxPlayers());
if (config.replaceOnlinePlayerCount()) ping.onlinePlayers(players.size());
if (setVersion) ping.version(new ServerPing.Version(config.versionProtocol(), config.versionName()));
event.setPing(ping.build());
try {
this.processPing(serverPing)
.ifPresent(event::setPing);
} catch (InvalidServerPingException e) {
this.logger.debug("Caught InvalidServerPingException", e);
this.logger.info("Server ping is invalid, skipping");
}
});
}

@NonNull
private Optional<ServerPing> processPing(@NonNull final ServerPing serverPing) {
final ServerPing.Builder builder = serverPing.asBuilder();

// apply all server ping processors:
boolean isModified = false;
for (ServerPingProcessor processor : this.serverPingProcessors) {
if (processor.isEnabled()) {
processor.applyToServerPing(builder);
isModified = true;
}
}

if (builder.getVersion() == null) {
throw new InvalidServerPingException("Missing version info in server ping even after processing");
}

return isModified
? Optional.of(builder.build())
: Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package voruti.velocityplayerlistquery.model.exception;

import lombok.experimental.StandardException;

@StandardException
public class InvalidServerPingException extends IllegalStateException {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package voruti.velocityplayerlistquery.service;

import com.google.inject.Inject;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.experimental.FieldDefaults;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import voruti.velocityplayerlistquery.model.Config;

import javax.inject.Singleton;

@Singleton
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ConfigService {

@Inject
Logger logger;

@Inject
PersistenceService persistenceService;

@Nullable
Config cachedConfig;


@NonNull
public Config getConfig() {
if (this.cachedConfig == null) {
this.reloadConfig();
}

return this.cachedConfig;
}

public void reloadConfig() {
logger.trace("Loading config from persistence service...");

this.cachedConfig = this.persistenceService.loadConfig();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,46 @@

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.inject.Inject;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.slf4j.Logger;
import voruti.velocityplayerlistquery.model.Config;

import javax.inject.Singleton;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@Singleton
@FieldDefaults(level = AccessLevel.PRIVATE)
public class PersistenceService {

@NonNull
Logger logger;
@NonNull
Path dataDirectory;

@NonNull
Gson gson = new GsonBuilder()
final Gson gson = new GsonBuilder()
.setPrettyPrinting()
.create();

@Inject
Logger logger;

@Inject
@DataDirectory
Path dataDirectory;


@NonNull
public Config loadConfig() {
logger.trace("Loading config...");
this.logger.trace("Loading config...");

try {
// checks:
if (!Files.exists(dataDirectory)) {
Files.createDirectories(dataDirectory);
if (!Files.exists(this.dataDirectory)) {
Files.createDirectories(this.dataDirectory);
}

final Path configPath = dataDirectory.resolve(Config.FILE_NAME);
final Path configPath = this.dataDirectory.resolve(Config.FILE_NAME);
if (!Files.exists(configPath)) {
this.writeFile(configPath, Config.DEFAULT);
return Config.DEFAULT;
Expand All @@ -48,22 +51,22 @@ public Config loadConfig() {
}

// load:
final Config loadedConfig = gson.fromJson(
final Config loadedConfig = this.gson.fromJson(
Files.readString(configPath),
Config.class
);
return loadedConfig != null
? loadedConfig
: Config.DEFAULT;
} catch (IOException | IllegalStateException e) {
logger.error("Error while loading config", e);
this.logger.error("Error while loading config", e);
return Config.DEFAULT;
}
}

private <T> void writeFile(@NonNull final Path path, @NonNull final T content) throws IOException {
logger.trace("Writing file: {}", path);
this.logger.trace("Writing file: {}", path);

Files.write(path, gson.toJson(content).getBytes());
Files.write(path, this.gson.toJson(content).getBytes());
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
package voruti.velocityplayerlistquery.util;
package voruti.velocityplayerlistquery.service;

import com.velocitypowered.api.proxy.Player;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.slf4j.Logger;
import voruti.velocityplayerlistquery.model.Config;

@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class ServerListEntryBuilder {
import javax.inject.Inject;
import javax.inject.Singleton;

@NonNull
@Singleton
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ServerListEntryBuilderService {

@Inject
Logger logger;
@NonNull
Config config;

@Inject
ConfigService configService;

public String buildForPlayer(Player player) {

@NonNull
public String buildForPlayer(@NonNull final Player player) {
logger.trace("Building server list entry for player {}...", player.getUsername());
return String.format(
this.config.serverListEntryFormat(),
this.configService.getConfig()
.serverListEntryFormat(),
player.getGameProfile().getName(),
player.getCurrentServer()
.map(serverConnection -> serverConnection.getServerInfo().getName())
Expand Down

0 comments on commit 298c750

Please sign in to comment.