/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the Software), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.lanternpowered.server.data.io.store.entity; import static org.lanternpowered.server.data.util.DataUtil.deserializeManipulatorList; import static org.lanternpowered.server.data.util.DataUtil.getOrCreateView; import static org.lanternpowered.server.data.util.DataUtil.getSerializedManipulatorList; import com.flowpowered.math.vector.Vector3d; import com.google.common.collect.Lists; import org.lanternpowered.server.data.io.store.IdentifiableObjectStore; import org.lanternpowered.server.data.io.store.SimpleValueContainer; import org.lanternpowered.server.data.io.store.data.DataHolderStore; import org.lanternpowered.server.data.key.LanternKeys; import org.lanternpowered.server.data.util.DataQueries; import org.lanternpowered.server.effect.potion.LanternPotionEffect; import org.lanternpowered.server.effect.potion.LanternPotionEffectType; import org.lanternpowered.server.entity.LanternEntity; import org.lanternpowered.server.game.Lantern; import org.lanternpowered.server.game.registry.type.effect.PotionEffectTypeRegistryModule; import org.lanternpowered.server.text.LanternTexts; import org.spongepowered.api.data.DataQuery; import org.spongepowered.api.data.DataView; import org.spongepowered.api.data.MemoryDataContainer; import org.spongepowered.api.data.key.Keys; import org.spongepowered.api.data.manipulator.DataManipulator; import org.spongepowered.api.data.persistence.InvalidDataException; import org.spongepowered.api.effect.potion.PotionEffect; import org.spongepowered.api.effect.potion.PotionEffectType; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; public class EntityStore<T extends LanternEntity> extends DataHolderStore<T> implements IdentifiableObjectStore<T> { private static final DataQuery SPONGE_DATA = DataQueries.FORGE_DATA.then(DataQueries.SPONGE_DATA); private static final DataQuery POSITION = DataQuery.of("Pos"); private static final DataQuery VELOCITY = DataQuery.of("Motion"); private static final DataQuery ROTATION = DataQuery.of("Rotation"); private static final DataQuery SCALE = DataQuery.of("Scale"); // Lantern private static final DataQuery FALL_DISTANCE = DataQuery.of("FallDistance"); private static final DataQuery FIRE_TICKS = DataQuery.of("Fire"); private static final DataQuery UNIQUE_ID_MOST = DataQuery.of("UUIDMost"); private static final DataQuery UNIQUE_ID_LEAST = DataQuery.of("UUIDLeast"); static final DataQuery UNIQUE_ID = DataQuery.of("UUID"); private static final DataQuery OLD_UNIQUE_ID_MOST = DataQuery.of("PersistentIDMSB"); private static final DataQuery OLD_UNIQUE_ID_LEAST = DataQuery.of("PersistentIDLSB"); // A field that will be converted if present private static final DataQuery BUKKIT_MAX_HEALTH = DataQuery.of("Bukkit.MaxHealth"); private static final DataQuery OLD_HEALTH = DataQuery.of("HealF"); private static final DataQuery HEALTH = DataQuery.of("Health"); private static final DataQuery REMAINING_AIR = DataQuery.of("Air"); private static final DataQuery MAX_AIR = DataQuery.of("maxAir"); private static final DataQuery DISPLAY_NAME = DataQuery.of("CustomName"); private static final DataQuery CUSTOM_NAME_VISIBLE = DataQuery.of("CustomNameVisible"); private static final DataQuery CUSTOM_NAME = DataQuery.of("CustomName"); private static final DataQuery INVULNERABLE = DataQuery.of("Invulnerable"); private static final DataQuery PORTAL_COOLDOWN_TICKS = DataQuery.of("PortalCooldown"); private static final DataQuery ON_GROUND = DataQuery.of("OnGround"); private static final DataQuery NO_AI = DataQuery.of("NoAI"); private static final DataQuery PERSISTS = DataQuery.of("PersistenceRequired"); private static final DataQuery CAN_GRIEF = DataQuery.of("CanGrief"); private static final DataQuery ABSORPTION_AMOUNT = DataQuery.of("AbsorptionAmount"); private static final DataQuery CAN_PICK_UP_LOOT = DataQuery.of("CanPickUpLoot"); private static final DataQuery POTION_EFFECTS = DataQuery.of("ActiveEffects"); private static final DataQuery POTION_EFFECT_ID = DataQuery.of("Id"); private static final DataQuery POTION_EFFECT_AMPLIFIER = DataQuery.of("Amplifier"); private static final DataQuery POTION_EFFECT_DURATION = DataQuery.of("Duration"); private static final DataQuery POTION_EFFECT_AMBIENT = DataQuery.of("Ambient"); private static final DataQuery POTION_EFFECT_SHOW_PARTICLES = DataQuery.of("ShowParticles"); private static final DataQuery EXHAUSTION = DataQuery.of("foodExhaustionLevel"); private static final DataQuery SATURATION = DataQuery.of("foodSaturationLevel"); private static final DataQuery FOOD_LEVEL = DataQuery.of("foodLevel"); private static final DataQuery FOOD_TICK_TIMER = DataQuery.of("foodTickTimer"); // TODO private static final DataQuery IS_ELYTRA_FLYING = DataQuery.of("FallFlying"); private static final DataQuery IS_GLOWING = DataQuery.of("Glowing"); // TODO: Use this more globally? private static final DataQuery DATA_VERSION = DataQuery.of("DataVersion"); /** * The current data version. */ private static final int CURRENT_DATA_VERSION = 158; @Override public UUID deserializeUniqueId(DataView dataView) { Optional<Long> uuidMost = dataView.getLong(UNIQUE_ID_MOST); Optional<Long> uuidLeast = dataView.getLong(UNIQUE_ID_LEAST); if (uuidMost.isPresent() && uuidLeast.isPresent()) { return new UUID(uuidMost.get(), uuidLeast.get()); } else { // Try to convert from an older format uuidMost = dataView.getLong(OLD_UNIQUE_ID_MOST); uuidLeast = dataView.getLong(OLD_UNIQUE_ID_LEAST); if (uuidMost.isPresent() && uuidLeast.isPresent()) { return new UUID(uuidMost.get(), uuidLeast.get()); } else { final Optional<String> uuidString = dataView.getString(UNIQUE_ID); if (uuidString.isPresent()) { return UUID.fromString(uuidString.get()); } } } return UUID.randomUUID(); } @Override public void serializeUniqueId(DataView dataView, UUID uniqueId) { dataView.set(UNIQUE_ID_MOST, uniqueId.getMostSignificantBits()); dataView.set(UNIQUE_ID_LEAST, uniqueId.getLeastSignificantBits()); } @Override public void deserialize(T entity, DataView dataView) { entity.setPosition(fromDoubleList(dataView.getDoubleList(POSITION).get())); entity.setVelocity(fromDoubleList(dataView.getDoubleList(VELOCITY).get())); dataView.getDoubleList(SCALE).ifPresent(list -> entity.setScale(fromDoubleList(list))); final List<Float> rotationList = dataView.getFloatList(ROTATION).get(); // Yaw, Pitch, Roll (X, Y, Z) - Index 0 and 1 are swapped! entity.setRotation(new Vector3d(rotationList.get(1), rotationList.get(0), rotationList.size() > 2 ? rotationList.get(2) : 0)); entity.setOnGround(dataView.getInt(ON_GROUND).orElse(0) > 0); super.deserialize(entity, dataView); } @Override public void serialize(T entity, DataView dataView) { dataView.set(DATA_VERSION, CURRENT_DATA_VERSION); dataView.set(POSITION, toDoubleList(entity.getPosition())); final Vector3d rotation = entity.getRotation(); // Yaw, Pitch, Roll (X, Y, Z) - Index 0 and 1 are swapped! dataView.set(ROTATION, Lists.newArrayList((float) rotation.getY(), (float) rotation.getX(), (float) rotation.getZ())); dataView.set(SCALE, toDoubleList(entity.getScale())); dataView.set(ON_GROUND, (byte) (entity.isOnGround() ? 1 : 0)); super.serialize(entity, dataView); } static Vector3d fromDoubleList(List<Double> doubleList) { return new Vector3d(doubleList.get(0), doubleList.get(1), doubleList.get(2)); } static List<Double> toDoubleList(Vector3d vector3d) { return Lists.newArrayList(vector3d.getX(), vector3d.getY(), vector3d.getZ()); } @Override public void serializeValues(T object, SimpleValueContainer valueContainer, DataView dataView) { // Here will we remove all the vanilla properties and delegate the // rest through the default serialization system valueContainer.remove(Keys.VELOCITY).ifPresent(v -> dataView.set(VELOCITY, toDoubleList(v))); valueContainer.remove(Keys.FIRE_TICKS).ifPresent(v -> dataView.set(FIRE_TICKS, v)); valueContainer.remove(Keys.FALL_DISTANCE).ifPresent(v -> dataView.set(FALL_DISTANCE, v)); valueContainer.remove(Keys.HEALTH).ifPresent(v -> dataView.set(HEALTH, v.floatValue())); valueContainer.remove(Keys.REMAINING_AIR).ifPresent(v -> dataView.set(REMAINING_AIR, v)); valueContainer.remove(LanternKeys.ABSORPTION_AMOUNT).ifPresent(v -> dataView.set(ABSORPTION_AMOUNT, v.floatValue())); final DataView spongeView = getOrCreateView(dataView, SPONGE_DATA); valueContainer.remove(Keys.MAX_AIR).ifPresent(v -> spongeView.set(MAX_AIR, v)); valueContainer.remove(Keys.CAN_GRIEF).ifPresent(v -> spongeView.set(CAN_GRIEF, (byte) (v ? 1 : 0))); valueContainer.remove(Keys.DISPLAY_NAME).ifPresent(v -> dataView.set(DISPLAY_NAME, LanternTexts.toLegacy(v))); valueContainer.remove(Keys.CUSTOM_NAME_VISIBLE).ifPresent(v -> dataView.set(CUSTOM_NAME_VISIBLE, (byte) (v ? 1 : 0))); valueContainer.remove(LanternKeys.INVULNERABLE).ifPresent(v -> dataView.set(INVULNERABLE, (byte) (v ? 1 : 0))); valueContainer.remove(LanternKeys.PORTAL_COOLDOWN_TICKS).ifPresent(v -> dataView.set(PORTAL_COOLDOWN_TICKS, v)); valueContainer.remove(Keys.AI_ENABLED).ifPresent(v -> dataView.set(NO_AI, (byte) (v ? 0 : 1))); valueContainer.remove(Keys.PERSISTS).ifPresent(v -> dataView.set(PERSISTS, (byte) (v ? 1 : 0))); valueContainer.remove(LanternKeys.CAN_PICK_UP_LOOT).ifPresent(v -> dataView.set(CAN_PICK_UP_LOOT, (byte) (v ? 1 : 0))); valueContainer.remove(Keys.DISPLAY_NAME).ifPresent(v -> dataView.set(CUSTOM_NAME, LanternTexts.toLegacy(v))); valueContainer.remove(Keys.POTION_EFFECTS).ifPresent(v -> { if (v.isEmpty()) { return; } // TODO: Allow out impl to use integers as amplifier and use a string as effect id, // without breaking the official format dataView.set(POTION_EFFECTS, v.stream().map(effect -> new MemoryDataContainer(DataView.SafetyMode.NO_DATA_CLONED) .set(POTION_EFFECT_ID, (byte) ((LanternPotionEffectType) effect.getType()).getInternalId()) .set(POTION_EFFECT_AMPLIFIER, (byte) effect.getAmplifier()) .set(POTION_EFFECT_DURATION, effect.getDuration()) .set(POTION_EFFECT_AMBIENT, (byte) (effect.isAmbient() ? 1 : 0)) .set(POTION_EFFECT_SHOW_PARTICLES, (byte) (effect.getShowParticles() ? 1 : 0)))); }); valueContainer.remove(Keys.FOOD_LEVEL).ifPresent(v -> dataView.set(FOOD_LEVEL, v)); valueContainer.remove(Keys.EXHAUSTION).ifPresent(v -> dataView.set(EXHAUSTION, v.floatValue())); valueContainer.remove(Keys.SATURATION).ifPresent(v -> dataView.set(SATURATION, v.floatValue())); valueContainer.remove(LanternKeys.IS_ELYTRA_FLYING).ifPresent(v -> dataView.set(IS_ELYTRA_FLYING, (byte) (v ? 1 : 0))); valueContainer.remove(Keys.GLOWING).ifPresent(v -> dataView.set(IS_GLOWING, (byte) (v ? 1 : 0))); super.serializeValues(object, valueContainer, dataView); } @Override public void deserializeValues(T object, SimpleValueContainer valueContainer, DataView dataView) { dataView.getInt(FIRE_TICKS).ifPresent(v -> valueContainer.set(Keys.FIRE_TICKS, v)); dataView.getDouble(FALL_DISTANCE).ifPresent(v -> valueContainer.set(Keys.FALL_DISTANCE, v.floatValue())); dataView.getInt(REMAINING_AIR).ifPresent(v -> valueContainer.set(Keys.REMAINING_AIR, v)); // The health Optional<Double> health = dataView.getDouble(HEALTH); if (!health.isPresent()) { // Try to convert old data health = dataView.getDouble(OLD_HEALTH); } health.ifPresent(v -> valueContainer.set(Keys.HEALTH, v)); dataView.getString(DISPLAY_NAME).ifPresent(v -> valueContainer.set(Keys.DISPLAY_NAME, LanternTexts.fromLegacy(v))); dataView.getInt(CUSTOM_NAME_VISIBLE).ifPresent(v -> valueContainer.set(Keys.CUSTOM_NAME_VISIBLE, v > 0)); dataView.getInt(INVULNERABLE).ifPresent(v -> valueContainer.set(LanternKeys.INVULNERABLE, v > 0)); dataView.getInt(PORTAL_COOLDOWN_TICKS).ifPresent(v -> valueContainer.set(LanternKeys.PORTAL_COOLDOWN_TICKS, v)); dataView.getInt(NO_AI).ifPresent(v -> valueContainer.set(Keys.AI_ENABLED, v == 0)); dataView.getInt(PERSISTS).ifPresent(v -> valueContainer.set(Keys.PERSISTS, v > 0)); dataView.getView(SPONGE_DATA).ifPresent(view -> { view.getInt(MAX_AIR).ifPresent(v -> valueContainer.set(Keys.MAX_AIR, v)); view.getInt(CAN_GRIEF).ifPresent(v -> valueContainer.set(Keys.CAN_GRIEF, v > 0)); }); dataView.getDouble(ABSORPTION_AMOUNT).ifPresent(v -> valueContainer.set(LanternKeys.ABSORPTION_AMOUNT, v)); dataView.getDouble(CAN_PICK_UP_LOOT).ifPresent(v -> valueContainer.set(LanternKeys.CAN_PICK_UP_LOOT, v > 0)); dataView.getString(CUSTOM_NAME).ifPresent(v -> valueContainer.set(Keys.DISPLAY_NAME, LanternTexts.fromLegacy(v))); dataView.getViewList(POTION_EFFECTS).ifPresent(v -> { if (v.isEmpty()) { return; } final PotionEffectTypeRegistryModule module = Lantern.getRegistry().getRegistryModule(PotionEffectTypeRegistryModule.class).get(); final List<PotionEffect> potionEffects = new ArrayList<>(); for (DataView view : v) { final int internalId = view.getInt(POTION_EFFECT_ID).get() & 0xff; final PotionEffectType type = module.getByInternalId(internalId).orElse(null); if (type == null) { Lantern.getLogger().warn("Deserialized a potion effect type with unknown id: " + internalId); continue; } final int amplifier = view.getInt(POTION_EFFECT_AMPLIFIER).get() & 0xff; final int duration = view.getInt(POTION_EFFECT_DURATION).get(); final boolean ambient = view.getInt(POTION_EFFECT_AMBIENT).orElse(0) > 0; final boolean showParticles = view.getInt(POTION_EFFECT_SHOW_PARTICLES).orElse(0) > 0; potionEffects.add(new LanternPotionEffect(type, duration, amplifier, ambient, showParticles)); } if (!potionEffects.isEmpty()) { valueContainer.set(Keys.POTION_EFFECTS, potionEffects); } }); dataView.getInt(FOOD_LEVEL).ifPresent(v -> valueContainer.set(Keys.FOOD_LEVEL, v)); dataView.getDouble(EXHAUSTION).ifPresent(v -> valueContainer.set(Keys.EXHAUSTION, v)); dataView.getDouble(SATURATION).ifPresent(v -> valueContainer.set(Keys.SATURATION, v)); dataView.getInt(IS_ELYTRA_FLYING).ifPresent(v -> valueContainer.set(LanternKeys.IS_ELYTRA_FLYING, v > 0)); dataView.getInt(IS_GLOWING).ifPresent(v -> valueContainer.set(Keys.GLOWING, v > 0)); super.deserializeValues(object, valueContainer, dataView); } @Override public void serializeAdditionalData(T object, List<DataManipulator<?, ?>> manipulators, DataView dataView) { DataView spongeData = getOrCreateView(dataView, SPONGE_DATA); if (!manipulators.isEmpty()) { spongeData.set(DataQueries.CUSTOM_MANIPULATORS, getSerializedManipulatorList(manipulators)); } } @Override public void deserializeAdditionalData(T object, List<DataManipulator<?, ?>> manipulators, DataView dataView) { try { dataView.getView(SPONGE_DATA).ifPresent(view -> dataView.getViewList(DataQueries.CUSTOM_MANIPULATORS) .ifPresent(views -> manipulators.addAll(deserializeManipulatorList(views)))); } catch (InvalidDataException e) { Lantern.getLogger().error("Could not deserialize custom plugin data! ", e); } } }