diff --git a/SpongeAPI b/SpongeAPI index cb4ee935103..22676ca8118 160000 --- a/SpongeAPI +++ b/SpongeAPI @@ -1 +1 @@ -Subproject commit cb4ee935103e4cd09e9bcb7a4e8cd649e0287fc9 +Subproject commit 22676ca81183bbb78c814e694416457954d1cedc diff --git a/src/main/java/org/spongepowered/common/bridge/data/VanishableBridge.java b/src/main/java/org/spongepowered/common/bridge/data/VanishableBridge.java index b8673470a2b..bc63ad39777 100644 --- a/src/main/java/org/spongepowered/common/bridge/data/VanishableBridge.java +++ b/src/main/java/org/spongepowered/common/bridge/data/VanishableBridge.java @@ -24,6 +24,7 @@ */ package org.spongepowered.common.bridge.data; +import org.spongepowered.api.data.DataPerspective; import org.spongepowered.api.effect.VanishState; public interface VanishableBridge { @@ -32,6 +33,8 @@ public interface VanishableBridge { void bridge$vanishState(VanishState state); + void bridge$vanishState(VanishState state, DataPerspective perspective); + boolean bridge$isInvisible(); void bridge$setInvisible(boolean invisible); diff --git a/src/main/java/org/spongepowered/common/bridge/world/entity/EntityBridge_Contextual.java b/src/main/java/org/spongepowered/common/bridge/world/entity/EntityBridge_Contextual.java new file mode 100644 index 00000000000..5fbe2164186 --- /dev/null +++ b/src/main/java/org/spongepowered/common/bridge/world/entity/EntityBridge_Contextual.java @@ -0,0 +1,32 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.bridge.world.entity; + +import org.spongepowered.common.data.contextual.ContextualDataHolder; + +public interface EntityBridge_Contextual { + + ContextualDataHolder bridge$contextualData(); +} diff --git a/src/main/java/org/spongepowered/common/bridge/world/scores/PlayerTeamBridge.java b/src/main/java/org/spongepowered/common/bridge/world/scores/PlayerTeamBridge.java index d6cfa6c9b9a..625a5b0d802 100644 --- a/src/main/java/org/spongepowered/common/bridge/world/scores/PlayerTeamBridge.java +++ b/src/main/java/org/spongepowered/common/bridge/world/scores/PlayerTeamBridge.java @@ -29,6 +29,8 @@ import net.kyori.adventure.text.format.NamedTextColor; import net.minecraft.server.level.ServerPlayer; +import java.util.List; + public interface PlayerTeamBridge { Component bridge$getDisplayName(); @@ -50,4 +52,10 @@ public interface PlayerTeamBridge { Audience bridge$getTeamChannel(ServerPlayer player); Audience bridge$getNonTeamChannel(); + + void bridge$addPlayer(ServerPlayer player); + + void bridge$removePlayer(ServerPlayer player, boolean sendPackets); + + List bridge$getPlayers(); } diff --git a/src/main/java/org/spongepowered/common/bridge/world/scores/PlayerTeamBridge_Contextual.java b/src/main/java/org/spongepowered/common/bridge/world/scores/PlayerTeamBridge_Contextual.java new file mode 100644 index 00000000000..839a1e9adfe --- /dev/null +++ b/src/main/java/org/spongepowered/common/bridge/world/scores/PlayerTeamBridge_Contextual.java @@ -0,0 +1,32 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.bridge.world.scores; + +import org.spongepowered.common.data.contextual.ContextualDataDelegate; + +public interface PlayerTeamBridge_Contextual { + + ContextualDataDelegate bridge$contextualData(); +} diff --git a/src/main/java/org/spongepowered/common/data/SpongeDataManager.java b/src/main/java/org/spongepowered/common/data/SpongeDataManager.java index 56b219c64ea..8a0a50714bd 100644 --- a/src/main/java/org/spongepowered/common/data/SpongeDataManager.java +++ b/src/main/java/org/spongepowered/common/data/SpongeDataManager.java @@ -39,6 +39,7 @@ import org.spongepowered.api.data.DataHolderBuilder; import org.spongepowered.api.data.DataManager; import org.spongepowered.api.data.DataManipulator.Mutable; +import org.spongepowered.api.data.DataPerspectiveResolver; import org.spongepowered.api.data.DataProvider; import org.spongepowered.api.data.Key; import org.spongepowered.api.data.meta.BannerPatternLayer; @@ -91,6 +92,7 @@ import org.spongepowered.common.data.builder.util.weighted.BaseAndVarianceBuilder; import org.spongepowered.common.data.builder.util.weighted.FixedBuilder; import org.spongepowered.common.data.builder.util.weighted.OptionalVarianceBuilder; +import org.spongepowered.common.data.contextual.DataPerspectiveResolverRegistry; import org.spongepowered.common.data.datasync.entity.EntityAirSupplyConverter; import org.spongepowered.common.data.datasync.entity.EntityBabyConverter; import org.spongepowered.common.data.datasync.entity.EntityCustomNameConverter; @@ -144,6 +146,7 @@ public final class SpongeDataManager implements DataManager { private final DataStoreRegistry dataStoreRegistry; private final DataProviderRegistry dataProviderRegistry; + private final DataPerspectiveResolverRegistry dataPerspectiveResolverRegistry; private final Map, DataBuilder> builders; private final Map>, DataHolderBuilder.Immutable> immutableDataBuilderMap; private final Map, List> updatersMap; @@ -158,6 +161,7 @@ private SpongeDataManager() { this.dataStoreRegistry = new DataStoreRegistry(); this.dataProviderRegistry = new DataProviderRegistry(); + this.dataPerspectiveResolverRegistry = new DataPerspectiveResolverRegistry(); this.builders = new HashMap<>(); this.immutableDataBuilderMap = new MapMaker() .concurrencyLevel(4) @@ -333,6 +337,9 @@ public void registerCustomDataRegistration(final SpongeDataRegistration registra for (final Key key : registration.keys()) { this.registerCustomDataProviderForKey(registration, key); + + final Optional> resolver = registration.dataPerspectiveResolverFor(key); + resolver.ifPresent(this.dataPerspectiveResolverRegistry::register); } } @@ -363,6 +370,8 @@ public void registerDataRegistration(final SpongeDataRegistration registration) for (DataProvider provider : providers) { this.dataProviderRegistry.register(provider); } + final Optional> resolver = registration.dataPerspectiveResolverFor(key); + resolver.ifPresent(this.dataPerspectiveResolverRegistry::register); } } @@ -389,6 +398,10 @@ public static DataProviderRegistry getProviderRegistry() { return SpongeDataManager.INSTANCE.dataProviderRegistry; } + public static DataPerspectiveResolverRegistry getDataPerspectiveResolverRegistry() { + return SpongeDataManager.INSTANCE.dataPerspectiveResolverRegistry; + } + public void registerDefaultBuilders() { this.registerBuilder(ItemStack.class, new SpongeItemStack.BuilderImpl()); this.registerBuilder(ItemStackSnapshot.class, new SpongeItemStackSnapshotDataBuilder()); diff --git a/src/main/java/org/spongepowered/common/data/SpongeDataRegistration.java b/src/main/java/org/spongepowered/common/data/SpongeDataRegistration.java index 5a42a79c5a9..881a4e7320f 100644 --- a/src/main/java/org/spongepowered/common/data/SpongeDataRegistration.java +++ b/src/main/java/org/spongepowered/common/data/SpongeDataRegistration.java @@ -28,6 +28,7 @@ import io.leangen.geantyref.GenericTypeReflector; import io.leangen.geantyref.TypeToken; import org.spongepowered.api.data.DataHolder; +import org.spongepowered.api.data.DataPerspectiveResolver; import org.spongepowered.api.data.DataProvider; import org.spongepowered.api.data.DataRegistration; import org.spongepowered.api.data.Key; @@ -46,11 +47,13 @@ public final class SpongeDataRegistration implements DataRegistration { final List> keys; final Map dataStoreMap; final Multimap dataProviderMap; + final Map dataPerspectiveResolverMap; SpongeDataRegistration(final SpongeDataRegistrationBuilder builder) { this.keys = builder.keys; this.dataStoreMap = builder.dataStoreMap; this.dataProviderMap = builder.dataProviderMap; + this.dataPerspectiveResolverMap = builder.dataPerspectiveResolverMap; } @Override @@ -69,6 +72,11 @@ public Optional dataStore(final Class token) { return this.getDataStore0(token); } + @Override + public , E> Optional> dataPerspectiveResolverFor(Key key) { + return Optional.ofNullable(this.dataPerspectiveResolverMap.get(key)); + } + private Optional getDataStore0(final Type type) { DataStore dataStore = this.dataStoreMap.get(type); if (dataStore != null) { diff --git a/src/main/java/org/spongepowered/common/data/SpongeDataRegistrationBuilder.java b/src/main/java/org/spongepowered/common/data/SpongeDataRegistrationBuilder.java index aeecb69aab1..c08e46eadc8 100644 --- a/src/main/java/org/spongepowered/common/data/SpongeDataRegistrationBuilder.java +++ b/src/main/java/org/spongepowered/common/data/SpongeDataRegistrationBuilder.java @@ -26,6 +26,7 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; +import org.spongepowered.api.data.DataPerspectiveResolver; import org.spongepowered.api.data.DataProvider; import org.spongepowered.api.data.DataRegistration; import org.spongepowered.api.data.DuplicateDataStoreException; @@ -45,6 +46,7 @@ public final class SpongeDataRegistrationBuilder implements DataRegistration.Bui Multimap dataProviderMap = HashMultimap.create(); Map dataStoreMap = new HashMap<>(); + Map dataPerspectiveResolverMap = new HashMap<>(); List> keys = new ArrayList<>(); @Override @@ -61,6 +63,12 @@ public DataRegistration.Builder provider(final DataProvider provider) thro return this; } + @Override + public DataRegistration.Builder perspectiveResolver(DataPerspectiveResolver resolver) { + this.dataPerspectiveResolverMap.put(resolver.key(), resolver); + return this; + } + @Override public DataRegistration.Builder dataKey(final Key key) { this.keys.add(key); @@ -89,6 +97,7 @@ public DataRegistration build() { public SpongeDataRegistrationBuilder reset() { this.dataProviderMap = HashMultimap.create(); this.dataStoreMap = new IdentityHashMap<>(); + this.dataPerspectiveResolverMap = new HashMap<>(); this.keys = new ArrayList<>(); return this; } diff --git a/src/main/java/org/spongepowered/common/data/contextual/ContextualData.java b/src/main/java/org/spongepowered/common/data/contextual/ContextualData.java new file mode 100644 index 00000000000..7569271c18e --- /dev/null +++ b/src/main/java/org/spongepowered/common/data/contextual/ContextualData.java @@ -0,0 +1,41 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.data.contextual; + +import net.minecraft.network.protocol.Packet; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.data.DataPerspective; + +public interface ContextualData { + + @Nullable PerspectiveContainer dataPerception(DataPerspective perspective); + + PerspectiveContainer createDataPerception(DataPerspective perspective); + + void linkContextualOwner(ContextualDataOwner owner); + void unlinkContextualOwner(ContextualDataOwner owner); + + void broadcastToPerceives(Packet packet); +} diff --git a/src/main/java/org/spongepowered/common/data/contextual/ContextualDataDelegate.java b/src/main/java/org/spongepowered/common/data/contextual/ContextualDataDelegate.java new file mode 100644 index 00000000000..cc06609f46a --- /dev/null +++ b/src/main/java/org/spongepowered/common/data/contextual/ContextualDataDelegate.java @@ -0,0 +1,133 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.data.contextual; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.data.DataPerspective; +import org.spongepowered.api.data.DataPerspectiveResolver; +import org.spongepowered.api.data.Key; +import org.spongepowered.api.data.value.Value; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.scoreboard.Team; +import org.spongepowered.api.world.World; +import org.spongepowered.common.data.SpongeDataManager; + +import java.util.Objects; + +public final class ContextualDataDelegate extends ContextualDataOwner { + + public ContextualDataDelegate(final DataPerspective perspective) { + super(perspective); + } + + @Override + public PerspectiveContainer createDataPerception(final DataPerspective perspective) { + return this.perspectives.computeIfAbsent(perspective, p -> { + if (p instanceof final Entity entity) { + return new EntityPerspectiveContainer(this, entity); + } else if (p instanceof final Team team) { + return new TeamPerspectiveContainer(this, team); + } else if (p instanceof final World world) { + return new WorldPerspectiveContainer(this, world); + } + throw new UnsupportedOperationException(); + }); + } + + private static abstract class AbstractPerspectiveContainer

extends PerspectiveContainer { + + protected AbstractPerspectiveContainer(final PerspectiveType perspectiveType, final ContextualDataDelegate holder, final P perspective) { + super(perspectiveType, holder, perspective); + } + + @Override + protected final void offer(final PerspectiveType perspectiveType, final DataPerspectiveResolver, E> resolver, final E value) { + if (Objects.equals(this.activeValues.put(resolver.key(), value), value)) { + return; + } + + for (final DataPerspective perspective : this.holder.owner.perceives()) { + ((ContextualData) perspective).createDataPerception(this.perspective).offer(PerspectiveType.TEAM, this.holder.owner, resolver, value); + } + } + + @Override + protected void remove(PerspectiveType perspectiveType, DataPerspectiveResolver, ?> resolver) { + this.activeValues.remove(resolver.key()); + + for (final DataPerspective perspective : this.holder.owner.perceives()) { + final @Nullable PerspectiveContainer container = ((ContextualData) perspective).dataPerception(this.perspective); + if (container != null) { + container.remove(PerspectiveType.TEAM, this.holder.owner, resolver); + } + } + } + + @Override + protected void addPerspective(final DataPerspective perspective) { + final PerspectiveContainer container = ((ContextualData) perspective).createDataPerception(this.perspective); + this.activeValues.forEach((k, v) -> { + final DataPerspectiveResolver, Object> resolver = + Objects.requireNonNull(SpongeDataManager.getDataPerspectiveResolverRegistry().get((Key) k)); + + container.offer(PerspectiveType.TEAM, this.holder.owner, resolver, v); + }); + } + + @Override + protected void removePerspective(final DataPerspective perspective) { + final @Nullable PerspectiveContainer container = ((ContextualData) perspective).dataPerception(this.perspective); + if (container != null) { + this.activeValues.forEach((k, v) -> { + final DataPerspectiveResolver, ?> resolver = + Objects.requireNonNull(SpongeDataManager.getDataPerspectiveResolverRegistry().get((Key) k)); + + container.remove(PerspectiveType.TEAM, this.holder.owner, resolver); + }); + } + } + } + + private static final class EntityPerspectiveContainer extends AbstractPerspectiveContainer { + + private EntityPerspectiveContainer(final ContextualDataDelegate holder, final Entity entity) { + super(PerspectiveType.ENTITY, holder, entity); + } + } + + private static final class TeamPerspectiveContainer extends AbstractPerspectiveContainer { + + private TeamPerspectiveContainer(final ContextualDataDelegate holder, final Team team) { + super(PerspectiveType.TEAM, holder, team); + } + } + + private static final class WorldPerspectiveContainer extends AbstractPerspectiveContainer> { + + private WorldPerspectiveContainer(final ContextualDataDelegate holder, final World world) { + super(PerspectiveType.WORLD, holder, world); + } + } +} diff --git a/src/main/java/org/spongepowered/common/data/contextual/ContextualDataHolder.java b/src/main/java/org/spongepowered/common/data/contextual/ContextualDataHolder.java new file mode 100644 index 00000000000..ab2301f80af --- /dev/null +++ b/src/main/java/org/spongepowered/common/data/contextual/ContextualDataHolder.java @@ -0,0 +1,198 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.data.contextual; + +import com.google.common.collect.Maps; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.data.DataPerspective; +import org.spongepowered.api.data.DataPerspectiveResolver; +import org.spongepowered.api.data.Key; +import org.spongepowered.api.data.value.Value; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.scoreboard.Team; +import org.spongepowered.api.world.World; +import org.spongepowered.common.util.CopyHelper; + +import java.util.EnumMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("unchecked") +public final class ContextualDataHolder extends ContextualDataOwner { + + public ContextualDataHolder(final Entity entity) { + super(entity); + } + + @Override + public PerspectiveContainer createDataPerception(final DataPerspective perspective) { + return this.perspectives.computeIfAbsent(perspective, p -> { + if (p instanceof final Entity entity) { + return new EntityPerspectiveContainer(this, entity); + } else if (p instanceof final Team team) { + return new TeamPerspectiveContainer(this, team); + } else if (p instanceof final World world) { + return new WorldPerspectiveContainer(this, world); + } + throw new UnsupportedOperationException(); + }); + } + + private static abstract class AbstractPerspectiveContainer

extends PerspectiveContainer { + + protected AbstractPerspectiveContainer(PerspectiveType perspectiveType, ContextualDataHolder holder, P perspective) { + super(perspectiveType, holder, perspective); + } + + @Override + public final Optional get(final Key> key) { + Objects.requireNonNull(key, "key"); + final @Nullable E value = (E) this.activeValues.get(key); + if (value == null) { + return this.holder.owner.get(key); + } + return Optional.ofNullable(CopyHelper.copy(value)); + } + + @Override + public final > Optional getValue(final Key key) { + Objects.requireNonNull(key, "key"); + final @Nullable E value = (E) this.activeValues.get(key); + if (value == null) { + return this.holder.owner.getValue(key); + } + return Optional.of(Value.genericMutableOf(key, CopyHelper.copy(value))); + } + + @Override + protected void addPerspective(final DataPerspective perspective) { + throw new UnsupportedOperationException(); + } + + @Override + protected void removePerspective(final DataPerspective perspective) { + throw new UnsupportedOperationException(); + } + } + + private static final class EntityPerspectiveContainer extends AbstractPerspectiveContainer { + + private final Map, EnumMap> perspectiveValues; + + private EntityPerspectiveContainer(final ContextualDataHolder holder, final Entity entity) { + super(PerspectiveType.ENTITY, holder, entity); + + this.perspectiveValues = Maps.newHashMap(); + } + + private EnumMap getValues(final Key> key) { + return (EnumMap) this.perspectiveValues.computeIfAbsent(key, k -> Maps.newEnumMap(PerspectiveType.class)); + } + + @Override + protected void offer(final PerspectiveType perspectiveType, final DataPerspectiveResolver, E> resolver, final E value) { + final EnumMap values = this.getValues(resolver.key()); + values.put(perspectiveType, value); + this.updatePerspective(values, resolver); + } + + @Override + protected void remove(final PerspectiveType perspectiveType, final DataPerspectiveResolver, ?> resolver) { + final @Nullable EnumMap values = this.perspectiveValues.get(resolver.key()); + if (values == null) { + return; + } + + values.remove(perspectiveType); + if (values.isEmpty()) { + this.perspectiveValues.remove(resolver.key()); + ((DataPerspectiveResolver) resolver).apply(this.holder.owner, this.perspective, this.holder.owner.require((Key) resolver.key())); + return; + } + + this.updatePerspective(values, (DataPerspectiveResolver) resolver); + } + + private void updatePerspective(final EnumMap values, final DataPerspectiveResolver, E> resolver) { + for (final PerspectiveType type : PerspectiveType.values()) { + final @Nullable E value = values.get(type); + if (value == null) { + continue; + } + + if (!Objects.equals(this.activeValues.put(resolver.key(), value), value)) { + resolver.apply(this.holder.owner, this.perspective, value); + } + + return; + } + } + } + + private static abstract class NonEntityContainer

extends AbstractPerspectiveContainer

{ + + protected NonEntityContainer(final PerspectiveType perspectiveType, final ContextualDataHolder holder, final P perspective) { + super(perspectiveType, holder, perspective); + } + + @Override + protected void offer(final PerspectiveType perspectiveType, final DataPerspectiveResolver, E> resolver, final E value) { + if (Objects.equals(this.activeValues.put(resolver.key(), value), value)) { + return; + } + + for (final DataPerspective perspective : this.perspective.perceives()) { + this.holder.createDataPerception(perspective).offer(perspectiveType, this.perspective, resolver, value); + } + } + + @Override + protected void remove(final PerspectiveType perspectiveType, final DataPerspectiveResolver, ?> resolver) { + this.activeValues.remove(resolver.key()); + + for (final DataPerspective perspective : this.perspective.perceives()) { + final @Nullable PerspectiveContainer container = this.holder.dataPerception(perspective); + if (container != null) { + container.remove(perspectiveType, this.perspective, resolver); + } + } + } + } + + private static final class TeamPerspectiveContainer extends NonEntityContainer { + + private TeamPerspectiveContainer(final ContextualDataHolder holder, final Team team) { + super(PerspectiveType.TEAM, holder, team); + } + } + + private static final class WorldPerspectiveContainer extends NonEntityContainer> { + + private WorldPerspectiveContainer(final ContextualDataHolder holder, final World world) { + super(PerspectiveType.WORLD, holder, world); + } + } +} diff --git a/src/main/java/org/spongepowered/common/data/contextual/ContextualDataOwner.java b/src/main/java/org/spongepowered/common/data/contextual/ContextualDataOwner.java new file mode 100644 index 00000000000..0e31981a64f --- /dev/null +++ b/src/main/java/org/spongepowered/common/data/contextual/ContextualDataOwner.java @@ -0,0 +1,89 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.data.contextual; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.data.DataHolder; +import org.spongepowered.api.data.DataPerspective; +import org.spongepowered.plugin.PluginContainer; + +import java.io.Closeable; +import java.util.Map; +import java.util.Set; + +public abstract class ContextualDataOwner implements Closeable { + + protected final T owner; + + protected final Map> perspectives; + private final Set> links; + + ContextualDataOwner(final T owner) { + this.owner = owner; + + this.perspectives = Maps.newHashMap(); + this.links = Sets.newHashSet(); + } + + public final @Nullable PerspectiveContainer dataPerception(final DataPerspective perspective) { + return this.perspectives.get(perspective); + } + + //TODO: Abstract method on ContextualData? + public abstract PerspectiveContainer createDataPerception(final DataPerspective perspective); + + public final DataHolder.Mutable createDataPerception(final PluginContainer plugin, final DataPerspective perspective) { + return new ContextualDataProvider(this.createDataPerception(perspective), plugin); + } + + public final void linkContextualOwner(final ContextualDataOwner owner) { + this.links.add(owner); + } + + public final void unlinkContextualOwner(final ContextualDataOwner owner) { + this.links.remove(owner); + } + + public void perceiveAdded(final DataPerspective perspective) { + for (final PerspectiveContainer container : this.perspectives.values()) { + container.addPerspective(perspective); + } + } + + public void perceiveRemoved(final DataPerspective perspective) { + for (final PerspectiveContainer container : this.perspectives.values()) { + container.removePerspective(perspective); + } + } + + @Override + public final void close() { + this.perspectives.values().forEach(PerspectiveContainer::close); + + this.links.forEach(o -> o.perspectives.remove(this.owner)); + } +} diff --git a/src/main/java/org/spongepowered/common/data/contextual/ContextualDataProvider.java b/src/main/java/org/spongepowered/common/data/contextual/ContextualDataProvider.java new file mode 100644 index 00000000000..ca38c414e2c --- /dev/null +++ b/src/main/java/org/spongepowered/common/data/contextual/ContextualDataProvider.java @@ -0,0 +1,169 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.data.contextual; + +import org.spongepowered.api.data.DataHolder; +import org.spongepowered.api.data.DataTransactionResult; +import org.spongepowered.api.data.Key; +import org.spongepowered.api.data.value.CollectionValue; +import org.spongepowered.api.data.value.MapValue; +import org.spongepowered.api.data.value.MergeFunction; +import org.spongepowered.api.data.value.Value; +import org.spongepowered.api.data.value.ValueContainer; +import org.spongepowered.plugin.PluginContainer; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +@SuppressWarnings("unchecked") +final class ContextualDataProvider implements DataHolder.Mutable { + + private final PerspectiveContainer container; + private final PluginContainer plugin; + + ContextualDataProvider(final PerspectiveContainer container, final PluginContainer plugin) { + this.container = container; + this.plugin = plugin; + } + + @Override + public DataTransactionResult offer(final Key> key, final E value) { + Objects.requireNonNull(key, "key"); + return this.container.offer(this.plugin, key, value); + } + + @Override + public DataTransactionResult offer(Value value) { + return this.container.offer(this.plugin, (Key>)value.key(), value.get()); + } + + @Override + public DataTransactionResult offerSingle(Key> key, E element) { + return null; + } + + @Override + public DataTransactionResult offerSingle(Key> key, K valueKey, V value) { + return null; + } + + @Override + public DataTransactionResult offerAll(Key> key, Map map) { + return null; + } + + @Override + public DataTransactionResult offerAll(MapValue value) { + return null; + } + + @Override + public DataTransactionResult offerAll(CollectionValue value) { + return null; + } + + @Override + public DataTransactionResult offerAll(Key> key, Collection elements) { + return null; + } + + @Override + public DataTransactionResult removeSingle(Key> key, E element) { + return null; + } + + @Override + public DataTransactionResult removeKey(Key> key, K mapKey) { + return null; + } + + @Override + public DataTransactionResult removeAll(CollectionValue value) { + return null; + } + + @Override + public DataTransactionResult removeAll(Key> key, Collection elements) { + return null; + } + + @Override + public DataTransactionResult removeAll(MapValue value) { + return null; + } + + @Override + public DataTransactionResult removeAll(Key> key, Map map) { + return null; + } + + @Override + public DataTransactionResult tryOffer(Key> key, E value) { + return null; + } + + @Override + public DataTransactionResult remove(Key key) { + return this.container.remove(this.plugin, key); + } + + @Override + public DataTransactionResult undo(DataTransactionResult result) { + return null; + } + + @Override + public DataTransactionResult copyFrom(ValueContainer that, MergeFunction function) { + return null; + } + + @Override + public Optional get(Key> key) { + return Optional.empty(); + } + + @Override + public > Optional getValue(Key key) { + return Optional.empty(); + } + + @Override + public boolean supports(Key key) { + return false; + } + + @Override + public Set> getKeys() { + return null; + } + + @Override + public Set> getValues() { + return null; + } +} diff --git a/src/main/java/org/spongepowered/common/data/contextual/DataPerspectiveResolverRegistry.java b/src/main/java/org/spongepowered/common/data/contextual/DataPerspectiveResolverRegistry.java new file mode 100644 index 00000000000..06e33687325 --- /dev/null +++ b/src/main/java/org/spongepowered/common/data/contextual/DataPerspectiveResolverRegistry.java @@ -0,0 +1,50 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.data.contextual; + +import com.google.common.collect.Maps; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.data.DataPerspectiveResolver; +import org.spongepowered.api.data.Key; +import org.spongepowered.api.data.value.Value; + +import java.util.Map; + +public class DataPerspectiveResolverRegistry { + + private final Map, DataPerspectiveResolver> resolvers; + + public DataPerspectiveResolverRegistry() { + this.resolvers = Maps.newHashMap(); + } + + public @Nullable DataPerspectiveResolver, E> get(final Key> key) { + return (DataPerspectiveResolver, E>)this.resolvers.get(key); + } + + public void register(final DataPerspectiveResolver resolver) { + this.resolvers.put(resolver.key(), resolver); + } +} diff --git a/src/main/java/org/spongepowered/common/data/contextual/GenericDataPerspectiveResolver.java b/src/main/java/org/spongepowered/common/data/contextual/GenericDataPerspectiveResolver.java new file mode 100644 index 00000000000..d7e4cbf8e75 --- /dev/null +++ b/src/main/java/org/spongepowered/common/data/contextual/GenericDataPerspectiveResolver.java @@ -0,0 +1,52 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.data.contextual; + +import org.spongepowered.api.data.DataHolder; +import org.spongepowered.api.data.DataPerspective; +import org.spongepowered.api.data.DataPerspectiveResolver; +import org.spongepowered.api.data.Key; +import org.spongepowered.api.data.value.Value; + +public abstract class GenericDataPerspectiveResolver implements DataPerspectiveResolver, E> { + + private final Key> key; + + public GenericDataPerspectiveResolver(final Key> key) { + this.key = (Key>)key; + } + + @Override + public Key> key() { + return this.key; + } + + @Override + public void apply(DataHolder dataHolder, DataPerspective perspective, E value) { + this.apply((H) dataHolder, perspective, value); + } + + public abstract void apply(H dataHolder, DataPerspective perspective, E value); +} diff --git a/src/main/java/org/spongepowered/common/data/contextual/PerspectiveContainer.java b/src/main/java/org/spongepowered/common/data/contextual/PerspectiveContainer.java new file mode 100644 index 00000000000..abb58029c6e --- /dev/null +++ b/src/main/java/org/spongepowered/common/data/contextual/PerspectiveContainer.java @@ -0,0 +1,175 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.data.contextual; + +import com.google.common.collect.Maps; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.data.DataPerspective; +import org.spongepowered.api.data.DataPerspectiveResolver; +import org.spongepowered.api.data.DataTransactionResult; +import org.spongepowered.api.data.Key; +import org.spongepowered.api.data.Keys; +import org.spongepowered.api.data.value.Value; +import org.spongepowered.api.data.value.ValueContainer; +import org.spongepowered.common.accessor.world.entity.EntityAccessor; +import org.spongepowered.common.data.SpongeDataManager; +import org.spongepowered.common.util.CopyHelper; +import org.spongepowered.plugin.PluginContainer; + +import java.io.Closeable; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +@SuppressWarnings("unchecked") +public abstract class PerspectiveContainer, P extends DataPerspective> implements ValueContainer, Closeable { + + private final PerspectiveType perspectiveType; + protected final H holder; + protected final P perspective; + + private final Map, Map> valuesByOwner; + protected final Map, Object> activeValues; + + private long entityDataFlags; + + protected PerspectiveContainer(final PerspectiveType perspectiveType, final H holder, final P perspective) { + this.perspectiveType = perspectiveType; + this.holder = holder; + this.perspective = perspective; + + this.valuesByOwner = Maps.newHashMap(); + this.activeValues = Maps.newHashMap(); + + ((ContextualData) perspective).linkContextualOwner(this.holder); + } + + public final long entityDataFlags() { + return this.entityDataFlags; + } + + final DataTransactionResult offer(final PluginContainer pluginContainer, final Key> key, final E value) { + final @Nullable DataPerspectiveResolver, E> resolver = SpongeDataManager.getDataPerspectiveResolverRegistry().get(key); + if (resolver == null) { + return DataTransactionResult.failResult(Value.immutableOf(key, value)); + } + + return this.offer(this.perspectiveType, pluginContainer, resolver, value); + } + + final DataTransactionResult offer(final PerspectiveType perspectiveType, final Object owner, final DataPerspectiveResolver, E> resolver, final E value) { + final Map valueMap = (Map) this.valuesByOwner.computeIfAbsent(resolver.key(), k -> Maps.newLinkedHashMap()); + if (Objects.equals(value, valueMap.put(owner, value))) { + return DataTransactionResult.successResult(Value.immutableOf(resolver.key(), value)); + } + + if (resolver.key() == (Key)Keys.CUSTOM_NAME) { + this.entityDataFlags |= 1L << EntityAccessor.accessor$DATA_CUSTOM_NAME().getId(); + } + + final E mergedValue = valueMap.size() == 1 ? value : resolver.merge(valueMap.values()); + this.offer(perspectiveType, resolver, mergedValue); + return DataTransactionResult.successResult(Value.immutableOf(resolver.key(), mergedValue)); + } + + protected abstract void offer(final PerspectiveType perspectiveType, final DataPerspectiveResolver, E> resolver, final E value); + + final DataTransactionResult remove(final PluginContainer pluginContainer, final Key key) { + final @Nullable DataPerspectiveResolver, ?> resolver = SpongeDataManager.getDataPerspectiveResolverRegistry().get((Key) key); + if (resolver == null) { + return DataTransactionResult.failNoData(); + } + + return this.remove(this.perspectiveType, pluginContainer, resolver); + } + + final DataTransactionResult remove(final PerspectiveType perspectiveType, final Object owner, final DataPerspectiveResolver, ?> resolver) { + final @Nullable Map valueMap = (Map) this.valuesByOwner.get(resolver.key()); + if (valueMap == null) { + return DataTransactionResult.failNoData(); + } + + final @Nullable Object value = valueMap.remove(owner); + if (value == null) { + return DataTransactionResult.failNoData(); + } + + if (resolver.key() == (Key)Keys.CUSTOM_NAME) { + this.entityDataFlags &= ~(1L << EntityAccessor.accessor$DATA_CUSTOM_NAME().getId()); + } + + if (valueMap.isEmpty()) { + this.valuesByOwner.remove(resolver.key()); + this.activeValues.remove(resolver.key()); + this.remove(perspectiveType, resolver); + return DataTransactionResult.successNoData(); + } + + final Object mergedValue = valueMap.size() == 1 ? valueMap.values().iterator().next() : ((DataPerspectiveResolver) resolver).merge(valueMap.values()); + this.offer(perspectiveType, owner, (DataPerspectiveResolver) resolver, mergedValue); + return DataTransactionResult.successNoData(); + } + + protected abstract void remove(final PerspectiveType perspectiveType, final DataPerspectiveResolver, ?> resolver); + + protected abstract void addPerspective(final DataPerspective perspective); + protected abstract void removePerspective(final DataPerspective perspective); + + @Override + public Optional get(final Key> key) { + Objects.requireNonNull(key, "key"); + final @Nullable E value = (E) this.activeValues.get(key); + return Optional.ofNullable(CopyHelper.copy(value)); + } + + @Override + public > Optional getValue(final Key key) { + Objects.requireNonNull(key, "key"); + final @Nullable E value = (E) this.activeValues.get(key); + return Optional.of(Value.genericMutableOf(key, CopyHelper.copy(value))); + } + + @Override + public boolean supports(Key key) { + return false; + } + + @Override + public Set> getKeys() { + return Collections.emptySet(); + } + + @Override + public Set> getValues() { + return Collections.emptySet(); + } + + @Override + public final void close() { + ((ContextualData) this.perspective).unlinkContextualOwner(this.holder); + } +} diff --git a/src/main/java/org/spongepowered/common/data/contextual/PerspectiveType.java b/src/main/java/org/spongepowered/common/data/contextual/PerspectiveType.java new file mode 100644 index 00000000000..e88bb62c05c --- /dev/null +++ b/src/main/java/org/spongepowered/common/data/contextual/PerspectiveType.java @@ -0,0 +1,31 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.data.contextual; + +public enum PerspectiveType { + ENTITY, + TEAM, + WORLD +} diff --git a/src/main/java/org/spongepowered/common/data/contextual/util/ContextualPacketUtil.java b/src/main/java/org/spongepowered/common/data/contextual/util/ContextualPacketUtil.java new file mode 100644 index 00000000000..3b8855a3620 --- /dev/null +++ b/src/main/java/org/spongepowered/common/data/contextual/util/ContextualPacketUtil.java @@ -0,0 +1,71 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.data.contextual.util; + +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.syncher.SynchedEntityData; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.data.DataPerspective; +import org.spongepowered.api.data.Keys; +import org.spongepowered.common.accessor.world.entity.EntityAccessor; +import org.spongepowered.common.adventure.SpongeAdventure; +import org.spongepowered.common.data.contextual.ContextualData; +import org.spongepowered.common.data.contextual.PerspectiveContainer; +import org.spongepowered.common.network.syncher.SpongeSynchedEntityDataList; + +import java.util.ArrayList; +import java.util.List; + +public final class ContextualPacketUtil { + + public static ClientboundSetEntityDataPacket createContextualPacket( + final ClientboundSetEntityDataPacket original, final ContextualData context, final DataPerspective perspective) { + final @Nullable PerspectiveContainer contextualData = context.dataPerception(perspective); + if (contextualData == null) { + return original; + } + return ContextualPacketUtil.createContextualPacket(original, contextualData); + } + + public static ClientboundSetEntityDataPacket createContextualPacket( + final ClientboundSetEntityDataPacket original, final PerspectiveContainer contextualData) { + if (original.packedItems() instanceof final SpongeSynchedEntityDataList entityDataList + && (entityDataList.flags() & contextualData.entityDataFlags()) == 0L) { + return original; + } + + List> values = new ArrayList<>(original.packedItems().size()); + for (final SynchedEntityData.DataValue dataValue : original.packedItems()) { + if (dataValue.id() == EntityAccessor.accessor$DATA_CUSTOM_NAME().getId()) { + values.add(SynchedEntityData.DataValue.create( + EntityAccessor.accessor$DATA_CUSTOM_NAME(), SpongeAdventure.asVanillaOpt(contextualData.require(Keys.CUSTOM_NAME)))); + } else { + values.add(dataValue); + } + } + + return new ClientboundSetEntityDataPacket(original.id(), values); + } +} diff --git a/src/main/java/org/spongepowered/common/data/provider/DataProviderRegistrator.java b/src/main/java/org/spongepowered/common/data/provider/DataProviderRegistrator.java index c1d471545fa..340dd77e9f4 100644 --- a/src/main/java/org/spongepowered/common/data/provider/DataProviderRegistrator.java +++ b/src/main/java/org/spongepowered/common/data/provider/DataProviderRegistrator.java @@ -31,6 +31,8 @@ import org.spongepowered.api.Sponge; import org.spongepowered.api.data.DataHolder; import org.spongepowered.api.data.DataManipulator; +import org.spongepowered.api.data.DataPerspective; +import org.spongepowered.api.data.DataPerspectiveResolver; import org.spongepowered.api.data.DataProvider; import org.spongepowered.api.data.DataRegistration; import org.spongepowered.api.data.DataTransactionResult; @@ -48,7 +50,9 @@ import org.spongepowered.common.data.SpongeDataManager; import org.spongepowered.common.data.SpongeDataRegistration; import org.spongepowered.common.data.SpongeDataRegistrationBuilder; +import org.spongepowered.common.data.contextual.GenericDataPerspectiveResolver; import org.spongepowered.common.data.persistence.datastore.SpongeDataStoreBuilder; +import org.spongepowered.common.function.TriConsumer; import org.spongepowered.common.util.CopyHelper; import org.spongepowered.common.util.TypeTokenUtil; @@ -183,7 +187,7 @@ public MutableRegistrator(final SpongeDataRegistrationBuilder builder, final Cla * @param The key type * @return The registration */ - public MutableRegistration create(final Supplier>> suppliedKey) { + public > MutableRegistration create(final Supplier>> suppliedKey) { return this.create(suppliedKey.get()); } @@ -193,18 +197,25 @@ public MutableRegistration create(final Supplier The key type * @return The registration */ - public MutableRegistration create(final Key> key) { - final MutableRegistration registration = new MutableRegistration<>(this, key); + public > MutableRegistration create(final Key> key) { + final MutableRegistration registration = new MutableRegistration<>(this, key); this.register(registration); return registration; } @SuppressWarnings({"unchecked", "UnstableApiUsage"}) - protected > MutableRegistrator register(final MutableRegistration registration) { - final DataProvider provider = registration.build(this.target); + protected , R extends MutableRegistration> MutableRegistrator register(final MutableRegistration registration) { + final DataProvider provider = registration.buildProvider(this.target); this.registrationBuilder.dataKey(provider.key()).provider(provider); return this; } + + @SuppressWarnings({"unchecked", "UnstableApiUsage"}) + protected , R extends MutableRegistration> MutableRegistrator register(final MutableContextualRegistration registration) { + this.register((MutableRegistration) registration); + this.registrationBuilder.perspectiveResolver(registration.buildPerspectiveResolver()); + return this; + } } public static final class ImmutableRegistrator extends DataProviderRegistrator { @@ -323,7 +334,7 @@ public R supports(final Function supports) { return (R) this; } - public DataProvider build(Class target) { + public DataProvider buildProvider(Class target) { final MutableRegistrationBase registration = this; return new GenericMutableDataProvider(registration.key, target) { final boolean isBooleanKey = registration.key.elementType() == Boolean.class; @@ -407,7 +418,7 @@ protected boolean supports(final H dataHolder) { } - public static final class MutableRegistration extends MutableRegistrationBase> { + public static class MutableRegistration> extends MutableRegistrationBase { private final MutableRegistrator registrator; @@ -416,12 +427,18 @@ private MutableRegistration(final MutableRegistrator registrator, final Key MutableRegistration create(final DefaultedRegistryReference>> suppliedKey) { + public > MutableRegistration create(final DefaultedRegistryReference>> suppliedKey) { return this.create(suppliedKey.get()); } - public MutableRegistration create(final Key> key) { - final MutableRegistration registration = new MutableRegistration<>(this.registrator, key); + public > MutableRegistration create(final Key> key) { + final MutableRegistration registration = new MutableRegistration<>(this.registrator, key); + this.registrator.register(registration); + return registration; + } + + public MutableContextualRegistration createContextual(final Key> key) { + final MutableContextualRegistration registration = new MutableContextualRegistration<>(this.registrator, key); this.registrator.register(registration); return registration; } @@ -443,7 +460,46 @@ public ImmutableRegistrator asImmutable(final Class target) { } } - @SuppressWarnings("unchecked") + public static final class MutableContextualRegistration extends MutableRegistration> { + + private @Nullable Function, E> dataPerspectiveMerge; + private @Nullable TriConsumer dataPerspectiveApply; + + private MutableContextualRegistration(MutableRegistrator registrator, Key> key) { + super(registrator, key); + } + + public MutableContextualRegistration dataPerspectiveMerge(final Function, E> merge) { + this.dataPerspectiveMerge = merge; + return this; + } + + public MutableContextualRegistration dataPerspectiveApply(final TriConsumer apply) { + this.dataPerspectiveApply = apply; + return this; + } + + public DataPerspectiveResolver buildPerspectiveResolver() { + final MutableContextualRegistration registration = this; + return new GenericDataPerspectiveResolver(registration.key) { + + @Override + public E merge(Iterable values) { + return registration.dataPerspectiveMerge.apply(values); + } + + @Override + public void apply(H dataHolder, DataPerspective perspective, E value) { + if (registration.dataPerspectiveApply != null) { + registration.dataPerspectiveApply.accept(dataHolder, perspective, value); + } + } + }; + } + } + + + @SuppressWarnings("unchecked") private static class ImmutableRegistrationBase> { private final Key> key; private @Nullable BiFunction> constructValue; @@ -700,7 +756,7 @@ public MutableDataProviderBuilder reset() { @Override public DataProvider build() { - return this.registration.build((Class) GenericTypeReflector.erase(this.holder)); + return this.registration.buildProvider((Class) GenericTypeReflector.erase(this.holder)); } } diff --git a/src/main/java/org/spongepowered/common/data/provider/entity/EntityData.java b/src/main/java/org/spongepowered/common/data/provider/entity/EntityData.java index 385a55b994a..baa709f7702 100644 --- a/src/main/java/org/spongepowered/common/data/provider/entity/EntityData.java +++ b/src/main/java/org/spongepowered/common/data/provider/entity/EntityData.java @@ -25,6 +25,8 @@ package org.spongepowered.common.data.provider.entity; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; @@ -37,6 +39,7 @@ import org.spongepowered.common.adventure.SpongeAdventure; import org.spongepowered.common.bridge.world.entity.EntityBridge; import org.spongepowered.common.bridge.world.entity.EntityMaxAirBridge; +import org.spongepowered.common.data.contextual.ContextualData; import org.spongepowered.common.data.provider.DataProviderRegistrator; import org.spongepowered.common.entity.SpongeEntityArchetype; import org.spongepowered.common.entity.SpongeEntitySnapshot; @@ -44,6 +47,7 @@ import org.spongepowered.common.util.SpongeTicks; import org.spongepowered.common.util.VecHelper; +import java.util.Collections; import java.util.stream.Collectors; public final class EntityData { @@ -74,13 +78,17 @@ public static void register(final DataProviderRegistrator registrator) { } return (org.spongepowered.api.entity.Entity) rootVehicle; }) - .create(Keys.CUSTOM_NAME) + .createContextual(Keys.CUSTOM_NAME) .get(h -> h.hasCustomName() ? SpongeAdventure.asAdventure(h.getCustomName()) : null) .set((h, v) -> h.setCustomName(SpongeAdventure.asVanilla(v))) .delete(h -> { h.setCustomName(null); h.setCustomNameVisible(false); }) + .dataPerspectiveMerge(values -> values.iterator().next()) + .dataPerspectiveApply((h, p, v) -> ((ContextualData) p).broadcastToPerceives(new ClientboundSetEntityDataPacket(h.getId(), + Collections.singletonList(SynchedEntityData.DataValue.create(EntityAccessor.accessor$DATA_CUSTOM_NAME(), SpongeAdventure.asVanillaOpt(v))))) + ) .create(Keys.DISPLAY_NAME) .get(h -> SpongeAdventure.asAdventure(h.getDisplayName())) .create(Keys.EYE_HEIGHT) diff --git a/src/main/java/org/spongepowered/common/data/provider/entity/VanishableData.java b/src/main/java/org/spongepowered/common/data/provider/entity/VanishableData.java index 4fb03550573..697fb87e6a0 100644 --- a/src/main/java/org/spongepowered/common/data/provider/entity/VanishableData.java +++ b/src/main/java/org/spongepowered/common/data/provider/entity/VanishableData.java @@ -27,6 +27,7 @@ import net.minecraft.world.entity.Entity; import org.spongepowered.api.ResourceKey; import org.spongepowered.api.data.Keys; +import org.spongepowered.api.effect.VanishState; import org.spongepowered.common.bridge.data.VanishableBridge; import org.spongepowered.common.data.SpongeDataManager; import org.spongepowered.common.data.provider.DataProviderRegistrator; @@ -46,7 +47,7 @@ public static void register(final DataProviderRegistrator registrator) { .create(Keys.IS_INVISIBLE) .get(VanishableBridge::bridge$isInvisible) .set(VanishableBridge::bridge$setInvisible) - .create(Keys.VANISH_STATE) + .createContextual(Keys.VANISH_STATE) .get(VanishableBridge::bridge$vanishState) .setAnd((h, v) -> { if (h instanceof Entity && ((Entity) h).level().isClientSide) { @@ -54,6 +55,46 @@ public static void register(final DataProviderRegistrator registrator) { } h.bridge$vanishState(v); return true; + }) + .dataPerspectiveMerge(values -> { + VanishState current = VanishState.unvanished(); + for (final VanishState value : values) { + if (!value.invisible()) { + continue; + } + else if (!current.invisible()) { + current = value; + continue; + } + + if (!value.affectsMonsterSpawning()) { + current = current.affectMonsterSpawning(false); + } + + if (value.untargetable()) { + current = current.untargetable(true); + } + + if (!value.createsSounds()) { + current = current.createSounds(false); + } + + if (!value.createsParticles()) { + current = current.createParticles(false); + } + + if (!value.triggerVibrations()) { + current = current.triggerVibrations(false); + } + } + + return current; + }) + .dataPerspectiveApply((h, p, v) -> { + if (h instanceof Entity && ((Entity) h).level().isClientSide) { + return; + } + h.bridge$vanishState(v, p); }); final ResourceKey dataStoreKey = ResourceKey.sponge("invisibility"); registrator.spongeDataStore(dataStoreKey, VanishableBridge.class, Keys.IS_INVISIBLE, Keys.VANISH_STATE); diff --git a/src/main/java/org/spongepowered/common/entity/player/SpongeUserData.java b/src/main/java/org/spongepowered/common/entity/player/SpongeUserData.java index 55a3815c87a..2303099ee16 100644 --- a/src/main/java/org/spongepowered/common/entity/player/SpongeUserData.java +++ b/src/main/java/org/spongepowered/common/entity/player/SpongeUserData.java @@ -42,6 +42,7 @@ import org.spongepowered.api.ResourceKey; import org.spongepowered.api.Sponge; import org.spongepowered.api.data.DataHolder; +import org.spongepowered.api.data.DataPerspective; import org.spongepowered.api.data.Keys; import org.spongepowered.api.data.persistence.DataContainer; import org.spongepowered.api.data.persistence.DataSerializable; @@ -543,6 +544,16 @@ public Inventory enderChestInventory() { } + @Override + public void bridge$vanishState(final VanishState state, final DataPerspective perspective) { + final Optional playerOpt = this.player(); + if (playerOpt.isPresent()) { + ((VanishableBridge) playerOpt.get()).bridge$vanishState(state, perspective); + return; + } + //TODO: Make this work + } + @Override public boolean bridge$isInvisible() { return this.player().map(player -> ((VanishableBridge) player).bridge$isInvisible()).orElseGet(() -> this.isInvisible); diff --git a/src/main/java/org/spongepowered/common/function/TriConsumer.java b/src/main/java/org/spongepowered/common/function/TriConsumer.java new file mode 100644 index 00000000000..c5ce5ae55ef --- /dev/null +++ b/src/main/java/org/spongepowered/common/function/TriConsumer.java @@ -0,0 +1,41 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.function; + +import java.util.Objects; + +@FunctionalInterface +public interface TriConsumer { + void accept(T t, U u, V v); + + default TriConsumer andThen(TriConsumer after) { + Objects.requireNonNull(after); + + return (t, u, v) -> { + accept(t, u, v); + after.accept(t, u, v); + }; + } +} diff --git a/src/main/java/org/spongepowered/common/network/syncher/SpongeSynchedEntityDataList.java b/src/main/java/org/spongepowered/common/network/syncher/SpongeSynchedEntityDataList.java new file mode 100644 index 00000000000..ad0b17b2fe4 --- /dev/null +++ b/src/main/java/org/spongepowered/common/network/syncher/SpongeSynchedEntityDataList.java @@ -0,0 +1,75 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.spongepowered.common.network.syncher; + +import net.minecraft.network.syncher.SynchedEntityData; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; + +public final class SpongeSynchedEntityDataList extends AbstractList> { + + private final List> list; + + private long flags; + + public SpongeSynchedEntityDataList() { + this.list = new ArrayList<>(); + } + + public long flags() { + return this.flags; + } + + @Override + public int size() { + return this.list.size(); + } + + @Override + public SynchedEntityData.DataValue get(final int index) { + return this.list.get(index); + } + + @Override + public SynchedEntityData.DataValue set(final int index, final SynchedEntityData.DataValue element) { + this.flags |= 1L << element.id(); + return this.list.set(index, element); + } + + @Override + public void add(final int index, final SynchedEntityData.DataValue element) { + this.flags |= 1L << element.id(); + this.list.add(index, element); + } + + @Override + public SynchedEntityData.DataValue remove(final int index) { + final SynchedEntityData.DataValue element = this.list.remove(index); + this.flags &= ~(1L << element.id()); + return element; + } +} diff --git a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/entity/EntityMixin_API.java b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/entity/EntityMixin_API.java index 9e5b2fe1c8f..ca0bae960a8 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/entity/EntityMixin_API.java +++ b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/entity/EntityMixin_API.java @@ -38,10 +38,13 @@ import net.minecraft.world.entity.RelativeMovement; import net.minecraft.world.phys.Vec3; import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.data.DataHolder; +import org.spongepowered.api.data.DataPerspective; import org.spongepowered.api.data.Keys; import org.spongepowered.api.data.persistence.DataContainer; import org.spongepowered.api.data.persistence.Queries; import org.spongepowered.api.data.value.Value; +import org.spongepowered.api.data.value.ValueContainer; import org.spongepowered.api.entity.EntityArchetype; import org.spongepowered.api.entity.EntitySnapshot; import org.spongepowered.api.entity.EntityType; @@ -60,13 +63,16 @@ import org.spongepowered.common.SpongeCommon; import org.spongepowered.common.bridge.data.VanishableBridge; import org.spongepowered.common.bridge.world.entity.EntityBridge; +import org.spongepowered.common.bridge.world.entity.EntityBridge_Contextual; import org.spongepowered.common.data.persistence.NBTTranslator; import org.spongepowered.common.entity.SpongeEntityArchetypeBuilder; import org.spongepowered.common.event.tracking.PhaseTracker; import org.spongepowered.common.util.Constants; import org.spongepowered.common.util.VecHelper; import org.spongepowered.math.vector.Vector3d; +import org.spongepowered.plugin.PluginContainer; +import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.Objects; @@ -391,4 +397,23 @@ public HoverEvent asHoverEvent(final UnaryOperator perceives() { + return Collections.singleton(this); + } + + @Override + public ValueContainer getDataPerception(final DataPerspective perspective) { + final @Nullable ValueContainer container = ((EntityBridge_Contextual) this).bridge$contextualData().dataPerception(perspective); + if (container != null) { + return container; + } + return this; + } + + @Override + public DataHolder.Mutable createDataPerception(final PluginContainer plugin, final DataPerspective perspective) { + return ((EntityBridge_Contextual) this).bridge$contextualData().createDataPerception(plugin, perspective); + } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/scores/PlayerTeamMixin_API.java b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/scores/PlayerTeamMixin_API.java index e5cd023b341..4e8c404b68a 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/scores/PlayerTeamMixin_API.java +++ b/src/mixins/java/org/spongepowered/common/mixin/api/minecraft/world/scores/PlayerTeamMixin_API.java @@ -30,6 +30,9 @@ import net.minecraft.world.scores.PlayerTeam; import net.minecraft.world.scores.Scoreboard; import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.data.DataHolder; +import org.spongepowered.api.data.DataPerspective; +import org.spongepowered.api.data.value.ValueContainer; import org.spongepowered.api.scoreboard.Team; import org.spongepowered.api.scoreboard.Visibility; import org.spongepowered.asm.mixin.Final; @@ -41,6 +44,8 @@ import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.common.bridge.world.scores.PlayerTeamBridge; +import org.spongepowered.common.bridge.world.scores.PlayerTeamBridge_Contextual; +import org.spongepowered.plugin.PluginContainer; import java.util.Collection; import java.util.Optional; @@ -221,4 +226,19 @@ public boolean unregister() { return true; } + @Override + public Iterable perceives() { + return (Iterable) (Object) ((PlayerTeamBridge) this).bridge$getPlayers(); + } + + @Override + public ValueContainer getDataPerception(final DataPerspective perspective) { + return null; + } + + @Override + public DataHolder.Mutable createDataPerception(final PluginContainer plugin, final DataPerspective perspective) { + return ((PlayerTeamBridge_Contextual) this).bridge$contextualData().createDataPerception(plugin, perspective); + } + } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/network/syncher/SynchedEntityDataMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/network/syncher/SynchedEntityDataMixin.java index bb6aca6c5ac..eadc1e24fa5 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/network/syncher/SynchedEntityDataMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/network/syncher/SynchedEntityDataMixin.java @@ -37,11 +37,16 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.common.bridge.network.syncher.EntityDataAccessorBridge; import org.spongepowered.common.bridge.world.entity.EntityBridge; import org.spongepowered.common.data.datasync.DataParameterConverter; import org.spongepowered.common.event.tracking.PhaseTracker; +import org.spongepowered.common.network.syncher.SpongeSynchedEntityDataList; +import java.util.List; import java.util.Optional; @Mixin(SynchedEntityData.class) @@ -108,4 +113,20 @@ public void set(final EntityDataAccessor key, T value) { this.isDirty = true; } } + + @ModifyVariable(method = "packDirty", at = @At("STORE"), slice = @Slice( + from = @At(value = "INVOKE", target = "java/util/ArrayList.()V", remap = false), + to = @At(value = "INVOKE", target = "java/util/List.add (Ljava/lang/Object;)Z", remap = false) + )) + private List> impl$useTrackingList(final List> original) { + return new SpongeSynchedEntityDataList(); + } + + @ModifyVariable(method = "getNonDefaultValues", at = @At("STORE"), slice = @Slice( + from = @At(value = "INVOKE", target = "java/util/ArrayList.()V", remap = false), + to = @At(value = "INVOKE", target = "java/util/List.add (Ljava/lang/Object;)Z", remap = false) + )) + private List> impl$useTrackingList2(final List> original) { + return new SpongeSynchedEntityDataList(); + } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/ServerScoreboardMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/ServerScoreboardMixin.java index b9dc29ee5d9..49a9052834a 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/ServerScoreboardMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/ServerScoreboardMixin.java @@ -24,6 +24,7 @@ */ package org.spongepowered.common.mixin.core.server; +import com.google.common.collect.Maps; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket; import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket; @@ -36,6 +37,7 @@ import net.minecraft.world.scores.ScoreHolder; import net.minecraft.world.scores.Scoreboard; import net.minecraft.world.scores.criteria.ObjectiveCriteria; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.scoreboard.Score; import org.spongepowered.api.scoreboard.Team; import org.spongepowered.api.scoreboard.criteria.Criterion; @@ -46,19 +48,22 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.common.accessor.world.scores.PlayerTeamAccessor; import org.spongepowered.common.accessor.world.scores.ScoreboardAccessor; import org.spongepowered.common.adventure.SpongeAdventure; import org.spongepowered.common.bridge.server.ServerScoreboardBridge; import org.spongepowered.common.bridge.world.scores.ObjectiveBridge; +import org.spongepowered.common.bridge.world.scores.PlayerTeamBridge; +import org.spongepowered.common.bridge.world.scores.PlayerTeamBridge_Contextual; import org.spongepowered.common.scoreboard.SpongeObjective; import org.spongepowered.common.scoreboard.SpongeScore; import org.spongepowered.common.util.Constants; -import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -68,7 +73,7 @@ public abstract class ServerScoreboardMixin extends Scoreboard implements ServerScoreboardBridge { - private final List impl$players = new ArrayList<>(); + private final Map impl$players = Maps.newHashMap(); private boolean impl$apiCall; @@ -178,14 +183,18 @@ public abstract class ServerScoreboardMixin extends Scoreboard implements Server @Override public void bridge$sendToPlayers(final Packet packet) { - for (final ServerPlayer player: this.impl$players) { + for (final ServerPlayer player: this.impl$players.values()) { player.connection.send(packet); } } @Override public void bridge$addPlayer(final ServerPlayer player, final boolean sendPackets) { - this.impl$players.add(player); + this.impl$players.put(player.getScoreboardName(), player); + final @Nullable PlayerTeam playerTeam = this.getPlayersTeam(player.getScoreboardName()); + if (playerTeam != null) { + ((PlayerTeamBridge) playerTeam).bridge$addPlayer(player); + } if (sendPackets) { for (final PlayerTeam team : this.getPlayerTeams()) { player.connection.send(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)); @@ -213,7 +222,11 @@ public abstract class ServerScoreboardMixin extends Scoreboard implements Server @Override public void bridge$removePlayer(final ServerPlayer player, final boolean sendPackets) { - this.impl$players.remove(player); + this.impl$players.remove(player.getScoreboardName()); + final @Nullable PlayerTeam playerTeam = this.getPlayersTeam(player.getScoreboardName()); + if (playerTeam != null) { + ((PlayerTeamBridge) playerTeam).bridge$removePlayer(player, sendPackets); + } if (sendPackets) { this.impl$removeScoreboard(player); } @@ -309,13 +322,13 @@ private boolean onUpdateScoreValue(final Set set, final Object object) { @Redirect(method = "startTrackingObjective", at = @At(value = "INVOKE", target = "Ljava/util/List;iterator()Ljava/util/Iterator;", ordinal = 0, remap = false)) private Iterator impl$useOurScoreboardForPlayers(final List list) { - return this.impl$players.iterator(); + return this.impl$players.values().iterator(); } @Redirect(method = "stopTrackingObjective", at = @At(value = "INVOKE", target = "Ljava/util/List;iterator()Ljava/util/Iterator;", ordinal = 0, remap = false)) private Iterator impl$useOurScoreboardForPlayersOnRemoval(final List list) { - return this.impl$players.iterator(); + return this.impl$players.values().iterator(); } private void impl$removeScoreboard(final ServerPlayer player) { @@ -344,4 +357,29 @@ private boolean onUpdateScoreValue(final Set set, final Object object) { ((SpongeScore) spongeScore).unregister(mcObjective); this.impl$apiCall = false; } + + @Inject(method = "addPlayerToTeam", at = @At("RETURN")) + private void impl$onAddPlayerToTeam(final String scoreboardName, final PlayerTeam team, final CallbackInfoReturnable cir) { + if (!cir.getReturnValue()) { + return; + } + + final @Nullable ServerPlayer player = this.impl$players.get(scoreboardName); + if (player != null) { + ((PlayerTeamBridge) team).bridge$addPlayer(player); + } + } + + @Inject(method = "removePlayerFromTeam", at = @At("TAIL")) + private void impl$onPlayerRemovedFromTeam(final String scoreboardName, final PlayerTeam team, final CallbackInfo ci) { + final @Nullable ServerPlayer player = this.impl$players.get(scoreboardName); + if (player != null) { + ((PlayerTeamBridge) team).bridge$removePlayer(player, true); + } + } + + @Inject(method = "onTeamRemoved", at = @At("TAIL")) + private void impl$onTeamRemoved(final PlayerTeam team, final CallbackInfo ci) { + ((PlayerTeamBridge_Contextual) team).bridge$contextualData().close(); + } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ChunkMap_TrackedEntityMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ChunkMap_TrackedEntityMixin.java index 2164fa49bf1..b8001c474c9 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ChunkMap_TrackedEntityMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ChunkMap_TrackedEntityMixin.java @@ -25,18 +25,24 @@ package org.spongepowered.common.mixin.core.server.level; import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.network.ServerGamePacketListenerImpl; import net.minecraft.server.network.ServerPlayerConnection; import net.minecraft.world.entity.Entity; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.data.DataPerspective; +import org.spongepowered.api.data.Keys; +import org.spongepowered.api.data.value.ValueContainer; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.common.bridge.data.VanishableBridge; +import org.spongepowered.common.data.contextual.ContextualData; +import org.spongepowered.common.data.contextual.PerspectiveContainer; +import org.spongepowered.common.data.contextual.util.ContextualPacketUtil; import org.spongepowered.common.entity.living.human.HumanEntity; import java.util.stream.Stream; @@ -44,9 +50,14 @@ @Mixin(targets = "net/minecraft/server/level/ChunkMap$TrackedEntity") public abstract class ChunkMap_TrackedEntityMixin { - @Shadow @Final private Entity entity; + @Shadow @Final Entity entity; /** + * @author gabizou + * @reason Because of the public availability of some methods, a packet + * being sent for a "vanished" entity is not permissible since the vanished + * entity is being "removed" from clients by way of literally being mimiced being + * "untracked". This safeguards the players being updated erroneously. * @author gabizou * @reason Instead of attempting to fetch the player set within * {@link org.spongepowered.common.mixin.core.server.level.ServerEntityMixin#impl$sendHumanMetadata(CallbackInfo)}, @@ -58,7 +69,17 @@ public abstract class ChunkMap_TrackedEntityMixin { @Redirect(method = "broadcast(Lnet/minecraft/network/protocol/Packet;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerConnection;send(Lnet/minecraft/network/protocol/Packet;)V")) private void impl$sendQueuedHumanPackets(final ServerPlayerConnection serverPlayNetHandler, final Packet packetIn) { - serverPlayNetHandler.send(packetIn); + final @Nullable PerspectiveContainer contextualData = ((ContextualData) this.entity).dataPerception((DataPerspective) serverPlayNetHandler.getPlayer()); + final ValueContainer dataPerception = contextualData != null ? contextualData : (ValueContainer) this.entity; + if (dataPerception.require(Keys.VANISH_STATE).invisible()) { + return; + } + + if (contextualData != null && packetIn instanceof final ClientboundSetEntityDataPacket entityDataPacket) { + serverPlayNetHandler.send(ContextualPacketUtil.createContextualPacket(entityDataPacket, contextualData)); + } else { + serverPlayNetHandler.send(packetIn); + } if (this.entity instanceof HumanEntity && serverPlayNetHandler instanceof ServerGamePacketListenerImpl) { final ServerPlayer player = ((ServerGamePacketListenerImpl) serverPlayNetHandler).player; @@ -67,32 +88,15 @@ public abstract class ChunkMap_TrackedEntityMixin { } } - /** - * @author gabizou - * @reason Because of the public availability of some methods, a packet - * being sent for a "vanished" entity is not permissible since the vanished - * entity is being "removed" from clients by way of literally being mimiced being - * "untracked". This safeguards the players being updated erroneously. - */ - @Inject(method = "broadcast(Lnet/minecraft/network/protocol/Packet;)V", at = @At("HEAD"), cancellable = true) - private void impl$ignoreVanished(final Packet p_219391_1_, final CallbackInfo ci) { - if (this.entity instanceof VanishableBridge) { - if (((VanishableBridge) this.entity).bridge$vanishState().invisible()) { - ci.cancel(); - } - } - } - @Redirect(method = "updatePlayer(Lnet/minecraft/server/level/ServerPlayer;)V", at = @At( value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;broadcastToPlayer(Lnet/minecraft/server/level/ServerPlayer;)Z")) private boolean impl$isSpectatedOrVanished(final Entity entity, final ServerPlayer player) { - if (entity instanceof VanishableBridge) { - if (((VanishableBridge) entity).bridge$vanishState().invisible()) { - return false; - } + if (((DataPerspective) entity).getDataPerception((DataPerspective) player).require(Keys.VANISH_STATE).invisible()) { + return false; } + return entity.broadcastToPlayer(player); } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerEntityMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerEntityMixin.java index eb423b64f86..2640a0e8942 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerEntityMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerEntityMixin.java @@ -25,14 +25,17 @@ package org.spongepowered.common.mixin.core.server.level; import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.level.ServerEntity; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import org.spongepowered.api.data.DataPerspective; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mutable; @@ -45,6 +48,8 @@ import org.spongepowered.common.accessor.world.entity.LivingEntityAccessor; import org.spongepowered.common.bridge.data.VanishableBridge; import org.spongepowered.common.bridge.server.level.ServerPlayerBridge; +import org.spongepowered.common.data.contextual.ContextualData; +import org.spongepowered.common.data.contextual.util.ContextualPacketUtil; import org.spongepowered.common.entity.living.human.HumanEntity; import java.util.Collection; @@ -166,4 +171,10 @@ public abstract class ServerEntityMixin { return packed; } + + @Redirect(method = "sendPairingData", at = @At(value = "INVOKE", remap = false, target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V", ordinal = 1)) + private void impl$modifyContextualEntityData(final Consumer> instance, final Object packet, final ServerPlayer player) { + instance.accept(ContextualPacketUtil.createContextualPacket((ClientboundSetEntityDataPacket) packet, (ContextualData) this.entity, (DataPerspective) player)); + } + } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/EntityMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/EntityMixin.java index de09ff02afb..a0310495242 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/EntityMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/entity/EntityMixin.java @@ -32,6 +32,7 @@ import net.minecraft.core.particles.ParticleOptions; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; import net.minecraft.network.syncher.SynchedEntityData; @@ -70,6 +71,7 @@ import org.objectweb.asm.Opcodes; import org.spongepowered.api.Sponge; import org.spongepowered.api.data.DataHolder; +import org.spongepowered.api.data.DataPerspective; import org.spongepowered.api.data.DataTransactionResult; import org.spongepowered.api.data.Keys; import org.spongepowered.api.data.value.Value; @@ -110,10 +112,15 @@ import org.spongepowered.common.bridge.data.TransientBridge; import org.spongepowered.common.bridge.data.VanishableBridge; import org.spongepowered.common.bridge.world.entity.EntityBridge; +import org.spongepowered.common.bridge.world.entity.EntityBridge_Contextual; import org.spongepowered.common.bridge.world.entity.PlatformEntityBridge; import org.spongepowered.common.bridge.world.level.LevelBridge; import org.spongepowered.common.bridge.world.level.PlatformServerLevelBridge; import org.spongepowered.common.data.DataUtil; +import org.spongepowered.common.data.contextual.ContextualData; +import org.spongepowered.common.data.contextual.ContextualDataHolder; +import org.spongepowered.common.data.contextual.ContextualDataOwner; +import org.spongepowered.common.data.contextual.PerspectiveContainer; import org.spongepowered.common.data.provider.nbt.NBTDataType; import org.spongepowered.common.data.provider.nbt.NBTDataTypes; import org.spongepowered.common.data.value.ImmutableSpongeValue; @@ -141,7 +148,7 @@ import java.util.UUID; @Mixin(Entity.class) -public abstract class EntityMixin implements EntityBridge, PlatformEntityBridge, VanishableBridge, CommandSourceProviderBridge, DataCompoundHolder, TransientBridge { +public abstract class EntityMixin implements EntityBridge, PlatformEntityBridge, VanishableBridge, CommandSourceProviderBridge, DataCompoundHolder, TransientBridge, EntityBridge_Contextual, ContextualData { // @formatter:off @@ -256,6 +263,8 @@ public abstract class EntityMixin implements EntityBridge, PlatformEntityBridge, // Structure: tileNbt - ForgeData - SpongeData - customdata private CompoundTag impl$customDataCompound; + private final ContextualDataHolder impl$contextualData = new ContextualDataHolder((org.spongepowered.api.entity.Entity) this); + @Override public boolean bridge$isConstructing() { return this.impl$isConstructing; @@ -421,12 +430,16 @@ public abstract class EntityMixin implements EntityBridge, PlatformEntityBridge, if (this.bridge$vanishState().invisible()) { for (final ServerPlayerConnection playerConnection : trackerAccessor.accessor$seenBy()) { - trackerAccessor.accessor$removePlayer(playerConnection.getPlayer()); + final ServerPlayer player = playerConnection.getPlayer(); + if (((DataPerspective) this).getDataPerception((DataPerspective) player).require(Keys.VANISH_STATE).invisible()) { + trackerAccessor.accessor$removePlayer(player); + } } if ((Entity) (Object) this instanceof ServerPlayer) { for (final ServerPlayer entityPlayerMP : SpongeCommon.server().getPlayerList().getPlayers()) { - if ((Object) this == entityPlayerMP) { + if ((Object) this == entityPlayerMP + || !((DataPerspective) this).getDataPerception((DataPerspective) entityPlayerMP).require(Keys.VANISH_STATE).invisible()) { continue; } entityPlayerMP.connection.send(new ClientboundPlayerInfoRemovePacket(List.of(this.uuid))); @@ -434,7 +447,8 @@ public abstract class EntityMixin implements EntityBridge, PlatformEntityBridge, } } else { for (final ServerPlayer entityPlayerMP : SpongeCommon.server().getPlayerList().getPlayers()) { - if ((Object) this == entityPlayerMP) { + if ((Object) this == entityPlayerMP + || ((DataPerspective) this).getDataPerception((DataPerspective) entityPlayerMP).require(Keys.VANISH_STATE).invisible()) { continue; } if ((Entity) (Object) this instanceof ServerPlayer player) { @@ -445,6 +459,32 @@ public abstract class EntityMixin implements EntityBridge, PlatformEntityBridge, } } + @Override + public void bridge$vanishState(final VanishState state, final DataPerspective perspective) { + final ChunkMap_TrackedEntityAccessor trackerAccessor = ((ChunkMapAccessor) ((ServerWorld) this.shadow$level()).chunkManager()).accessor$entityMap().get(this.shadow$getId()); + if (trackerAccessor == null) { + return; + } + + for (final DataPerspective perceive : perspective.perceives()) { + if (!(perceive instanceof final ServerPlayer entityPlayerMP) || this == perceive) { + continue; + } + + if (state.invisible()) { + trackerAccessor.accessor$removePlayer(entityPlayerMP); + if ((Entity) (Object) this instanceof ServerPlayer) { + entityPlayerMP.connection.send(new ClientboundPlayerInfoRemovePacket(List.of(this.uuid))); + } + } else { + if ((Entity) (Object) this instanceof final ServerPlayer player) { + entityPlayerMP.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player))); + } + trackerAccessor.accessor$updatePlayer(entityPlayerMP); + } + } + } + @Override public boolean bridge$isTransient() { return this.shadow$getEncodeId() == null; @@ -1298,4 +1338,41 @@ public void stopRiding() { }*/ + @Override + public ContextualDataHolder bridge$contextualData() { + return this.impl$contextualData; + } + + @Override + public @Nullable PerspectiveContainer dataPerception(final DataPerspective perspective) { + return this.impl$contextualData.dataPerception(perspective); + } + + @Override + public PerspectiveContainer createDataPerception(final DataPerspective perspective) { + return this.impl$contextualData.createDataPerception(perspective); + } + + @Override + public void linkContextualOwner(final ContextualDataOwner owner) { + this.impl$contextualData.linkContextualOwner(owner); + } + + @Override + public void unlinkContextualOwner(final ContextualDataOwner owner) { + this.impl$contextualData.unlinkContextualOwner(owner); + } + + @Override + public void broadcastToPerceives(final Packet packet) { + if ((Object) this instanceof ServerPlayer player) { + player.connection.send(packet); + } + } + + //TODO: We lose the contextual data when the entity changes dimensions + @Inject(method = "setRemoved", at = @At("TAIL")) + private void impl$clearContextualData(final Entity.RemovalReason $$0, final CallbackInfo ci) { + this.impl$contextualData.close(); + } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/world/scores/PlayerTeamMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/world/scores/PlayerTeamMixin.java index 6e46f440fa7..c49327d5c2b 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/world/scores/PlayerTeamMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/world/scores/PlayerTeamMixin.java @@ -29,6 +29,7 @@ import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.minecraft.ChatFormatting; +import net.minecraft.network.protocol.Packet; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.scores.PlayerTeam; import net.minecraft.world.scores.Scoreboard; @@ -36,6 +37,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.objectweb.asm.Opcodes; import org.spongepowered.api.Sponge; +import org.spongepowered.api.data.DataPerspective; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mutable; @@ -46,13 +48,20 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.common.adventure.SpongeAdventure; import org.spongepowered.common.bridge.world.scores.PlayerTeamBridge; +import org.spongepowered.common.bridge.world.scores.PlayerTeamBridge_Contextual; +import org.spongepowered.common.data.contextual.ContextualData; +import org.spongepowered.common.data.contextual.ContextualDataDelegate; +import org.spongepowered.common.data.contextual.ContextualDataOwner; +import org.spongepowered.common.data.contextual.PerspectiveContainer; +import java.util.ArrayList; import java.util.Collection; -import java.util.Optional; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; @Mixin(PlayerTeam.class) -public abstract class PlayerTeamMixin implements PlayerTeamBridge { +public abstract class PlayerTeamMixin implements PlayerTeamBridge, PlayerTeamBridge_Contextual, ContextualData { // @formatter:off @Shadow @Final @Mutable @Nullable private Scoreboard scoreboard; @@ -67,6 +76,9 @@ public abstract class PlayerTeamMixin implements PlayerTeamBridge { private @MonotonicNonNull Component bridge$prefix; private @MonotonicNonNull Component bridge$suffix; private @MonotonicNonNull NamedTextColor bridge$color; + private final List impl$players = new ArrayList<>(); + + private final ContextualDataDelegate impl$contextualData = new ContextualDataDelegate((DataPerspective) this); private void impl$teamChanged() { if (this.scoreboard != null) { @@ -190,12 +202,7 @@ public abstract class PlayerTeamMixin implements PlayerTeamBridge { @SuppressWarnings("EqualsBetweenInconvertibleTypes") @Override public Audience bridge$getTeamChannel(final ServerPlayer player) { - return Audience.audience(this.getPlayers().stream() - .map(name -> Sponge.game().server().player(name)) - .filter(Optional::isPresent) - .map(Optional::get) - .filter(member -> member != player) - .collect(Collectors.toSet())); + return Audience.audience((Iterable) (Object) this.impl$players); } @Override @@ -204,4 +211,55 @@ public abstract class PlayerTeamMixin implements PlayerTeamBridge { .filter(player -> ((ServerPlayer) player).getTeam() != (Object) this) .collect(Collectors.toSet())); } + + @Override + public ContextualDataDelegate bridge$contextualData() { + return this.impl$contextualData; + } + + @Override + public @Nullable PerspectiveContainer dataPerception(final DataPerspective perspective) { + return this.impl$contextualData.dataPerception(perspective); + } + + @Override + public PerspectiveContainer createDataPerception(final DataPerspective perspective) { + return this.impl$contextualData.createDataPerception(perspective); + } + + @Override + public void linkContextualOwner(final ContextualDataOwner owner) { + this.impl$contextualData.linkContextualOwner(owner); + } + + @Override + public void unlinkContextualOwner(final ContextualDataOwner owner) { + this.impl$contextualData.unlinkContextualOwner(owner); + } + + @Override + public void broadcastToPerceives(final Packet packet) { + for (final ServerPlayer player : this.impl$players) { + player.connection.send(packet); + } + } + + @Override + public void bridge$addPlayer(final ServerPlayer player) { + this.impl$players.add(player); + this.impl$contextualData.perceiveAdded((DataPerspective) player); + } + + @Override + public void bridge$removePlayer(final ServerPlayer player, final boolean sendPackets) { + this.impl$players.remove(player); + if (sendPackets) { + this.impl$contextualData.perceiveRemoved((DataPerspective) player); + } + } + + @Override + public List bridge$getPlayers() { + return this.impl$players; + } }