Fabric Platform Implemenation (#2018)

Adds a fabric specific Plan module that builds a separate jar.

Co-authored-by: Vankka <vankka.main@gmail.com>
Co-authored-by: DrexHD <nicknamedrex@gmail.com>

Affects issues:
- Close #1956
This commit is contained in:
Antti Koponen 2021-07-24 14:10:48 +03:00 committed by GitHub
parent 284372613c
commit 67153e8fc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 3454 additions and 12 deletions

3
.gitignore vendored
View File

@ -3,8 +3,9 @@ Plan.iml
PlanPluginBridge.iml
.sonar/
builds/
# Nukkit creates server.log during tests for some reason.
# Nukkit & Fabric create log files during tests for some reason.
server.log
/Plan/fabric/logs/
*.db

View File

@ -139,7 +139,7 @@ subprojects {
}
checkstyle {
toolVersion "8.33"
toolVersion "8.44"
getConfigDirectory().set file("$rootProject.projectDir/config/checkstyle")
}

View File

@ -24,6 +24,7 @@ task updateVersion(type: Copy) {
include 'plugin.yml'
include 'bungee.yml'
include 'nukkit.yml'
include 'fabric.mod.json'
}
into 'build/sources/resources/'
filter(ReplaceTokens, tokens: [version: '' + project.ext.fullVersion])

View File

@ -50,6 +50,10 @@ public class CommandWithSubcommands extends Subcommand {
return subcommands.stream().filter(sender::hasAllPermissionsFor).collect(Collectors.toList());
}
public List<Subcommand> getSubcommands() {
return subcommands;
}
public void onHelp(CMDSender sender, Arguments arguments) {
List<Subcommand> hasPermissionFor = getPermittedSubcommands(sender);
sender.buildMessage()

View File

@ -65,7 +65,7 @@ public class PlayersMutator {
.map(sessions -> sessions.stream().anyMatch(session -> {
long start = session.getStart();
long end = session.getEnd();
return (after <= start && start <= before) || (after <= end && end <= before);
return after <= start && start <= before || after <= end && end <= before;
})).orElse(false)
);
}

View File

@ -85,7 +85,8 @@ public class Contributors {
new Contributor("mbax", CODE),
new Contributor("rymiel", CODE),
new Contributor("Perchun_Pak", LANG),
new Contributor("HexedHero", CODE)
new Contributor("HexedHero", CODE),
new Contributor("DrexHD", CODE)
};
private Contributors() {

View File

@ -156,7 +156,7 @@ public class RequestHandler implements HttpHandler {
}
if (response.getCode() != 401 // Not failed
&& response.getCode() != 403 // Not blocked
&& (request != null && request.getUser().isPresent()) // Logged in
&& request != null && request.getUser().isPresent() // Logged in
) {
bruteForceGuard.resetAttemptCount(accessor);
}

View File

@ -60,7 +60,7 @@ public class PlayerPageResolver implements Resolver {
return uuidUtility.getNameOf(nameOrUUID).map(user.getName()::equalsIgnoreCase) // uuid matches user
.orElse(false); // uuid or name don't match
}).orElse(true); // No name or UUID given
return user.hasPermission("page.player.other") || (user.hasPermission("page.player.self") && isOwnPage);
return user.hasPermission("page.player.other") || user.hasPermission("page.player.self") && isOwnPage;
}
@Override

View File

@ -0,0 +1,80 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.settings;
import com.djrapitops.plan.settings.config.ConfigReader;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.changes.ConfigUpdater;
import com.djrapitops.plan.settings.config.paths.PluginSettings;
import com.djrapitops.plan.settings.network.ServerSettingsManager;
import com.djrapitops.plan.settings.theme.Theme;
import com.djrapitops.plan.storage.file.PlanFiles;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import net.playeranalytics.plugin.server.PluginLogger;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
/**
* Config system for Fabric.
*
* @author Kopo942
*/
@Singleton
public class FabricConfigSystem extends ConfigSystem {
private final ConfigUpdater configUpdater;
private final ServerSettingsManager serverSettingsManager;
@Inject
public FabricConfigSystem(
PlanFiles files,
PlanConfig config,
ConfigUpdater configUpdater,
ServerSettingsManager serverSettingsManager,
Theme theme,
PluginLogger logger,
ErrorLogger errorLogger
) {
super(files, config, theme, logger, errorLogger);
this.configUpdater = configUpdater;
this.serverSettingsManager = serverSettingsManager;
}
@Override
public void enable() {
super.enable();
if (config.isTrue(PluginSettings.PROXY_COPY_CONFIG)) {
serverSettingsManager.enable();
}
}
@Override
public void disable() {
serverSettingsManager.disable();
super.disable();
}
@Override
protected void copyDefaults() throws IOException {
configUpdater.applyConfigUpdate(config);
try (ConfigReader reader = new ConfigReader(files.getResourceFromJar("config.yml").asInputStream())) {
config.copyMissing(reader.read());
}
}
}

View File

