Better player (de)serialization

This commit is contained in:
kuroppoi 2022-08-14 04:34:10 +02:00
parent e5e6d42f27
commit b3a35920a5
7 changed files with 242 additions and 110 deletions

View file

@ -10,8 +10,10 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.serialization.AchievementSerializer;
import brainwine.gameserver.util.MathUtils;
@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type", defaultImpl = Achievement.class)
@ -27,6 +29,7 @@ import brainwine.gameserver.util.MathUtils;
@Type(name = "DiscoveryAchievement", value = DiscoveryAchievement.class),
@Type(name = "Journeyman", value = JourneymanAchievement.class)
})
@JsonSerialize(using = AchievementSerializer.class)
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class Achievement {

View file

@ -7,7 +7,6 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
@ -26,11 +25,11 @@ public class Inventory {
// TODO clean up, perhaps just merge with inventory somehow.
private final ItemContainer hotbar = new ItemContainer(10);
private final ItemContainer accessories = new ItemContainer(20);
@JsonBackReference
private Player player;
public Inventory(Player player) {
protected Inventory() {}
protected Inventory(Player player) {
this.player = player;
}
@ -49,6 +48,10 @@ public class Inventory {
}
}
protected void setPlayer(Player player) {
this.player = player;
}
public void moveItemToContainer(Item item, ContainerType type, int slot) {
boolean accessoriesUpdated = false;
hotbar.removeItem(item);

View file

@ -1,6 +1,5 @@
package brainwine.gameserver.entity.player;
import java.beans.ConstructorProperties;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -13,12 +12,6 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import brainwine.gameserver.GameConfiguration;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.achievements.Achievement;
@ -71,9 +64,6 @@ import brainwine.gameserver.zone.Chunk;
import brainwine.gameserver.zone.MetaBlock;
import brainwine.gameserver.zone.Zone;
// TODO re-evaluate how we handle saving/loading this thing..
@JsonIncludeProperties({"name", "email", "password_hash", "auth_tokens", "admin", "experience", "skill_points", "karma",
"crowns", "inventory", "statistics", "ignored_hints", "achievements", "skills", "equipped_clothing", "equipped_colors", "current_zone"})
public class Player extends Entity implements CommandExecutor {
public static final int MAX_SKILL_LEVEL = 15;
@ -87,57 +77,22 @@ public class Player extends Entity implements CommandExecutor {
public static final float ENTITY_VISIBILITY_RANGE = 40;
public static final float BASE_REGEN_AMOUNT = 0.1F;
private static int dialogDiscriminator;
@JacksonInject("documentId")
private final String documentId;
@JsonProperty("email")
private String email;
@JsonProperty("password_hash")
private String password;
@JsonProperty("auth_tokens")
private final List<String> authTokens = new ArrayList<>();
@JsonProperty("admin")
private boolean admin;
@JsonProperty("experience")
private int experience;
@JsonProperty("skill_points")
private int skillPoints;
@JsonProperty("karma")
private int karma;
@JsonProperty("crowns")
private int crowns;
@JsonManagedReference
@JsonProperty("inventory")
private final Inventory inventory = new Inventory(this);
@JsonManagedReference
@JsonProperty("statistics")
private final PlayerStatistics statistics = new PlayerStatistics(this);
@JsonProperty("ignored_hints")
private final Map<String, Float> ignoredHints = new HashMap<String, Float>();
@JsonProperty("achievements")
private final Set<Achievement> achievements = new HashSet<>();
@JsonProperty("skills")
private final Map<Skill, Integer> skills = new HashMap<>();
@JsonProperty("equipped_clothing")
private final Map<ClothingSlot, Item> clothing = new HashMap<>();
@JsonProperty("equipped_colors")
private final Map<ColorSlot, String> colors = new HashMap<>();
private Inventory inventory;
private PlayerStatistics statistics;
private List<String> authTokens;
private Set<Achievement> achievements;
private Map<String, Float> ignoredHints;
private Map<Skill, Integer> skills;
private Map<ClothingSlot, Item> equippedClothing;
private Map<ColorSlot, String> equippedColors;
private final Set<Item> wardrobe = new HashSet<>();
private final Map<String, Object> settings = new HashMap<>();
private final Set<Integer> activeChunks = new HashSet<>();
@ -154,24 +109,41 @@ public class Player extends Entity implements CommandExecutor {
private Zone nextZone;
private Connection connection;
@ConstructorProperties({"documentId", "name", "current_zone"})
public Player(@JacksonInject("documentId") String documentId, String name, Zone zone) {
protected Player(String documentId, PlayerConfigFile config) {
super(config.getCurrentZone());
this.documentId = documentId;
this.name = config.getName();
this.email = config.getEmail();
this.password = config.getPasswordHash();
this.admin = config.isAdmin();
this.experience = config.getExperience();
this.skillPoints = config.getSkillPoints();
this.karma = config.getKarma();
this.crowns = config.getCrowns();
this.inventory = config.getInventory();
this.statistics = config.getStatistics();
this.authTokens = config.getAuthTokens();
this.achievements = config.getAchievements();
this.ignoredHints = config.getIgnoredHints();
this.skills = config.getSkills();
this.equippedClothing = config.getEquippedClothing();
this.equippedColors = config.getEquippedColors();
inventory.setPlayer(this);
statistics.setPlayer(this);
}
public Player(String documentId, String name, Zone zone) {
super(zone);
for(Item item : ItemRegistry.getItems()) {
if(item.isClothing()) {
wardrobe.add(item);
}
}
this.documentId = documentId;
this.name = name;
settings.put("hotbar_presets", new ArrayList<>());
Map<String, Object> test = new HashMap<>();
test.put("fg", true);
test.put("to", true);
test.put("lo", true);
settings.put("appearance", test);
this.inventory = new Inventory(this);
this.statistics = new PlayerStatistics(this);
this.authTokens = new ArrayList<>();
this.achievements = new HashSet<>();
this.ignoredHints = new HashMap<>();
this.skills = new HashMap<>();
this.equippedClothing = new HashMap<>();
this.equippedColors = new HashMap<>();
}
@Override
@ -843,7 +815,7 @@ public class Player extends Entity implements CommandExecutor {
return;
}
clothing.put(slot, item);
equippedClothing.put(slot, item);
zone.sendMessage(new EntityChangeMessage(id, getAppearanceConfig()));
}
@ -856,17 +828,17 @@ public class Player extends Entity implements CommandExecutor {
}
public Map<ClothingSlot, Item> getEquippedClothing() {
return Collections.unmodifiableMap(clothing);
return Collections.unmodifiableMap(equippedClothing);
}
public void setColor(ColorSlot slot, String hex) {
// TODO check if the string is actually a valid hex color
colors.put(slot, hex);
equippedColors.put(slot, hex);
zone.sendMessage(new EntityChangeMessage(id, getAppearanceConfig()));
}
public Map<ColorSlot, String> getEquippedColors(){
return Collections.unmodifiableMap(colors);
public Map<ColorSlot, String> getEquippedColors() {
return Collections.unmodifiableMap(equippedColors);
}
public void setSkillLevel(Skill skill, int level) {
@ -1060,36 +1032,14 @@ public class Player extends Entity implements CommandExecutor {
return connection != null && connection.isOpen();
}
@JsonValue
public Map<String, Object> getJsonValue() {
Map<String, Object> map = new HashMap<>();
map.put("email", email);
map.put("password_hash", password);
map.put("auth_tokens", authTokens);
map.put("name", name);
map.put("admin", admin);
map.put("experience", experience);
map.put("skill_points", skillPoints);
map.put("karma", karma);
map.put("crowns", crowns);
map.put("current_zone", zone.getDocumentId());
map.put("ignored_hints", ignoredHints);
map.put("achievements", achievements);
map.put("skills", skills);
map.put("equipped_colors", colors);
map.put("equipped_clothing", clothing);
map.put("inventory", inventory);
map.put("statistics", statistics);
return map;
}
private Map<String, Object> getAppearanceConfig() {
Map<String, Object> appearance = new HashMap<>();
for(Entry<ClothingSlot, Item> entry : clothing.entrySet()) {
for(Entry<ClothingSlot, Item> entry : equippedClothing.entrySet()) {
appearance.put(entry.getKey().getId(), entry.getValue().getId());
}
for(Entry<ColorSlot, String> entry : colors.entrySet()) {
for(Entry<ColorSlot, String> entry : equippedColors.entrySet()) {
appearance.put(entry.getKey().getId(), entry.getValue());
}

View file

@ -0,0 +1,139 @@
package brainwine.gameserver.entity.player;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
import brainwine.gameserver.achievements.Achievement;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.zone.Zone;
@JsonIgnoreProperties(ignoreUnknown = true)
public class PlayerConfigFile {
private String name;
private String email;
private String passwordHash;
private Zone currentZone;
private boolean admin;
private int experience;
private int skillPoints;
private int karma;
private int crowns;
private Inventory inventory = new Inventory();
private PlayerStatistics statistics = new PlayerStatistics();
private List<String> authTokens = new ArrayList<>();
private Set<Achievement> achievements = new HashSet<>();
private Map<String, Float> ignoredHints = new HashMap<>();
private Map<Skill, Integer> skills = new HashMap<>();
private Map<ClothingSlot, Item> equippedClothing = new HashMap<>();
private Map<ColorSlot, String> equippedColors = new HashMap<>();
public PlayerConfigFile(Player player) {
this.name = player.getName();
this.email = player.getEmail();
this.passwordHash = player.getPassword();
this.currentZone = player.getZone();
this.admin = player.isAdmin();
this.experience = player.getExperience();
this.skillPoints = player.getSkillPoints();
this.karma = player.getKarma();
this.crowns = player.getCrowns();
this.inventory = player.getInventory();
this.statistics = player.getStatistics();
this.authTokens = player.getAuthTokens();
this.achievements = player.getAchievements();
this.ignoredHints = player.getIgnoredHints();
this.skills = player.getSkills();
this.equippedClothing = player.getEquippedClothing();
this.equippedColors = player.getEquippedColors();
}
@JsonCreator
private PlayerConfigFile() {}
@JsonSetter(nulls = Nulls.FAIL)
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getPasswordHash() {
return passwordHash;
}
public Zone getCurrentZone() {
return currentZone;
}
public boolean isAdmin() {
return admin;
}
@JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP)
public List<String> getAuthTokens() {
return authTokens;
}
public int getExperience() {
return experience;
}
public int getSkillPoints() {
return skillPoints;
}
public int getKarma() {
return karma;
}
public int getCrowns() {
return crowns;
}
@JsonSetter(nulls = Nulls.SKIP)
public Inventory getInventory() {
return inventory;
}
@JsonSetter(nulls = Nulls.SKIP)
public PlayerStatistics getStatistics() {
return statistics;
}
@JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP)
public Set<Achievement> getAchievements() {
return achievements;
}
@JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP)
public Map<String, Float> getIgnoredHints() {
return ignoredHints;
}
@JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP)
public Map<Skill, Integer> getSkills() {
return skills;
}
@JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP)
public Map<ClothingSlot, Item> getEquippedClothing() {
return equippedClothing;
}
@JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP)
public Map<ColorSlot, String> getEquippedColors() {
return equippedColors;
}
}

View file

@ -12,8 +12,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mindrot.jbcrypt.BCrypt;
import com.fasterxml.jackson.databind.InjectableValues;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.server.pipeline.Connection;
import brainwine.shared.JsonHelper;
@ -53,7 +51,8 @@ public class PlayerManager {
String id = file.getName().replace(".json", "");
try {
Player player = JsonHelper.readValue(file, Player.class, new InjectableValues.Std().addValue("documentId", id));
PlayerConfigFile configFile = JsonHelper.readValue(file, PlayerConfigFile.class);
Player player = new Player(id, configFile);
if(player.getZone() == null) {
player.setZone(GameServer.getInstance().getZoneManager().getRandomZone());
@ -83,7 +82,7 @@ public class PlayerManager {
File file = new File("players", player.getDocumentId() + ".json");
try {
JsonHelper.writeValue(file, player);
JsonHelper.writeValue(file, new PlayerConfigFile(player));
} catch(Exception e) {
logger.error("Could not save player id {}", player.getDocumentId(), e);
}

View file

@ -7,8 +7,8 @@ import java.util.Map.Entry;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import brainwine.gameserver.achievements.CraftingAchievement;
@ -39,13 +39,17 @@ public class PlayerStatistics {
private int dungeonsRaided;
private int deaths;
@JsonBackReference
@JsonIgnore
private Player player;
@JsonCreator
private PlayerStatistics() {}
protected PlayerStatistics() {}
public PlayerStatistics(Player player) {
protected PlayerStatistics(Player player) {
this.player = player;
}
protected void setPlayer(Player player) {
this.player = player;
}

View file

@ -0,0 +1,34 @@
package brainwine.gameserver.serialization;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import brainwine.gameserver.achievements.Achievement;
/**
* Seriously, Jackson has the stupidest problems sometimes.
*/
public class AchievementSerializer extends StdSerializer<Achievement> {
public static final AchievementSerializer INSTANCE = new AchievementSerializer();
private static final long serialVersionUID = 7660825036762291561L;
protected AchievementSerializer() {
super(Achievement.class);
}
@Override
public void serialize(Achievement achievement, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeString(achievement.getTitle());
}
@Override
public void serializeWithType(Achievement achievement, JsonGenerator generator, SerializerProvider provider,
TypeSerializer typeSerializer) throws IOException {
serialize(achievement, generator, provider);
}
}