Implemented infinite-radius teammate tracking

This commit is contained in:
Vftdan 2024-09-11 22:07:46 +02:00
parent 1f28554a23
commit dfbf2ca76f
9 changed files with 345 additions and 5 deletions

View File

@ -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<T extends EntityVisibilityRule> 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;
}
}
};

View File

@ -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();
}

View File

@ -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<EntityVisibilityRule> registry = newIdentityHashSet();
public static PriorityBooleanMonoid applyAll(ServerPlayerEntity player, EntityTrackerFieldsAccessor tracker) {
return applyAll(registry, player, tracker);
}
public static PriorityBooleanMonoid applyAll(final Iterable<EntityVisibilityRule> rules, final ServerPlayerEntity player, final EntityTrackerFieldsAccessor tracker) {
var mappedIterator = new Iterator<PriorityBooleanMonoid>() {
final Iterator<EntityVisibilityRule> 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);
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,141 @@
package io.github.vftdan.mcentityvisibilityrules;
import java.util.Iterator;
import java.util.Arrays;
public final class PriorityBooleanMonoid implements Comparable<PriorityBooleanMonoid> /* 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<PriorityBooleanMonoid> items) {
PriorityBooleanMonoid result = IGNORE;
if (items == null) {
return result;
}
while (items.hasNext()) {
result = result.combineWith(items.next());
}
return result;
}
public static PriorityBooleanMonoid fold(Iterable<PriorityBooleanMonoid> 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));
}
}

View File

@ -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<PlayerAssociatedNetworkHandler> getListeners();
@Invoker("stopTracking")
protected abstract void stopTrackingPlayer(ServerPlayerEntity player);
protected void startTrackingPlayer(ServerPlayerEntity player) {
if (getListeners().add(player.networkHandler)) {
getEntry().startTracking(player);
}
}
}

View File

@ -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);;
}
}

View File

@ -3,9 +3,10 @@
"package": "io.github.vftdan.mcentityvisibilityrules.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
"ExampleMixin"
"ExampleMixin",
"EntityTrackerMixin"
],
"injectors": {
"defaultRequire": 1
}
}
}

View File

@ -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"