@ -40,8 +40,8 @@ public class ExtensionTableRowValueLengthPatch extends Patch {
@Override
public boolean hasBeenApplied() {
return dbType == DBType.SQLITE || // SQLite does not limit varchar lengths
(columnVarcharLength(playerTable, ExtensionPlayerTableValueTable.VALUE_4) >= 250
&& columnVarcharLength(serverTable, ExtensionServerTableValueTable.VALUE_5) >= 250);
columnVarcharLength(playerTable, ExtensionPlayerTableValueTable.VALUE_4) >= 250
&& columnVarcharLength(serverTable, ExtensionServerTableValueTable.VALUE_5) >= 250;
}
private int columnVarcharLength(String table, String column) {

View File

@ -38,10 +38,10 @@ public class GeoInfoOptimizationPatch extends Patch {
@Override
public boolean hasBeenApplied() {
return !hasTable(oldTableName)
|| (hasColumn(oldTableName, GeoInfoTable.ID)
|| hasColumn(oldTableName, GeoInfoTable.ID)
&& hasColumn(oldTableName, GeoInfoTable.USER_UUID)
&& !hasColumn(oldTableName, "user_id")
&& !hasTable(tempTableName)); // If this table exists the patch has failed to finish.
&& !hasTable(tempTableName); // If this table exists the patch has failed to finish.
}
@Override

View File

@ -41,7 +41,7 @@ public class KillsServerIDPatch extends Patch {
// KillsOptimizationPatch makes this patch incompatible with newer patch versions.
return hasColumn(tableName, KillsTable.SERVER_UUID)
|| (hasColumn(tableName, columnName) && allValuesHaveValueZero(tableName, columnName));
|| hasColumn(tableName, columnName) && allValuesHaveValueZero(tableName, columnName);
}
@Override

View File

@ -0,0 +1,28 @@
{
"schemaVersion": 1,
"id": "plan",
"name": "Plan",
"version": "@version@",
"environment": "server",
"entrypoints": {
"server": [
"net.playeranalytics.plan.PlanFabric"
]
},
"mixins": [
"plan.mixins.json"
],
"depends": {
"minecraft": ">=1.17",
"java": ">=16",
"fabric-api-base": "*",
"fabric-command-api-v1": "*",
"fabric-entity-events-v1": "*",
"fabric-lifecycle-events-v1": "*",
"fabric-networking-v0": "*"
},
"suggests": {
"fabric-permissions-api-v0": "*"
}
}

View File

@ -0,0 +1,18 @@
{
"required": true,
"minVersion": "0.8",
"package": "net.playeranalytics.plan.gathering.listeners.events.mixin",
"compatibilityLevel": "JAVA_16",
"mixins": [
"EntityMixin",
"KickCommandMixin",
"PlayerEntityMixin",
"PlayerManagerMixin",
"ServerCommandSourceMixin",
"ServerPlayerEntityMixin",
"ServerPlayNetworkHandlerMixin"
],
"injectors": {
"defaultRequire": 1
}
}

63
Plan/fabric/build.gradle Normal file
View File

@ -0,0 +1,63 @@
plugins {
id 'fabric-loom' version '0.8-SNAPSHOT'
}
dependencies {
shadow "net.playeranalytics:platform-abstraction-layer-api:$palVersion"
implementation project(path: ":common", configuration: 'shadow')
shadow project(path: ":common", configuration: 'shadow')
compileOnly project(":api")
modImplementation('me.lucko:fabric-permissions-api:0.1-SNAPSHOT')
minecraft "com.mojang:minecraft:1.17.1"
mappings "net.fabricmc:yarn:1.17.1+build.21:v2"
modImplementation "net.fabricmc:fabric-loader:0.11.6"
// Fabric API
Set<String> apiModules = [
'fabric-api-base',
'fabric-command-api-v1',
'fabric-entity-events-v1',
'fabric-lifecycle-events-v1',
'fabric-networking-api-v1'
]
apiModules.forEach {
modImplementation(fabricApi.module(it, "0.37.0+1.17"))
}
testImplementation project(path: ":common", configuration: 'testArtifacts')
}
compileJava {
options.release = 16
}
shadowJar {
configurations = [project.configurations.shadow]
exclude('net.fabricmc:*')
exclude('/mappings/')
relocate('org.apache', 'plan.org.apache') {
exclude 'org/apache/logging/**'
}
relocate 'dagger', 'plan.dagger'
relocate 'com.mysql', 'plan.com.mysql'
// Don't relocate SQLite since the org.sqlite.NativeDB class calls are not relocated properly
// relocate 'org.sqlite', 'plan.org.sqlite'
relocate 'javax.inject', 'plan.javax.inject'
relocate 'com.github.benmanes', 'plan.com.github.benmanes'
}
remapJar {
dependsOn tasks.shadowJar
mustRunAfter tasks.shadowJar
input = shadowJar.archiveFile.get()
addNestedDependencies = true
destinationDirectory.set(file("$rootDir/builds/"))
archiveBaseName.set('PlanFabric')
archiveClassifier.set('')
}
shadowJar.finalizedBy(remapJar)

View File

@ -0,0 +1,55 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan;
import com.djrapitops.plan.gathering.ServerShutdownSave;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import net.playeranalytics.plugin.server.PluginLogger;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* ServerShutdownSave implementation for Fabric-based servers.
*
* @author Kopo942
*/
@Singleton
public class FabricServerShutdownSave extends ServerShutdownSave {
private final MinecraftDedicatedServer server;
@Inject
public FabricServerShutdownSave(
MinecraftDedicatedServer server,
Locale locale,
DBSystem dbSystem,
PluginLogger logger,
ErrorLogger errorLogger
) {
super(locale, dbSystem, logger, errorLogger);
this.server = server;
}
@Override
protected boolean checkServerShuttingDownStatus() {
return !server.isRunning();
}
}

View File

@ -0,0 +1,172 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.commands.use.ColorScheme;
import com.djrapitops.plan.commands.use.Subcommand;
import com.djrapitops.plan.exceptions.EnableException;
import com.djrapitops.plan.gathering.ServerShutdownSave;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.locale.lang.PluginLang;
import com.djrapitops.plan.settings.theme.PlanColorScheme;
import net.fabricmc.api.DedicatedServerModInitializer;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import net.playeranalytics.plan.commands.CommandManager;
import net.playeranalytics.plugin.FabricPlatformLayer;
import net.playeranalytics.plugin.PlatformAbstractionLayer;
import net.playeranalytics.plugin.scheduling.RunnableFactory;
import net.playeranalytics.plugin.server.PluginLogger;
import java.io.File;
import java.io.InputStream;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Main class for Plan's Fabric version.
*
* @author Kopo942
*/
public class PlanFabric implements PlanPlugin, DedicatedServerModInitializer {
private MinecraftDedicatedServer server;
private CommandManager commandManager;
private PlanSystem system;
private Locale locale;
private ServerShutdownSave serverShutdownSave;
private PluginLogger pluginLogger;
private RunnableFactory runnableFactory;
private PlatformAbstractionLayer abstractionLayer;
@Override
public InputStream getResource(String resource) {
return this.getClass().getResourceAsStream("/" + resource);
}
@Override
public ColorScheme getColorScheme() {
return PlanColorScheme.create(system.getConfigSystem().getConfig(), pluginLogger);
}
@Override
public PlanSystem getSystem() {
return system;
}
@Override
public void registerCommand(Subcommand command) {
commandManager.registerRoot(command, runnableFactory);
}
@Override
public void onEnable() {
abstractionLayer = new FabricPlatformLayer(this);
pluginLogger = abstractionLayer.getPluginLogger();
runnableFactory = abstractionLayer.getRunnableFactory();
PlanFabricComponent component = DaggerPlanFabricComponent.builder()
.plan(this)
.abstractionLayer(abstractionLayer)
.server(server)
.build();
try {
system = component.system();
serverShutdownSave = component.serverShutdownSave();
locale = system.getLocaleSystem().getLocale();
system.enable();
pluginLogger.info(locale.getString(PluginLang.ENABLED));
} catch (AbstractMethodError e) {
pluginLogger.error("Plugin ran into AbstractMethodError, server restart is required! This error is likely caused by updating the JAR without a restart.");
} catch (EnableException e) {
pluginLogger.error("----------------------------------------");
pluginLogger.error("Error: " + e.getMessage());
pluginLogger.error("----------------------------------------");
pluginLogger.error("Plugin failed to initialize correctly. If this issue is caused by config settings you can use /plan reload");
onDisable();
} catch (Exception e) {
String version = abstractionLayer.getPluginInformation().getVersion();
pluginLogger.error(this.getClass().getSimpleName() + "-v" + version, e);
pluginLogger.error("Plugin Failed to Initialize Correctly. If this issue is caused by config settings you can use /plan reload");
pluginLogger.error("This error should be reported at https://github.com/plan-player-analytics/Plan/issues");
onDisable();
}
registerCommand(component.planCommand().build());
if (system != null) {
system.getProcessing().submitNonCritical(() -> system.getListenerSystem().callEnableEvent(this));
}
}
@Override
public void onDisable() {
storeSessionsOnShutdown();
runnableFactory.cancelAllKnownTasks();
if (system != null) system.disable();
pluginLogger.info(Locale.getStringNullSafe(locale, PluginLang.DISABLED));
}
private void storeSessionsOnShutdown() {
if (serverShutdownSave != null) {
Optional<Future<?>> complete = serverShutdownSave.performSave();
if (complete.isPresent()) {
try {
complete.get().get(4, TimeUnit.SECONDS); // wait for completion for 4s
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
pluginLogger.error("Failed to save sessions to database on shutdown: " + e.getCause().getMessage());
} catch (TimeoutException e) {
pluginLogger.info(Locale.getStringNullSafe(locale, PluginLang.DISABLED_UNSAVED_SESSIONS_TIMEOUT));
}
}
}
}
@Override
public File getDataFolder() {
return null;
}
@Override
public void onInitializeServer() {
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
this.server = (MinecraftDedicatedServer) server;
onEnable();
});
CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> commandManager = new CommandManager(dispatcher, system.getErrorLogger()));
ServerLifecycleEvents.SERVER_STOPPING.register(server -> onDisable());
}
public MinecraftServer getServer() {
return server;
}
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.commands.PlanCommand;
import com.djrapitops.plan.gathering.ServerShutdownSave;
import com.djrapitops.plan.modules.FiltersModule;
import com.djrapitops.plan.modules.PlatformAbstractionLayerModule;
import com.djrapitops.plan.modules.ServerCommandModule;
import com.djrapitops.plan.modules.SystemObjectProvidingModule;
import dagger.BindsInstance;
import dagger.Component;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import net.playeranalytics.plan.identification.properties.FabricServerProperties;
import net.playeranalytics.plan.modules.fabric.FabricServerPropertiesModule;
import net.playeranalytics.plan.modules.fabric.FabricSuperClassBindingModule;
import net.playeranalytics.plan.modules.fabric.FabricTaskModule;
import net.playeranalytics.plugin.PlatformAbstractionLayer;
import javax.inject.Singleton;
/**
* Dagger component for constructing the required plugin systems on Fabric.
*
* @author Kopo942
*/
@Singleton
@Component(modules = {
SystemObjectProvidingModule.class,
PlatformAbstractionLayerModule.class,
FiltersModule.class,
ServerCommandModule.class,
FabricServerPropertiesModule.class,
FabricSuperClassBindingModule.class,
FabricTaskModule.class
})
public interface PlanFabricComponent {
PlanCommand planCommand();
PlanSystem system();
ServerShutdownSave serverShutdownSave();
@Component.Builder
interface Builder {
@BindsInstance
Builder plan(PlanPlugin plan);
@BindsInstance
Builder abstractionLayer(PlatformAbstractionLayer abstractionLayer);
@BindsInstance
Builder server(MinecraftDedicatedServer server);
@BindsInstance
Builder serverProperties(FabricServerProperties serverProperties);
PlanFabricComponent build();
}
}

View File

@ -0,0 +1,148 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.commands;
import com.djrapitops.plan.commands.use.Arguments;
import com.djrapitops.plan.commands.use.CMDSender;
import com.djrapitops.plan.commands.use.CommandWithSubcommands;
import com.djrapitops.plan.commands.use.Subcommand;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.command.CommandSource;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.LiteralText;
import net.playeranalytics.plugin.scheduling.RunnableFactory;
import java.util.concurrent.CompletableFuture;
public class CommandManager {
private final CommandDispatcher<ServerCommandSource> dispatcher;
private RunnableFactory runnableFactory;
private LiteralArgumentBuilder<ServerCommandSource> root;
private final ErrorLogger errorLogger;
public CommandManager(CommandDispatcher<ServerCommandSource> dispatcher, ErrorLogger errorLogger) {
this.dispatcher = dispatcher;
this.errorLogger = errorLogger;
}
public static boolean checkPermission(ServerCommandSource src, String permission) {
if (isPermissionsApiAvailable()) {
return Permissions.check(src, permission, 2);
} else if (src.hasPermissionLevel(2)) {
return true;
} else {
return switch (permission) {
case "plan.player.self", "plan.ingame.self", "plan.register.self", "plan.unregister.self", "plan.json.self" -> true;
default -> false;
};
}
}
public static boolean isPermissionsApiAvailable() {
try {
Class.forName("me.lucko.fabric.api.permissions.v0.Permissions");
return true;
} catch (ClassNotFoundException e) {
return false; // not available
}
}
public CompletableFuture<Suggestions> arguments(final Subcommand subcommand, final CommandContext<ServerCommandSource> ctx, final SuggestionsBuilder builder) {
return CommandSource.suggestMatching(subcommand.getArgumentResolver().apply((CMDSender) ctx.getSource(), new Arguments(new String[0])), builder);
}
private int execute(CommandContext<ServerCommandSource> ctx, Subcommand subcommand) {
runnableFactory.create(() -> {
try {
subcommand.getExecutor().accept((CMDSender) ctx.getSource(), new Arguments(getCommandArguments(ctx)));
} catch (Exception e) {
ctx.getSource().sendError(new LiteralText("An internal error occurred, see the console for details."));
errorLogger.error(e, ErrorContext.builder()
.related(ctx.getSource().getClass())
.related(subcommand.getPrimaryAlias() + " " + getCommandArguments(ctx))
.build());
}
}).runTaskAsynchronously();
return 1;
}
private String getCommandArguments(CommandContext<ServerCommandSource> ctx) {
String arguments;
try {
arguments = StringArgumentType.getString(ctx, "arguments");
} catch (IllegalArgumentException e) {
arguments = "";
}
return arguments;
}
private void build() {
dispatcher.register(root);
}
public void registerRoot(Subcommand subcommand, RunnableFactory runnableFactory) {
this.runnableFactory = runnableFactory;
root = buildCommand(subcommand, subcommand.getPrimaryAlias());
if (subcommand instanceof CommandWithSubcommands withSubcommands) {
for (Subcommand cmd : withSubcommands.getSubcommands()) {
registerChild(cmd, root);
}
}
build();
}
public void registerChild(Subcommand subcommand, ArgumentBuilder<ServerCommandSource, ?> parent) {
for (String alias : subcommand.getAliases()) {
LiteralArgumentBuilder<ServerCommandSource> argumentBuilder = buildCommand(subcommand, alias);
if (subcommand instanceof CommandWithSubcommands withSubcommands) {
for (Subcommand cmd : withSubcommands.getSubcommands()) {
registerChild(cmd, argumentBuilder);
}
}
parent.then(argumentBuilder);
}
}
private LiteralArgumentBuilder<ServerCommandSource> buildCommand(Subcommand subcommand, String alias) {
RequiredArgumentBuilder<ServerCommandSource, String> arguments = RequiredArgumentBuilder.argument("arguments", StringArgumentType.greedyString());
arguments.suggests((context, builder) -> arguments(subcommand, context, builder));
arguments.executes(ctx -> execute(ctx, subcommand));
LiteralArgumentBuilder<ServerCommandSource> literal = LiteralArgumentBuilder.literal(alias);
literal.executes(ctx -> execute(ctx, subcommand));
literal.requires(src -> {
for (String permission : subcommand.getRequiredPermissions()) {
if (!checkPermission(src, permission)) return false;
}
return true;
});
literal.then(arguments);
return literal;
}
}

View File

@ -0,0 +1,109 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.commands.use;
import com.djrapitops.plan.commands.use.CMDSender;
import com.djrapitops.plan.commands.use.MessageBuilder;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.*;
import org.apache.commons.text.TextStringBuilder;
import java.util.Collection;
public class FabricMessageBuilder implements MessageBuilder {
private final ServerCommandSource sender;
private final MutableText builder;
private final FabricMessageBuilder previous;
public FabricMessageBuilder(ServerCommandSource sender) {
this(sender, null);
}
FabricMessageBuilder(ServerCommandSource sender, FabricMessageBuilder previous) {
this.sender = sender;
this.builder = new LiteralText("");
this.previous = previous;
}
@Override
public MessageBuilder addPart(String s) {
FabricMessageBuilder newBuilder = new FabricMessageBuilder(sender, this);
newBuilder.builder.append(Text.of(s));
return newBuilder;
}
@Override
public MessageBuilder newLine() {
builder.append(Text.of("\n"));
return this;
}
@Override
public MessageBuilder link(String url) {
builder.styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)));
return this;
}
@Override
public MessageBuilder command(String command) {
builder.styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, command.charAt(0) == '/' ? command : '/' + command)));
return this;
}
@Override
public MessageBuilder hover(String message) {
builder.styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new LiteralText(message))));
return this;
}
@Override
public MessageBuilder hover(String... lines) {
builder.styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new LiteralText(new TextStringBuilder().appendWithSeparators(lines, "\n").toString()))));
return this;
}
@Override
public MessageBuilder hover(Collection<String> lines) {
builder.styled(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new LiteralText(new TextStringBuilder().appendWithSeparators(lines, "\n").toString()))));
return this;
}
@Override
public MessageBuilder indent(int amount) {
for (int i = 0; i < amount; i++) {
builder.append(Text.of(" "));
}
return this;
}
@Override
public MessageBuilder tabular(CharSequence charSequence) {
addPart(((CMDSender) sender).getFormatter().table(charSequence.toString(), ":"));
return this;
}
@Override
public void send() {
if (previous == null) {
sender.sendFeedback(builder, false);
} else {
previous.builder.append(builder);
previous.send();
}
}
}

