9
0
mirror of https://github.com/WiIIiam278/HuskSync.git synced 2025-12-23 00:29:18 +00:00

feat: Add attribute syncing (#276)

* refactor: add attribute syncing

* fix: don't sync unmodified attributes

* fix: register json serializer for Attributes

* fix: improve Attribute API methods

* docs: update Sync Features

* refactor: make attributes a set

Because they're unique (by UUID)
This commit is contained in:
William
2024-04-10 19:38:37 +01:00
committed by GitHub
parent 82dc765f66
commit 676ba7a10a
13 changed files with 233 additions and 108 deletions

View File

@@ -29,9 +29,6 @@ import net.william278.desertwell.util.UpdateChecker;
import net.william278.desertwell.util.Version;
import net.william278.husksync.adapter.DataAdapter;
import net.william278.husksync.config.ConfigProvider;
import net.william278.husksync.config.Locales;
import net.william278.husksync.config.Server;
import net.william278.husksync.config.Settings;
import net.william278.husksync.data.Data;
import net.william278.husksync.data.Identifier;
import net.william278.husksync.data.Serializer;
@@ -180,31 +177,6 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
log(Level.INFO, "Successfully initialized " + name);
}
/**
* Returns the plugin {@link Settings}
*
* @return the {@link Settings}
*/
@NotNull
Settings getSettings();
void setSettings(@NotNull Settings settings);
@NotNull
String getServerName();
void setServerName(@NotNull Server serverName);
/**
* Returns the plugin {@link Locales}
*
* @return the {@link Locales}
*/
@NotNull
Locales getLocales();
void setLocales(@NotNull Locales locales);
/**
* Returns if a dependency is loaded
*

View File

@@ -19,7 +19,9 @@
package net.william278.husksync.data;
import com.google.common.collect.Sets;
import com.google.gson.annotations.SerializedName;
import net.kyori.adventure.key.Key;
import net.william278.husksync.HuskSync;
import net.william278.husksync.user.OnlineUser;
import org.jetbrains.annotations.NotNull;
@@ -286,15 +288,97 @@ public interface Data {
void setHealth(double health);
double getMaxHealth();
/**
* @deprecated Use {@link Attributes#getMaxHealth()} instead
*/
@Deprecated(forRemoval = true, since = "3.5")
default double getMaxHealth() {
return getHealth();
}
void setMaxHealth(double maxHealth);
/**
* @deprecated Use {@link Attributes#setMaxHealth(double)} instead
*/
@Deprecated(forRemoval = true, since = "3.5")
default void setMaxHealth(double maxHealth) {
}
double getHealthScale();
void setHealthScale(double healthScale);
}
/**
* A data container holding player attribute data
*/
interface Attributes extends Data {
Key MAX_HEALTH_KEY = Key.key("generic.max_health");
List<Attribute> getAttributes();
record Attribute(
@NotNull String name,
double baseValue,
@NotNull Set<Modifier> modifiers
) {
public double getValue() {
double value = baseValue;
for (Modifier modifier : modifiers) {
value = modifier.modify(value);
}
return value;
}
}
record Modifier(
@NotNull UUID uuid,
@NotNull String name,
double amount,
@SerializedName("operation") int operationType,
@SerializedName("equipment_slot") int equipmentSlot
) {
@Override
public boolean equals(Object obj) {
return obj instanceof Modifier modifier && modifier.uuid.equals(uuid);
}
public double modify(double value) {
return switch (operationType) {
case 0 -> value + amount;
case 1 -> value * amount;
case 2 -> value * (1 + amount);
default -> value;
};
}
}
default Optional<Attribute> getAttribute(@NotNull Key key) {
return getAttributes().stream()
.filter(attribute -> attribute.name().equals(key.asString()))
.findFirst();
}
default void removeAttribute(@NotNull Key key) {
getAttributes().removeIf(attribute -> attribute.name().equals(key.asString()));
}
default double getMaxHealth() {
return getAttribute(MAX_HEALTH_KEY)
.map(Attribute::getValue)
.orElse(20.0);
}
default void setMaxHealth(double maxHealth) {
removeAttribute(MAX_HEALTH_KEY);
getAttributes().add(new Attribute(MAX_HEALTH_KEY.asString(), maxHealth, Sets.newHashSet()));
}
}
/**
* A data container holding data for:
* <ul>

View File

@@ -110,6 +110,15 @@ public interface DataHolder {
getData().put(Identifier.HUNGER, hunger);
}
@NotNull
default Optional<Data.Attributes> getAttributes() {
return Optional.ofNullable((Data.Attributes) getData().get(Identifier.ATTRIBUTES));
}
default void setAttributes(@NotNull Data.Attributes attributes) {
getData().put(Identifier.ATTRIBUTES, attributes);
}
@NotNull
default Optional<Data.Experience> getExperience() {
return Optional.ofNullable((Data.Experience) getData().get(Identifier.EXPERIENCE));

View File

@@ -659,6 +659,21 @@ public class DataSnapshot {
return data(Identifier.HUNGER, hunger);
}
/**
* Set the attributes of the snapshot
* <p>
* Equivalent to {@code data(Identifier.ATTRIBUTES, attributes)}
* </p>
*
* @param attributes The user's attributes
* @return The builder
* @since 3.5
*/
@NotNull
public Builder attributes(@NotNull Data.Attributes attributes) {
return data(Identifier.ATTRIBUTES, attributes);
}
/**
* Set the experience of the snapshot
* <p>

View File

@@ -41,6 +41,7 @@ public class Identifier {
public static Identifier STATISTICS = huskSync("statistics", true);
public static Identifier HEALTH = huskSync("health", true);
public static Identifier HUNGER = huskSync("hunger", true);
public static Identifier ATTRIBUTES = huskSync("attributes", true);
public static Identifier EXPERIENCE = huskSync("experience", true);
public static Identifier GAME_MODE = huskSync("game_mode", true);
public static Identifier FLIGHT_STATUS = huskSync("flight_status", true);
@@ -114,8 +115,8 @@ public class Identifier {
@SuppressWarnings("unchecked")
public static Map<String, Boolean> getConfigMap() {
return Map.ofEntries(Stream.of(
INVENTORY, ENDER_CHEST, POTION_EFFECTS, ADVANCEMENTS, LOCATION,
STATISTICS, HEALTH, HUNGER, EXPERIENCE, GAME_MODE, FLIGHT_STATUS, PERSISTENT_DATA
INVENTORY, ENDER_CHEST, POTION_EFFECTS, ADVANCEMENTS, LOCATION, STATISTICS,
HEALTH, HUNGER, ATTRIBUTES, EXPERIENCE, GAME_MODE, FLIGHT_STATUS, PERSISTENT_DATA
)
.map(Identifier::getConfigEntry)
.toArray(Map.Entry[]::new));

View File

@@ -184,7 +184,7 @@ public class PlanHook {
public String getHealth(@NotNull UUID uuid) {
return getLatestSnapshot(uuid)
.flatMap(DataHolder::getHealth)
.map(health -> String.format("%s / %s", health.getHealth(), health.getMaxHealth()))
.map(health -> String.format("%s", health.getHealth()))
.orElse(UNKNOWN_STRING);
}

View File

@@ -77,16 +77,17 @@ public class DataSnapshotOverview {
// User status data, if present in the snapshot
final Optional<Data.Health> health = snapshot.getHealth();
final Optional<Data.Attributes> attributes = snapshot.getAttributes();
final Optional<Data.Hunger> food = snapshot.getHunger();
final Optional<Data.Experience> experience = snapshot.getExperience();
final Optional<Data.GameMode> gameMode = snapshot.getGameMode();
if (health.isPresent() && food.isPresent() && experience.isPresent() && gameMode.isPresent()) {
final Optional<Data.Experience> exp = snapshot.getExperience();
final Optional<Data.GameMode> mode = snapshot.getGameMode();
if (health.isPresent() && attributes.isPresent() && food.isPresent() && exp.isPresent() && mode.isPresent()) {
locales.getLocale("data_manager_status",
Integer.toString((int) health.get().getHealth()),
Integer.toString((int) health.get().getMaxHealth()),
Integer.toString((int) attributes.get().getMaxHealth()),
Integer.toString(food.get().getFoodLevel()),
Integer.toString(experience.get().getExpLevel()),
gameMode.get().getGameMode().toLowerCase(Locale.ENGLISH))
Integer.toString(exp.get().getExpLevel()),
mode.get().getGameMode().toLowerCase(Locale.ENGLISH))
.ifPresent(user::sendMessage);
}