Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ The configuration is done in the plugin's `config.yml`. If it does not exist, th

The following settings are **for the plugin only**:

* **`data → worldPath`**: The absolute path of the world directory that contains the player statistics (i.e., the `stats` directory with JSON files in it) and advancements. Leave this to `null` to use the server's default world, which is correct for most setups. Set it only if your player data is stored in a different world than the server's default one, for example because the world was moved or your host uses a custom world container. For player data in `/srv/minecraft/smp/stats`, set this to `/srv/minecraft/smp`. Note that the remaining server files (user cache, banned players, ops and the server icon) continue to be read from the server directory itself. If no `stats` directory is found, a warning is logged and no players will be found.

*MinecraftStats* supports both world layouts and detects them automatically: newer Minecraft versions store the player data in the world's `players` subdirectory (`<world>/players/stats`), while older versions store it directly in the world directory (`<world>/stats`). You therefore do not need to set `worldPath` just because your server uses the newer layout.
* **`data → webSubdir`**: The name of the directory that *MinecraftStats* creates in the webserver's document root. This only applies if `data → documentRoot` is `null` and the plugin found a supported webserver.
* **`data → unpackWebFiles`**: If set to `true`, the plugin will automatically unpack all the necessary files required for the web frontend. This only applies if `data → documentRoot` is `null` and the plugin found a supported webserver. :warning: If you modify the web frontend, set this to `false` to ensure that your changes won't be overridden.
* **`data → updateInterval`**: The data for the web frontend is updated every this many minutes.
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ application {
repositories {
mavenCentral()
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
maven { url 'https://repo.papermc.io/repository/maven-public/' }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
maven { url 'https://oss.sonatype.org/content/repositories/central' }
maven { url 'https://repo.codemc.org/repository/maven-releases' }
Expand Down
7 changes: 7 additions & 0 deletions config/plugin/config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
data:
documentRoot: null
# The absolute path of the world directory containing the player stats and advancements.
# Leave this at null to use the server's default world, which is correct for most setups.
# Both world layouts are detected automatically, so this does not need to be set just because
# your server stores the player data in the world's "players" subdirectory.
# Set it only if the player data lives in a different world than the server's default one,
# e.g. "/srv/minecraft/smp" for stats in "/srv/minecraft/smp/stats".
worldPath: null
eventsDir: "events"
statsDir: "stats"
unpackWebFiles: true
Expand Down
43 changes: 36 additions & 7 deletions src/main/java/de/pdinklag/mcstats/FileSystemDataSource.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.pdinklag.mcstats;

import java.nio.file.Files;
import java.nio.file.Path;

/**
Expand All @@ -8,32 +9,60 @@
public class FileSystemDataSource implements DataSource {
private static final String ADVANCEMENTS_PATH_NAME = "advancements";
private static final String STATS_PATH_NAME = "stats";
private static final String PLAYERS_PATH_NAME = "players";

private final Path serverPath;
private final String worldName;
private final Path worldPath;

/**
* Constructs a data source.
* @param serverPath
* @param worldName
* Constructs a data source for a world located in the server directory.
* @param serverPath the server directory
* @param worldName the name of the world directory within the server directory
*/
public FileSystemDataSource(Path serverPath, String worldName) {
this(serverPath, serverPath.resolve(worldName));
}

/**
* Constructs a data source for a world at an arbitrary location.
* The server path is kept separate, because the server files (user cache, banned players, ops and icon)
* are read relative to it no matter where the world itself is stored.
* @param serverPath the server directory
* @param worldPath the world directory containing the player stats and advancements
*/
public FileSystemDataSource(Path serverPath, Path worldPath) {
this.serverPath = serverPath;
this.worldName = worldName;
this.worldPath = worldPath;
}

@Override
public Path getServerPath() {
return serverPath;
}

/**
* Gets the directory that contains the player data directories.
* Newer Minecraft versions store the player data in a "players" subdirectory of the world,
* while older versions store it directly in the world directory.
* Both layouts are supported, so that the same configuration works before and after a server update.
* @return the directory containing the player stats and advancements directories
*/
private Path getPlayerDataPath() {
final Path playersPath = worldPath.resolve(PLAYERS_PATH_NAME);
if (Files.isDirectory(playersPath.resolve(STATS_PATH_NAME))) {
return playersPath;
} else {
return worldPath;
}
}

@Override
public Path getPlayerStatsPath() {
return serverPath.resolve(worldName).resolve(STATS_PATH_NAME);
return getPlayerDataPath().resolve(STATS_PATH_NAME);
}

@Override
public Path getPlayerAdvancementsPath() {
return serverPath.resolve(worldName).resolve(ADVANCEMENTS_PATH_NAME);
return getPlayerDataPath().resolve(ADVANCEMENTS_PATH_NAME);
}
}
4 changes: 4 additions & 0 deletions src/main/java/de/pdinklag/mcstats/Updater.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ private HashMap<String, Player> processPlayers(PlayerFilter filter) {
log.writeError("failed to process file: " + path.toString(), e);
}
});
} else {
// without this, a misconfigured world path silently yields no players at all
log.writeLine("WARNING: no player stats directory at " + statsPath.toString()
+ " -- check that the configured world path points at the world containing the player data");
}
} catch (IOException e) {
log.writeError("failed to run discovery on data source: " + statsPath.toString(), e);
Expand Down
15 changes: 11 additions & 4 deletions src/main/java/de/pdinklag/mcstats/bukkit/BukkitConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,19 @@ public class BukkitConfig extends Config {
private String webSubdir = "stats";

public BukkitConfig(Plugin plugin) {
// create data source for default world
final String defaultWorldName = plugin.getServer().getWorlds().get(0).getName();
getDataSources().add(new FileSystemDataSource(Path.of(plugin.getServer().getWorldContainer().getAbsolutePath()), defaultWorldName));

// read config
final Configuration bukkitConfig = plugin.getConfig();

// create the data source, either for an explicitly configured world or for the default world
final Path serverPath = Path.of(plugin.getServer().getWorldContainer().getAbsolutePath());
final String configuredWorldPath = bukkitConfig.getString("data.worldPath");
if (configuredWorldPath != null && !configuredWorldPath.isEmpty()) {
getDataSources().add(new FileSystemDataSource(serverPath, Path.of(configuredWorldPath)));
} else {
final String defaultWorldName = plugin.getServer().getWorlds().get(0).getName();
getDataSources().add(new FileSystemDataSource(serverPath, defaultWorldName));
}

String documentRoot = bukkitConfig.getString("data.documentRoot");
if(documentRoot != null)
{
Expand Down