View File

@ -0,0 +1,83 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering;
import com.djrapitops.plan.gathering.ServerSensor;
import net.minecraft.entity.Entity;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import net.minecraft.server.world.ServerWorld;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.concurrent.TimeUnit;
@Singleton
public class FabricSensor implements ServerSensor<ServerWorld> {
private final MinecraftDedicatedServer server;
@Inject
public FabricSensor(
MinecraftDedicatedServer server
) {
this.server = server;
}
@Override
public double getTPS() {
//Returns the ticks per second of the last 100 ticks
int length = server.lastTickLengths.length;
double totalTickLength = 0;
int count = 0;
for (long tickLength : server.lastTickLengths) {
if (tickLength == 0) continue; // Ignore uninitialized values in array
totalTickLength += Math.max(tickLength, TimeUnit.MILLISECONDS.toNanos(50));
count++;
}
double averageTickLength = totalTickLength / count;
return count != 0 ? TimeUnit.SECONDS.toNanos(1) / averageTickLength : -1;
}
@Override
public Iterable<ServerWorld> getWorlds() {
return server.getWorlds();
}
@Override
public int getEntityCount(ServerWorld world) {
int entities = 0;
for (Entity ignored : world.iterateEntities()) {
entities++;
}
return entities;
}
@Override
public int getChunkCount(ServerWorld world) {
return world.getChunkManager().getLoadedChunkCount();
}
@Override
public boolean supportsDirectTPS() {
return true;
}
@Override
public int getOnlinePlayerCount() {
return server.getCurrentPlayerCount();
}
}

View File

@ -0,0 +1,32 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners;
/**
* Interface for all listeners on the Fabric platform.
* Enables registering, enabling and disabling a listener.
*/
public interface FabricListener {
void register();
boolean isEnabled();
void enable();
void disable();
}

View File

@ -0,0 +1,90 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.capability.CapabilitySvc;
import com.djrapitops.plan.gathering.listeners.ListenerSystem;
import net.playeranalytics.plan.PlanFabric;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import net.playeranalytics.plan.gathering.listeners.fabric.*;
import net.playeranalytics.plugin.server.Listeners;
import javax.inject.Inject;
/**
* Listener system for the Fabric platform.
*
* @author Kopo942
*/
public class FabricListenerSystem extends ListenerSystem {
private final ChatListener chatListener;
private final DeathEventListener deathEventListener;
private final FabricAFKListener fabricAFKListener;
private final GameModeChangeListener gameModeChangeListener;
private final PlayerOnlineListener playerOnlineListener;
private final WorldChangeListener worldChangeListener;
private final Listeners listeners;
@Inject
public FabricListenerSystem(
ChatListener chatListener,
DeathEventListener deathEventListener,
FabricAFKListener fabricAFKListener,
GameModeChangeListener gameModeChangeListener,
PlayerOnlineListener playerOnlineListener,
WorldChangeListener worldChangeListener,
Listeners listeners
) {
this.chatListener = chatListener;
this.deathEventListener = deathEventListener;
this.fabricAFKListener = fabricAFKListener;
this.playerOnlineListener = playerOnlineListener;
this.gameModeChangeListener = gameModeChangeListener;
this.worldChangeListener = worldChangeListener;
this.listeners = listeners;
}
@Override
protected void registerListeners() {
listeners.registerListener(chatListener);
listeners.registerListener(deathEventListener);
listeners.registerListener(fabricAFKListener);
listeners.registerListener(gameModeChangeListener);
listeners.registerListener(playerOnlineListener);
listeners.registerListener(worldChangeListener);
}
@Override
protected void unregisterListeners() {
listeners.unregisterListener(chatListener);
listeners.unregisterListener(deathEventListener);
listeners.unregisterListener(fabricAFKListener);
listeners.unregisterListener(gameModeChangeListener);
listeners.unregisterListener(playerOnlineListener);
listeners.unregisterListener(worldChangeListener);
}
@Override
public void callEnableEvent(PlanPlugin plugin) {
boolean isEnabled = plugin.isSystemEnabled();
PlanFabricEvents.ON_ENABLE.invoker().onEnable((PlanFabric) plugin);
CapabilitySvc.notifyAboutEnable(isEnabled);
}
}

View File

