refactor: use registry for statistics

also updates BukkitKeyedAdapter methods to use the registries and moves to just using the Json serializer for Bukkit locations
feat/data-edit-commands
William 10 months ago
parent 98cf42065b
commit 4bb38f67d3
No known key found for this signature in database

@ -140,14 +140,14 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
registerSerializer(Identifier.INVENTORY, new BukkitSerializer.Inventory(this));
registerSerializer(Identifier.ENDER_CHEST, new BukkitSerializer.EnderChest(this));
registerSerializer(Identifier.ADVANCEMENTS, new BukkitSerializer.Advancements(this));
registerSerializer(Identifier.LOCATION, new BukkitSerializer.Location(this));
registerSerializer(Identifier.LOCATION, new BukkitSerializer.Json<>(this, BukkitData.Location.class));
registerSerializer(Identifier.HEALTH, new BukkitSerializer.Json<>(this, BukkitData.Health.class));
registerSerializer(Identifier.HUNGER, new BukkitSerializer.Json<>(this, BukkitData.Hunger.class));
registerSerializer(Identifier.ATTRIBUTES, new BukkitSerializer.Json<>(this, BukkitData.Attributes.class));
registerSerializer(Identifier.GAME_MODE, new BukkitSerializer.Json<>(this, BukkitData.GameMode.class));
registerSerializer(Identifier.FLIGHT_STATUS, new BukkitSerializer.Json<>(this, BukkitData.FlightStatus.class));
registerSerializer(Identifier.POTION_EFFECTS, new BukkitSerializer.PotionEffects(this));
registerSerializer(Identifier.STATISTICS, new BukkitSerializer.Statistics(this));
registerSerializer(Identifier.STATISTICS, new BukkitSerializer.Json<>(this, BukkitData.Statistics.class));
registerSerializer(Identifier.EXPERIENCE, new BukkitSerializer.Json<>(this, BukkitData.Experience.class));
registerSerializer(Identifier.PERSISTENT_DATA, new BukkitSerializer.PersistentData(this));
});

