diff --git a/src/main/java/org/spongepowered/api/data/DataPerspective.java b/src/main/java/org/spongepowered/api/data/DataPerspective.java
new file mode 100644
index 0000000000..daf5fcdd31
--- /dev/null
+++ b/src/main/java/org/spongepowered/api/data/DataPerspective.java
@@ -0,0 +1,13 @@
+package org.spongepowered.api.data;
+
+import org.spongepowered.api.data.value.ValueContainer;
+import org.spongepowered.plugin.PluginContainer;
+
+public interface DataPerspective {
+
+ Iterable perceives();
+
+ ValueContainer getDataPerception(DataPerspective perspective);
+
+ DataHolder.Mutable createDataPerception(PluginContainer plugin, DataPerspective perspective);
+}
diff --git a/src/main/java/org/spongepowered/api/data/DataPerspectiveResolver.java b/src/main/java/org/spongepowered/api/data/DataPerspectiveResolver.java
new file mode 100644
index 0000000000..42bc08d230
--- /dev/null
+++ b/src/main/java/org/spongepowered/api/data/DataPerspectiveResolver.java
@@ -0,0 +1,31 @@
+package org.spongepowered.api.data;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.spongepowered.api.data.value.Value;
+
+public interface DataPerspectiveResolver, E> {
+
+ /**
+ * Gets the {@link Key} this resolver supports.
+ *
+ * @return The key
+ */
+ Key key();
+
+ /**
+ * When multiple plugins provide the same key this is used to
+ * merge and pick the best.
+ *
+ * @return The merged value
+ */
+ E merge(Iterable values);
+
+ /**
+ * When data holders value changes when looking at perspective of.
+ *
+ * @param dataHolder The data holder which value was overridden.
+ * @param perspective The perspective it is perceived from.
+ * @param value The value.
+ */
+ void apply(DataHolder dataHolder, DataPerspective perspective, @Nullable E value);
+}
diff --git a/src/main/java/org/spongepowered/api/data/DataRegistration.java b/src/main/java/org/spongepowered/api/data/DataRegistration.java
index 9f46cfbe5e..60bc6fb5ad 100644
--- a/src/main/java/org/spongepowered/api/data/DataRegistration.java
+++ b/src/main/java/org/spongepowered/api/data/DataRegistration.java
@@ -111,6 +111,8 @@ static Builder builder() {
*/
Optional dataStore(Class extends DataHolder> token);
+ , E> Optional> dataPerspectiveResolverFor(Key key);
+
/**
* Gets the registered {@link Key Keys} this controls. Note that each
* {@link Key} can only be registered/owned by a single
@@ -181,6 +183,8 @@ interface Builder extends org.spongepowered.api.util.Builder provider) throws DuplicateProviderException;
+ Builder perspectiveResolver(DataPerspectiveResolver, ?> resolver);
+
/**
* Gives the {@link Key} to this builder signifying the key is to be
* registered either with an applicable {@link DataProvider} or an
diff --git a/src/main/java/org/spongepowered/api/entity/Entity.java b/src/main/java/org/spongepowered/api/entity/Entity.java
index 2f39680f34..a6a20da829 100644
--- a/src/main/java/org/spongepowered/api/entity/Entity.java
+++ b/src/main/java/org/spongepowered/api/entity/Entity.java
@@ -28,6 +28,7 @@
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEventSource;
+import org.spongepowered.api.data.DataPerspective;
import org.spongepowered.api.data.Keys;
import org.spongepowered.api.data.SerializableDataHolder;
import org.spongepowered.api.data.value.ListValue;
@@ -79,7 +80,7 @@
*/
@DoNotStore
public interface Entity extends Identifiable, HoverEventSource, Locatable, EntityProjectileSource, Sound.Emitter,
- SerializableDataHolder.Mutable, RandomProvider, TeamMember {
+ SerializableDataHolder.Mutable, RandomProvider, TeamMember, DataPerspective {
/**
* Gets the {@link EntityType}.
diff --git a/src/main/java/org/spongepowered/api/scoreboard/Team.java b/src/main/java/org/spongepowered/api/scoreboard/Team.java
index 725c16dc6b..b4f5939480 100644
--- a/src/main/java/org/spongepowered/api/scoreboard/Team.java
+++ b/src/main/java/org/spongepowered/api/scoreboard/Team.java
@@ -27,6 +27,7 @@
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.spongepowered.api.Sponge;
+import org.spongepowered.api.data.DataPerspective;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.util.CopyableBuilder;
@@ -57,7 +58,7 @@
* For it to work, both players must have the same scoreboard, and be on a team
* registered to said scoreboard.
*/
-public interface Team {
+public interface Team extends DataPerspective {
/**
* Creates a new {@link Builder} to build a {@link Team}.
diff --git a/src/main/java/org/spongepowered/api/world/World.java b/src/main/java/org/spongepowered/api/world/World.java
index 1a81f4bc66..ed2a540093 100644
--- a/src/main/java/org/spongepowered/api/world/World.java
+++ b/src/main/java/org/spongepowered/api/world/World.java
@@ -27,6 +27,7 @@
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import org.spongepowered.api.Server;
+import org.spongepowered.api.data.DataPerspective;
import org.spongepowered.api.effect.Viewer;
import org.spongepowered.api.entity.Entity;
import org.spongepowered.api.entity.living.player.Player;
@@ -59,7 +60,8 @@ public interface World, L extends Location> extends
Viewer,
ArchetypeVolumeCreator,
WeatherUniverse,
- RegistryHolder {
+ RegistryHolder,
+ DataPerspective {
/**
* Gets the {@link WorldProperties properties}.