From dfbf2ca76f3b6ef4421afc700dbc792922f31b2a Mon Sep 17 00:00:00 2001 From: Vftdan Date: Wed, 11 Sep 2024 22:07:46 +0200 Subject: [PATCH] Implemented infinite-radius teammate tracking --- .../DynamicPriorityEntityVisibilityRule.java | 38 +++++ .../EntityTrackerFieldsAccessor.java | 12 ++ .../EntityVisibilityRule.java | 37 +++++ .../EntityVisibilityRules.java | 7 +- .../PriorityBooleanMonoid.java | 141 ++++++++++++++++++ .../mixin/EntityTrackerMixin.java | 62 ++++++++ .../rules/GloballyVisibleTeammates.java | 46 ++++++ .../entity-visibility-rules.mixins.json | 5 +- src/main/resources/fabric.mod.json | 2 +- 9 files changed, 345 insertions(+), 5 deletions(-) create mode 100644 src/main/java/io/github/vftdan/mcentityvisibilityrules/DynamicPriorityEntityVisibilityRule.java create mode 100644 src/main/java/io/github/vftdan/mcentityvisibilityrules/EntityTrackerFieldsAccessor.java create mode 100644 src/main/java/io/github/vftdan/mcentityvisibilityrules/EntityVisibilityRule.java create mode 100644 src/main/java/io/github/vftdan/mcentityvisibilityrules/PriorityBooleanMonoid.java create mode 100644 src/main/java/io/github/vftdan/mcentityvisibilityrules/mixin/EntityTrackerMixin.java create mode 100644 src/main/java/io/github/vftdan/mcentityvisibilityrules/rules/GloballyVisibleTeammates.java diff --git a/src/main/java/io/github/vftdan/mcentityvisibilityrules/DynamicPriorityEntityVisibilityRule.java b/src/main/java/io/github/vftdan/mcentityvisibilityrules/DynamicPriorityEntityVisibilityRule.java new file mode 100644 index 0000000..4cc1017 --- /dev/null +++ b/src/main/java/io/github/vftdan/mcentityvisibilityrules/DynamicPriorityEntityVisibilityRule.java @@ -0,0 +1,38 @@ +package io.github.vftdan.mcentityvisibilityrules; + +import net.minecraft.server.network.ServerPlayerEntity; + +public interface DynamicPriorityEntityVisibilityRule extends EntityVisibilityRule { + void setRulePriority(int priority); + + public static class Wrapper implements DynamicPriorityEntityVisibilityRule { + protected T wrapped; + protected int priority; + + public Wrapper(T inner, int initialPriority) { + wrapped = inner; + priority = initialPriority; + } + + public T unwrap() { + return wrapped; + } + + @Override + public PriorityBooleanMonoid shouldBeVisible(ServerPlayerEntity player, EntityTrackerFieldsAccessor tracker) { + if (wrapped == null) { + return PriorityBooleanMonoid.IGNORE; + } + PriorityBooleanMonoid result = wrapped.shouldBeVisible(player, tracker); + if (result == null) { + return result; + } + return result.setPriority(priority); + } + + @Override + public void setRulePriority(int priority) { + this.priority = priority; + } + } +}; diff --git a/src/main/java/io/github/vftdan/mcentityvisibilityrules/EntityTrackerFieldsAccessor.java b/src/main/java/io/github/vftdan/mcentityvisibilityrules/EntityTrackerFieldsAccessor.java new file mode 100644 index 0000000..10eecfe --- /dev/null +++ b/src/main/java/io/github/vftdan/mcentityvisibilityrules/EntityTrackerFieldsAccessor.java @@ -0,0 +1,12 @@ +package io.github.vftdan.mcentityvisibilityrules; + +import net.minecraft.entity.Entity; +import net.minecraft.server.network.EntityTrackerEntry; +import net.minecraft.util.math.ChunkSectionPos; + +public interface EntityTrackerFieldsAccessor { + EntityTrackerEntry getEntry(); + Entity getEntity(); + int getMaxDistance(); + ChunkSectionPos getTrackedSection(); +} diff --git a/src/main/java/io/github/vftdan/mcentityvisibilityrules/EntityVisibilityRule.java b/src/main/java/io/github/vftdan/mcentityvisibilityrules/EntityVisibilityRule.java new file mode 100644 index 0000000..75471ef --- /dev/null +++ b/src/main/java/io/github/vftdan/mcentityvisibilityrules/EntityVisibilityRule.java @@ -0,0 +1,37 @@ +package io.github.vftdan.mcentityvisibilityrules; + +import static com.google.common.collect.Sets.newIdentityHashSet; +import java.util.Iterator; +import java.util.Set; +import net.minecraft.server.network.ServerPlayerEntity; + +public interface EntityVisibilityRule { + PriorityBooleanMonoid shouldBeVisible(ServerPlayerEntity player, EntityTrackerFieldsAccessor tracker); + + public static final Set registry = newIdentityHashSet(); + + public static PriorityBooleanMonoid applyAll(ServerPlayerEntity player, EntityTrackerFieldsAccessor tracker) { + return applyAll(registry, player, tracker); + } + + public static PriorityBooleanMonoid applyAll(final Iterable rules, final ServerPlayerEntity player, final EntityTrackerFieldsAccessor tracker) { + var mappedIterator = new Iterator() { + final Iterator wrapped = rules.iterator(); + + @Override + public boolean hasNext() { + return wrapped.hasNext(); + } + + @Override + public PriorityBooleanMonoid next() { + EntityVisibilityRule rule = wrapped.next(); + if (rule == null) { + return null; + } + return rule.shouldBeVisible(player, tracker); + } + }; + return PriorityBooleanMonoid.fold(mappedIterator); + } +} diff --git a/src/main/java/io/github/vftdan/mcentityvisibilityrules/EntityVisibilityRules.java b/src/main/java/io/github/vftdan/mcentityvisibilityrules/EntityVisibilityRules.java index d09fb68..d680159 100644 --- a/src/main/java/io/github/vftdan/mcentityvisibilityrules/EntityVisibilityRules.java +++ b/src/main/java/io/github/vftdan/mcentityvisibilityrules/EntityVisibilityRules.java @@ -1,5 +1,7 @@ package io.github.vftdan.mcentityvisibilityrules; +import io.github.vftdan.mcentityvisibilityrules.rules.GloballyVisibleTeammates; + import net.fabricmc.api.ModInitializer; import org.slf4j.Logger; @@ -19,6 +21,7 @@ public class EntityVisibilityRules implements ModInitializer { // However, some things (like resources) may still be uninitialized. // Proceed with mild caution. - LOGGER.info("Hello Fabric world!"); + LOGGER.info("Initializing entity-visibility-rules"); + EntityVisibilityRule.registry.add(GloballyVisibleTeammates.INSTANCE); } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/vftdan/mcentityvisibilityrules/PriorityBooleanMonoid.java b/src/main/java/io/github/vftdan/mcentityvisibilityrules/PriorityBooleanMonoid.java new file mode 100644 index 0000000..3100a96 --- /dev/null +++ b/src/main/java/io/github/vftdan/mcentityvisibilityrules/PriorityBooleanMonoid.java @@ -0,0 +1,141 @@ +package io.github.vftdan.mcentityvisibilityrules; + +import java.util.Iterator; +import java.util.Arrays; + +public final class PriorityBooleanMonoid implements Comparable /* partial ordering (ignores logicalValue) */ { + private final boolean present; + private final boolean logicalValue; + private final int priority; + + public static final PriorityBooleanMonoid IGNORE = new PriorityBooleanMonoid(); + + public PriorityBooleanMonoid(boolean logicalValue, int priority) { + present = true; + this.logicalValue = logicalValue; + this.priority = priority; + } + + private PriorityBooleanMonoid() { + present = false; + logicalValue = false; + priority = 0; + } + + public static PriorityBooleanMonoid acceptWith(int priority) { + return new PriorityBooleanMonoid(true, priority); + } + + public static PriorityBooleanMonoid rejectWith(int priority) { + return new PriorityBooleanMonoid(false, priority); + } + + @Override + public int compareTo(PriorityBooleanMonoid other) { + if (other == null) { + return present ? 1 : 0; + } + if (!present) { + return other.present ? -1 : 0; + } + if (!other.present) { + return 1; + } + return priority - other.priority; + } + + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + + if (other instanceof PriorityBooleanMonoid casted) { + return equals(casted); + } + return super.equals(other); + } + + public boolean equals(PriorityBooleanMonoid other) { + if (this == other) { + return true; + } + + if (!present) { + return !other.present; + } + + if (!other.present) { + return false; + } + + return priority == other.priority && logicalValue == other.logicalValue; + } + + public PriorityBooleanMonoid setPriority(int newPriority) { + if (!present) { + return this; + } + return new PriorityBooleanMonoid(logicalValue, newPriority); + } + + public Boolean getLogicalValueOrNull() { + if (!present) { + return null; + } + return logicalValue; + } + + public Integer getPriorityOrNull() { + if (!present) { + return null; + } + return priority; + } + + public boolean getLogicalValueOr(boolean dflt) { + if (!present) { + return dflt; + } + return logicalValue; + } + + public boolean isPresent() { + return present; + } + + public PriorityBooleanMonoid combineWith(PriorityBooleanMonoid other) { + if (other == null) { + return this; + } + if (!present) { + return other; + } + return (other.priority >= priority) ? other : this; + } + + public static PriorityBooleanMonoid fold(Iterator items) { + PriorityBooleanMonoid result = IGNORE; + if (items == null) { + return result; + } + while (items.hasNext()) { + result = result.combineWith(items.next()); + } + return result; + } + + public static PriorityBooleanMonoid fold(Iterable items) { + if (items == null) { + return IGNORE; + } + return fold(items.iterator()); + } + + public static PriorityBooleanMonoid fold(PriorityBooleanMonoid... items) { + if (items == null) { + return IGNORE; + } + return fold(Arrays.asList(items)); + } +} diff --git a/src/main/java/io/github/vftdan/mcentityvisibilityrules/mixin/EntityTrackerMixin.java b/src/main/java/io/github/vftdan/mcentityvisibilityrules/mixin/EntityTrackerMixin.java new file mode 100644 index 0000000..a2a6981 --- /dev/null +++ b/src/main/java/io/github/vftdan/mcentityvisibilityrules/mixin/EntityTrackerMixin.java @@ -0,0 +1,62 @@ +package io.github.vftdan.mcentityvisibilityrules.mixin; + +import io.github.vftdan.mcentityvisibilityrules.EntityTrackerFieldsAccessor; +import io.github.vftdan.mcentityvisibilityrules.EntityVisibilityRule; +import java.util.Set; +import net.minecraft.entity.Entity; +import net.minecraft.server.network.EntityTrackerEntry; +import net.minecraft.server.network.PlayerAssociatedNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerChunkLoadingManager; +import net.minecraft.util.math.ChunkSectionPos; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(targets = "net.minecraft.server.world.ServerChunkLoadingManager$EntityTracker") +public abstract class EntityTrackerMixin implements EntityTrackerFieldsAccessor { + @Inject(at = @At("HEAD"), method = "updateTrackedStatus", cancellable = true) + private void onUpdateTrackedStatus(ServerPlayerEntity player, CallbackInfo info) { + if (player == getEntity()) { + return; + } + + Boolean result = EntityVisibilityRule.applyAll(player, this).getLogicalValueOrNull(); + if (result == null) { + return; // do not cancel + } + info.cancel(); + if (result) { + startTrackingPlayer(player); + } else { + stopTrackingPlayer(player); + } + } + + @Accessor + public abstract EntityTrackerEntry getEntry(); + + @Accessor + public abstract Entity getEntity(); + + @Accessor + public abstract int getMaxDistance(); + + @Accessor + public abstract ChunkSectionPos getTrackedSection(); + + @Accessor + protected abstract Set getListeners(); + + @Invoker("stopTracking") + protected abstract void stopTrackingPlayer(ServerPlayerEntity player); + + protected void startTrackingPlayer(ServerPlayerEntity player) { + if (getListeners().add(player.networkHandler)) { + getEntry().startTracking(player); + } + } +} diff --git a/src/main/java/io/github/vftdan/mcentityvisibilityrules/rules/GloballyVisibleTeammates.java b/src/main/java/io/github/vftdan/mcentityvisibilityrules/rules/GloballyVisibleTeammates.java new file mode 100644 index 0000000..5de68b5 --- /dev/null +++ b/src/main/java/io/github/vftdan/mcentityvisibilityrules/rules/GloballyVisibleTeammates.java @@ -0,0 +1,46 @@ +package io.github.vftdan.mcentityvisibilityrules.rules; + +import io.github.vftdan.mcentityvisibilityrules.DynamicPriorityEntityVisibilityRule; +import io.github.vftdan.mcentityvisibilityrules.EntityTrackerFieldsAccessor; +import io.github.vftdan.mcentityvisibilityrules.PriorityBooleanMonoid; +import static io.github.vftdan.mcentityvisibilityrules.PriorityBooleanMonoid.IGNORE; +import net.minecraft.scoreboard.Team; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.entity.player.PlayerEntity; + +public class GloballyVisibleTeammates implements DynamicPriorityEntityVisibilityRule { + public static final GloballyVisibleTeammates INSTANCE = new GloballyVisibleTeammates(); + + private PriorityBooleanMonoid ACCEPT = PriorityBooleanMonoid.acceptWith(0); + + protected GloballyVisibleTeammates() { + + } + + @Override + public PriorityBooleanMonoid shouldBeVisible(ServerPlayerEntity player, EntityTrackerFieldsAccessor tracker) { + if (tracker.getEntity() instanceof PlayerEntity otherPlayer) { + Team observerTeam = player.getScoreboardTeam(); + if (observerTeam == null) { + return IGNORE; + } + + Team observeeTeam = otherPlayer.getScoreboardTeam(); + if (!observerTeam.isEqual(observeeTeam)) { + return IGNORE; + } + + if (!observerTeam.shouldShowFriendlyInvisibles()) { + return IGNORE; + } + + return ACCEPT; + } + return IGNORE; + } + + @Override + public void setRulePriority(int priority) { + ACCEPT = PriorityBooleanMonoid.acceptWith(priority);; + } +} diff --git a/src/main/resources/entity-visibility-rules.mixins.json b/src/main/resources/entity-visibility-rules.mixins.json index e1faa70..996caa0 100644 --- a/src/main/resources/entity-visibility-rules.mixins.json +++ b/src/main/resources/entity-visibility-rules.mixins.json @@ -3,9 +3,10 @@ "package": "io.github.vftdan.mcentityvisibilityrules.mixin", "compatibilityLevel": "JAVA_21", "mixins": [ - "ExampleMixin" + "ExampleMixin", + "EntityTrackerMixin" ], "injectors": { "defaultRequire": 1 } -} \ No newline at end of file +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index bcc8f31..f0a90ba 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -13,7 +13,7 @@ }, "license": "CC0-1.0", "icon": "assets/entity-visibility-rules/icon.png", - "environment": "server", + "environment": "*", "entrypoints": { "main": [ "io.github.vftdan.mcentityvisibilityrules.EntityVisibilityRules"