@ -0,0 +1,165 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.events;
import com.mojang.authlib.GameProfile;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.world.GameMode;
import net.playeranalytics.plan.PlanFabric;
import java.net.SocketAddress;
import java.util.Collection;
public class PlanFabricEvents {
public static final Event<OnKilled> ON_KILLED = EventFactory.createArrayBacked(OnKilled.class, callbacks -> (killed, killer) -> {
for (OnKilled callback : callbacks) {
callback.onKilled(killed, killer);
}
});
public static final Event<OnChat> ON_CHAT = EventFactory.createArrayBacked(OnChat.class, callbacks -> (handler, message) -> {
for (OnChat callback : callbacks) {
callback.onChat(handler, message);
}
});
public static final Event<OnMove> ON_MOVE = EventFactory.createArrayBacked(OnMove.class, callbacks -> (handler, packet) -> {
for (OnMove callback : callbacks) {
callback.onMove(handler, packet);
}
});
public static final Event<OnGameModeChange> ON_GAMEMODE_CHANGE = EventFactory.createArrayBacked(OnGameModeChange.class, callbacks -> (handler, packet) -> {
for (OnGameModeChange callback : callbacks) {
callback.onGameModeChange(handler, packet);
}
});
public static final Event<OnPlayerKicked> ON_KICKED = EventFactory.createArrayBacked(OnPlayerKicked.class, callbacks -> (source, targets, reason) -> {
for (OnPlayerKicked callback : callbacks) {
callback.onKicked(source, targets, reason);
}
});
public static final Event<OnLogin> ON_LOGIN = EventFactory.createArrayBacked(OnLogin.class, callbacks -> (address, profile, reason) -> {
for (OnLogin callback : callbacks) {
callback.onLogin(address, profile, reason);
}
});
/**
* Called when Plan is enabled.
* <p>
* This includes, but might not be limited to:
* <ul>
* <li>First time the plugin enables successfully</li>
* <li>Plan is reloaded</li>
* <li>Plan is enabled after it was disabled</li>
* </ul>
* <p>
* This event provides full access to the Plan instance. However, <strong>it is advised to
* only call {@link PlanFabric#isSystemEnabled} to determine if the enable was successful.</strong>
* It is not guaranteed that this event is called when the plugin fails to enable properly.
*/
public static final Event<OnEnable> ON_ENABLE = EventFactory.createArrayBacked(OnEnable.class, callbacks -> plugin -> {
for (OnEnable callback : callbacks) {
callback.onEnable(plugin);
}
});
@FunctionalInterface
public interface OnKilled {
/**
* Called when a living entity is killed
*
* @param killed the entity that died
* @param killer the entity that killed
*/
void onKilled(LivingEntity killed, Entity killer);
}
@FunctionalInterface
public interface OnChat {
/**
* Called when a player sends a chat message / command
*
* @param handler the handler of the sending player
* @param message the message sent (starts with "/" if it is a command)
*/
void onChat(ServerPlayNetworkHandler handler, String message);
}
@FunctionalInterface
public interface OnMove {
/**
* Called when a sends a valid movement packet
*
* @param handler the handler of the sending player
* @param packet the send packet
*/
void onMove(ServerPlayNetworkHandler handler, PlayerMoveC2SPacket packet);
}
@FunctionalInterface
public interface OnGameModeChange {
/**
* Called when a player changes gamemode
*
* @param player the player that changed gamemodes
* @param newGameMode the new gamemode
*/
void onGameModeChange(ServerPlayerEntity player, GameMode newGameMode);
}
@FunctionalInterface
public interface OnPlayerKicked {
/**
* Called when a player (or multiple) get kicked from the server
*
* @param source the source that initated the kick
* @param targets the player(s) that got kicked
* @param reason the provided kick reason
*/
void onKicked(ServerCommandSource source, Collection<ServerPlayerEntity> targets, Text reason);
}
@FunctionalInterface
public interface OnLogin {
/**
* Called when a player attempts to login
*
* @param address the address of the player
* @param profile the profile of the player
* @param reason the provided kick reason (null if player is permitted to join)
*/
void onLogin(SocketAddress address, GameProfile profile, Text reason);
}
@FunctionalInterface
public interface OnEnable {
void onEnable(PlanFabric plugin);
}
}

View File

@ -0,0 +1,36 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.events.mixin;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.server.world.ServerWorld;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Entity.class)
public class EntityMixin {
@Inject(method = "onKilledOther", at = @At(value = "TAIL"))
public void onDeath(ServerWorld world, LivingEntity other, CallbackInfo ci) {
PlanFabricEvents.ON_KILLED.invoker().onKilled(other, (Entity) (Object) this);
}
}

View File

@ -0,0 +1,39 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.events.mixin;
import net.minecraft.server.command.KickCommand;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Collection;
@Mixin(KickCommand.class)
public class KickCommandMixin {
@Inject(method = "execute", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayNetworkHandler;disconnect(Lnet/minecraft/text/Text;)V"))
private static void onKickPlayer(ServerCommandSource source, Collection<ServerPlayerEntity> targets, Text reason, CallbackInfoReturnable<Integer> cir) {
PlanFabricEvents.ON_KICKED.invoker().onKicked(source, targets, reason);
}
}

View File

@ -0,0 +1,36 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.events.mixin;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PlayerEntity.class)
public class PlayerEntityMixin {
@Inject(method = "onKilledOther", at = @At(value = "TAIL"))
public void onDeath(ServerWorld world, LivingEntity other, CallbackInfo ci) {
PlanFabricEvents.ON_KILLED.invoker().onKilled(other, (PlayerEntity) (Object) this);
}
}

View File

@ -0,0 +1,38 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.events.mixin;
import com.mojang.authlib.GameProfile;
import net.minecraft.server.PlayerManager;
import net.minecraft.text.Text;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.net.SocketAddress;
@Mixin(PlayerManager.class)
public class PlayerManagerMixin {
@Inject(method = "checkCanJoin", at = @At(value = "TAIL"))
public void onLogin(SocketAddress address, GameProfile profile, CallbackInfoReturnable<Text> cir) {
PlanFabricEvents.ON_LOGIN.invoker().onLogin(address, profile, cir.getReturnValue());
}
}

View File

@ -0,0 +1,94 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.events.mixin;
import com.djrapitops.plan.commands.use.*;
import net.minecraft.entity.Entity;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.playeranalytics.plan.commands.CommandManager;
import net.playeranalytics.plan.commands.use.FabricMessageBuilder;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.Optional;
import java.util.UUID;
@Mixin(ServerCommandSource.class)
public abstract class ServerCommandSourceMixin implements CMDSender {
@Override
public boolean isPlayer() {
return getPlayer().isPresent();
}
@Override
public boolean supportsChatEvents() {
return true;
}
@Shadow
public abstract void sendFeedback(Text message, boolean broadcastToOps);
@Shadow
@Nullable
public abstract Entity getEntity();
@Override
public MessageBuilder buildMessage() {
return new FabricMessageBuilder((ServerCommandSource) (Object) this);
}
@Override
public Optional<String> getPlayerName() {
return getPlayer().map(ServerPlayerEntity::getEntityName);
}
@Override
public boolean hasPermission(String permission) {
return CommandManager.checkPermission((ServerCommandSource) (Object) this, permission);
}
@Override
public Optional<UUID> getUUID() {
return Optional.ofNullable(isConsole() ? null : getEntity().getUuid());
}
@Override
public void send(String message) {
this.sendFeedback(new LiteralText(message), false);
}
@Override
public ChatFormatter getFormatter() {
return isConsole() ? new ConsoleChatFormatter() : new PlayerChatFormatter();
}
private boolean isConsole() {
return getEntity() == null;
}
private Optional<ServerPlayerEntity> getPlayer() {
if (getEntity() instanceof ServerPlayerEntity player) {
return Optional.of(player);
}
return Optional.empty();
}
}

View File

@ -0,0 +1,41 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.events.mixin;
import net.minecraft.network.packet.c2s.play.ChatMessageC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerPlayNetworkHandler.class)
public class ServerPlayNetworkHandlerMixin {
@Inject(method = "onGameMessage", at = @At(value = "INVOKE", target = "Ljava/lang/String;startsWith(Ljava/lang/String;)Z"))
public void onChatMessage(ChatMessageC2SPacket packet, CallbackInfo ci) {
PlanFabricEvents.ON_CHAT.invoker().onChat((ServerPlayNetworkHandler) (Object) this, packet.getChatMessage());
}
@Inject(method = "onPlayerMove", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;getServerWorld()Lnet/minecraft/server/world/ServerWorld;"))
public void onPlayerMove(PlayerMoveC2SPacket packet, CallbackInfo ci) {
PlanFabricEvents.ON_MOVE.invoker().onMove((ServerPlayNetworkHandler) (Object) this, packet);
}
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.events.mixin;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.world.GameMode;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ServerPlayerEntity.class)
public class ServerPlayerEntityMixin {
@Inject(method = "changeGameMode", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayNetworkHandler;sendPacket(Lnet/minecraft/network/Packet;)V"))
public void onGameModeChanged(GameMode gameMode, CallbackInfoReturnable<Boolean> cir) {
PlanFabricEvents.ON_GAMEMODE_CHANGE.invoker().onGameModeChange((ServerPlayerEntity) (Object) this, gameMode);
}
}

View File

@ -0,0 +1,111 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.fabric;
import com.djrapitops.plan.delivery.domain.Nickname;
import com.djrapitops.plan.gathering.cache.NicknameCache;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.transactions.events.NicknameStoreTransaction;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.playeranalytics.plan.gathering.listeners.FabricListener;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import javax.inject.Inject;
import java.util.UUID;
/**
* Event Listener for chat events.
*
* @author AuroraLS3
*/
public class ChatListener implements FabricListener {
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final NicknameCache nicknameCache;
private final ErrorLogger errorLogger;
private boolean isEnabled = false;
@Inject
public ChatListener(
ServerInfo serverInfo,
DBSystem dbSystem,
NicknameCache nicknameCache,
ErrorLogger errorLogger
) {
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.nicknameCache = nicknameCache;
this.errorLogger = errorLogger;
}
public void onChat(ServerPlayNetworkHandler handler, String message) {
try {
actOnChatEvent(handler);
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(handler, message).build());
}
}
private void actOnChatEvent(ServerPlayNetworkHandler handler) {
long time = System.currentTimeMillis();
ServerPlayerEntity player = handler.player;
UUID uuid = player.getUuid();
String displayName = player.getDisplayName().asString();
dbSystem.getDatabase().executeTransaction(new NicknameStoreTransaction(
uuid, new Nickname(displayName, time, serverInfo.getServerUUID()),
(playerUUID, name) -> nicknameCache.getDisplayName(playerUUID).map(name::equals).orElse(false)
));
}
@Override
public void register() {
PlanFabricEvents.ON_CHAT.register((handler, message) -> {
if (!isEnabled) {
return;
}
onChat(handler, message);
});
this.enable();
}
@Override
public boolean isEnabled() {
return this.isEnabled;
}
@Override
public void enable() {
this.isEnabled = true;
}
@Override
public void disable() {
this.isEnabled = false;
}
}

View File

@ -0,0 +1,154 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.fabric;
import com.djrapitops.plan.delivery.formatting.EntityNameFormatter;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.PlayerKill;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.processing.processors.player.MobKillProcessor;
import com.djrapitops.plan.processing.processors.player.PlayerKillProcessor;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import net.minecraft.entity.Entity;
import net.minecraft.entity.passive.TameableEntity;
import net.minecraft.entity.projectile.ProjectileEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayerEntity;
import net.playeranalytics.plan.gathering.listeners.FabricListener;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import javax.inject.Inject;
import java.util.Optional;
public class DeathEventListener implements FabricListener {
private final Processing processing;
private final ErrorLogger errorLogger;
private final ServerInfo serverInfo;
private boolean isEnabled = false;
@Inject
public DeathEventListener(
ServerInfo serverInfo,
Processing processing,
ErrorLogger errorLogger
) {
this.serverInfo = serverInfo;
this.processing = processing;
this.errorLogger = errorLogger;
}
@Override
public void register() {
PlanFabricEvents.ON_KILLED.register((victim, killer) -> {
if (!this.isEnabled) {
return;
}
long time = System.currentTimeMillis();
if (victim instanceof ServerPlayerEntity) {
// Process Death
SessionCache.getCachedSession(victim.getUuid()).ifPresent(ActiveSession::addDeath);
}
try {
Optional<ServerPlayerEntity> foundKiller = getCause(killer);
if (foundKiller.isEmpty()) {
return;
}
ServerPlayerEntity player = foundKiller.get();
Runnable processor = victim instanceof ServerPlayerEntity
? new PlayerKillProcessor(getKiller(player), getVictim((ServerPlayerEntity) victim), serverInfo.getServerIdentifier(), findWeapon(player), time)
: new MobKillProcessor(player.getUuid());
processing.submitCritical(processor);
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(getClass(), victim, killer).build());
}
});
this.enable();
}
private PlayerKill.Killer getKiller(ServerPlayerEntity killer) {
return new PlayerKill.Killer(killer.getUuid(), killer.getName().asString());
}
private PlayerKill.Victim getVictim(ServerPlayerEntity victim) {
return new PlayerKill.Victim(victim.getUuid(), victim.getName().asString());
}
public Optional<ServerPlayerEntity> getCause(Entity killer) {
if (killer instanceof ServerPlayerEntity) return Optional.of((ServerPlayerEntity) killer);
if (killer instanceof TameableEntity) return getOwner((TameableEntity) killer);
if (killer instanceof ProjectileEntity) return getShooter((ProjectileEntity) killer);
return Optional.empty();
}
public String findWeapon(Entity killer) {
if (killer instanceof ServerPlayerEntity) return getItemInHand((ServerPlayerEntity) killer);
// Projectile, EnderCrystal and all other causes that are not known yet
return new EntityNameFormatter().apply(killer.getType().getName().asString());
}
private String getItemInHand(ServerPlayerEntity killer) {
ItemStack itemInHand = killer.getMainHandStack();
return itemInHand.getItem().getName().asString();
}
private Optional<ServerPlayerEntity> getShooter(ProjectileEntity projectile) {
Entity source = projectile.getOwner();
if (source instanceof ServerPlayerEntity) {
return Optional.of((ServerPlayerEntity) source);
}
return Optional.empty();
}
private Optional<ServerPlayerEntity> getOwner(TameableEntity tameable) {
if (!tameable.isTamed()) {
return Optional.empty();
}
Entity owner = tameable.getOwner();
if (owner instanceof ServerPlayerEntity) {
return Optional.of((ServerPlayerEntity) owner);
}
return Optional.empty();
}
@Override
public boolean isEnabled() {
return this.isEnabled;
}
@Override
public void enable() {
this.isEnabled = true;
}
@Override
public void disable() {
this.isEnabled = false;
}
}