@ -42,7 +42,6 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
@ -50,9 +49,9 @@ import org.jetbrains.annotations.Range;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static net.william278.husksync.util.BukkitKeyedAdapter.*;
import static net.william278.husksync.util.BukkitKeyedAdapter.matchAttribute;
import static net.william278.husksync.util.BukkitKeyedAdapter.matchStatistic;
public abstract class BukkitData implements Data {
@ -432,216 +431,106 @@ public abstract class BukkitData implements Data {
}
// TODO: Move to using Registry.STATISTIC as soon as possible!
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Statistics extends BukkitData implements Data.Statistics {
private Map<Statistic, Integer> genericStatistics;
private Map<Statistic, Map<Material, Integer>> blockStatistics;
private Map<Statistic, Map<Material, Integer>> itemStatistics;
private Map<Statistic, Map<EntityType, Integer>> entityStatistics;
public static class Statistics extends BukkitData implements Data.Statistics, Adaptable {
@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 BukkitData.Statistics adapt(@NotNull Player player) {
return new BukkitData.Statistics(
// Generic (untyped) stats
Arrays.stream(Statistic.values())
.filter(stat -> stat.getType() == Statistic.Type.UNTYPED)
.filter(stat -> player.getStatistic(stat) != 0)
.map(stat -> Map.entry(stat, player.getStatistic(stat)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)),
// Block stats
Arrays.stream(Statistic.values())
.filter(stat -> stat.getType() == Statistic.Type.BLOCK)
.map(stat -> Map.entry(stat, Arrays.stream(Material.values())
.filter(Material::isBlock)
.filter(material -> player.getStatistic(stat, material) != 0)
.map(material -> Map.entry(material, player.getStatistic(stat, material)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))))
.filter(entry -> !entry.getValue().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)),
// Item stats
Arrays.stream(Statistic.values())
.filter(stat -> stat.getType() == Statistic.Type.ITEM)
.map(stat -> Map.entry(stat, Arrays.stream(Material.values())
.filter(Material::isItem)
.filter(material -> player.getStatistic(stat, material) != 0)
.map(material -> Map.entry(material, player.getStatistic(stat, material)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))))
.filter(entry -> !entry.getValue().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)),
// Entity stats
Arrays.stream(Statistic.values())
.filter(stat -> stat.getType() == Statistic.Type.ENTITY)
.map(stat -> Map.entry(stat, Arrays.stream(EntityType.values())
.filter(EntityType::isAlive)
.filter(entityType -> player.getStatistic(stat, entityType) != 0)
.map(entityType -> Map.entry(entityType, player.getStatistic(stat, entityType)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))))
.filter(entry -> !entry.getValue().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
);
final Map<String, Integer> generic = Maps.newHashMap();
final Map<String, Map<String, Integer>> blocks = Maps.newHashMap();
final Map<String, Map<String, Integer>> items = Maps.newHashMap();
final Map<String, Map<String, Integer>> entities = Maps.newHashMap();
Registry.STATISTIC.forEach(id -> {
switch (id.getType()) {
case UNTYPED -> addStatistic(player, id, generic);
case BLOCK -> addMaterialStatistic(player, id, blocks, true);
case ITEM -> addMaterialStatistic(player, id, items, false);
case ENTITY -> addEntityStatistic(player, id, entities);
}
});
return new BukkitData.Statistics(generic, blocks, items, entities);
}
@NotNull
public static BukkitData.Statistics from(@NotNull StatisticsMap stats) {
return new BukkitData.Statistics(
stats.genericStats().entrySet().stream()
.flatMap(entry -> {
Statistic statistic = matchStatistic(entry.getKey());
return statistic != null ? Stream.of(new AbstractMap.SimpleEntry<>(statistic, entry.getValue())) : Stream.empty();
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)),
stats.blockStats().entrySet().stream()
.flatMap(entry -> {
Statistic statistic = matchStatistic(entry.getKey());
return statistic != null ? Stream.of(new AbstractMap.SimpleEntry<>(statistic, entry.getValue())) : Stream.empty();
})
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().entrySet().stream()
.flatMap(blockEntry -> {
Material material = matchMaterial(blockEntry.getKey());
return material != null ? Stream.of(new AbstractMap.SimpleEntry<>(material, blockEntry.getValue())) : Stream.empty();
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
)),
stats.itemStats().entrySet().stream()
.flatMap(entry -> {
Statistic statistic = matchStatistic(entry.getKey());
return statistic != null ? Stream.of(new AbstractMap.SimpleEntry<>(statistic, entry.getValue())) : Stream.empty();
})
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().entrySet().stream()
.flatMap(itemEntry -> {
Material material = matchMaterial(itemEntry.getKey());
return material != null ? Stream.of(new AbstractMap.SimpleEntry<>(material, itemEntry.getValue())) : Stream.empty();
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
)),
stats.entityStats().entrySet().stream()
.flatMap(entry -> {
Statistic statistic = matchStatistic(entry.getKey());
return statistic != null ? Stream.of(new AbstractMap.SimpleEntry<>(statistic, entry.getValue())) : Stream.empty();
})
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().entrySet().stream()
.flatMap(itemEntry -> {
EntityType entityType = matchEntityType(itemEntry.getKey());
return entityType != null ? Stream.of(new AbstractMap.SimpleEntry<>(entityType, itemEntry.getValue())) : Stream.empty();
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
))
);
public static BukkitData.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 BukkitData.Statistics(generic, blocks, items, entities);
}
@NotNull
public static BukkitData.Statistics from(@NotNull Map<Statistic, Integer> genericStats,
@NotNull Map<Statistic, Map<Material, Integer>> blockStats,
@NotNull Map<Statistic, Map<Material, Integer>> itemStats,
@NotNull Map<Statistic, Map<EntityType, Integer>> entityStats) {
return new BukkitData.Statistics(genericStats, blockStats, itemStats, entityStats);
private static void addStatistic(@NotNull Player p, @NotNull Statistic id, @NotNull Map<String, Integer> map) {
final int stat = p.getStatistic(id);
if (stat != 0) {
map.put(id.getKey().getKey(), stat);
}
}
@NotNull
@ApiStatus.Internal
public static StatisticsMap createStatisticsMap(@NotNull Map<String, Integer> genericStats,
@NotNull Map<String, Map<String, Integer>> blockStats,
@NotNull Map<String, Map<String, Integer>> itemStats,
@NotNull Map<String, Map<String, Integer>> entityStats) {
return new StatisticsMap(genericStats, blockStats, itemStats, entityStats);
private static void addMaterialStatistic(@NotNull Player p, @NotNull Statistic id,
@NotNull Map<String, Map<String, Integer>> map, boolean isBlock) {
Registry.MATERIAL.forEach(material -> {
if ((material.isBlock() && !isBlock) || (material.isItem() && isBlock)) {
return;
}
final int stat = p.getStatistic(id, material);
if (stat != 0) {
map.computeIfAbsent(id.getKey().getKey(), k -> Maps.newHashMap())
.put(material.getKey().getKey(), stat);
}
});
}
@Override
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) throws IllegalStateException {
genericStatistics.forEach((stat, value) -> applyStat(user, stat, null, value));
blockStatistics.forEach((stat, m) -> m.forEach((block, value) -> applyStat(user, stat, block, value)));
itemStatistics.forEach((stat, m) -> m.forEach((item, value) -> applyStat(user, stat, item, value)));
entityStatistics.forEach((stat, m) -> m.forEach((entity, value) -> applyStat(user, stat, entity, value)));
private static void addEntityStatistic(@NotNull Player p, @NotNull Statistic id,
@NotNull Map<String, Map<String, Integer>> map) {
Registry.ENTITY_TYPE.forEach(entity -> {
if (!entity.isAlive()) {
return;
}
final int stat = p.getStatistic(id, entity);
if (stat != 0) {
map.computeIfAbsent(id.getKey().getKey(), k -> Maps.newHashMap())
.put(entity.getKey().getKey(), stat);
}
});
}
private void applyStat(@NotNull UserDataHolder user, @NotNull Statistic stat, @Nullable Object type, int value) {
@Override
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) {
genericStatistics.forEach((id, v) -> applyStat(user, id, Statistic.Type.UNTYPED, v));
blockStatistics.forEach((id, m) -> m.forEach((b, v) -> applyStat(user, id, Statistic.Type.BLOCK, v, b)));
itemStatistics.forEach((id, m) -> m.forEach((i, v) -> applyStat(user, id, Statistic.Type.ITEM, v, i)));
entityStatistics.forEach((id, m) -> m.forEach((e, v) -> applyStat(user, id, Statistic.Type.ENTITY, v, e)));
}
private void applyStat(@NotNull UserDataHolder user, @NotNull String id, @NotNull Statistic.Type type,
int value, @NotNull Object... object) {
final Player player = ((BukkitUser) user).getPlayer();
final Statistic stat = matchStatistic(id);
if (stat == null) {
return;
}
try {
final Player player = ((BukkitUser) user).getPlayer();
if (type == null) {
player.setStatistic(stat, value);
} else if (type instanceof Material) {
player.setStatistic(stat, (Material) type, value);
} else if (type instanceof EntityType) {
player.setStatistic(stat, (EntityType) type, value);
switch (type) {
case UNTYPED -> player.setStatistic(stat, value);
case BLOCK, ITEM -> player.setStatistic(stat, (Material) object[0], value);
case ENTITY -> player.setStatistic(stat, (EntityType) object[0], value);
}
} catch (IllegalArgumentException ignored) {
}
}
@NotNull
@Override
public Map<String, Integer> getGenericStatistics() {
return convertStatistics(genericStatistics);
}
@NotNull
@Override
public Map<String, Map<String, Integer>> getBlockStatistics() {
return blockStatistics.entrySet().stream().filter(entry -> entry.getKey() != null).collect(
TreeMap::new,
(m, e) -> getKeyName(e.getKey()).ifPresent(key -> m.put(key, convertStatistics(e.getValue()))),
TreeMap::putAll
);
}
@NotNull
@Override
public Map<String, Map<String, Integer>> getItemStatistics() {
return itemStatistics.entrySet().stream().filter(entry -> entry.getKey() != null).collect(
TreeMap::new,
(m, e) -> getKeyName(e.getKey()).ifPresent(key -> m.put(key, convertStatistics(e.getValue()))),
TreeMap::putAll
);
}
@NotNull
@Override
public Map<String, Map<String, Integer>> getEntityStatistics() {
return entityStatistics.entrySet().stream().filter(entry -> entry.getKey() != null).collect(
TreeMap::new,
(m, e) -> getKeyName(e.getKey()).ifPresent(key -> m.put(key, convertStatistics(e.getValue()))),
TreeMap::putAll
);
}
@NotNull
private <T extends Keyed> Map<String, Integer> convertStatistics(@NotNull Map<T, Integer> stats) {
return stats.entrySet().stream().filter(entry -> entry.getKey() != null).collect(
TreeMap::new,
(m, e) -> getKeyName(e.getKey()).ifPresent(key -> m.put(key, e.getValue())),
TreeMap::putAll
);
}
@NotNull
protected StatisticsMap getStatisticsSet() {
return new StatisticsMap(
getGenericStatistics(),
getBlockStatistics(),
getItemStatistics(),
getEntityStatistics()
);
}
public record StatisticsMap(
@SerializedName("generic") @NotNull Map<String, Integer> genericStats,
@SerializedName("blocks") @NotNull Map<String, Map<String, Integer>> blockStats,
@SerializedName("items") @NotNull Map<String, Map<String, Integer>> itemStats,
@SerializedName("entities") @NotNull Map<String, Map<String, Integer>> entityStats
) {
}
}
@Getter
@ -696,6 +585,10 @@ public abstract class BukkitData implements Data {
return attributes.stream().filter(attribute -> attribute.name().equals(id.getKey().toString())).findFirst();
}
public Optional<Attribute> getAttribute(@NotNull String key) {
return getAttribute(matchAttribute(key));
}
@NotNull
private static Attribute adapt(@NotNull AttributeInstance instance) {
return new Attribute(

@ -148,46 +148,6 @@ public class BukkitSerializer {
}
}
public static class Location extends BukkitSerializer implements Serializer<BukkitData.Location> {
public Location(@NotNull HuskSync plugin) {
super(plugin);
}
@Override
public BukkitData.Location deserialize(@NotNull String serialized) throws DeserializationException {
return plugin.getDataAdapter().fromJson(serialized, BukkitData.Location.class);
}
@NotNull
@Override
public String serialize(@NotNull BukkitData.Location element) throws SerializationException {
return plugin.getDataAdapter().toJson(element);
}
}
public static class Statistics extends BukkitSerializer implements Serializer<BukkitData.Statistics> {
public Statistics(@NotNull HuskSync plugin) {
super(plugin);
}
@Override
public BukkitData.Statistics deserialize(@NotNull String serialized) throws DeserializationException {
return BukkitData.Statistics.from(plugin.getGson().fromJson(
serialized,
BukkitData.Statistics.StatisticsMap.class
));
}
@NotNull
@Override
public String serialize(@NotNull BukkitData.Statistics element) throws SerializationException {
return plugin.getGson().toJson(element.getStatisticsSet());
}
}
public static class PersistentData extends BukkitSerializer implements Serializer<BukkitData.PersistentData> {
public PersistentData(@NotNull HuskSync plugin) {

@ -323,12 +323,11 @@ public class LegacyMigrator extends Migrator {
// Stats
.statistics(BukkitData.Statistics.from(
BukkitData.Statistics.createStatisticsMap(
convertStatisticMap(stats.untypedStatisticValues()),
convertMaterialStatisticMap(stats.blockStatisticValues()),
convertMaterialStatisticMap(stats.itemStatisticValues()),
convertEntityStatisticMap(stats.entityStatisticValues())
)))
convertStatisticMap(stats.untypedStatisticValues()),
convertMaterialStatisticMap(stats.blockStatisticValues()),
convertMaterialStatisticMap(stats.itemStatisticValues()),
convertEntityStatisticMap(stats.entityStatisticValues())
))
// Health, hunger, experience & game mode
.health(BukkitData.Health.from(health, healthScale))

@ -19,56 +19,38 @@
package net.william278.husksync.util;
import org.bukkit.Keyed;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.Statistic;
import org.bukkit.attribute.Attribute;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Optional;
import java.util.Objects;
// Utility class for adapting "Keyed" Bukkit objects
public final class BukkitKeyedAdapter {
@Nullable
public static Statistic matchStatistic(@NotNull String key) {
try {
return Arrays.stream(Statistic.values())
.filter(stat -> stat.getKey().toString().equals(key))
.findFirst().orElse(null);
} catch (Throwable e) {
return null;
}
return Registry.STATISTIC.get(Objects.requireNonNull(NamespacedKey.fromString(key), "Null key"));
}
@Nullable
public static EntityType matchEntityType(@NotNull String key) {
try {
return Arrays.stream(EntityType.values())
.filter(entityType -> entityType.getKey().toString().equals(key))
.findFirst().orElse(null);
} catch (Throwable e) {
return null;
}
return Registry.ENTITY_TYPE.get(Objects.requireNonNull(NamespacedKey.fromString(key), "Null key"));
}
@Nullable
public static Material matchMaterial(@NotNull String key) {
try {
return Material.matchMaterial(key);
} catch (Throwable e) {
return null;
}
return Registry.MATERIAL.get(Objects.requireNonNull(NamespacedKey.fromString(key), "Null key"));
}
public static Optional<String> getKeyName(@NotNull Keyed keyed) {
try {
return Optional.of(keyed.getKey().toString());
} catch (Throwable e) {
return Optional.empty();
}
@Nullable
public static Attribute matchAttribute(@NotNull String key) {
return Registry.ATTRIBUTE.get(Objects.requireNonNull(NamespacedKey.fromString(key), "Null key"));
}
}

@ -27,9 +27,6 @@ import net.william278.husksync.data.BukkitData;
import net.william278.husksync.data.Data;
import net.william278.husksync.data.DataSnapshot;
import net.william278.husksync.data.Identifier;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.jetbrains.annotations.NotNull;
@ -45,7 +42,8 @@ import java.time.OffsetDateTime;
import java.util.*;
import java.util.logging.Level;
import static net.william278.husksync.util.BukkitKeyedAdapter.*;
import static net.william278.husksync.util.BukkitKeyedAdapter.matchEntityType;
import static net.william278.husksync.util.BukkitKeyedAdapter.matchMaterial;
public class BukkitLegacyConverter extends LegacyConverter {
@ -205,39 +203,42 @@ public class BukkitLegacyConverter extends LegacyConverter {
private BukkitData.Statistics readStatisticMaps(@NotNull JSONObject untyped, @NotNull JSONObject blocks,
@NotNull JSONObject items, @NotNull JSONObject entities) {
// Read generic stats
final Map<Statistic, Integer> genericStats = Maps.newHashMap();
untyped.keys().forEachRemaining(stat -> genericStats.put(matchStatistic(stat), untyped.getInt(stat)));
final Map<String, Integer> genericStats = Maps.newHashMap();
untyped.keys().forEachRemaining(stat -> genericStats.put(stat, untyped.getInt(stat)));
// Read block & item stats
final Map<Statistic, Map<Material, Integer>> blockStats, itemStats;
final Map<String, Map<String, Integer>> blockStats, itemStats, entityStats;
blockStats = readMaterialStatistics(blocks);
itemStats = readMaterialStatistics(items);
// Read entity stats
final Map<Statistic, Map<EntityType, Integer>> entityStats = Maps.newHashMap();
entityStats = Maps.newHashMap();
entities.keys().forEachRemaining(stat -> {
final JSONObject entityStat = entities.getJSONObject(stat);
final Map<EntityType, Integer> entityMap = Maps.newHashMap();
entityStat.keys().forEachRemaining(entity -> entityMap.put(matchEntityType(entity), entityStat.getInt(entity)));
entityStats.put(matchStatistic(stat), entityMap);
final Map<String, Integer> entityMap = Maps.newHashMap();
entityStat.keys().forEachRemaining(entity -> {
if (matchEntityType(entity) != null) {
entityMap.put(entity, entityStat.getInt(entity));
}
});
entityStats.put(stat, entityMap);
});
return BukkitData.Statistics.from(genericStats, blockStats, itemStats, entityStats);
}
@NotNull
private Map<Statistic, Map<Material, Integer>> readMaterialStatistics(@NotNull JSONObject items) {
final Map<Statistic, Map<Material, Integer>> itemStats = Maps.newHashMap();
private Map<String, Map<String, Integer>> readMaterialStatistics(@NotNull JSONObject items) {
final Map<String, Map<String, Integer>> itemStats = Maps.newHashMap();
items.keys().forEachRemaining(stat -> {
final JSONObject itemStat = items.getJSONObject(stat);
final Map<Material, Integer> itemMap = Maps.newHashMap();
final Map<String, Integer> itemMap = Maps.newHashMap();
itemStat.keys().forEachRemaining(item -> {
final Material material = matchMaterial(item);
if (material != null) {
itemMap.put(material, itemStat.getInt(item));
if (matchMaterial(item) != null) {
itemMap.put(item, itemStat.getInt(item));
}
});
itemStats.put(matchStatistic(stat), itemMap);
itemStats.put(stat, itemMap);
});
return itemStats;
}

@ -266,6 +266,10 @@ public interface Data {
@NotNull
Map<String, Map<String, Integer>> getEntityStatistics();
record StatisticsMap(
) {
}
}
/**

Loading…
Cancel
Save