Start work on Plan integration

feat/data-edit-commands
William 3 years ago
parent 0fce3c44ab
commit d1e9f858fe

@ -19,6 +19,7 @@ import net.william278.husksync.database.MySqlDatabase;
import net.william278.husksync.editor.DataEditor;
import net.william278.husksync.event.BukkitEventCannon;
import net.william278.husksync.event.EventCannon;
import net.william278.husksync.hook.PlanHook;
import net.william278.husksync.listener.BukkitEventListener;
import net.william278.husksync.listener.EventListener;
import net.william278.husksync.migrator.LegacyMigrator;
@ -186,6 +187,13 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
getLoggingAdapter().log(Level.INFO, "Successfully registered permissions & commands");
}
return succeeded;
}).thenApply(succeeded -> {
if (succeeded && Bukkit.getPluginManager().getPlugin("Plan") != null) {
getLoggingAdapter().log(Level.INFO, "Enabling Plan integration...");
new PlanHook(database, logger).hookIntoPlan();
getLoggingAdapter().log(Level.INFO, "Plan integration enabled!");
}
return succeeded;
}).thenApply(succeeded -> {
// Check for updates
if (succeeded && settings.getBooleanValue(Settings.ConfigOption.CHECK_FOR_UPDATES)) {

@ -5,7 +5,9 @@ api-version: 1.16
author: William278
description: 'A modern, cross-server player data synchronization system'
website: 'https://william278.net'
softdepend: [ MysqlPlayerDataBridge ]
softdepend:
- MysqlPlayerDataBridge
- Plan
libraries:
- 'mysql:mysql-connector-java:8.0.29'
- 'org.xerial.snappy:snappy-java:1.1.8.4'

@ -12,6 +12,11 @@ dependencies {
compileOnly 'dev.dejvokep:boosted-yaml:1.2'
compileOnly 'org.xerial.snappy:snappy-java:1.1.8.4'
compileOnly 'org.jetbrains:annotations:23.0.0'
compileOnly 'com.github.plan-player-analytics:Plan:5.4.1690'
testImplementation 'org.xerial.snappy:snappy-java:1.1.8.4'
testImplementation 'com.github.plan-player-analytics:Plan:5.4.1690'
testCompileOnly 'org.jetbrains:annotations:23.0.0'
}
shadowJar {

@ -0,0 +1,98 @@
package net.william278.husksync.hook;
import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.DataExtension;
import com.djrapitops.plan.extension.FormatType;
import com.djrapitops.plan.extension.annotation.NumberProvider;
import com.djrapitops.plan.extension.annotation.PluginInfo;
import com.djrapitops.plan.extension.annotation.StringProvider;
import com.djrapitops.plan.extension.icon.Color;
import com.djrapitops.plan.extension.icon.Family;
import net.william278.husksync.data.VersionedUserData;
import net.william278.husksync.database.Database;
import net.william278.husksync.player.User;
import org.jetbrains.annotations.NotNull;
import java.util.Date;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
@PluginInfo(
name = "HuskSync",
iconName = "arrow-right-arrow-left",
iconFamily = Family.SOLID,
color = Color.LIGHT_BLUE
)
public class PlanDataExtension implements DataExtension {
private Database database;
//todo add more providers
protected PlanDataExtension(@NotNull Database database) {
this.database = database;
}
protected PlanDataExtension() {
}
@Override
public CallEvents[] callExtensionMethodsOn() {
return new CallEvents[]{
CallEvents.PLAYER_JOIN,
CallEvents.PLAYER_LEAVE
};
}
private CompletableFuture<Optional<VersionedUserData>> getCurrentUserData(@NotNull UUID uuid) {
return CompletableFuture.supplyAsync(() -> {
final Optional<User> optionalUser = database.getUser(uuid).join();
if (optionalUser.isPresent()) {
return database.getCurrentUserData(optionalUser.get()).join();
}
return Optional.empty();
});
}
@NumberProvider(
text = "Data Sync Time",
description = "The last time the user had their data synced with the server.",
iconName = "clock",
iconFamily = Family.SOLID,
format = FormatType.TIME_MILLISECONDS,
priority = 1
)
public long getCurrentDataTimestamp(@NotNull UUID uuid) {
return getCurrentUserData(uuid).join().map(
versionedUserData -> versionedUserData.versionTimestamp().getTime())
.orElse(new Date().getTime());
}
@StringProvider(
text = "Data Version ID",
description = "ID of the data version that the user is currently using.",
iconName = "bolt",
iconFamily = Family.SOLID,
priority = 2
)
public String getCurrentDataId(@NotNull UUID uuid) {
return getCurrentUserData(uuid).join().map(
versionedUserData -> versionedUserData.versionUUID().toString()
.split(Pattern.quote("-"))[0])
.orElse("unknown");
}
@NumberProvider(
text = "Advancements",
description = "The number of advancements & recipes the player has progressed in",
iconName = "award",
iconFamily = Family.SOLID,
priority = 3
)
public long getAdvancementsCompleted(@NotNull UUID playerUUID) {
return getCurrentUserData(playerUUID).join().map(
versionedUserData -> (long) versionedUserData.userData().getAdvancementData().size())
.orElse(0L);
}
}

@ -0,0 +1,55 @@
package net.william278.husksync.hook;
import com.djrapitops.plan.capability.CapabilityService;
import com.djrapitops.plan.extension.ExtensionService;
import net.william278.husksync.database.Database;
import net.william278.husksync.util.Logger;
import org.jetbrains.annotations.NotNull;
import java.util.logging.Level;
public class PlanHook {
private final Database database;
private final Logger logger;
public PlanHook(@NotNull Database database, @NotNull Logger logger) {
this.database = database;
this.logger = logger;
}
public void hookIntoPlan() {
if (!areAllCapabilitiesAvailable()) {
return;
}
registerDataExtension();
handlePlanReload();
}
private boolean areAllCapabilitiesAvailable() {
CapabilityService capabilities = CapabilityService.getInstance();
return capabilities.hasCapability("DATA_EXTENSION_VALUES");
}
private void registerDataExtension() {
try {
ExtensionService.getInstance().register(new PlanDataExtension(database));
} catch (IllegalStateException planIsNotEnabled) {
logger.log(Level.SEVERE, "Plan extension hook failed to register. Plan is not enabled.", planIsNotEnabled);
// Plan is not enabled, handle exception
} catch (IllegalArgumentException dataExtensionImplementationIsInvalid) {
logger.log(Level.SEVERE, "Plan extension hook failed to register. Data hook implementation is invalid.", dataExtensionImplementationIsInvalid);
// The DataExtension implementation has an implementation error, handle exception
}
}
// Re-register the extension when plan enables
private void handlePlanReload() {
CapabilityService.getInstance().registerEnableListener(isPlanEnabled -> {
if (isPlanEnabled) {
registerDataExtension();
}
});
}
}

@ -7,6 +7,9 @@ import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
/**
* Tests for the data system {@link DataAdapter}
*/
public class DataAdaptionTests {
@Test
@ -42,7 +45,7 @@ public class DataAdaptionTests {
final DataAdapter dataAdapter = new JsonDataAdapter();
final byte[] data = dataAdapter.toBytes(dummyUserData);
final String json = new String(data, StandardCharsets.UTF_8);
final String expectedJson = "{\"status\":{\"health\":20.0,\"max_health\":20.0,\"health_scale\":0.0,\"hunger\":20,\"saturation\":5.0,\"saturation_exhaustion\":5.0,\"selected_item_slot\":1,\"total_experience\":100,\"experience_level\":1,\"experience_progress\":1.0,\"game_mode\":\"SURVIVAL\",\"is_flying\":false},\"inventory\":{\"serialized_inventory\":\"\"},\"ender_chest\":{\"serialized_inventory\":\"\"},\"potion_effects\":{\"serialized_potion_effects\":\"\"},\"advancements\":[],\"statistics\":{\"untyped_statistics\":{},\"block_statistics\":{},\"item_statistics\":{},\"entity_statistics\":{}},\"location\":{\"world_name\":\"dummy_world\",\"world_uuid\":\"00000000-0000-0000-0000-000000000000\",\"world_environment\":\"NORMAL\",\"x\":0.0,\"y\":64.0,\"z\":0.0,\"yaw\":90.0,\"pitch\":180.0},\"persistent_data_container\":{\"persistent_data_map\":{}},\"format_version\":1}";
final String expectedJson = "{\"status\":{\"health\":20.0,\"max_health\":20.0,\"health_scale\":0.0,\"hunger\":20,\"saturation\":5.0,\"saturation_exhaustion\":5.0,\"selected_item_slot\":1,\"total_experience\":100,\"experience_level\":1,\"experience_progress\":1.0,\"game_mode\":\"SURVIVAL\",\"is_flying\":false},\"inventory\":{\"serialized_items\":\"\"},\"ender_chest\":{\"serialized_items\":\"\"},\"potion_effects\":{\"serialized_potion_effects\":\"\"},\"advancements\":[],\"statistics\":{\"untyped_statistics\":{},\"block_statistics\":{},\"item_statistics\":{},\"entity_statistics\":{}},\"location\":{\"world_name\":\"dummy_world\",\"world_uuid\":\"00000000-0000-0000-0000-000000000000\",\"world_environment\":\"NORMAL\",\"x\":0.0,\"y\":64.0,\"z\":0.0,\"yaw\":90.0,\"pitch\":180.0},\"persistent_data_container\":{\"persistent_data_map\":{}},\"format_version\":1}";
Assertions.assertEquals(expectedJson, json);
}

@ -0,0 +1,19 @@
package net.william278.husksync.hook;
import com.djrapitops.plan.extension.extractor.ExtensionExtractor;
import org.junit.jupiter.api.Test;
/**
* Tests for the {@link PlanHook} and {@link PlanDataExtension} implementation
*/
public class PlanHookTests {
/**
* Throws IllegalArgumentException if there is an implementation error or warning.
*/
@Test
public void testExtensionImplementationErrors() {
new ExtensionExtractor(new PlanDataExtension()).validateAnnotations();
}
}
Loading…
Cancel
Save