View File

@ -0,0 +1,121 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.fabric;
import com.djrapitops.plan.gathering.afk.AFKTracker;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.server.network.ServerPlayerEntity;
import net.playeranalytics.plan.commands.CommandManager;
import net.playeranalytics.plan.gathering.listeners.FabricListener;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class FabricAFKListener implements FabricListener {
// Static so that /reload does not cause afk tracking to fail.
static AFKTracker afkTracker;
private final Map<UUID, Boolean> ignorePermissionInfo;
private final ErrorLogger errorLogger;
private boolean isEnabled = false;
@Inject
public FabricAFKListener(PlanConfig config, ErrorLogger errorLogger) {
this.errorLogger = errorLogger;
this.ignorePermissionInfo = new HashMap<>();
FabricAFKListener.assignAFKTracker(config);
}
private static void assignAFKTracker(PlanConfig config) {
if (afkTracker == null) {
afkTracker = new AFKTracker(config);
}
}
private void event(ServerPlayerEntity player) {
try {
UUID uuid = player.getUuid();
long time = System.currentTimeMillis();
boolean ignored = ignorePermissionInfo.computeIfAbsent(uuid, keyUUID -> checkPermission(player, com.djrapitops.plan.settings.Permissions.IGNORE_AFK.getPermission()));
if (ignored) {
afkTracker.hasIgnorePermission(uuid);
ignorePermissionInfo.put(uuid, true);
return;
} else {
ignorePermissionInfo.put(uuid, false);
}
afkTracker.performedAction(uuid, time);
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(getClass(), player).build());
}
}
private boolean checkPermission(ServerPlayerEntity player, String permission) {
if (CommandManager.isPermissionsApiAvailable()) {
return Permissions.check(player, permission);
} else return player.hasPermissionLevel(2);
}
@Override
public void register() {
this.enable();
PlanFabricEvents.ON_CHAT.register((handler, message) -> {
if (!isEnabled) {
return;
}
event(handler.player);
boolean isAfkCommand = message.substring(1).toLowerCase().startsWith("afk");
if (isAfkCommand) {
UUID uuid = handler.player.getUuid();
afkTracker.usedAfkCommand(uuid, System.currentTimeMillis());
}
});
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
if (!this.isEnabled) {
return;
}
ignorePermissionInfo.remove(handler.player.getUuid());
});
PlanFabricEvents.ON_MOVE.register((handler, packet) -> event(handler.player));
}
@Override
public boolean isEnabled() {
return this.isEnabled;
}
@Override
public void enable() {
this.isEnabled = true;
}
@Override
public void disable() {
this.isEnabled = false;
}
}

View File

@ -0,0 +1,107 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.fabric;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.settings.config.WorldAliasSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.transactions.events.WorldNameStoreTransaction;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.world.GameMode;
import net.playeranalytics.plan.gathering.listeners.FabricListener;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import javax.inject.Inject;
import java.util.Optional;
import java.util.UUID;
/**
* Event Listener for PlayerGameModeChangeEvents.
*
* @author AuroraLS3
*/
public class GameModeChangeListener implements FabricListener {
private final WorldAliasSettings worldAliasSettings;
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final ErrorLogger errorLogger;
private boolean isEnabled = false;
@Inject
public GameModeChangeListener(
WorldAliasSettings worldAliasSettings,
ServerInfo serverInfo,
DBSystem dbSystem,
ErrorLogger errorLogger
) {
this.worldAliasSettings = worldAliasSettings;
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.errorLogger = errorLogger;
}
public void onGameModeChange(ServerPlayerEntity player, GameMode newGameMode) {
try {
actOnEvent(player, newGameMode);
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(getClass(), player, newGameMode).build());
}
}
private void actOnEvent(ServerPlayerEntity player, GameMode newGameMode) {
UUID uuid = player.getUuid();
long time = System.currentTimeMillis();
String gameMode = newGameMode.name();
String worldName = player.getServerWorld().getRegistryKey().getValue().toString();
dbSystem.getDatabase().executeTransaction(new WorldNameStoreTransaction(serverInfo.getServerUUID(), worldName));
worldAliasSettings.addWorld(worldName);
Optional<ActiveSession> cachedSession = SessionCache.getCachedSession(uuid);
cachedSession.ifPresent(session -> session.changeState(worldName, gameMode, time));
}
@Override
public void register() {
if (!this.isEnabled) {
return;
}
PlanFabricEvents.ON_GAMEMODE_CHANGE.register(this::onGameModeChange);
this.enable();
}
@Override
public boolean isEnabled() {
return this.isEnabled;
}
@Override
public void enable() {
this.isEnabled = true;
}
@Override
public void disable() {
this.isEnabled = false;
}
}

View File

