forked from public-mirrors/HuskSync
feat: add support for Fabric targeting Minecraft 1.20.1 (#217)
* Upgrade the Fabric version and rewrite the code. * Migrate the completed code of version 1.19.2. * fabric: some events. * Updated open source license to Apache 2.0. * Add Plan analyzer support. * Fix build. * `UnsupportedOperationException` * More fabric implementation work, update to v3's structure * Suppress compiler warnings * Add commands, adjust registration order * Inventory and ender chest data/serializers * Update license headers * Fixup shaded library relocations * Fix build * Potion effects & location serializers * Catch `Files.createDirectory(path);` in `#getDataFolder` * Update fabric.mod.json metadata, correct icon * Events for Fabric (#218) * Added apache commons pool2 dependency A NoClassDefFoundError would get thrown without this dependency. Relocation appears to not work very well either, so it has been excluded for now * Added in Item Pickup and Drop events and mixins * Update husksync.mixins.json * Switch drop item event to using Network Handler mixin * Implemented even more events - Interact block (place too) - Interact Entity - Use Item - Block Break - Player damage - Inventory Click (handles drops) - Player Commands * Re-implement the dropItem mixin * Set dropItem mixin as cancellable * deps: Include all bukkit runtime deps * fix/fabric: Supply AudienceProvider to `ConsoleUser` constructor * docs: credit Fabric porters :) * fix: Item deserialization now working * refactor: Remove inventory debug log * docs: Update `fabric.mod.json` * refactor: update with upstream changes * fix: dangling JD comment * fix: config file reference fixes * refactor: optimize imports, fix relocation * refactor: move tag references to common * refactor: use lombok for data / serializer methods * fix: bad annotating * refactor: adjust callback formatting * fabric: bump deps, refactor to match main branch * fabric: more serializer type work * feat: register more fabric data serializers also fixes a compile issue on bukkit, and refactors the JSON serializer to be in the common module * feat: implement remaining Fabric serializers * feat: add on-the-fly DFU for Fabric Now auto-upgrades item data to support version bumps. Also improved the schema a lil' bit. * feat: add missing mixins * feat: implement toKeep/toDrop option on Fabric * feat: apply stats on sync * build: append fabric MC version to file name * feat: add HuskSync API support for Fabric Also updates the docs * refactor: fixup a deprecation in the wrong spot * refactor: optimize fabric item serializing in-line with Bukkit * feat: implement viewer GUIs on Fabric * docs: Fabric is in Alpha for now --------- Co-authored-by: hanbings <hanbings@hanbings.io> Co-authored-by: Stampede <carterblowers01@gmail.com>feat/data-edit-commands
parent
e3fb1762a1
commit
89368778f3
@ -0,0 +1,71 @@
|
|||||||
|
plugins {
|
||||||
|
id 'fabric-loom' version '1.6-SNAPSHOT'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'fabric-loom'
|
||||||
|
loom.serverOnlyMinecraftJar()
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
|
||||||
|
maven { url 'https://maven.nucleoid.xyz' }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
minecraft "com.mojang:minecraft:${fabric_minecraft_version}"
|
||||||
|
mappings "net.fabricmc:yarn:${fabric_yarn_mappings}:v2"
|
||||||
|
modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}"
|
||||||
|
|
||||||
|
modImplementation include("net.kyori:adventure-platform-fabric:${adventure_platform_fabric_version}")
|
||||||
|
modImplementation include("me.lucko:fabric-permissions-api:${fabric_permissions_api_version}")
|
||||||
|
modImplementation include("eu.pb4:sgui:${sgui_version}")
|
||||||
|
modCompileOnly "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
|
||||||
|
|
||||||
|
// Runtime dependencies on Bukkit; "include" them on Fabric. (todo: minify JAR?)
|
||||||
|
implementation include("redis.clients:jedis:$jedis_version")
|
||||||
|
implementation include("com.mysql:mysql-connector-j:$mysql_driver_version")
|
||||||
|
implementation include("org.mariadb.jdbc:mariadb-java-client:$mariadb_driver_version")
|
||||||
|
implementation include("org.xerial.snappy:snappy-java:$snappy_version")
|
||||||
|
|
||||||
|
compileOnly 'org.jetbrains:annotations:24.0.1'
|
||||||
|
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||||
|
compileOnly 'org.projectlombok:lombok:1.18.30'
|
||||||
|
|
||||||
|
annotationProcessor 'org.projectlombok:lombok:1.18.30'
|
||||||
|
|
||||||
|
shadow project(path: ":common")
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
configurations = [project.configurations.shadow]
|
||||||
|
destinationDirectory.set(file("$projectDir/build/libs"))
|
||||||
|
|
||||||
|
exclude('net.fabricmc:.*')
|
||||||
|
exclude('net.kyori:.*')
|
||||||
|
exclude '/mappings/*'
|
||||||
|
|
||||||
|
relocate 'org.apache.commons.io', 'net.william278.husksync.libraries.commons.io'
|
||||||
|
relocate 'org.apache.commons.text', 'net.william278.husksync.libraries.commons.text'
|
||||||
|
relocate 'org.apache.commons.lang3', 'net.william278.husksync.libraries.commons.lang3'
|
||||||
|
relocate 'com.google.gson', 'net.william278.husksync.libraries.gson'
|
||||||
|
relocate 'com.fatboyindustrial', 'net.william278.husksync.libraries'
|
||||||
|
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
||||||
|
relocate 'org.jetbrains', 'net.william278.husksync.libraries'
|
||||||
|
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
||||||
|
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
||||||
|
relocate 'de.exlll', 'net.william278.husksync.libraries'
|
||||||
|
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
||||||
|
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
||||||
|
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
||||||
|
}
|
||||||
|
|
||||||
|
remapJar {
|
||||||
|
dependsOn tasks.shadowJar
|
||||||
|
mustRunAfter tasks.shadowJar
|
||||||
|
inputFile = shadowJar.archiveFile.get()
|
||||||
|
addNestedDependencies = true
|
||||||
|
|
||||||
|
destinationDirectory.set(file("$rootDir/target/"))
|
||||||
|
archiveClassifier.set('')
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar.finalizedBy(remapJar)
|
@ -0,0 +1,341 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import net.fabricmc.api.DedicatedServerModInitializer;
|
||||||
|
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||||
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import net.fabricmc.loader.api.ModContainer;
|
||||||
|
import net.kyori.adventure.platform.AudienceProvider;
|
||||||
|
import net.kyori.adventure.platform.fabric.FabricServerAudiences;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.william278.desertwell.util.Version;
|
||||||
|
import net.william278.husksync.adapter.DataAdapter;
|
||||||
|
import net.william278.husksync.adapter.GsonAdapter;
|
||||||
|
import net.william278.husksync.adapter.SnappyGsonAdapter;
|
||||||
|
import net.william278.husksync.api.FabricHuskSyncAPI;
|
||||||
|
import net.william278.husksync.command.Command;
|
||||||
|
import net.william278.husksync.command.FabricCommand;
|
||||||
|
import net.william278.husksync.config.Locales;
|
||||||
|
import net.william278.husksync.config.Server;
|
||||||
|
import net.william278.husksync.config.Settings;
|
||||||
|
import net.william278.husksync.data.*;
|
||||||
|
import net.william278.husksync.database.Database;
|
||||||
|
import net.william278.husksync.database.MySqlDatabase;
|
||||||
|
import net.william278.husksync.event.FabricEventDispatcher;
|
||||||
|
import net.william278.husksync.hook.PlanHook;
|
||||||
|
import net.william278.husksync.listener.EventListener;
|
||||||
|
import net.william278.husksync.listener.FabricEventListener;
|
||||||
|
import net.william278.husksync.migrator.Migrator;
|
||||||
|
import net.william278.husksync.redis.RedisManager;
|
||||||
|
import net.william278.husksync.sync.DataSyncer;
|
||||||
|
import net.william278.husksync.user.ConsoleUser;
|
||||||
|
import net.william278.husksync.user.FabricUser;
|
||||||
|
import net.william278.husksync.user.OnlineUser;
|
||||||
|
import net.william278.husksync.util.FabricTask;
|
||||||
|
import net.william278.husksync.util.LegacyConverter;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.slf4j.spi.LoggingEventBuilder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync, FabricTask.Supplier,
|
||||||
|
FabricEventDispatcher {
|
||||||
|
|
||||||
|
private static final String PLATFORM_TYPE_ID = "fabric";
|
||||||
|
|
||||||
|
private final TreeMap<Identifier, Serializer<? extends Data>> serializers = Maps.newTreeMap(
|
||||||
|
SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR
|
||||||
|
);
|
||||||
|
private final Map<UUID, Map<Identifier, Data>> playerCustomDataStore = Maps.newConcurrentMap();
|
||||||
|
private final Map<String, Boolean> permissions = Maps.newHashMap();
|
||||||
|
private final List<Migrator> availableMigrators = Lists.newArrayList();
|
||||||
|
private final Set<UUID> lockedPlayers = Sets.newConcurrentHashSet();
|
||||||
|
|
||||||
|
private Logger logger;
|
||||||
|
private ModContainer mod;
|
||||||
|
private MinecraftServer minecraftServer;
|
||||||
|
private boolean disabling;
|
||||||
|
private Gson gson;
|
||||||
|
private AudienceProvider audiences;
|
||||||
|
private Database database;
|
||||||
|
private RedisManager redisManager;
|
||||||
|
private EventListener eventListener;
|
||||||
|
private DataAdapter dataAdapter;
|
||||||
|
@Setter
|
||||||
|
private DataSyncer dataSyncer;
|
||||||
|
@Setter
|
||||||
|
private Settings settings;
|
||||||
|
@Setter
|
||||||
|
private Locales locales;
|
||||||
|
@Setter
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
private Server serverName;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeServer() {
|
||||||
|
// Get the logger and mod container
|
||||||
|
this.logger = LoggerFactory.getLogger("HuskSync");
|
||||||
|
this.mod = FabricLoader.getInstance().getModContainer("husksync").orElseThrow();
|
||||||
|
this.disabling = false;
|
||||||
|
this.gson = createGson();
|
||||||
|
|
||||||
|
// Load settings and locales
|
||||||
|
initialize("plugin config & locale files", (plugin) -> {
|
||||||
|
loadSettings();
|
||||||
|
loadLocales();
|
||||||
|
loadServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register commands
|
||||||
|
initialize("commands", (plugin) -> this.registerCommands());
|
||||||
|
|
||||||
|
// Load HuskSync after server startup
|
||||||
|
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
|
||||||
|
this.minecraftServer = server;
|
||||||
|
this.onEnable();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Unload HuskSync before server shutdown
|
||||||
|
ServerLifecycleEvents.SERVER_STOPPING.register(server -> this.onDisable());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onEnable() {
|
||||||
|
// Initial plugin setup
|
||||||
|
this.audiences = FabricServerAudiences.of(minecraftServer);
|
||||||
|
|
||||||
|
// Prepare data adapter
|
||||||
|
initialize("data adapter", (plugin) -> {
|
||||||
|
if (getSettings().getSynchronization().isCompressData()) {
|
||||||
|
this.dataAdapter = new SnappyGsonAdapter(this);
|
||||||
|
} else {
|
||||||
|
this.dataAdapter = new GsonAdapter(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
initialize("data serializers", (plugin) -> {
|
||||||
|
// PERSISTENT_DATA is not registered / available on the Fabric platform
|
||||||
|
registerSerializer(Identifier.INVENTORY, new FabricSerializer.Inventory(this));
|
||||||
|
registerSerializer(Identifier.ENDER_CHEST, new FabricSerializer.EnderChest(this));
|
||||||
|
registerSerializer(Identifier.ADVANCEMENTS, new FabricSerializer.Advancements(this));
|
||||||
|
registerSerializer(Identifier.STATISTICS, new Serializer.Json<>(this, FabricData.Statistics.class)); // TODO APPLY
|
||||||
|
registerSerializer(Identifier.POTION_EFFECTS, new FabricSerializer.PotionEffects(this));
|
||||||
|
registerSerializer(Identifier.GAME_MODE, new Serializer.Json<>(this, FabricData.GameMode.class));
|
||||||
|
registerSerializer(Identifier.FLIGHT_STATUS, new Serializer.Json<>(this, FabricData.FlightStatus.class));
|
||||||
|
registerSerializer(Identifier.ATTRIBUTES, new Serializer.Json<>(this, FabricData.Attributes.class));
|
||||||
|
registerSerializer(Identifier.HEALTH, new Serializer.Json<>(this, FabricData.Health.class));
|
||||||
|
registerSerializer(Identifier.HUNGER, new Serializer.Json<>(this, FabricData.Hunger.class));
|
||||||
|
registerSerializer(Identifier.EXPERIENCE, new Serializer.Json<>(this, FabricData.Experience.class));
|
||||||
|
registerSerializer(Identifier.LOCATION, new Serializer.Json<>(this, FabricData.Location.class));
|
||||||
|
validateDependencies();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize the database
|
||||||
|
initialize(getSettings().getDatabase().getType().getDisplayName() + " database connection", (plugin) -> {
|
||||||
|
this.database = new MySqlDatabase(this);
|
||||||
|
this.database.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prepare redis connection
|
||||||
|
initialize("Redis server connection", (plugin) -> {
|
||||||
|
this.redisManager = new RedisManager(this);
|
||||||
|
this.redisManager.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prepare data syncer
|
||||||
|
initialize("data syncer", (plugin) -> {
|
||||||
|
dataSyncer = getSettings().getSynchronization().getMode().create(this);
|
||||||
|
dataSyncer.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register events
|
||||||
|
initialize("events", (plugin) -> this.eventListener = new FabricEventListener(this));
|
||||||
|
|
||||||
|
// Register plugin hooks
|
||||||
|
initialize("hooks", (plugin) -> {
|
||||||
|
if (isDependencyLoaded("Plan") && getSettings().isEnablePlanHook()) {
|
||||||
|
new PlanHook(this).hookIntoPlan();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register API
|
||||||
|
initialize("api", (plugin) -> {
|
||||||
|
FabricHuskSyncAPI.register(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for updates
|
||||||
|
this.checkForUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDisable() {
|
||||||
|
// Handle shutdown
|
||||||
|
this.disabling = true;
|
||||||
|
|
||||||
|
// Close the event listener / data syncer
|
||||||
|
if (this.dataSyncer != null) {
|
||||||
|
this.dataSyncer.terminate();
|
||||||
|
}
|
||||||
|
if (this.eventListener != null) {
|
||||||
|
this.eventListener.handlePluginDisable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel tasks, close audiences
|
||||||
|
if (audiences != null) {
|
||||||
|
this.audiences.close();
|
||||||
|
}
|
||||||
|
this.cancelTasks();
|
||||||
|
|
||||||
|
// Complete shutdown
|
||||||
|
log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerCommands() {
|
||||||
|
final List<Command> commands = FabricCommand.Type.getCommands(this);
|
||||||
|
CommandRegistrationCallback.EVENT.register((dispatcher, registry, environment) ->
|
||||||
|
commands.forEach(command -> new FabricCommand(command, this).register(dispatcher))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String getServerName() {
|
||||||
|
return serverName.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDependencyLoaded(@NotNull String name) {
|
||||||
|
return FabricLoader.getInstance().isModLoaded(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Set<OnlineUser> getOnlineUsers() {
|
||||||
|
return minecraftServer.getPlayerManager().getPlayerList()
|
||||||
|
.stream().map(user -> (OnlineUser) FabricUser.adapt(user, this))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Optional<OnlineUser> getOnlineUser(@NotNull UUID uuid) {
|
||||||
|
return Optional.ofNullable(minecraftServer.getPlayerManager().getPlayer(uuid))
|
||||||
|
.map(user -> FabricUser.adapt(user, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public InputStream getResource(@NotNull String name) {
|
||||||
|
return this.mod.findPath(name)
|
||||||
|
.map(path -> {
|
||||||
|
try {
|
||||||
|
return Files.newInputStream(path);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log(Level.WARNING, "Failed to load resource: " + name, e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.orElse(this.getClass().getClassLoader().getResourceAsStream(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Path getConfigDirectory() {
|
||||||
|
final Path path = FabricLoader.getInstance().getConfigDir().resolve("husksync");
|
||||||
|
if (!Files.isDirectory(path)) {
|
||||||
|
try {
|
||||||
|
Files.createDirectory(path);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log(Level.SEVERE, "Failed to create config directory", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable) {
|
||||||
|
LoggingEventBuilder logEvent = logger.makeLoggingEventBuilder(
|
||||||
|
switch (level.getName()) {
|
||||||
|
case "WARNING" -> org.slf4j.event.Level.WARN;
|
||||||
|
case "SEVERE" -> org.slf4j.event.Level.ERROR;
|
||||||
|
default -> org.slf4j.event.Level.INFO;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (throwable.length >= 1) {
|
||||||
|
logEvent = logEvent.setCause(throwable[0]);
|
||||||
|
}
|
||||||
|
logEvent.log(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public ConsoleUser getConsole() {
|
||||||
|
return new ConsoleUser(audiences);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Version getPluginVersion() {
|
||||||
|
return Version.fromString(mod.getMetadata().getVersion().getFriendlyString(), "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Version getMinecraftVersion() {
|
||||||
|
return Version.fromString(minecraftServer.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String getPlatformType() {
|
||||||
|
return PLATFORM_TYPE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<LegacyConverter> getLegacyConverter() {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public FabricHuskSync getPlugin() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,260 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.api;
|
||||||
|
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.william278.desertwell.util.ThrowingConsumer;
|
||||||
|
import net.william278.husksync.FabricHuskSync;
|
||||||
|
import net.william278.husksync.data.DataHolder;
|
||||||
|
import net.william278.husksync.data.FabricData;
|
||||||
|
import net.william278.husksync.user.FabricUser;
|
||||||
|
import net.william278.husksync.user.OnlineUser;
|
||||||
|
import net.william278.husksync.user.User;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HuskSync API implementation for the Fabric platform
|
||||||
|
* </p>
|
||||||
|
* Retrieve an instance of the API class via {@link #getInstance()}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class FabricHuskSyncAPI extends HuskSyncAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>(Internal use only)</b> - Constructor, instantiating the API.
|
||||||
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
|
private FabricHuskSyncAPI(@NotNull FabricHuskSync plugin) {
|
||||||
|
super(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entrypoint to the HuskSync API on the Fabric platform - returns an instance of the API
|
||||||
|
*
|
||||||
|
* @return instance of the HuskSync API
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static FabricHuskSyncAPI getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
throw new NotRegisteredException();
|
||||||
|
}
|
||||||
|
return (FabricHuskSyncAPI) instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>(Internal use only)</b> - Register the API for this platform.
|
||||||
|
*
|
||||||
|
* @param plugin the plugin instance
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public static void register(@NotNull FabricHuskSync plugin) {
|
||||||
|
instance = new FabricHuskSyncAPI(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link OnlineUser} instance for the given Fabric {@link ServerPlayerEntity}.
|
||||||
|
*
|
||||||
|
* @param player the Fabric player to get the {@link OnlineUser} instance for
|
||||||
|
* @return the {@link OnlineUser} instance for the given Fabric {@link ServerPlayerEntity}
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public FabricUser getUser(@NotNull ServerPlayerEntity player) {
|
||||||
|
return FabricUser.adapt(player, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current {@link FabricData.Items.Inventory} of the given {@link User}
|
||||||
|
*
|
||||||
|
* @param user the user to get the inventory of
|
||||||
|
* @return the {@link FabricData.Items.Inventory} of the given {@link User}
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
public CompletableFuture<Optional<FabricData.Items.Inventory>> getCurrentInventory(@NotNull User user) {
|
||||||
|
return getCurrentData(user).thenApply(data -> data.flatMap(DataHolder::getInventory)
|
||||||
|
.map(FabricData.Items.Inventory.class::cast));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current {@link FabricData.Items.Inventory} of the given {@link ServerPlayerEntity}
|
||||||
|
*
|
||||||
|
* @param user the user to get the inventory of
|
||||||
|
* @return the {@link FabricData.Items.Inventory} of the given {@link ServerPlayerEntity}
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
public CompletableFuture<Optional<ItemStack[]>> getCurrentInventoryContents(@NotNull User user) {
|
||||||
|
return getCurrentInventory(user)
|
||||||
|
.thenApply(inventory -> inventory.map(FabricData.Items.Inventory::getContents));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current {@link FabricData.Items.Inventory} of the given {@link User}
|
||||||
|
*
|
||||||
|
* @param user the user to set the inventory of
|
||||||
|
* @param contents the contents to set the inventory to
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
public void setCurrentInventory(@NotNull User user, @NotNull FabricData.Items.Inventory contents) {
|
||||||
|
editCurrentData(user, dataHolder -> dataHolder.setInventory(contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current {@link FabricData.Items.Inventory} of the given {@link User}
|
||||||
|
*
|
||||||
|
* @param user the user to set the inventory of
|
||||||
|
* @param contents the contents to set the inventory to
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
public void setCurrentInventoryContents(@NotNull User user, @NotNull ItemStack[] contents) {
|
||||||
|
editCurrentData(
|
||||||
|
user,
|
||||||
|
dataHolder -> dataHolder.getInventory().ifPresent(
|
||||||
|
inv -> inv.setContents(adaptItems(contents))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit the current {@link FabricData.Items.Inventory} of the given {@link User}
|
||||||
|
*
|
||||||
|
* @param user the user to edit the inventory of
|
||||||
|
* @param editor the editor to apply to the inventory
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
public void editCurrentInventory(@NotNull User user, ThrowingConsumer<FabricData.Items.Inventory> editor) {
|
||||||
|
editCurrentData(user, dataHolder -> dataHolder.getInventory()
|
||||||
|
.map(FabricData.Items.Inventory.class::cast)
|
||||||
|
.ifPresent(editor));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit the current {@link FabricData.Items.Inventory} of the given {@link User}
|
||||||
|
*
|
||||||
|
* @param user the user to edit the inventory of
|
||||||
|
* @param editor the editor to apply to the inventory
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
public void editCurrentInventoryContents(@NotNull User user, ThrowingConsumer<ItemStack[]> editor) {
|
||||||
|
editCurrentData(user, dataHolder -> dataHolder.getInventory()
|
||||||
|
.map(FabricData.Items.Inventory.class::cast)
|
||||||
|
.ifPresent(inventory -> editor.accept(inventory.getContents())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current {@link FabricData.Items.EnderChest} of the given {@link User}
|
||||||
|
*
|
||||||
|
* @param user the user to get the ender chest of
|
||||||
|
* @return the {@link FabricData.Items.EnderChest} of the given {@link User}, or {@link Optional#empty()} if the
|
||||||
|
* user data could not be found
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
public CompletableFuture<Optional<FabricData.Items.EnderChest>> getCurrentEnderChest(@NotNull User user) {
|
||||||
|
return getCurrentData(user).thenApply(data -> data.flatMap(DataHolder::getEnderChest)
|
||||||
|
.map(FabricData.Items.EnderChest.class::cast));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current {@link FabricData.Items.EnderChest} of the given {@link ServerPlayerEntity}
|
||||||
|
*
|
||||||
|
* @param user the user to get the ender chest of
|
||||||
|
* @return the {@link FabricData.Items.EnderChest} of the given {@link ServerPlayerEntity}, or {@link Optional#empty()} if the
|
||||||
|
* user data could not be found
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
public CompletableFuture<Optional<ItemStack[]>> getCurrentEnderChestContents(@NotNull User user) {
|
||||||
|
return getCurrentEnderChest(user)
|
||||||
|
.thenApply(enderChest -> enderChest.map(FabricData.Items.EnderChest::getContents));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current {@link FabricData.Items.EnderChest} of the given {@link User}
|
||||||
|
*
|
||||||
|
* @param user the user to set the ender chest of
|
||||||
|
* @param contents the contents to set the ender chest to
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
public void setCurrentEnderChest(@NotNull User user, @NotNull FabricData.Items.EnderChest contents) {
|
||||||
|
editCurrentData(user, dataHolder -> dataHolder.setEnderChest(contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current {@link FabricData.Items.EnderChest} of the given {@link User}
|
||||||
|
*
|
||||||
|
* @param user the user to set the ender chest of
|
||||||
|
* @param contents the contents to set the ender chest to
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
public void setCurrentEnderChestContents(@NotNull User user, @NotNull ItemStack[] contents) {
|
||||||
|
editCurrentData(
|
||||||
|
user,
|
||||||
|
dataHolder -> dataHolder.getEnderChest().ifPresent(
|
||||||
|
enderChest -> enderChest.setContents(adaptItems(contents))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit the current {@link FabricData.Items.EnderChest} of the given {@link User}
|
||||||
|
*
|
||||||
|
* @param user the user to edit the ender chest of
|
||||||
|
* @param editor the editor to apply to the ender chest
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
public void editCurrentEnderChest(@NotNull User user, Consumer<FabricData.Items.EnderChest> editor) {
|
||||||
|
editCurrentData(user, dataHolder -> dataHolder.getEnderChest()
|
||||||
|
.map(FabricData.Items.EnderChest.class::cast)
|
||||||
|
.ifPresent(editor));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit the current {@link FabricData.Items.EnderChest} of the given {@link User}
|
||||||
|
*
|
||||||
|
* @param user the user to edit the ender chest of
|
||||||
|
* @param editor the editor to apply to the ender chest
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
public void editCurrentEnderChestContents(@NotNull User user, Consumer<ItemStack[]> editor) {
|
||||||
|
editCurrentData(user, dataHolder -> dataHolder.getEnderChest()
|
||||||
|
.map(FabricData.Items.EnderChest.class::cast)
|
||||||
|
.ifPresent(enderChest -> editor.accept(enderChest.getContents())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapts an array of {@link ItemStack} to a {@link FabricData.Items} instance
|
||||||
|
*
|
||||||
|
* @param contents the contents to adapt
|
||||||
|
* @return the adapted {@link FabricData.Items} instance
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public FabricData.Items adaptItems(@NotNull ItemStack[] contents) {
|
||||||
|
return FabricData.Items.ItemArray.adapt(contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
|
import me.lucko.fabric.api.permissions.v0.PermissionCheckEvent;
|
||||||
|
import me.lucko.fabric.api.permissions.v0.Permissions;
|
||||||
|
import net.fabricmc.fabric.api.util.TriState;
|
||||||
|
import net.minecraft.server.command.ServerCommandSource;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.william278.husksync.FabricHuskSync;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.user.CommandUser;
|
||||||
|
import net.william278.husksync.user.FabricUser;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
|
||||||
|
import static net.minecraft.server.command.CommandManager.argument;
|
||||||
|
import static net.minecraft.server.command.CommandManager.literal;
|
||||||
|
|
||||||
|
public class FabricCommand {
|
||||||
|
|
||||||
|
private final FabricHuskSync plugin;
|
||||||
|
private final Command command;
|
||||||
|
|
||||||
|
public FabricCommand(@NotNull Command command, @NotNull FabricHuskSync plugin) {
|
||||||
|
this.command = command;
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register(@NotNull CommandDispatcher<ServerCommandSource> dispatcher) {
|
||||||
|
// Register brigadier command
|
||||||
|
final Predicate<ServerCommandSource> predicate = Permissions
|
||||||
|
.require(command.getPermission(), command.isOperatorCommand() ? 3 : 0);
|
||||||
|
final LiteralArgumentBuilder<ServerCommandSource> builder = literal(command.getName())
|
||||||
|
.requires(predicate).executes(getBrigadierExecutor());
|
||||||
|
plugin.getPermissions().put(command.getPermission(), command.isOperatorCommand());
|
||||||
|
if (!command.getRawUsage().isBlank()) {
|
||||||
|
builder.then(argument(command.getRawUsage().replaceAll("[<>\\[\\]]", ""), greedyString())
|
||||||
|
.executes(getBrigadierExecutor())
|
||||||
|
.suggests(getBrigadierSuggester()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register additional permissions
|
||||||
|
final Map<String, Boolean> permissions = command.getAdditionalPermissions();
|
||||||
|
permissions.forEach((permission, isOp) -> plugin.getPermissions().put(permission, isOp));
|
||||||
|
PermissionCheckEvent.EVENT.register((player, node) -> {
|
||||||
|
if (permissions.containsKey(node) && permissions.get(node) && player.hasPermissionLevel(3)) {
|
||||||
|
return TriState.TRUE;
|
||||||
|
}
|
||||||
|
return TriState.DEFAULT;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register aliases
|
||||||
|
final LiteralCommandNode<ServerCommandSource> node = dispatcher.register(builder);
|
||||||
|
dispatcher.register(literal("husksync:" + command.getName())
|
||||||
|
.requires(predicate).executes(getBrigadierExecutor()).redirect(node));
|
||||||
|
command.getAliases().forEach(alias -> dispatcher.register(literal(alias)
|
||||||
|
.requires(predicate).executes(getBrigadierExecutor()).redirect(node)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private com.mojang.brigadier.Command<ServerCommandSource> getBrigadierExecutor() {
|
||||||
|
return (context) -> {
|
||||||
|
command.onExecuted(
|
||||||
|
resolveExecutor(context.getSource()),
|
||||||
|
command.removeFirstArg(context.getInput().split(" "))
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private com.mojang.brigadier.suggestion.SuggestionProvider<ServerCommandSource> getBrigadierSuggester() {
|
||||||
|
if (!(command instanceof TabProvider provider)) {
|
||||||
|
return (context, builder) -> com.mojang.brigadier.suggestion.Suggestions.empty();
|
||||||
|
}
|
||||||
|
return (context, builder) -> {
|
||||||
|
final String[] args = command.removeFirstArg(context.getInput().split(" ", -1));
|
||||||
|
provider.getSuggestions(resolveExecutor(context.getSource()), args).stream()
|
||||||
|
.map(suggestion -> {
|
||||||
|
final String completedArgs = String.join(" ", args);
|
||||||
|
int lastIndex = completedArgs.lastIndexOf(" ");
|
||||||
|
if (lastIndex == -1) {
|
||||||
|
return suggestion;
|
||||||
|
}
|
||||||
|
return completedArgs.substring(0, lastIndex + 1) + suggestion;
|
||||||
|
})
|
||||||
|
.forEach(builder::suggest);
|
||||||
|
return builder.buildFuture();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommandUser resolveExecutor(@NotNull ServerCommandSource source) {
|
||||||
|
if (source.getEntity() instanceof ServerPlayerEntity player) {
|
||||||
|
return FabricUser.adapt(player, plugin);
|
||||||
|
}
|
||||||
|
return plugin.getConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commands available on the Fabric HuskSync implementation.
|
||||||
|
*/
|
||||||
|
public enum Type {
|
||||||
|
|
||||||
|
HUSKSYNC_COMMAND(HuskSyncCommand::new),
|
||||||
|
USERDATA_COMMAND(UserDataCommand::new),
|
||||||
|
INVENTORY_COMMAND(InventoryCommand::new),
|
||||||
|
ENDER_CHEST_COMMAND(EnderChestCommand::new);
|
||||||
|
|
||||||
|
private final Function<HuskSync, Command> supplier;
|
||||||
|
|
||||||
|
Type(@NotNull Function<HuskSync, Command> supplier) {
|
||||||
|
this.supplier = supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public Command createCommand(@NotNull HuskSync plugin) {
|
||||||
|
return supplier.apply(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static List<Command> getCommands(@NotNull FabricHuskSync plugin) {
|
||||||
|
return Arrays.stream(values()).map(type -> type.createCommand(plugin)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,808 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.*;
|
||||||
|
import net.fabricmc.fabric.api.dimension.v1.FabricDimensions;
|
||||||
|
import net.minecraft.advancement.AdvancementProgress;
|
||||||
|
import net.minecraft.advancement.PlayerAdvancementTracker;
|
||||||
|
import net.minecraft.enchantment.EnchantmentHelper;
|
||||||
|
import net.minecraft.entity.attribute.EntityAttribute;
|
||||||
|
import net.minecraft.entity.attribute.EntityAttributeInstance;
|
||||||
|
import net.minecraft.entity.attribute.EntityAttributeModifier;
|
||||||
|
import net.minecraft.entity.effect.StatusEffect;
|
||||||
|
import net.minecraft.entity.effect.StatusEffectInstance;
|
||||||
|
import net.minecraft.entity.player.HungerManager;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.nbt.NbtCompound;
|
||||||
|
import net.minecraft.registry.Registries;
|
||||||
|
import net.minecraft.registry.Registry;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.minecraft.stat.StatType;
|
||||||
|
import net.minecraft.stat.Stats;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
import net.minecraft.world.TeleportTarget;
|
||||||
|
import net.william278.desertwell.util.ThrowingConsumer;
|
||||||
|
import net.william278.husksync.FabricHuskSync;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.adapter.Adaptable;
|
||||||
|
import net.william278.husksync.user.FabricUser;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.annotations.Range;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static net.william278.husksync.util.FabricKeyedAdapter.*;
|
||||||
|
|
||||||
|
public abstract class FabricData implements Data {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull UserDataHolder user, @NotNull HuskSync plugin) {
|
||||||
|
this.apply((FabricUser) user, (FabricHuskSync) plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin);
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static abstract class Items extends FabricData implements Data.Items {
|
||||||
|
|
||||||
|
private final @Nullable ItemStack @NotNull [] contents;
|
||||||
|
|
||||||
|
private Items(@Nullable ItemStack @NotNull [] contents) {
|
||||||
|
this.contents = Arrays.stream(contents.clone())
|
||||||
|
.map(i -> i == null || i.isEmpty() ? null : i)
|
||||||
|
.toArray(ItemStack[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Stack @NotNull [] getStack() {
|
||||||
|
return Arrays.stream(contents)
|
||||||
|
.map(stack -> stack != null ? new Stack(
|
||||||
|
stack.getItem().toString(),
|
||||||
|
stack.getCount(),
|
||||||
|
stack.getName().getString(),
|
||||||
|
Optional.ofNullable(stack.getSubNbt(ItemStack.DISPLAY_KEY))
|
||||||
|
.flatMap(display -> Optional.ofNullable(display.get(ItemStack.LORE_KEY))
|
||||||
|
.map(lore -> ((List<String>) lore).stream().toList())) //todo check this is ok
|
||||||
|
.orElse(null),
|
||||||
|
stack.getEnchantments().stream()
|
||||||
|
.map(element -> EnchantmentHelper.getIdFromNbt((NbtCompound) element))
|
||||||
|
.filter(Objects::nonNull).map(Identifier::toString)
|
||||||
|
.toList()
|
||||||
|
) : null)
|
||||||
|
.toArray(Stack[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
Arrays.fill(contents, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContents(@NotNull Data.Items contents) {
|
||||||
|
this.setContents(((FabricData.Items) contents).getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContents(@Nullable ItemStack @NotNull [] contents) {
|
||||||
|
// Ensure the array is the correct length for the inventory
|
||||||
|
if (contents.length != this.contents.length) {
|
||||||
|
contents = Arrays.copyOf(contents, this.contents.length);
|
||||||
|
}
|
||||||
|
System.arraycopy(contents, 0, this.contents, 0, this.contents.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj instanceof FabricData.Items items) {
|
||||||
|
return Arrays.equals(contents, items.getContents());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
public static class Inventory extends FabricData.Items implements Data.Items.Inventory {
|
||||||
|
|
||||||
|
@Range(from = 0, to = 8)
|
||||||
|
private int heldItemSlot;
|
||||||
|
|
||||||
|
public Inventory(@Nullable ItemStack @NotNull [] contents, int heldItemSlot) {
|
||||||
|
super(contents);
|
||||||
|
this.heldItemSlot = heldItemSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Items.Inventory from(@Nullable ItemStack @NotNull [] contents, int heldItemSlot) {
|
||||||
|
return new FabricData.Items.Inventory(contents, heldItemSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Items.Inventory from(@NotNull Collection<ItemStack> contents, int heldItemSlot) {
|
||||||
|
return from(contents.toArray(ItemStack[]::new), heldItemSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Items.Inventory empty() {
|
||||||
|
return new FabricData.Items.Inventory(new ItemStack[INVENTORY_SLOT_COUNT], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSlotCount() {
|
||||||
|
return INVENTORY_SLOT_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
|
this.clearInventoryCraftingSlots(player);
|
||||||
|
player.currentScreenHandler.setCursorStack(ItemStack.EMPTY);
|
||||||
|
final ItemStack[] items = getContents();
|
||||||
|
for (int slot = 0; slot < player.getInventory().size(); slot++) {
|
||||||
|
player.getInventory().setStack(
|
||||||
|
slot, items[slot] == null ? ItemStack.EMPTY : items[slot]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
player.getInventory().selectedSlot = heldItemSlot;
|
||||||
|
player.playerScreenHandler.sendContentUpdates();
|
||||||
|
player.getInventory().updateItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearInventoryCraftingSlots(@NotNull ServerPlayerEntity player) {
|
||||||
|
player.playerScreenHandler.clearCraftingSlots();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EnderChest extends FabricData.Items implements Data.Items.EnderChest {
|
||||||
|
|
||||||
|
private EnderChest(@Nullable ItemStack @NotNull [] contents) {
|
||||||
|
super(contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Items.EnderChest adapt(@Nullable ItemStack @NotNull [] contents) {
|
||||||
|
return new FabricData.Items.EnderChest(contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Items.EnderChest adapt(@NotNull Collection<ItemStack> items) {
|
||||||
|
return adapt(items.toArray(ItemStack[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Items.EnderChest empty() {
|
||||||
|
return new FabricData.Items.EnderChest(new ItemStack[ENDER_CHEST_SLOT_COUNT]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
|
final ItemStack[] items = getContents();
|
||||||
|
for (int slot = 0; slot < user.getPlayer().getEnderChestInventory().size(); slot++) {
|
||||||
|
user.getPlayer().getEnderChestInventory().setStack(
|
||||||
|
slot, items[slot] == null ? ItemStack.EMPTY : items[slot]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ItemArray extends FabricData.Items implements Data.Items {
|
||||||
|
|
||||||
|
private ItemArray(@Nullable ItemStack @NotNull [] contents) {
|
||||||
|
super(contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static ItemArray adapt(@NotNull Collection<ItemStack> drops) {
|
||||||
|
return new ItemArray(drops.toArray(ItemStack[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static ItemArray adapt(@Nullable ItemStack @NotNull [] drops) {
|
||||||
|
return new ItemArray(drops);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
|
throw new UnsupportedOperationException("A generic item array cannot be applied to a player");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class PotionEffects extends FabricData implements Data.PotionEffects {
|
||||||
|
|
||||||
|
private final Collection<StatusEffectInstance> effects;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.PotionEffects from(@NotNull Collection<StatusEffectInstance> effects) {
|
||||||
|
return new FabricData.PotionEffects(effects);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.PotionEffects adapt(@NotNull Collection<Effect> effects) {
|
||||||
|
return from(effects.stream()
|
||||||
|
.map(effect -> {
|
||||||
|
final StatusEffect type = matchEffectType(effect.type());
|
||||||
|
return type != null ? new StatusEffectInstance(
|
||||||
|
type,
|
||||||
|
effect.duration(),
|
||||||
|
effect.amplifier(),
|
||||||
|
effect.isAmbient(),
|
||||||
|
effect.showParticles(),
|
||||||
|
effect.hasIcon()
|
||||||
|
) : null;
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static FabricData.PotionEffects empty() {
|
||||||
|
return new FabricData.PotionEffects(List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
|
player.getActiveStatusEffects().forEach((effect, instance) -> player.removeStatusEffect(effect));
|
||||||
|
getEffects().forEach(player::addStatusEffect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public List<Effect> getActiveEffects() {
|
||||||
|
return effects.stream()
|
||||||
|
.map(potionEffect -> {
|
||||||
|
final String key = getEffectId(potionEffect.getEffectType());
|
||||||
|
return key != null ? new Effect(
|
||||||
|
key,
|
||||||
|
potionEffect.getAmplifier(),
|
||||||
|
potionEffect.getDuration(),
|
||||||
|
potionEffect.isAmbient(),
|
||||||
|
potionEffect.shouldShowParticles(),
|
||||||
|
potionEffect.shouldShowIcon()
|
||||||
|
) : null;
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class Advancements extends FabricData implements Data.Advancements {
|
||||||
|
|
||||||
|
private List<Advancement> completed;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Advancements adapt(@NotNull ServerPlayerEntity player) {
|
||||||
|
final MinecraftServer server = Objects.requireNonNull(player.getServer(), "Server is null");
|
||||||
|
final List<Advancement> advancements = Lists.newArrayList();
|
||||||
|
forEachAdvancement(server, advancement -> {
|
||||||
|
final AdvancementProgress advancementProgress = player.getAdvancementTracker().getProgress(advancement);
|
||||||
|
final Map<String, Date> awardedCriteria = Maps.newHashMap();
|
||||||
|
|
||||||
|
advancementProgress.getObtainedCriteria().forEach((criteria) -> awardedCriteria.put(criteria,
|
||||||
|
advancementProgress.getEarliestProgressObtainDate()));
|
||||||
|
|
||||||
|
// Only save the advancement if criteria has been completed
|
||||||
|
if (!awardedCriteria.isEmpty()) {
|
||||||
|
advancements.add(Advancement.adapt(advancement.getId().asString(), awardedCriteria));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return new FabricData.Advancements(advancements);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Advancements from(@NotNull List<Advancement> advancements) {
|
||||||
|
return new FabricData.Advancements(advancements);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
|
final MinecraftServer server = Objects.requireNonNull(player.getServer(), "Server is null");
|
||||||
|
plugin.runAsync(() -> forEachAdvancement(server, advancement -> {
|
||||||
|
final AdvancementProgress progress = player.getAdvancementTracker().getProgress(advancement);
|
||||||
|
final Optional<Advancement> record = completed.stream()
|
||||||
|
.filter(r -> r.getKey().equals(advancement.getId().toString()))
|
||||||
|
.findFirst();
|
||||||
|
if (record.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, Date> criteria = record.get().getCompletedCriteria();
|
||||||
|
final List<String> awarded = Lists.newArrayList(progress.getObtainedCriteria());
|
||||||
|
this.setAdvancement(
|
||||||
|
plugin, advancement, player, user,
|
||||||
|
criteria.keySet().stream().filter(key -> !awarded.contains(key)).toList(),
|
||||||
|
awarded.stream().filter(key -> !criteria.containsKey(key)).toList()
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAdvancement(@NotNull FabricHuskSync plugin,
|
||||||
|
@NotNull net.minecraft.advancement.Advancement advancement,
|
||||||
|
@NotNull ServerPlayerEntity player,
|
||||||
|
@NotNull FabricUser user,
|
||||||
|
@NotNull List<String> toAward,
|
||||||
|
@NotNull List<String> toRevoke) {
|
||||||
|
plugin.runSync(() -> {
|
||||||
|
// Track player exp level & progress
|
||||||
|
final int expLevel = player.experienceLevel;
|
||||||
|
final float expProgress = player.experienceProgress;
|
||||||
|
|
||||||
|
// Award and revoke advancement criteria
|
||||||
|
final PlayerAdvancementTracker progress = player.getAdvancementTracker();
|
||||||
|
toAward.forEach(a -> progress.grantCriterion(advancement, a));
|
||||||
|
toRevoke.forEach(r -> progress.revokeCriterion(advancement, r));
|
||||||
|
|
||||||
|
// Restore player exp level & progress
|
||||||
|
if (!toAward.isEmpty()
|
||||||
|
&& (player.experienceLevel != expLevel || player.experienceProgress != expProgress)) {
|
||||||
|
player.setExperienceLevel(expLevel);
|
||||||
|
player.setExperiencePoints((int) (player.getNextLevelExperience() * expProgress));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performs a consuming function for every advancement registered on the server
|
||||||
|
private static void forEachAdvancement(@NotNull MinecraftServer server,
|
||||||
|
@NotNull ThrowingConsumer<net.minecraft.advancement.Advancement> con) {
|
||||||
|
server.getAdvancementLoader().getAdvancements().forEach(con);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class Location extends FabricData implements Data.Location, Adaptable {
|
||||||
|
@SerializedName("x")
|
||||||
|
private double x;
|
||||||
|
@SerializedName("y")
|
||||||
|
private double y;
|
||||||
|
@SerializedName("z")
|
||||||
|
private double z;
|
||||||
|
@SerializedName("yaw")
|
||||||
|
private float yaw;
|
||||||
|
@SerializedName("pitch")
|
||||||
|
private float pitch;
|
||||||
|
@SerializedName("world")
|
||||||
|
private World world;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Location from(double x, double y, double z,
|
||||||
|
float yaw, float pitch, @NotNull World world) {
|
||||||
|
return new FabricData.Location(x, y, z, yaw, pitch, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Location adapt(@NotNull ServerPlayerEntity player) {
|
||||||
|
return from(
|
||||||
|
player.getX(),
|
||||||
|
player.getY(),
|
||||||
|
player.getZ(),
|
||||||
|
player.getYaw(),
|
||||||
|
player.getPitch(),
|
||||||
|
new World(
|
||||||
|
Objects.requireNonNull(
|
||||||
|
player.getWorld(), "World is null"
|
||||||
|
).getRegistryKey().getValue().toString(),
|
||||||
|
UUID.nameUUIDFromBytes(
|
||||||
|
player.getWorld().getDimensionKey().getValue().toString().getBytes()
|
||||||
|
),
|
||||||
|
player.getWorld().getDimensionKey().getValue().toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
|
final MinecraftServer server = plugin.getMinecraftServer();
|
||||||
|
try {
|
||||||
|
player.dismountVehicle();
|
||||||
|
FabricDimensions.teleport(
|
||||||
|
player,
|
||||||
|
server.getWorld(server.getWorldRegistryKeys().stream()
|
||||||
|
.filter(key -> key.getValue().equals(Identifier.tryParse(world.name())))
|
||||||
|
.findFirst().orElseThrow(
|
||||||
|
() -> new IllegalStateException("Invalid world")
|
||||||
|
)),
|
||||||
|
new TeleportTarget(
|
||||||
|
new Vec3d(x, y, z),
|
||||||
|
Vec3d.ZERO,
|
||||||
|
yaw,
|
||||||
|
pitch
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new IllegalStateException("Failed to apply location", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class Statistics extends FabricData implements Data.Statistics, Adaptable {
|
||||||
|
|
||||||
|
private static final String BLOCK_STAT_TYPE = "block";
|
||||||
|
private static final String ITEM_STAT_TYPE = "item";
|
||||||
|
private static final String ENTITY_STAT_TYPE = "entity_type";
|
||||||
|
|
||||||
|
@SerializedName("generic")
|
||||||
|
private Map<String, Integer> genericStatistics;
|
||||||
|
@SerializedName("blocks")
|
||||||
|
private Map<String, Map<String, Integer>> blockStatistics;
|
||||||
|
@SerializedName("items")
|
||||||
|
private Map<String, Map<String, Integer>> itemStatistics;
|
||||||
|
@SerializedName("entities")
|
||||||
|
private Map<String, Map<String, Integer>> entityStatistics;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Statistics adapt(@NotNull ServerPlayerEntity player) throws IllegalStateException {
|
||||||
|
// Adapt typed stats
|
||||||
|
final Map<String, Map<String, Integer>> blocks = Maps.newHashMap(),
|
||||||
|
items = Maps.newHashMap(), entities = Maps.newHashMap();
|
||||||
|
Registries.STAT_TYPE.getEntrySet().forEach(stat -> {
|
||||||
|
final Registry<?> registry = stat.getValue().getRegistry();
|
||||||
|
|
||||||
|
final String registryId = registry.getKey().getValue().value();
|
||||||
|
if (registryId.equals("custom_stat")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Map<String, Integer> map = (switch (registryId) {
|
||||||
|
case BLOCK_STAT_TYPE -> blocks;
|
||||||
|
case ITEM_STAT_TYPE -> items;
|
||||||
|
case ENTITY_STAT_TYPE -> entities;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: %s".formatted(registryId));
|
||||||
|
}).compute(stat.getKey().getValue().asString(), (k, v) -> v == null ? Maps.newHashMap() : v);
|
||||||
|
|
||||||
|
registry.getEntrySet().forEach(entry -> {
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"}) final int value = player.getStatHandler()
|
||||||
|
.getStat((StatType) stat.getValue(), entry.getValue());
|
||||||
|
if (value != 0) {
|
||||||
|
map.put(entry.getKey().getValue().asString(), value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add generic stats
|
||||||
|
final Map<String, Integer> generic = Maps.newHashMap();
|
||||||
|
Registries.CUSTOM_STAT.getEntrySet().forEach(stat -> {
|
||||||
|
final int value = player.getStatHandler().getStat(Stats.CUSTOM.getOrCreateStat(stat.getValue()));
|
||||||
|
if (value != 0) {
|
||||||
|
generic.put(stat.getKey().getValue().asString(), value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return new FabricData.Statistics(generic, blocks, items, entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Statistics from(@NotNull Map<String, Integer> generic,
|
||||||
|
@NotNull Map<String, Map<String, Integer>> blocks,
|
||||||
|
@NotNull Map<String, Map<String, Integer>> items,
|
||||||
|
@NotNull Map<String, Map<String, Integer>> entities) {
|
||||||
|
return new FabricData.Statistics(generic, blocks, items, entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) {
|
||||||
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
|
genericStatistics.forEach((id, v) -> applyStat(player, id, null, v));
|
||||||
|
blockStatistics.forEach((id, m) -> m.forEach((b, v) -> applyStat(player, id, BLOCK_STAT_TYPE, v, b)));
|
||||||
|
itemStatistics.forEach((id, m) -> m.forEach((i, v) -> applyStat(player, id, ITEM_STAT_TYPE, v, i)));
|
||||||
|
entityStatistics.forEach((id, m) -> m.forEach((e, v) -> applyStat(player, id, ENTITY_STAT_TYPE, v, e)));
|
||||||
|
player.getStatHandler().updateStatSet();
|
||||||
|
player.getStatHandler().sendStats(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> void applyStat(@NotNull ServerPlayerEntity player, @NotNull String id,
|
||||||
|
@Nullable String type, int value, @NotNull String... key) {
|
||||||
|
final Identifier statId = Identifier.tryParse(id);
|
||||||
|
if (statId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type == null) {
|
||||||
|
player.getStatHandler().setStat(
|
||||||
|
player,
|
||||||
|
Stats.CUSTOM.getOrCreateStat(Registries.CUSTOM_STAT.get(statId)),
|
||||||
|
value
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Identifier typeId = Identifier.tryParse(type);
|
||||||
|
final StatType<T> statType = (StatType<T>) Registries.STAT_TYPE.get(typeId);
|
||||||
|
if (statType == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Registry<T> typeReg = statType.getRegistry();
|
||||||
|
final T typeInstance = typeReg.get(Identifier.tryParse(key[0]));
|
||||||
|
if (typeInstance == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.getStatHandler().setStat(player, statType.getOrCreateStat(typeInstance), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class Attributes extends FabricData implements Data.Attributes, Adaptable {
|
||||||
|
|
||||||
|
private List<Attribute> attributes;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Attributes adapt(@NotNull ServerPlayerEntity player, @NotNull HuskSync plugin) {
|
||||||
|
final List<Attribute> attributes = Lists.newArrayList();
|
||||||
|
Registries.ATTRIBUTE.forEach(id -> {
|
||||||
|
final EntityAttributeInstance instance = player.getAttributeInstance(id);
|
||||||
|
final Identifier key = Registries.ATTRIBUTE.getId(id);
|
||||||
|
if (instance == null || key == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Set<Modifier> modifiers = Sets.newHashSet();
|
||||||
|
instance.getModifiers().forEach(modifier -> modifiers.add(new Modifier(
|
||||||
|
modifier.getId(),
|
||||||
|
modifier.getName(),
|
||||||
|
modifier.getValue(),
|
||||||
|
modifier.getOperation().getId(),
|
||||||
|
-1
|
||||||
|
)));
|
||||||
|
attributes.add(new Attribute(
|
||||||
|
key.asString(),
|
||||||
|
instance.getBaseValue(),
|
||||||
|
modifiers
|
||||||
|
));
|
||||||
|
});
|
||||||
|
return new FabricData.Attributes(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Attribute> getAttribute(@NotNull EntityAttribute id) {
|
||||||
|
return Optional.ofNullable(Registries.ATTRIBUTE.getId(id)).map(Identifier::asString)
|
||||||
|
.flatMap(key -> attributes.stream().filter(attribute -> attribute.name().equals(key)).findFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public Optional<Attribute> getAttribute(@NotNull String key) {
|
||||||
|
final EntityAttribute attribute = matchAttribute(key);
|
||||||
|
if (attribute == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return getAttribute(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) {
|
||||||
|
Registries.ATTRIBUTE.forEach(id -> applyAttribute(
|
||||||
|
user.getPlayer().getAttributeInstance(id),
|
||||||
|
getAttribute(id).orElse(null)
|
||||||
|
));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyAttribute(@Nullable EntityAttributeInstance instance,
|
||||||
|
@Nullable Attribute attribute) {
|
||||||
|
if (instance == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
instance.setBaseValue(attribute == null ? instance.getAttribute().getDefaultValue() : attribute.baseValue());
|
||||||
|
instance.getModifiers().forEach(instance::removeModifier);
|
||||||
|
if (attribute != null) {
|
||||||
|
attribute.modifiers().forEach(modifier -> instance.addPersistentModifier(new EntityAttributeModifier(
|
||||||
|
modifier.uuid(),
|
||||||
|
modifier.name(),
|
||||||
|
modifier.amount(),
|
||||||
|
EntityAttributeModifier.Operation.fromId(modifier.operationType())
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class Health extends FabricData implements Data.Health, Adaptable {
|
||||||
|
@SerializedName("health")
|
||||||
|
private double health;
|
||||||
|
@SerializedName("health_scale")
|
||||||
|
private double healthScale;
|
||||||
|
@SerializedName("is_health_scaled")
|
||||||
|
private boolean isHealthScaled;
|
||||||
|
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Health from(double health, double scale, boolean isScaled) {
|
||||||
|
return new FabricData.Health(health, scale, isScaled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Health adapt(@NotNull ServerPlayerEntity player) {
|
||||||
|
return from(
|
||||||
|
player.getHealth(),
|
||||||
|
20.0f, false // Health scale is a Bukkit API feature, not used in Fabric
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
|
player.setHealth((float) health);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class Hunger extends FabricData implements Data.Hunger, Adaptable {
|
||||||
|
|
||||||
|
@SerializedName("food_level")
|
||||||
|
private int foodLevel;
|
||||||
|
@SerializedName("saturation")
|
||||||
|
private float saturation;
|
||||||
|
@SerializedName("exhaustion")
|
||||||
|
private float exhaustion;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Hunger adapt(@NotNull ServerPlayerEntity player) {
|
||||||
|
final HungerManager hunger = player.getHungerManager();
|
||||||
|
return from(hunger.getFoodLevel(), hunger.getSaturationLevel(), hunger.getExhaustion());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Hunger from(int foodLevel, float saturation, float exhaustion) {
|
||||||
|
return new FabricData.Hunger(foodLevel, saturation, exhaustion);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
|
final HungerManager hunger = player.getHungerManager();
|
||||||
|
hunger.setFoodLevel(foodLevel);
|
||||||
|
hunger.setSaturationLevel(saturation);
|
||||||
|
hunger.setExhaustion(exhaustion);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class Experience extends FabricData implements Data.Experience, Adaptable {
|
||||||
|
|
||||||
|
@SerializedName("total_experience")
|
||||||
|
private int totalExperience;
|
||||||
|
|
||||||
|
@SerializedName("exp_level")
|
||||||
|
private int expLevel;
|
||||||
|
|
||||||
|
@SerializedName("exp_progress")
|
||||||
|
private float expProgress;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Experience from(int totalExperience, int expLevel, float expProgress) {
|
||||||
|
return new FabricData.Experience(totalExperience, expLevel, expProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.Experience adapt(@NotNull ServerPlayerEntity player) {
|
||||||
|
return from(player.totalExperience, player.experienceLevel, player.experienceProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
|
player.totalExperience = totalExperience;
|
||||||
|
player.setExperienceLevel(expLevel);
|
||||||
|
player.setExperiencePoints((int) (player.getNextLevelExperience() * expProgress));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class GameMode extends FabricData implements Data.GameMode, Adaptable {
|
||||||
|
|
||||||
|
@SerializedName("game_mode")
|
||||||
|
private String gameMode;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.GameMode from(@NotNull String gameMode) {
|
||||||
|
return new FabricData.GameMode(gameMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.GameMode adapt(@NotNull ServerPlayerEntity player) {
|
||||||
|
return from(player.interactionManager.getGameMode().asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
|
user.getPlayer().interactionManager.changeGameMode(net.minecraft.world.GameMode.byName(gameMode));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class FlightStatus extends FabricData implements Data.FlightStatus, Adaptable {
|
||||||
|
|
||||||
|
@SerializedName("allow_flight")
|
||||||
|
private boolean allowFlight;
|
||||||
|
@SerializedName("is_flying")
|
||||||
|
private boolean flying;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.FlightStatus from(boolean allowFlight, boolean flying) {
|
||||||
|
return new FabricData.FlightStatus(allowFlight, allowFlight && flying);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static FabricData.FlightStatus adapt(@NotNull ServerPlayerEntity player) {
|
||||||
|
return from(player.getAbilities().allowFlying, player.getAbilities().flying);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
|
player.getAbilities().allowFlying = allowFlight;
|
||||||
|
player.getAbilities().flying = allowFlight && flying;
|
||||||
|
player.sendAbilitiesUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,288 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.mojang.serialization.Dynamic;
|
||||||
|
import com.mojang.serialization.DynamicOps;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import net.minecraft.datafixer.TypeReferences;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.nbt.*;
|
||||||
|
import net.william278.desertwell.util.Version;
|
||||||
|
import net.william278.husksync.FabricHuskSync;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.api.HuskSyncAPI;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static net.william278.husksync.data.Data.Items.Inventory.*;
|
||||||
|
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public abstract class FabricSerializer {
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
protected final HuskSync plugin;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public FabricSerializer(@NotNull HuskSyncAPI api) {
|
||||||
|
this.plugin = api.getPlugin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
@NotNull
|
||||||
|
public HuskSync getPlugin() {
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Inventory extends FabricSerializer implements Serializer<FabricData.Items.Inventory>,
|
||||||
|
ItemDeserializer {
|
||||||
|
|
||||||
|
public Inventory(@NotNull HuskSync plugin) {
|
||||||
|
super(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FabricData.Items.Inventory deserialize(@NotNull String serialized, @NotNull Version dataMcVersion)
|
||||||
|
throws DeserializationException {
|
||||||
|
// Read item NBT from string
|
||||||
|
final FabricHuskSync plugin = (FabricHuskSync) getPlugin();
|
||||||
|
final NbtCompound root;
|
||||||
|
try {
|
||||||
|
root = StringNbtReader.parse(serialized);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new DeserializationException("Failed to read item NBT from string (%s)".formatted(serialized), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize the inventory data
|
||||||
|
final NbtCompound items = root.contains(ITEMS_TAG) ? root.getCompound(ITEMS_TAG) : null;
|
||||||
|
return FabricData.Items.Inventory.from(
|
||||||
|
items != null ? getItems(items, dataMcVersion, plugin) : new ItemStack[INVENTORY_SLOT_COUNT],
|
||||||
|
root.contains(HELD_ITEM_SLOT_TAG) ? root.getInt(HELD_ITEM_SLOT_TAG) : 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FabricData.Items.Inventory deserialize(@NotNull String serialized) {
|
||||||
|
return deserialize(serialized, plugin.getMinecraftVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String serialize(@NotNull FabricData.Items.Inventory data) throws SerializationException {
|
||||||
|
try {
|
||||||
|
final NbtCompound root = new NbtCompound();
|
||||||
|
root.put(ITEMS_TAG, serializeItemArray(data.getContents()));
|
||||||
|
root.putInt(HELD_ITEM_SLOT_TAG, data.getHeldItemSlot());
|
||||||
|
return root.toString();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new SerializationException("Failed to serialize inventory item NBT to string", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EnderChest extends FabricSerializer implements Serializer<FabricData.Items.EnderChest>,
|
||||||
|
ItemDeserializer {
|
||||||
|
|
||||||
|
public EnderChest(@NotNull HuskSync plugin) {
|
||||||
|
super(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FabricData.Items.EnderChest deserialize(@NotNull String serialized, @NotNull Version dataMcVersion)
|
||||||
|
throws DeserializationException {
|
||||||
|
final FabricHuskSync plugin = (FabricHuskSync) getPlugin();
|
||||||
|
try {
|
||||||
|
final NbtCompound items = StringNbtReader.parse(serialized);
|
||||||
|
return FabricData.Items.EnderChest.adapt(getItems(items, dataMcVersion, plugin));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new DeserializationException("Failed to read item NBT from string (%s)".formatted(serialized), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FabricData.Items.EnderChest deserialize(@NotNull String serialized) {
|
||||||
|
return deserialize(serialized, plugin.getMinecraftVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String serialize(@NotNull FabricData.Items.EnderChest data) throws SerializationException {
|
||||||
|
try {
|
||||||
|
return serializeItemArray(data.getContents()).toString();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new SerializationException("Failed to serialize ender chest item NBT to string", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface ItemDeserializer {
|
||||||
|
|
||||||
|
int VERSION1_16_5 = 2586;
|
||||||
|
int VERSION1_17_1 = 2730;
|
||||||
|
int VERSION1_18_2 = 2975;
|
||||||
|
int VERSION1_19_2 = 3120;
|
||||||
|
int VERSION1_19_4 = 3337;
|
||||||
|
int VERSION1_20_1 = 3465;
|
||||||
|
int VERSION1_20_2 = 3578; // Future
|
||||||
|
int VERSION1_20_4 = 3700; // Future
|
||||||
|
int VERSION1_20_5 = 3837; // Future
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
default ItemStack[] getItems(@NotNull NbtCompound tag, @NotNull Version mcVersion, @NotNull FabricHuskSync plugin) {
|
||||||
|
try {
|
||||||
|
if (mcVersion.compareTo(plugin.getMinecraftVersion()) < 0) {
|
||||||
|
return upgradeItemStacks(tag, mcVersion, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int size = tag.getInt("size");
|
||||||
|
final NbtList items = tag.getList("items", NbtElement.COMPOUND_TYPE);
|
||||||
|
final ItemStack[] itemStacks = new ItemStack[size];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
final NbtCompound compound = items.getCompound(i);
|
||||||
|
final int slot = compound.getInt("Slot");
|
||||||
|
itemStacks[slot] = ItemStack.fromNbt(compound);
|
||||||
|
}
|
||||||
|
return itemStacks;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new Serializer.DeserializationException("Failed to read item NBT string (%s)".formatted(tag), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize items slot-by-slot
|
||||||
|
@NotNull
|
||||||
|
default NbtCompound serializeItemArray(@Nullable ItemStack @NotNull [] items) {
|
||||||
|
final NbtCompound container = new NbtCompound();
|
||||||
|
container.putInt("size", items.length);
|
||||||
|
final NbtList itemList = new NbtList();
|
||||||
|
for (int i = 0; i < items.length; i++) {
|
||||||
|
final ItemStack item = items[i];
|
||||||
|
if (item == null || item.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
NbtCompound entry = new NbtCompound();
|
||||||
|
entry.putInt("Slot", i);
|
||||||
|
item.writeNbt(entry);
|
||||||
|
itemList.add(entry);
|
||||||
|
}
|
||||||
|
container.put(ITEMS_TAG, itemList);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private ItemStack @NotNull [] upgradeItemStacks(@NotNull NbtCompound items, @NotNull Version mcVersion,
|
||||||
|
@NotNull FabricHuskSync plugin) {
|
||||||
|
final int size = items.getInt("size");
|
||||||
|
final NbtList list = items.getList("items", NbtElement.COMPOUND_TYPE);
|
||||||
|
final ItemStack[] itemStacks = new ItemStack[size];
|
||||||
|
Arrays.fill(itemStacks, ItemStack.EMPTY);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
if (list.getCompound(i) == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final NbtCompound compound = list.getCompound(i);
|
||||||
|
final int slot = compound.getInt("Slot");
|
||||||
|
itemStacks[slot] = ItemStack.fromNbt(upgradeItemData(list.getCompound(i), mcVersion, plugin));
|
||||||
|
}
|
||||||
|
return itemStacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"}) // For NBTOps lookup
|
||||||
|
private NbtCompound upgradeItemData(@NotNull NbtCompound tag, @NotNull Version mcVersion,
|
||||||
|
@NotNull FabricHuskSync plugin) {
|
||||||
|
return (NbtCompound) plugin.getMinecraftServer().getDataFixer().update(
|
||||||
|
TypeReferences.ITEM_STACK, new Dynamic<Object>((DynamicOps) NbtOps.INSTANCE, tag),
|
||||||
|
getDataVersion(mcVersion), getDataVersion(plugin.getMinecraftVersion())
|
||||||
|
).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getDataVersion(@NotNull Version mcVersion) {
|
||||||
|
return switch (mcVersion.toStringWithoutMetadata()) {
|
||||||
|
case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> VERSION1_16_5;
|
||||||
|
case "1.17", "1.17.1" -> VERSION1_17_1;
|
||||||
|
case "1.18", "1.18.1", "1.18.2" -> VERSION1_18_2;
|
||||||
|
case "1.19", "1.19.1", "1.19.2" -> VERSION1_19_2;
|
||||||
|
case "1.19.4" -> VERSION1_19_4;
|
||||||
|
case "1.20", "1.20.1" -> VERSION1_20_1;
|
||||||
|
case "1.20.2" -> VERSION1_20_2; // Future
|
||||||
|
case "1.20.4" -> VERSION1_20_4; // Future
|
||||||
|
case "1.20.5", "1.20.6" -> VERSION1_20_5; // Future
|
||||||
|
default -> VERSION1_20_1; // Current supported ver
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PotionEffects extends FabricSerializer implements Serializer<FabricData.PotionEffects> {
|
||||||
|
|
||||||
|
private static final TypeToken<List<Data.PotionEffects.Effect>> TYPE = new TypeToken<>() {
|
||||||
|
};
|
||||||
|
|
||||||
|
public PotionEffects(@NotNull HuskSync plugin) {
|
||||||
|
super(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FabricData.PotionEffects deserialize(@NotNull String serialized) throws DeserializationException {
|
||||||
|
return FabricData.PotionEffects.adapt(
|
||||||
|
plugin.getGson().fromJson(serialized, TYPE.getType())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String serialize(@NotNull FabricData.PotionEffects element) throws SerializationException {
|
||||||
|
return plugin.getGson().toJson(element.getActiveEffects());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Advancements extends FabricSerializer implements Serializer<FabricData.Advancements> {
|
||||||
|
|
||||||
|
private static final TypeToken<List<Data.Advancements.Advancement>> TYPE = new TypeToken<>() {
|
||||||
|
};
|
||||||
|
|
||||||
|
public Advancements(@NotNull HuskSync plugin) {
|
||||||
|
super(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FabricData.Advancements deserialize(@NotNull String serialized) throws DeserializationException {
|
||||||
|
return FabricData.Advancements.from(
|
||||||
|
plugin.getGson().fromJson(serialized, TYPE.getType())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String serialize(@NotNull FabricData.Advancements element) throws SerializationException {
|
||||||
|
return plugin.getGson().toJson(element.getCompleted());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
|
import net.minecraft.entity.player.PlayerInventory;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static net.william278.husksync.config.Settings.SynchronizationSettings.SaveOnDeathSettings;
|
||||||
|
|
||||||
|
public interface FabricUserDataHolder extends UserDataHolder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Optional<? extends Data> getData(@NotNull Identifier id) {
|
||||||
|
if (!id.isCustom()) {
|
||||||
|
try {
|
||||||
|
return switch (id.getKeyValue()) {
|
||||||
|
case "inventory" -> getInventory();
|
||||||
|
case "ender_chest" -> getEnderChest();
|
||||||
|
case "potion_effects" -> getPotionEffects();
|
||||||
|
case "advancements" -> getAdvancements();
|
||||||
|
case "location" -> getLocation();
|
||||||
|
case "statistics" -> getStatistics();
|
||||||
|
case "health" -> getHealth();
|
||||||
|
case "hunger" -> getHunger();
|
||||||
|
case "attributes" -> getAttributes();
|
||||||
|
case "experience" -> getExperience();
|
||||||
|
case "game_mode" -> getGameMode();
|
||||||
|
case "flight_status" -> getFlightStatus();
|
||||||
|
case "persistent_data" -> getPersistentData();
|
||||||
|
default -> throw new IllegalStateException(String.format("Unexpected data type: %s", id));
|
||||||
|
};
|
||||||
|
} catch (Throwable e) {
|
||||||
|
getPlugin().debug("Failed to get data for key: " + id.getKeyValue(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(getCustomDataStore().get(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setData(@NotNull Identifier id, @NotNull Data data) {
|
||||||
|
if (id.isCustom()) {
|
||||||
|
getCustomDataStore().put(id, data);
|
||||||
|
}
|
||||||
|
UserDataHolder.super.setData(id, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Optional<Data.Items.Inventory> getInventory() {
|
||||||
|
final SaveOnDeathSettings death = getPlugin().getSettings().getSynchronization().getSaveOnDeath();
|
||||||
|
if ((isDead() && !death.isSyncDeadPlayersChangingServer())) {
|
||||||
|
return Optional.of(FabricData.Items.Inventory.empty());
|
||||||
|
}
|
||||||
|
final PlayerInventory inventory = getPlayer().getInventory();
|
||||||
|
return Optional.of(FabricData.Items.Inventory.from(
|
||||||
|
getCombinedInventory(inventory),
|
||||||
|
inventory.selectedSlot
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the player's combined inventory; their inventory, plus offhand and armor.
|
||||||
|
@Nullable
|
||||||
|
private ItemStack @NotNull [] getCombinedInventory(@NotNull PlayerInventory inv) {
|
||||||
|
final ItemStack[] combined = new ItemStack[inv.main.size() + inv.armor.size() + inv.offHand.size()];
|
||||||
|
System.arraycopy(
|
||||||
|
inv.main.toArray(new ItemStack[0]), 0, combined,
|
||||||
|
0, inv.main.size()
|
||||||
|
);
|
||||||
|
System.arraycopy(
|
||||||
|
inv.armor.toArray(new ItemStack[0]), 0, combined,
|
||||||
|
inv.main.size(), inv.armor.size()
|
||||||
|
);
|
||||||
|
System.arraycopy(
|
||||||
|
inv.offHand.toArray(new ItemStack[0]), 0, combined,
|
||||||
|
inv.main.size() + inv.armor.size(), inv.offHand.size()
|
||||||
|
);
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Optional<Data.Items.EnderChest> getEnderChest() {
|
||||||
|
return Optional.of(FabricData.Items.EnderChest.adapt(
|
||||||
|
getPlayer().getEnderChestInventory().stacks
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Optional<Data.PotionEffects> getPotionEffects() {
|
||||||
|
return Optional.of(FabricData.PotionEffects.from(getPlayer().getActiveStatusEffects().values()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Optional<Data.Advancements> getAdvancements() {
|
||||||
|
return Optional.of(FabricData.Advancements.adapt(getPlayer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Optional<Data.Location> getLocation() {
|
||||||
|
return Optional.of(FabricData.Location.adapt(getPlayer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Optional<Data.Statistics> getStatistics() {
|
||||||
|
return Optional.of(FabricData.Statistics.adapt(getPlayer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
default Optional<Data.Attributes> getAttributes() {
|
||||||
|
return Optional.of(FabricData.Attributes.adapt(getPlayer(), getPlugin()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Optional<Data.Health> getHealth() {
|
||||||
|
return Optional.of(FabricData.Health.adapt(getPlayer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Optional<Data.Hunger> getHunger() {
|
||||||
|
return Optional.of(FabricData.Hunger.adapt(getPlayer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Optional<Data.Experience> getExperience() {
|
||||||
|
return Optional.of(FabricData.Experience.adapt(getPlayer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Optional<Data.GameMode> getGameMode() {
|
||||||
|
return Optional.of(FabricData.GameMode.adapt(getPlayer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Optional<Data.FlightStatus> getFlightStatus() {
|
||||||
|
return Optional.of(FabricData.FlightStatus.adapt(getPlayer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Optional<Data.PersistentData> getPersistentData() {
|
||||||
|
return Optional.empty(); // Not implemented on Fabric, but maybe we'll do data keys or something
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isDead();
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
ServerPlayerEntity getPlayer();
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
Map<Identifier, Data> getCustomDataStore();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.data.DataSnapshot;
|
||||||
|
import net.william278.husksync.user.User;
|
||||||
|
import org.apache.commons.lang3.function.TriFunction;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface FabricDataSaveCallback extends FabricEventCallback<DataSaveEvent> {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Event<FabricDataSaveCallback> EVENT = EventFactory.createArrayBacked(FabricDataSaveCallback.class,
|
||||||
|
(listeners) -> (event) -> {
|
||||||
|
for (FabricDataSaveCallback listener : listeners) {
|
||||||
|
final ActionResult result = listener.invoke(event);
|
||||||
|
if (event.isCancelled()) {
|
||||||
|
return ActionResult.CONSUME;
|
||||||
|
} else if (result != ActionResult.PASS) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionResult.PASS;
|
||||||
|
});
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
TriFunction<User, DataSnapshot.Packed, HuskSync, DataSaveEvent> SUPPLIER = (user, data, plugin) ->
|
||||||
|
|
||||||
|
new DataSaveEvent() {
|
||||||
|
private boolean cancelled = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCancelled(boolean cancelled) {
|
||||||
|
this.cancelled = cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public DataSnapshot.Packed getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public HuskSync getPlugin() {
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public User getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public Event<FabricDataSaveCallback> getEvent() {
|
||||||
|
return EVENT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface FabricEventCallback<E extends Event> {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
ActionResult invoke(@NotNull E event);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.william278.husksync.data.DataSnapshot;
|
||||||
|
import net.william278.husksync.user.OnlineUser;
|
||||||
|
import net.william278.husksync.user.User;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public interface FabricEventDispatcher extends EventDispatcher {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
default <T extends Event> boolean fireIsCancelled(@NotNull T event) {
|
||||||
|
try {
|
||||||
|
final Method field = event.getClass().getDeclaredMethod("getEvent");
|
||||||
|
field.setAccessible(true);
|
||||||
|
|
||||||
|
net.fabricmc.fabric.api.event.Event<?> fabricEvent =
|
||||||
|
(net.fabricmc.fabric.api.event.Event<?>) field.invoke(event);
|
||||||
|
|
||||||
|
final FabricEventCallback<T> invoker = (FabricEventCallback<T>) fabricEvent.invoker();
|
||||||
|
return invoker.invoke(event) == ActionResult.FAIL;
|
||||||
|
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
|
||||||
|
getPlugin().log(Level.WARNING, "Failed to fire event (" + event.getClass().getName() + ")", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default PreSyncEvent getPreSyncEvent(@NotNull OnlineUser user, @NotNull DataSnapshot.Packed userData) {
|
||||||
|
return FabricPreSyncCallback.SUPPLIER.apply(user, userData, getPlugin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default DataSaveEvent getDataSaveEvent(@NotNull User user, @NotNull DataSnapshot.Packed saveCause) {
|
||||||
|
return FabricDataSaveCallback.SUPPLIER.apply(user, saveCause, getPlugin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default SyncCompleteEvent getSyncCompleteEvent(@NotNull OnlineUser user) {
|
||||||
|
return FabricSyncCompleteCallback.SUPPLIER.apply(user, getPlugin());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.data.DataSnapshot;
|
||||||
|
import net.william278.husksync.user.OnlineUser;
|
||||||
|
import org.apache.commons.lang3.function.TriFunction;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface FabricPreSyncCallback extends FabricEventCallback<PreSyncEvent> {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Event<FabricPreSyncCallback> EVENT = EventFactory.createArrayBacked(FabricPreSyncCallback.class,
|
||||||
|
(listeners) -> (event) -> {
|
||||||
|
for (FabricPreSyncCallback listener : listeners) {
|
||||||
|
final ActionResult result = listener.invoke(event);
|
||||||
|
if (event.isCancelled()) {
|
||||||
|
return ActionResult.CONSUME;
|
||||||
|
} else if (result != ActionResult.PASS) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionResult.PASS;
|
||||||
|
});
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
TriFunction<OnlineUser, DataSnapshot.Packed, HuskSync, PreSyncEvent> SUPPLIER = (user, data, plugin) ->
|
||||||
|
|
||||||
|
new PreSyncEvent() {
|
||||||
|
private boolean cancelled = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCancelled(boolean cancelled) {
|
||||||
|
this.cancelled = cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public DataSnapshot.Packed getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public HuskSync getPlugin() {
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public OnlineUser getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public Event<FabricPreSyncCallback> getEvent() {
|
||||||
|
return EVENT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.user.OnlineUser;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
public interface FabricSyncCompleteCallback extends FabricEventCallback<SyncCompleteEvent> {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Event<FabricSyncCompleteCallback> EVENT = EventFactory.createArrayBacked(FabricSyncCompleteCallback.class,
|
||||||
|
(listeners) -> (event) -> {
|
||||||
|
for (FabricSyncCompleteCallback listener : listeners) {
|
||||||
|
listener.invoke(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionResult.PASS;
|
||||||
|
});
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
BiFunction<OnlineUser, HuskSync, SyncCompleteEvent> SUPPLIER = (user, plugin) ->
|
||||||
|
|
||||||
|
new SyncCompleteEvent() {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public OnlineUser getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public Event<FabricSyncCompleteCallback> getEvent() {
|
||||||
|
return EVENT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface InventoryClickCallback {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Event<InventoryClickCallback> EVENT = EventFactory.createArrayBacked(InventoryClickCallback.class,
|
||||||
|
(listeners) -> (player, itemStack) -> {
|
||||||
|
for (InventoryClickCallback listener : listeners) {
|
||||||
|
ActionResult result = listener.interact(player, itemStack);
|
||||||
|
|
||||||
|
if (result != ActionResult.PASS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionResult.PASS;
|
||||||
|
});
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
ActionResult interact(PlayerEntity player, ItemStack sheep);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface ItemDropCallback {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Event<ItemDropCallback> EVENT = EventFactory.createArrayBacked(ItemDropCallback.class,
|
||||||
|
(listeners) -> (player, itemStack) -> {
|
||||||
|
for (ItemDropCallback listener : listeners) {
|
||||||
|
ActionResult result = listener.interact(player, itemStack);
|
||||||
|
|
||||||
|
if (result != ActionResult.PASS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionResult.PASS;
|
||||||
|
});
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
ActionResult interact(PlayerEntity player, ItemStack sheep);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface ItemPickupCallback {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Event<ItemPickupCallback> EVENT = EventFactory.createArrayBacked(ItemPickupCallback.class,
|
||||||
|
(listeners) -> (player, itemStack) -> {
|
||||||
|
for (ItemPickupCallback listener : listeners) {
|
||||||
|
ActionResult result = listener.interact(player, itemStack);
|
||||||
|
|
||||||
|
if (result != ActionResult.PASS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionResult.PASS;
|
||||||
|
});
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
ActionResult interact(PlayerEntity player, ItemStack sheep);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public interface PlayerCommandCallback {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Event<PlayerCommandCallback> EVENT = EventFactory.createArrayBacked(PlayerCommandCallback.class,
|
||||||
|
(listeners) -> (player, command) -> {
|
||||||
|
for (PlayerCommandCallback listener : listeners) {
|
||||||
|
ActionResult result = listener.interact(player, command);
|
||||||
|
|
||||||
|
if (result != ActionResult.PASS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionResult.PASS;
|
||||||
|
});
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
ActionResult interact(PlayerEntity player, String command);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public interface PlayerDeathDropsCallback {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Event<PlayerDeathDropsCallback> EVENT = EventFactory.createArrayBacked(
|
||||||
|
PlayerDeathDropsCallback.class,
|
||||||
|
(listeners) -> (player, itemsToKeep, itemsToDrop) -> Arrays.stream(listeners)
|
||||||
|
.forEach(listener -> listener.drops(player, itemsToKeep, itemsToDrop))
|
||||||
|
);
|
||||||
|
|
||||||
|
void drops(@NotNull ServerPlayerEntity player,
|
||||||
|
@Nullable ItemStack @NotNull [] itemsToKeep,
|
||||||
|
@Nullable ItemStack @NotNull [] itemsToDrop);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
|
import net.minecraft.server.world.ServerWorld;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public interface WorldSaveCallback {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Event<WorldSaveCallback> EVENT = EventFactory.createArrayBacked(
|
||||||
|
WorldSaveCallback.class,
|
||||||
|
(listeners) -> (world) -> Arrays.stream(listeners).forEach(listener -> listener.save(world))
|
||||||
|
);
|
||||||
|
|
||||||
|
void save(@NotNull ServerWorld world);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.listener;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
|
||||||
|
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
|
||||||
|
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
|
||||||
|
import net.fabricmc.fabric.api.event.player.UseEntityCallback;
|
||||||
|
import net.fabricmc.fabric.api.event.player.UseItemCallback;
|
||||||
|
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||||
|
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.entity.Entity;
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import net.minecraft.entity.damage.DamageSource;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.minecraft.server.world.ServerWorld;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.minecraft.util.Hand;
|
||||||
|
import net.minecraft.util.TypedActionResult;
|
||||||
|
import net.minecraft.util.hit.BlockHitResult;
|
||||||
|
import net.minecraft.util.hit.EntityHitResult;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.config.Settings.SynchronizationSettings.SaveOnDeathSettings;
|
||||||
|
import net.william278.husksync.data.FabricData;
|
||||||
|
import net.william278.husksync.event.*;
|
||||||
|
import net.william278.husksync.user.FabricUser;
|
||||||
|
import net.william278.husksync.user.OnlineUser;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class FabricEventListener extends EventListener implements LockedHandler {
|
||||||
|
|
||||||
|
public FabricEventListener(@NotNull HuskSync plugin) {
|
||||||
|
super(plugin);
|
||||||
|
this.registerEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerEvents() {
|
||||||
|
ServerPlayConnectionEvents.JOIN.register(this::handlePlayerJoin);
|
||||||
|
ServerPlayConnectionEvents.DISCONNECT.register(this::handlePlayerQuit);
|
||||||
|
WorldSaveCallback.EVENT.register(this::handleWorldSave);
|
||||||
|
PlayerDeathDropsCallback.EVENT.register(this::handlePlayerDeathDrops);
|
||||||
|
|
||||||
|
// TODO: Events of extra things to cancel if the player has not been set yet
|
||||||
|
ItemPickupCallback.EVENT.register(this::handleItemPickup);
|
||||||
|
ItemDropCallback.EVENT.register(this::handleItemDrop);
|
||||||
|
UseBlockCallback.EVENT.register(this::handleBlockInteract);
|
||||||
|
UseEntityCallback.EVENT.register(this::handleEntityInteract);
|
||||||
|
UseItemCallback.EVENT.register(this::handleItemInteract);
|
||||||
|
PlayerBlockBreakEvents.BEFORE.register(this::handleBlockBreak);
|
||||||
|
ServerLivingEntityEvents.ALLOW_DAMAGE.register(this::handleEntityDamage);
|
||||||
|
InventoryClickCallback.EVENT.register(this::handleInventoryClick);
|
||||||
|
PlayerCommandCallback.EVENT.register(this::handlePlayerCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePlayerJoin(@NotNull ServerPlayNetworkHandler handler, @NotNull PacketSender sender,
|
||||||
|
@NotNull MinecraftServer server) {
|
||||||
|
handlePlayerJoin(FabricUser.adapt(handler.player, plugin));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePlayerQuit(@NotNull ServerPlayNetworkHandler handler, @NotNull MinecraftServer server) {
|
||||||
|
handlePlayerQuit(FabricUser.adapt(handler.player, plugin));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleWorldSave(@NotNull ServerWorld world) {
|
||||||
|
saveOnWorldSave(world.getPlayers().stream()
|
||||||
|
.map(player -> (OnlineUser) FabricUser.adapt(player, plugin)).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePlayerDeathDrops(@NotNull ServerPlayerEntity player, @Nullable ItemStack @NotNull [] toKeep,
|
||||||
|
@Nullable ItemStack @NotNull [] toDrop) {
|
||||||
|
final SaveOnDeathSettings settings = plugin.getSettings().getSynchronization().getSaveOnDeath();
|
||||||
|
saveOnPlayerDeath(
|
||||||
|
FabricUser.adapt(player, plugin),
|
||||||
|
FabricData.Items.ItemArray.adapt(
|
||||||
|
settings.getItemsToSave() == SaveOnDeathSettings.DeathItemsMode.DROPS ? toDrop : toKeep
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActionResult handleItemPickup(PlayerEntity player, ItemStack itemStack) {
|
||||||
|
return (cancelPlayerEvent(player.getUuid())) ? ActionResult.FAIL : ActionResult.PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActionResult handleItemDrop(PlayerEntity player, ItemStack itemStack) {
|
||||||
|
return (cancelPlayerEvent(player.getUuid())) ? ActionResult.FAIL : ActionResult.PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActionResult handleBlockInteract(PlayerEntity player, World world, Hand hand, BlockHitResult blockHitResult) {
|
||||||
|
return (cancelPlayerEvent(player.getUuid())) ? ActionResult.FAIL : ActionResult.PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActionResult handleEntityInteract(PlayerEntity player, World world, Hand hand, Entity entity, EntityHitResult entityHitResult) {
|
||||||
|
return (cancelPlayerEvent(player.getUuid())) ? ActionResult.FAIL : ActionResult.PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypedActionResult<ItemStack> handleItemInteract(PlayerEntity player, World world, Hand hand) {
|
||||||
|
ItemStack stackInHand = player.getStackInHand(hand);
|
||||||
|
return (cancelPlayerEvent(player.getUuid())) ? TypedActionResult.fail(stackInHand) : TypedActionResult.pass(stackInHand);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleBlockBreak(World world, PlayerEntity player, BlockPos blockPos, BlockState blockState, BlockEntity blockEntity) {
|
||||||
|
return !cancelPlayerEvent(player.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleEntityDamage(LivingEntity livingEntity, DamageSource damageSource, float v) {
|
||||||
|
if (livingEntity instanceof ServerPlayerEntity player) {
|
||||||
|
return !cancelPlayerEvent(player.getUuid());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActionResult handleInventoryClick(PlayerEntity player, ItemStack itemStack) {
|
||||||
|
return (cancelPlayerEvent(player.getUuid())) ? ActionResult.FAIL : ActionResult.PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActionResult handlePlayerCommand(PlayerEntity player, String s) {
|
||||||
|
return (cancelPlayerEvent(player.getUuid())) ? ActionResult.FAIL : ActionResult.PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public HuskSync getPlugin() {
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.mixins;
|
||||||
|
|
||||||
|
import net.minecraft.entity.ItemEntity;
|
||||||
|
import net.minecraft.entity.player.PlayerInventory;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.william278.husksync.event.ItemPickupCallback;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
@Mixin(ItemEntity.class)
|
||||||
|
public class ItemEntityMixin {
|
||||||
|
|
||||||
|
@Redirect(at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerInventory;insertStack(Lnet/minecraft/item/ItemStack;)Z"),
|
||||||
|
method = "onPlayerCollision")
|
||||||
|
public boolean onPlayerCollision(PlayerInventory inventory, ItemStack stack) {
|
||||||
|
ActionResult result = ItemPickupCallback.EVENT.invoker().interact(inventory.player, stack);
|
||||||
|
return (result != ActionResult.FAIL && inventory.insertStack(stack));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.mixins;
|
||||||
|
|
||||||
|
import net.minecraft.enchantment.EnchantmentHelper;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.entity.player.PlayerInventory;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.william278.husksync.event.PlayerDeathDropsCallback;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
@Final
|
||||||
|
@Shadow
|
||||||
|
private PlayerInventory inventory;
|
||||||
|
|
||||||
|
@Inject(method = "dropInventory", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;vanishCursedItems()V"))
|
||||||
|
protected void dropInventory(@NotNull CallbackInfo ci) {
|
||||||
|
final PlayerEntity player = (PlayerEntity) (Object) this;
|
||||||
|
PlayerDeathDropsCallback.EVENT.invoker().drops((ServerPlayerEntity) player, getItemsToKeep(), getItemsToDrop());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
@Nullable
|
||||||
|
private ItemStack @NotNull [] getItemsToKeep() {
|
||||||
|
final @Nullable ItemStack @NotNull [] toKeep = new ItemStack[inventory.size()];
|
||||||
|
for (int i = 0; i < inventory.size(); ++i) {
|
||||||
|
ItemStack itemStack = inventory.getStack(i);
|
||||||
|
if (!itemStack.isEmpty() && EnchantmentHelper.hasVanishingCurse(itemStack)) {
|
||||||
|
toKeep[i] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
toKeep[i] = itemStack;
|
||||||
|
}
|
||||||
|
return toKeep;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
@Nullable
|
||||||
|
private ItemStack @NotNull [] getItemsToDrop() {
|
||||||
|
final @Nullable ItemStack @NotNull [] toDrop = new ItemStack[inventory.size()];
|
||||||
|
for (int i = 0; i < inventory.size(); ++i) {
|
||||||
|
ItemStack itemStack = inventory.getStack(i);
|
||||||
|
if (!itemStack.isEmpty() && EnchantmentHelper.hasVanishingCurse(itemStack)) {
|
||||||
|
toDrop[i] = itemStack;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
toDrop[i] = null;
|
||||||
|
}
|
||||||
|
return toDrop;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.mixins;
|
||||||
|
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.network.packet.Packet;
|
||||||
|
import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket;
|
||||||
|
import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket;
|
||||||
|
import net.minecraft.network.packet.c2s.play.CreativeInventoryActionC2SPacket;
|
||||||
|
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
|
||||||
|
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
|
||||||
|
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.minecraft.util.Hand;
|
||||||
|
import net.william278.husksync.event.ItemDropCallback;
|
||||||
|
import net.william278.husksync.event.PlayerCommandCallback;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
// Adapted from simplerauth (https://github.com/lolicode-org/simplerauth), which is licensed under the MIT License
|
||||||
|
@Mixin(ServerPlayNetworkHandler.class)
|
||||||
|
public abstract class ServerPlayNetworkHandlerMixin {
|
||||||
|
@Shadow
|
||||||
|
public ServerPlayerEntity player;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract void sendPacket(Packet<?> packet);
|
||||||
|
|
||||||
|
@Inject(method = "onPlayerAction", at = @At("HEAD"), cancellable = true)
|
||||||
|
public void onPlayerAction(PlayerActionC2SPacket packet, CallbackInfo ci) {
|
||||||
|
if (packet.getAction() == PlayerActionC2SPacket.Action.DROP_ITEM
|
||||||
|
|| packet.getAction() == PlayerActionC2SPacket.Action.DROP_ALL_ITEMS) {
|
||||||
|
ItemStack stack = player.getStackInHand(Hand.MAIN_HAND);
|
||||||
|
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
||||||
|
|
||||||
|
if (result == ActionResult.FAIL) {
|
||||||
|
ci.cancel();
|
||||||
|
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(
|
||||||
|
-2,
|
||||||
|
1,
|
||||||
|
player.getInventory().getSlotWithStack(stack),
|
||||||
|
stack
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "onClickSlot", at = @At("HEAD"), cancellable = true)
|
||||||
|
public void onClickSlot(ClickSlotC2SPacket packet, CallbackInfo ci) {
|
||||||
|
int slot = packet.getSlot();
|
||||||
|
if (slot < 0) return;
|
||||||
|
|
||||||
|
ItemStack stack = this.player.getInventory().getStack(slot);
|
||||||
|
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
||||||
|
|
||||||
|
if (result == ActionResult.FAIL) {
|
||||||
|
ci.cancel();
|
||||||
|
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-2, 1, slot, stack));
|
||||||
|
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-1, 1, -1, ItemStack.EMPTY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "onCreativeInventoryAction", at = @At("HEAD"), cancellable = true)
|
||||||
|
public void onCreativeInventoryAction(CreativeInventoryActionC2SPacket packet, CallbackInfo ci) {
|
||||||
|
int slot = packet.getSlot();
|
||||||
|
if (slot < 0) return;
|
||||||
|
|
||||||
|
ItemStack stack = this.player.getInventory().getStack(slot);
|
||||||
|
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
||||||
|
|
||||||
|
if (result == ActionResult.FAIL) {
|
||||||
|
ci.cancel();
|
||||||
|
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-2, 1, slot, stack));
|
||||||
|
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-1, 1, -1, ItemStack.EMPTY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "onCommandExecution", at = @At("HEAD"), cancellable = true)
|
||||||
|
public void onCommandExecution(CommandExecutionC2SPacket packet, CallbackInfo ci) {
|
||||||
|
ActionResult result = PlayerCommandCallback.EVENT.invoker().interact(player, packet.command());
|
||||||
|
|
||||||
|
if (result == ActionResult.FAIL) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.mixins;
|
||||||
|
|
||||||
|
import net.minecraft.entity.ItemEntity;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
import net.william278.husksync.event.ItemDropCallback;
|
||||||
|
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 = "dropItem", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void onPlayerDropItem(ItemStack stack, boolean dropAtFeet, boolean saveThrower,
|
||||||
|
final CallbackInfoReturnable<ItemEntity> ci) {
|
||||||
|
ServerPlayerEntity player = (ServerPlayerEntity) (Object) this;
|
||||||
|
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
||||||
|
|
||||||
|
if (result == ActionResult.FAIL) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.mixins;
|
||||||
|
|
||||||
|
import net.minecraft.server.world.ServerWorld;
|
||||||
|
import net.william278.husksync.event.WorldSaveCallback;
|
||||||
|
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(ServerWorld.class)
|
||||||
|
public class ServerWorldMixin {
|
||||||
|
|
||||||
|
@Inject(method = "saveLevel", at = @At("HEAD"))
|
||||||
|
public void saveLevel(CallbackInfo ci) {
|
||||||
|
WorldSaveCallback.EVENT.invoker().save((ServerWorld) (Object) this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.user;
|
||||||
|
|
||||||
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
|
import eu.pb4.sgui.api.ClickType;
|
||||||
|
import eu.pb4.sgui.api.elements.GuiElementInterface;
|
||||||
|
import eu.pb4.sgui.api.gui.SimpleGui;
|
||||||
|
import me.lucko.fabric.api.permissions.v0.Permissions;
|
||||||
|
import net.kyori.adventure.audience.Audience;
|
||||||
|
import net.kyori.adventure.platform.fabric.FabricServerAudiences;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.screen.GenericContainerScreenHandler;
|
||||||
|
import net.minecraft.screen.ScreenHandlerType;
|
||||||
|
import net.minecraft.screen.slot.SlotActionType;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.william278.husksync.FabricHuskSync;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.data.Data;
|
||||||
|
import net.william278.husksync.data.FabricData;
|
||||||
|
import net.william278.husksync.data.FabricUserDataHolder;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class FabricUser extends OnlineUser implements FabricUserDataHolder {
|
||||||
|
|
||||||
|
private final HuskSync plugin;
|
||||||
|
private final ServerPlayerEntity player;
|
||||||
|
|
||||||
|
private FabricUser(@NotNull ServerPlayerEntity player, @NotNull HuskSync plugin) {
|
||||||
|
super(player.getUuid(), player.getName().getString());
|
||||||
|
this.player = player;
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public static FabricUser adapt(@NotNull ServerPlayerEntity player, @NotNull HuskSync plugin) {
|
||||||
|
return new FabricUser(player, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOffline() {
|
||||||
|
return player == null || player.isDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Audience getAudience() {
|
||||||
|
return plugin.getAudiences().player(player.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendToast(@NotNull MineDown title, @NotNull MineDown description, @NotNull String iconMaterial,
|
||||||
|
@NotNull String backgroundType) {
|
||||||
|
player.sendActionBar(title.toComponent()); // Toasts unimplemented for now
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showGui(@NotNull Data.Items.Items items, @NotNull MineDown title, boolean editable, int size,
|
||||||
|
@NotNull Consumer<Data.Items> onClose) {
|
||||||
|
plugin.runSync(
|
||||||
|
() -> new ItemViewerGui(size, player, title, (FabricData.Items) items, onClose, editable, plugin).open()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ItemViewerGui extends SimpleGui {
|
||||||
|
|
||||||
|
private final Consumer<Data.Items> onClose;
|
||||||
|
private final int size;
|
||||||
|
private final boolean editable;
|
||||||
|
|
||||||
|
public ItemViewerGui(int size, @NotNull ServerPlayerEntity player, @NotNull MineDown title,
|
||||||
|
@NotNull FabricData.Items items, @NotNull Consumer<Data.Items> onClose,
|
||||||
|
boolean editable, @NotNull HuskSync plugin) {
|
||||||
|
super(getScreenHandler(size), player, false);
|
||||||
|
this.onClose = onClose;
|
||||||
|
this.size = size;
|
||||||
|
this.editable = editable;
|
||||||
|
|
||||||
|
// Set title, items
|
||||||
|
this.setTitle(((FabricServerAudiences) plugin.getAudiences()).toNative(title.toComponent()));
|
||||||
|
this.setLockPlayerInventory(!editable);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
final ItemStack item = items.getContents()[i];
|
||||||
|
this.setSlot(i, item == null ? ItemStack.EMPTY : item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose() {
|
||||||
|
final ItemStack[] contents = new ItemStack[size];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
contents[i] = this.getSlot(i) == null ? null : this.getSlot(i).getItemStack();
|
||||||
|
}
|
||||||
|
onClose.accept(FabricData.Items.ItemArray.adapt(contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onAnyClick(int index, @NotNull ClickType type, @NotNull SlotActionType action) {
|
||||||
|
return editable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onClick(int index, @NotNull ClickType type, @NotNull SlotActionType action,
|
||||||
|
@NotNull GuiElementInterface element) {
|
||||||
|
return editable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static ScreenHandlerType<GenericContainerScreenHandler> getScreenHandler(int size) {
|
||||||
|
return switch (size / 9 + (size % 9 == 0 ? 0 : 1)) {
|
||||||
|
case 3 -> ScreenHandlerType.GENERIC_9X3;
|
||||||
|
case 4 -> ScreenHandlerType.GENERIC_9X4;
|
||||||
|
case 5 -> ScreenHandlerType.GENERIC_9X5;
|
||||||
|
default -> ScreenHandlerType.GENERIC_9X6;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(@NotNull String node) {
|
||||||
|
final boolean requiresOp = Boolean.TRUE.equals(
|
||||||
|
((FabricHuskSync) plugin).getPermissions().getOrDefault(node, true)
|
||||||
|
);
|
||||||
|
return Permissions.check(player, node, !requiresOp || player.hasPermissionLevel(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDead() {
|
||||||
|
return player.getHealth() <= 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocked() {
|
||||||
|
return plugin.getLockedPlayers().contains(player.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNpc() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public ServerPlayerEntity getPlayer() {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public HuskSync getPlugin() {
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.util;
|
||||||
|
|
||||||
|
import net.minecraft.entity.EntityType;
|
||||||
|
import net.minecraft.entity.attribute.EntityAttribute;
|
||||||
|
import net.minecraft.entity.effect.StatusEffect;
|
||||||
|
import net.minecraft.registry.Registries;
|
||||||
|
import net.minecraft.registry.Registry;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
// Utility class for adapting "Keyed" Minecraft objects
|
||||||
|
public final class FabricKeyedAdapter {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static EntityType<?> matchEntityType(@NotNull String key) {
|
||||||
|
return getRegistryValue(Registries.ENTITY_TYPE, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String getEntityTypeId(@NotNull EntityType<?> entityType) {
|
||||||
|
return getRegistryKey(Registries.ENTITY_TYPE, entityType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static EntityAttribute matchAttribute(@NotNull String key) {
|
||||||
|
return getRegistryValue(Registries.ATTRIBUTE, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String getAttributeId(@NotNull EntityAttribute attribute) {
|
||||||
|
return getRegistryKey(Registries.ATTRIBUTE, attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static StatusEffect matchEffectType(@NotNull String key) {
|
||||||
|
return getRegistryValue(Registries.STATUS_EFFECT, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String getEffectId(@NotNull StatusEffect effect) {
|
||||||
|
return getRegistryKey(Registries.STATUS_EFFECT, effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static <T> T getRegistryValue(@NotNull Registry<T> registry, @NotNull String keyString) {
|
||||||
|
final Identifier key = Identifier.tryParse(keyString);
|
||||||
|
return key != null ? registry.get(key) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static <T> String getRegistryKey(@NotNull Registry<T> registry, @NotNull T value) {
|
||||||
|
final Identifier key = registry.getId(value);
|
||||||
|
return key != null ? key.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.util;
|
||||||
|
|
||||||
|
import net.william278.husksync.FabricHuskSync;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.data.UserDataHolder;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public interface FabricTask extends Task {
|
||||||
|
|
||||||
|
class Sync extends Task.Sync implements FabricTask {
|
||||||
|
|
||||||
|
protected Sync(@NotNull HuskSync plugin, @NotNull Runnable runnable, long delayTicks) {
|
||||||
|
super(plugin, runnable, delayTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
super.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!cancelled) {
|
||||||
|
Executors.newSingleThreadScheduledExecutor().schedule(
|
||||||
|
() -> ((FabricHuskSync) getPlugin()).getMinecraftServer().executeSync(runnable),
|
||||||
|
delayTicks * 50,
|
||||||
|
TimeUnit.MILLISECONDS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Async extends Task.Async implements FabricTask {
|
||||||
|
private CompletableFuture<Void> task;
|
||||||
|
|
||||||
|
protected Async(@NotNull HuskSync plugin, @NotNull Runnable runnable, long delayTicks) {
|
||||||
|
super(plugin, runnable, delayTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
if (task != null && !cancelled) {
|
||||||
|
task.cancel(true);
|
||||||
|
}
|
||||||
|
super.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!cancelled) {
|
||||||
|
this.task = CompletableFuture.runAsync(runnable, ((FabricHuskSync) getPlugin()).getMinecraftServer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Repeating extends Task.Repeating implements FabricTask {
|
||||||
|
|
||||||
|
private ScheduledFuture<?> task;
|
||||||
|
|
||||||
|
protected Repeating(@NotNull HuskSync plugin, @NotNull Runnable runnable, long repeatingTicks) {
|
||||||
|
super(plugin, runnable, repeatingTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
if (task != null && !cancelled) {
|
||||||
|
task.cancel(true);
|
||||||
|
}
|
||||||
|
super.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!cancelled) {
|
||||||
|
this.task = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
|
||||||
|
runnable,
|
||||||
|
0,
|
||||||
|
repeatingTicks * 50,
|
||||||
|
TimeUnit.MILLISECONDS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Supplier extends Task.Supplier {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Task.Sync getSyncTask(@NotNull Runnable runnable, @Nullable UserDataHolder user, long delayTicks) {
|
||||||
|
return new Sync(getPlugin(), runnable, delayTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Task.Async getAsyncTask(@NotNull Runnable runnable, long delayTicks) {
|
||||||
|
return new Async(getPlugin(), runnable, delayTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
default Task.Repeating getRepeatingTask(@NotNull Runnable runnable, long repeatingTicks) {
|
||||||
|
return new Repeating(getPlugin(), runnable, repeatingTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void cancelTasks() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "husksync",
|
||||||
|
"version": "${version}",
|
||||||
|
"name": "husksync",
|
||||||
|
"icon": "assets/husksync/icon.png",
|
||||||
|
"description": "${description}",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "William278",
|
||||||
|
"contact": {
|
||||||
|
"sources": "https://github.com/WiIIiam278",
|
||||||
|
"homepage": "https://william278.net"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hanbings",
|
||||||
|
"contact": {
|
||||||
|
"sources": "https://github.com/hanbings"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Stampede2011",
|
||||||
|
"contact": {
|
||||||
|
"sources": "https://github.com/Stampede2011"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"contact": {
|
||||||
|
"homepage": "https://william278.net/project/husksync",
|
||||||
|
"repo": "https://github.com/WiIIiam278/HuskSync",
|
||||||
|
"issues": "https://github.com/WiIIiam278/HuskSync/issues"
|
||||||
|
},
|
||||||
|
"environment": "server",
|
||||||
|
"entrypoints": {
|
||||||
|
"server": [
|
||||||
|
"net.william278.husksync.FabricHuskSync"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"depends": {
|
||||||
|
"fabricloader": ">=${fabric_loader_version}",
|
||||||
|
"minecraft": ">=${fabric_minecraft_version}",
|
||||||
|
"fabric-api": "*"
|
||||||
|
},
|
||||||
|
"suggests": {
|
||||||
|
"plan": "*"
|
||||||
|
},
|
||||||
|
"mixins": [
|
||||||
|
"husksync.mixins.json"
|
||||||
|
],
|
||||||
|
"custom": {
|
||||||
|
"modmenu:api": true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "0.8",
|
||||||
|
"package": "net.william278.husksync.mixins",
|
||||||
|
"compatibilityLevel": "JAVA_17",
|
||||||
|
"server": [
|
||||||
|
"ItemEntityMixin",
|
||||||
|
"PlayerEntityMixin",
|
||||||
|
"ServerPlayerEntityMixin",
|
||||||
|
"ServerPlayNetworkHandlerMixin",
|
||||||
|
"ServerWorldMixin"
|
||||||
|
],
|
||||||
|
"client": [],
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
Loading…
Reference in New Issue