@ -0,0 +1,276 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.fabric;
import com.djrapitops.plan.delivery.domain.Nickname;
import com.djrapitops.plan.delivery.domain.PlayerName;
import com.djrapitops.plan.delivery.domain.ServerName;
import com.djrapitops.plan.delivery.export.Exporter;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.NicknameCache;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.ExportSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.transactions.events.*;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import com.google.common.net.InetAddresses;
import com.mojang.authlib.GameProfile;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.playeranalytics.plan.gathering.listeners.FabricListener;
import net.playeranalytics.plan.gathering.listeners.events.PlanFabricEvents;
import javax.inject.Inject;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
public class PlayerOnlineListener implements FabricListener {
private final PlanConfig config;
private final Processing processing;
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final ExtensionSvc extensionService;
private final Exporter exporter;
private final GeolocationCache geolocationCache;
private final NicknameCache nicknameCache;
private final SessionCache sessionCache;
private final ErrorLogger errorLogger;
private final MinecraftDedicatedServer server;
private final Map<UUID, String> joinAddresses;
private boolean isEnabled = false;
@Inject
public PlayerOnlineListener(
PlanConfig config,
Processing processing,
ServerInfo serverInfo,
DBSystem dbSystem,
ExtensionSvc extensionService,
Exporter exporter,
GeolocationCache geolocationCache,
NicknameCache nicknameCache,
SessionCache sessionCache,
ErrorLogger errorLogger,
MinecraftDedicatedServer server
) {
this.config = config;
this.processing = processing;
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.extensionService = extensionService;
this.exporter = exporter;
this.geolocationCache = geolocationCache;
this.nicknameCache = nicknameCache;
this.sessionCache = sessionCache;
this.errorLogger = errorLogger;
this.server = server;
joinAddresses = new HashMap<>();
}
@Override
public void register() {
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
if (!this.isEnabled) {
return;
}
onPlayerJoin(handler.player);
});
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
if (!this.isEnabled) {
return;
}
onPlayerQuit(handler.player);
});
PlanFabricEvents.ON_KICKED.register((source, targets, reason) -> {
if (!this.isEnabled) {
return;
}
for (ServerPlayerEntity target : targets) {
onPlayerKick(target);
}
});
PlanFabricEvents.ON_LOGIN.register((address, profile, reason) -> {
if (!this.isEnabled) {
return;
}
onPlayerLogin(address, profile, reason != null);
});
this.enable();
}
public void onPlayerLogin(SocketAddress address, GameProfile profile, boolean banned) {
try {
UUID playerUUID = profile.getId();
ServerUUID serverUUID = serverInfo.getServerUUID();
String joinAddress = address.toString();
if (!joinAddress.isEmpty()) {
joinAddresses.put(playerUUID, joinAddress.substring(0, joinAddress.lastIndexOf(':')));
}
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, () -> banned));
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(getClass(), address, profile, banned).build());
}
}
public void onPlayerKick(ServerPlayerEntity player) {
try {
UUID uuid = player.getUuid();
if (FabricAFKListener.afkTracker.isAfk(uuid)) {
return;
}
dbSystem.getDatabase().executeTransaction(new KickStoreTransaction(uuid));
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(getClass(), player).build());
}
}
public void onPlayerJoin(ServerPlayerEntity player) {
try {
actOnJoinEvent(player);
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(getClass(), player).build());
}
}
private void actOnJoinEvent(ServerPlayerEntity player) {
UUID playerUUID = player.getUuid();
ServerUUID serverUUID = serverInfo.getServerUUID();
long time = System.currentTimeMillis();
FabricAFKListener.afkTracker.performedAction(playerUUID, time);
String world = player.getServerWorld().getRegistryKey().getValue().toString();
String gm = player.interactionManager.getGameMode().name();
Database database = dbSystem.getDatabase();
database.executeTransaction(new WorldNameStoreTransaction(serverUUID, world));
InetSocketAddress socketAddress = (InetSocketAddress) player.networkHandler.connection.getAddress();
InetAddress address = InetAddresses.forString(socketAddress.getAddress().toString().replace("/", ""));
Supplier<String> getHostName = () -> getHostname(player);
String playerName = player.getEntityName();
String displayName = player.getDisplayName().asString();
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
if (gatheringGeolocations) {
database.executeTransaction(
new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry)
);
}
database.executeTransaction(new PlayerServerRegisterTransaction(playerUUID,
System::currentTimeMillis, playerName, serverUUID, getHostName));
database.executeTransaction(new OperatorStatusTransaction(playerUUID, serverUUID, server.getPlayerManager().getOpList().isOp(player.getGameProfile())));
ActiveSession session = new ActiveSession(playerUUID, serverUUID, time, world, gm);
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
session.getExtraData().put(ServerName.class, new ServerName(serverInfo.getServer().getIdentifiableName()));
sessionCache.cacheSession(playerUUID, session)
.ifPresent(previousSession -> database.executeTransaction(new SessionEndTransaction(previousSession)));
database.executeTransaction(new NicknameStoreTransaction(
playerUUID, new Nickname(displayName, time, serverUUID),
(uuid, name) -> nicknameCache.getDisplayName(playerUUID).map(name::equals).orElse(false)
));
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
}
private String getHostname(ServerPlayerEntity player) {
return joinAddresses.get(player.getUuid());
}
// No event priorities on Fabric, so this has to be called with onPlayerQuit()
public void beforePlayerQuit(ServerPlayerEntity player) {
UUID playerUUID = player.getUuid();
String playerName = player.getEntityName();
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE));
}
public void onPlayerQuit(ServerPlayerEntity player) {
beforePlayerQuit(player);
try {
actOnQuitEvent(player);
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(getClass(), player).build());
}
}
private void actOnQuitEvent(ServerPlayerEntity player) {
long time = System.currentTimeMillis();
String playerName = player.getEntityName();
UUID playerUUID = player.getUuid();
ServerUUID serverUUID = serverInfo.getServerUUID();
FabricAFKListener.afkTracker.loggedOut(playerUUID, time);
joinAddresses.remove(playerUUID);
nicknameCache.removeDisplayName(playerUUID);
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, () -> server.getPlayerManager().getUserBanList().contains(player.getGameProfile())));
sessionCache.endSession(playerUUID, time)
.ifPresent(endedSession -> dbSystem.getDatabase().executeTransaction(new SessionEndTransaction(endedSession)));
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
}
}
@Override
public boolean isEnabled() {
return this.isEnabled;
}
@Override
public void enable() {
this.isEnabled = true;
}
@Override
public void disable() {
this.isEnabled = false;
}
}

View File

@ -0,0 +1,104 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.gathering.listeners.fabric;
import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.settings.config.WorldAliasSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.transactions.events.WorldNameStoreTransaction;
import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.minecraft.server.network.ServerPlayerEntity;
import net.playeranalytics.plan.gathering.listeners.FabricListener;
import javax.inject.Inject;
import java.util.Optional;
import java.util.UUID;
public class WorldChangeListener implements FabricListener {
private final WorldAliasSettings worldAliasSettings;
private final ServerInfo serverInfo;
private final DBSystem dbSystem;
private final ErrorLogger errorLogger;
private boolean isEnabled = false;
@Inject
public WorldChangeListener(
WorldAliasSettings worldAliasSettings,
ServerInfo serverInfo,
DBSystem dbSystem,
ErrorLogger errorLogger
) {
this.worldAliasSettings = worldAliasSettings;
this.serverInfo = serverInfo;
this.dbSystem = dbSystem;
this.errorLogger = errorLogger;
}
public void onWorldChange(ServerPlayerEntity player) {
try {
actOnEvent(player);
} catch (Exception e) {
errorLogger.error(e, ErrorContext.builder().related(getClass(), player).build());
}
}
private void actOnEvent(ServerPlayerEntity player) {
long time = System.currentTimeMillis();
UUID uuid = player.getUuid();
String worldName = player.getServerWorld().getRegistryKey().getValue().toString();
String gameMode = player.interactionManager.getGameMode().name();
dbSystem.getDatabase().executeTransaction(new WorldNameStoreTransaction(serverInfo.getServerUUID(), worldName));
worldAliasSettings.addWorld(worldName);
Optional<ActiveSession> cachedSession = SessionCache.getCachedSession(uuid);
cachedSession.ifPresent(session -> session.changeState(worldName, gameMode, time));
}
@Override
public void register() {
this.enable();
if (!isEnabled) {
return;
}
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register((player, origin, destination) -> onWorldChange(player));
}
@Override
public boolean isEnabled() {
return this.isEnabled;
}
@Override
public void enable() {
this.isEnabled = true;
}
@Override
public void disable() {
this.isEnabled = false;
}
}

View File

@ -0,0 +1,188 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2018
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.playeranalytics.plan.gathering.timed;
import com.djrapitops.plan.TaskSystem;
import com.djrapitops.plan.delivery.domain.DateObj;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.TimeSettings;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.transactions.events.PingStoreTransaction;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.playeranalytics.plan.gathering.listeners.FabricListener;
import net.playeranalytics.plugin.scheduling.RunnableFactory;
import net.playeranalytics.plugin.scheduling.TimeAmount;
import net.playeranalytics.plugin.server.Listeners;
import javax.inject.Inject;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Task that handles player ping calculation on Fabric based servers.
*
* @author BrainStone
* @author DrexHD
*/
public class FabricPingCounter extends TaskSystem.Task implements FabricListener {
private final Map<UUID, List<DateObj<Integer>>> playerHistory;
private final Listeners listeners;
private final PlanConfig config;
private final DBSystem dbSystem;
private final ServerInfo serverInfo;
private final RunnableFactory runnableFactory;
private final MinecraftDedicatedServer server;
private boolean isEnabled = false;
@Inject
public FabricPingCounter(
Listeners listeners,
PlanConfig config,
DBSystem dbSystem,
ServerInfo serverInfo,
RunnableFactory runnableFactory,
MinecraftDedicatedServer server
) {
this.listeners = listeners;
this.config = config;
this.dbSystem = dbSystem;
this.serverInfo = serverInfo;
this.runnableFactory = runnableFactory;
this.server = server;
playerHistory = new HashMap<>();
ServerPlayConnectionEvents.JOIN.register((handler, sender, minecraftServer) -> onPlayerJoin(handler.player));
ServerPlayConnectionEvents.DISCONNECT.register((handler, minecraftServer) -> onPlayerQuit(handler.player));
}
@Override
public void run() {
if (!this.isEnabled) {
return;
}
long time = System.currentTimeMillis();
Iterator<Map.Entry<UUID, List<DateObj<Integer>>>> iterator = playerHistory.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<UUID, List<DateObj<Integer>>> entry = iterator.next();
UUID uuid = entry.getKey();
List<DateObj<Integer>> history = entry.getValue();
ServerPlayerEntity player = server.getPlayerManager().getPlayer(uuid);
if (player != null) {
int ping = getPing(player);
if (ping <= -1 || ping > TimeUnit.SECONDS.toMillis(8L)) {
// Don't accept bad values
continue;
}
history.add(new DateObj<>(time, ping));
if (history.size() >= 30) {
dbSystem.getDatabase().executeTransaction(
new PingStoreTransaction(uuid, serverInfo.getServerUUID(), new ArrayList<>(history))
);
history.clear();
}
} else {
iterator.remove();
}
}
}
@Override
public void register(RunnableFactory runnableFactory) {
Long startDelay = config.get(TimeSettings.PING_SERVER_ENABLE_DELAY);
if (startDelay < TimeUnit.HOURS.toMillis(1L) && config.isTrue(DataGatheringSettings.PING)) {
listeners.registerListener(this);
long delay = TimeAmount.toTicks(startDelay, TimeUnit.MILLISECONDS);
long period = 40L;
runnableFactory.create(this).runTaskTimer(delay, period);
}
this.enable();
}
public void addPlayer(ServerPlayerEntity player) {
playerHistory.put(player.getUuid(), new ArrayList<>());
}
public void removePlayer(ServerPlayerEntity player) {
playerHistory.remove(player.getUuid());
}
private int getPing(ServerPlayerEntity player) {
return player.pingMilliseconds;
}
public void onPlayerJoin(ServerPlayerEntity player) {
if (!this.isEnabled) {
return;
}
Long pingDelay = config.get(TimeSettings.PING_PLAYER_LOGIN_DELAY);
if (pingDelay >= TimeUnit.HOURS.toMillis(2L)) {
return;
}
runnableFactory.create(() -> {
if (server.getPlayerManager().getPlayerList().contains(player)) {
addPlayer(player);
}
}).runTaskLater(TimeAmount.toTicks(pingDelay, TimeUnit.MILLISECONDS));
}
public void onPlayerQuit(ServerPlayerEntity player) {
if (!this.isEnabled) {
return;
}
removePlayer(player);
}
public void clear() {
playerHistory.clear();
}
@Override
public void register() {
this.enable();
}
@Override
public boolean isEnabled() {
return this.isEnabled;
}
@Override
public void enable() {
this.isEnabled = true;
}
@Override
public void disable() {
this.isEnabled = false;
}
}

View File

@ -0,0 +1,46 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.identification.properties;
import com.djrapitops.plan.identification.properties.ServerProperties;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import javax.inject.Inject;
/**
* server.properties fetcher for Fabric
*
* @author Kopo942
*/
public class FabricServerProperties extends ServerProperties {
@Inject
public FabricServerProperties(MinecraftDedicatedServer server) {
super(
"Fabric",
server.getServerPort(),
server.getVersion(),
FabricLoader.getInstance().getModContainer("fabric").get().getMetadata().getVersion().getFriendlyString() +
" (API), " +
FabricLoader.getInstance().getModContainer("fabricloader").get().getMetadata().getVersion().getFriendlyString() +
" (loader)",
() -> (server.getServerIp() == null) ? "" : server.getServerIp(),
server.getProperties().maxPlayers
);
}
}

View File

@ -0,0 +1,39 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.modules.fabric;
import com.djrapitops.plan.identification.properties.ServerProperties;
import dagger.Module;
import dagger.Provides;
import net.playeranalytics.plan.identification.properties.FabricServerProperties;
import javax.inject.Singleton;
/**
* Dagger module for providing ServerProperties on Fabric.
*
* @author Kopo942
*/
@Module
public class FabricServerPropertiesModule {
@Provides
@Singleton
ServerProperties provideServerProperties(FabricServerProperties serverProperties) {
return serverProperties;
}
}

View File

@ -0,0 +1,63 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.modules.fabric;
import com.djrapitops.plan.gathering.ServerSensor;
import com.djrapitops.plan.gathering.ServerShutdownSave;
import com.djrapitops.plan.gathering.listeners.ListenerSystem;
import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerServerInfo;
import com.djrapitops.plan.settings.ConfigSystem;
import com.djrapitops.plan.settings.FabricConfigSystem;
import com.djrapitops.plan.storage.database.DBSystem;
import dagger.Binds;
import dagger.Module;
import net.minecraft.server.world.ServerWorld;
import net.playeranalytics.plan.FabricServerShutdownSave;
import net.playeranalytics.plan.gathering.FabricSensor;
import net.playeranalytics.plan.gathering.listeners.FabricListenerSystem;
import net.playeranalytics.plan.storage.database.FabricDBSystem;
/**
* Module for binding Fabric-specific classes as interface implementations.
*
* @author Kopo942
*/
@Module
public interface FabricSuperClassBindingModule {
@Binds
ServerInfo bindServerInfo(ServerServerInfo serverInfo);
@Binds
DBSystem bindDBSystem(FabricDBSystem dbSystem);
@Binds
ConfigSystem bindConfigSystem(FabricConfigSystem configSystem);
@Binds
ListenerSystem bindListenerSystem(FabricListenerSystem listenerSystem);
@Binds
ServerShutdownSave bindServerShutdownSave(FabricServerShutdownSave shutdownSave);
@Binds
ServerSensor<ServerWorld> bindServerSensor(FabricSensor sensor);
@Binds
ServerSensor<?> bindGenericsServerSensor(ServerSensor<ServerWorld> sensor);
}

View File

@ -0,0 +1,91 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.modules.fabric;
import com.djrapitops.plan.TaskSystem;
import com.djrapitops.plan.delivery.web.ResourceWriteTask;
import com.djrapitops.plan.delivery.webserver.cache.JSONFileStorage;
import com.djrapitops.plan.extension.ExtensionServerDataUpdater;
import com.djrapitops.plan.gathering.ShutdownDataPreservation;
import com.djrapitops.plan.gathering.ShutdownHook;
import com.djrapitops.plan.gathering.timed.ServerTPSCounter;
import com.djrapitops.plan.gathering.timed.SystemUsageBuffer;
import com.djrapitops.plan.settings.upkeep.ConfigStoreTask;
import com.djrapitops.plan.storage.upkeep.DBCleanTask;
import com.djrapitops.plan.storage.upkeep.LogsFolderCleanTask;
import com.djrapitops.plan.storage.upkeep.OldDependencyCacheDeletionTask;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoSet;
import net.minecraft.server.world.ServerWorld;
import net.playeranalytics.plan.gathering.timed.FabricPingCounter;
@Module
public interface FabricTaskModule {
@Binds
@IntoSet
TaskSystem.Task bindTPSCounter(ServerTPSCounter<ServerWorld> tpsCounter);
@Binds
@IntoSet
TaskSystem.Task bindPingCounter(FabricPingCounter pingCounter);
@Binds
@IntoSet
TaskSystem.Task bindExtensionServerDataUpdater(ExtensionServerDataUpdater extensionServerDataUpdater);
@Binds
@IntoSet
TaskSystem.Task bindLogCleanTask(LogsFolderCleanTask logsFolderCleanTask);
@Binds
@IntoSet
TaskSystem.Task bindConfigStoreTask(ConfigStoreTask configStoreTask);
@Binds
@IntoSet
TaskSystem.Task bindDBCleanTask(DBCleanTask cleanTask);
@Binds
@IntoSet
TaskSystem.Task bindRamAndCpuTask(SystemUsageBuffer.RamAndCpuTask ramAndCpuTask);
@Binds
@IntoSet
TaskSystem.Task bindDiskTask(SystemUsageBuffer.DiskTask diskTask);
@Binds
@IntoSet
TaskSystem.Task bindShutdownHookRegistration(ShutdownHook.Registrar registrar);
@Binds
@IntoSet
TaskSystem.Task bindJSONFileStorageCleanTask(JSONFileStorage.CleanTask cleanTask);
@Binds
@IntoSet
TaskSystem.Task bindShutdownDataPreservation(ShutdownDataPreservation dataPreservation);
@Binds
@IntoSet
TaskSystem.Task bindOldDependencyCacheDeletion(OldDependencyCacheDeletionTask deletionTask);
@Binds
@IntoSet
TaskSystem.Task bindResourceWriteTask(ResourceWriteTask resourceWriteTask);
}

View File

@ -0,0 +1,62 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan.storage.database;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DatabaseSettings;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.MySQLDB;
import com.djrapitops.plan.storage.database.SQLiteDB;
import net.playeranalytics.plugin.server.PluginLogger;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Fabric database system that initializes the required SQLite and MySQL database objects.
*
* @author Kopo942
*/
@Singleton
public class FabricDBSystem extends DBSystem {
private final PlanConfig config;
@Inject
public FabricDBSystem(
Locale locale,
MySQLDB mySQLDB,
SQLiteDB.Factory sqLiteDB,
PlanConfig config,
PluginLogger logger
) {
super(locale, sqLiteDB, logger);
this.config = config;
databases.add(mySQLDB);
databases.add(sqLiteDB.usingDefaultFile());
}
@Override
public void enable() {
String dbType = config.get(DatabaseSettings.TYPE).toLowerCase().trim();
db = getActiveDatabaseByName(dbType);
super.enable();
}
}

View File

@ -0,0 +1,72 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plugin;
import net.fabricmc.api.DedicatedServerModInitializer;
import net.playeranalytics.plugin.scheduling.FabricRunnableFactory;
import net.playeranalytics.plugin.scheduling.RunnableFactory;
import net.playeranalytics.plugin.server.FabricListeners;
import net.playeranalytics.plugin.server.FabricPluginLogger;
import net.playeranalytics.plugin.server.Listeners;
import net.playeranalytics.plugin.server.PluginLogger;
import org.apache.logging.log4j.LogManager;
public class FabricPlatformLayer implements PlatformAbstractionLayer {
private final DedicatedServerModInitializer plugin;
private PluginLogger pluginLogger;
private Listeners listeners;
private PluginInformation pluginInformation;
private RunnableFactory runnableFactory;
public FabricPlatformLayer(DedicatedServerModInitializer plugin) {
this.plugin = plugin;
}
@Override
public PluginLogger getPluginLogger() {
if (pluginLogger == null) {
pluginLogger = new FabricPluginLogger(LogManager.getLogger("Plan"));
}
return pluginLogger;
}
@Override
public Listeners getListeners() {
if (this.listeners == null) {
this.listeners = new FabricListeners();
}
return listeners;
}
@Override
public RunnableFactory getRunnableFactory() {
if (this.runnableFactory == null) {
this.runnableFactory = new FabricRunnableFactory();
}
return runnableFactory;
}
@Override
public PluginInformation getPluginInformation() {
if (this.pluginInformation == null) {
this.pluginInformation = new FabricPluginInformation(this.plugin);
}
return pluginInformation;
}
}

View File

@ -0,0 +1,47 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plugin;
import net.fabricmc.api.DedicatedServerModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import java.io.File;
import java.io.InputStream;
public class FabricPluginInformation implements PluginInformation {
private final DedicatedServerModInitializer plugin;
public FabricPluginInformation(DedicatedServerModInitializer plugin) {
this.plugin = plugin;
}
@Override
public InputStream getResourceFromJar(String resource) {
return this.getClass().getResourceAsStream("/" + resource);
}
@Override
public File getDataFolder() {
return FabricLoader.getInstance().getGameDir().resolve("mods").resolve("Plan").toFile();
}
@Override
public String getVersion() {
return FabricLoader.getInstance().getModContainer("plan").get().getMetadata().getVersion().getFriendlyString();
}
}

View File

@ -0,0 +1,52 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plugin.scheduling;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public class FabricRunnableFactory implements RunnableFactory {
private final ScheduledExecutorService executorService;
private final Set<FabricTask> tasks;
public FabricRunnableFactory() {
this.executorService = Executors.newScheduledThreadPool(8);
this.tasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
}
@Override
public UnscheduledTask create(Runnable runnable) {
return new UnscheduledFabricTask(executorService, runnable, task -> {
});
}
@Override
public UnscheduledTask create(PluginRunnable runnable) {
return new UnscheduledFabricTask(executorService, runnable, runnable::setCancellable);
}
@Override
public void cancelAllKnownTasks() {
this.tasks.forEach(Task::cancel);
this.tasks.clear();
executorService.shutdown();
}
}

View File

@ -0,0 +1,38 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plugin.scheduling;
import java.util.concurrent.Future;
public class FabricTask implements Task {
private final Future<?> task;
public FabricTask(Future<?> task) {
this.task = task;
}
@Override
public boolean isGameThread() {
return false;
}
@Override
public void cancel() {
task.cancel(false);
}
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plugin.scheduling;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class UnscheduledFabricTask implements UnscheduledTask {
private final ScheduledExecutorService scheduler;
private final Runnable runnable;
private final Consumer<Task> cancellableConsumer;
public UnscheduledFabricTask(ScheduledExecutorService scheduler, Runnable runnable, Consumer<Task> cancellableConsumer) {
this.scheduler = scheduler;
this.runnable = runnable;
this.cancellableConsumer = cancellableConsumer;
}
@Override
public Task runTaskAsynchronously() {
FabricTask task = new FabricTask(this.scheduler.submit(this.runnable));
cancellableConsumer.accept(task);
return task;
}
@Override
public Task runTaskLaterAsynchronously(long delayTicks) {
FabricTask task = new FabricTask(this.scheduler.schedule(
this.runnable,
delayTicks * 50,
TimeUnit.MILLISECONDS
));
cancellableConsumer.accept(task);
return task;
}
@Override
public Task runTaskTimerAsynchronously(long delayTicks, long periodTicks) {
FabricTask task = new FabricTask(this.scheduler.scheduleAtFixedRate(
runnable,
delayTicks * 50,
periodTicks * 50,
TimeUnit.MILLISECONDS
));
cancellableConsumer.accept(task);
return task;
}
@Override
public Task runTask() {
return runTaskAsynchronously();
}
@Override
public Task runTaskLater(long delayTicks) {
return runTaskLaterAsynchronously(delayTicks);
}
@Override
public Task runTaskTimer(long delayTicks, long periodTicks) {
return runTaskTimerAsynchronously(delayTicks, periodTicks);
}
}

View File

@ -0,0 +1,58 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plugin.server;
import net.playeranalytics.plan.gathering.listeners.FabricListener;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class FabricListeners implements Listeners {
private final Set<FabricListener> listeners;
public FabricListeners() {
this.listeners = Collections.newSetFromMap(new ConcurrentHashMap<>());
}
@Override
public void registerListener(Object listener) {
if (listener == null) {
throw new IllegalArgumentException("Listener can not be null!");
} else if (!(listener instanceof FabricListener)) {
throw new IllegalArgumentException("Listener needs to be of type " + listener.getClass().getName() + ", but was " + listener.getClass());
} else {
if (!((FabricListener) listener).isEnabled()) {
((FabricListener) listener).register();
listeners.add((FabricListener) listener);
}
}
}
@Override
public void unregisterListener(Object listener) {
((FabricListener) listener).disable();
listeners.remove(listener);
}
@Override
public void unregisterListeners() {
listeners.forEach(FabricListener::disable);
listeners.clear();
}
}

View File

@ -0,0 +1,58 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plugin.server;
import org.apache.logging.log4j.Logger;
public class FabricPluginLogger implements PluginLogger {
private final Logger logger;
public FabricPluginLogger(Logger logger) {
this.logger = logger;
}
@Override
public PluginLogger info(String message) {
logger.info("[Plan] " + message);
return this;
}
@Override
public PluginLogger warn(String message) {
logger.warn("[Plan] " + message);
return this;
}
@Override
public PluginLogger error(String message) {
logger.error("[Plan] " + message);
return this;
}
@Override
public PluginLogger warn(String message, Throwable throwable) {
logger.warn("[Plan] " + message, throwable);
return this;
}
@Override
public PluginLogger error(String message, Throwable throwable) {
logger.error("[Plan] " + message, throwable);
return this;
}
}

View File

@ -0,0 +1,73 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package net.playeranalytics.plan;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import utilities.RandomData;
import utilities.TestSettings;
import utilities.mocks.FabricMockComponent;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Test for Fabric PlanSystem.
*
* @author AuroraLS3
* @author Kopo942
*/
class FabricSystemTest {
private final int TEST_PORT_NUMBER = RandomData.randomInt(9005, 9500);
private PlanSystem system;
@BeforeEach
void prepareSystem(@TempDir Path temp) throws Exception {
system = new FabricMockComponent(temp).getPlanSystem();
system.getConfigSystem().getConfig()
.set(WebserverSettings.PORT, TEST_PORT_NUMBER);
}
@Test
void fabricSystemEnables() {
try {
system.enable();
assertTrue(system.isEnabled());
} finally {
system.disable();
}
}
@Test
void fabricSystemHasDefaultConfigValuesAfterEnable() throws IllegalAccessException {
try {
system.enable();
PlanConfig config = system.getConfigSystem().getConfig();
TestSettings.assertValidDefaultValuesForAllSettings(config, TestSettings.getServerSettings());
} finally {
system.disable();
}
}
}

View File

@ -0,0 +1,83 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package utilities.mocks;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.PlanSystem;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import net.playeranalytics.plan.DaggerPlanFabricComponent;
import net.playeranalytics.plan.PlanFabricComponent;
import net.playeranalytics.plan.identification.properties.FabricServerProperties;
import org.mockito.Mockito;
import java.nio.file.Path;
import static org.mockito.Mockito.doReturn;
/**
* Test utility for creating a dagger PlanComponent using a mocked Plan.
*
* @author AuroraLS3
* @author Kopo942
*/
public class FabricMockComponent {
private final Path tempDir;
private PlanPlugin planMock;
private PlanFabricComponent component;
public FabricMockComponent(Path tempDir) {
this.tempDir = tempDir;
}
public PlanPlugin getPlanMock() {
if (planMock == null) {
planMock = PlanFabricMocker.setUp()
.withDataFolder(tempDir.resolve("data").toFile())
.getPlanMock();
}
return planMock;
}
public PlanSystem getPlanSystem() {
if (component == null) {
PlanPlugin planMock = getPlanMock();
component = DaggerPlanFabricComponent.builder()
.plan(planMock)
.abstractionLayer(new TestPlatformAbstractionLayer(planMock))
.server(mockServer())
.serverProperties(mockServerProperties())
.build();
}
return component.system();
}
private MinecraftDedicatedServer mockServer() {
MinecraftDedicatedServer serverMock = Mockito.mock(MinecraftDedicatedServer.class);
doReturn("").when(serverMock).getServerIp();
doReturn(25565).when(serverMock).getPort();
doReturn("1.17.1").when(serverMock).getVersion();
return serverMock;
}
private FabricServerProperties mockServerProperties() {
FabricServerProperties propertiesMock = Mockito.mock(FabricServerProperties.class);
doReturn("").when(propertiesMock).getIp();
return propertiesMock;
}
}

View File

@ -0,0 +1,61 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package utilities.mocks;
import com.djrapitops.plan.commands.use.ColorScheme;
import net.playeranalytics.plan.PlanFabric;
import org.mockito.Mockito;
import java.io.File;
import static org.mockito.Mockito.doReturn;
/**
* Mocking utility for Fabric version of Plan.
*
* @author AuroraLS3
* @author Kopo942
*/
public class PlanFabricMocker extends Mocker {
private PlanFabric planMock;
private PlanFabricMocker() {
}
public static PlanFabricMocker setUp() {
return new PlanFabricMocker().mockPlugin();
}
private PlanFabricMocker mockPlugin() {
planMock = Mockito.mock(PlanFabric.class);
super.planMock = planMock;
doReturn(new ColorScheme("§1", "§2", "§3")).when(planMock).getColorScheme();
return this;
}
public PlanFabricMocker withDataFolder(File tempFolder) {
doReturn(tempFolder).when(planMock).getDataFolder();
return this;
}
PlanFabric getPlanMock() {
return planMock;
}
}

View File

@ -1,3 +1,13 @@
pluginManagement {
repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}
rootProject.name = 'Plan'
include 'api'
@ -8,4 +18,5 @@ include 'nukkit'
include 'bungeecord'
include 'velocity'
include 'plugin'
include 'extensions'
include 'extensions'
include 'fabric'