diff --git a/build.gradle b/build.gradle index a8396df..308e56b 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,10 @@ plugins { version = project.mod_version group = project.maven_group +loom { + accessWidenerPath = file("src/main/resources/minions.accesswidener") +} + base { archivesName = project.archives_base_name } diff --git a/gradle.properties b/gradle.properties index b72282b..3231941 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html minecraft_version=1.21.3 - loader_version=0.16.7 + loader_version=0.16.13 yarn_mappings=1.21.3+build.2 # Mod Properties diff --git a/src/main/java/io/github/skippyall/minions/Minions.java b/src/main/java/io/github/skippyall/minions/Minions.java index 203a692..ba7510d 100644 --- a/src/main/java/io/github/skippyall/minions/Minions.java +++ b/src/main/java/io/github/skippyall/minions/Minions.java @@ -8,6 +8,7 @@ import io.github.skippyall.minions.minion.MinionItem; import io.github.skippyall.minions.minion.MinionPersistentState; import io.github.skippyall.minions.module.Modules; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.minecraft.component.DataComponentTypes; @@ -21,6 +22,7 @@ import net.minecraft.registry.Registry; import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.tag.TagKey; +import net.minecraft.server.world.ChunkTicketManager; import net.minecraft.util.Identifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,6 +61,10 @@ public class Minions implements ModInitializer { }); }); + CommandRegistrationCallback.EVENT.register((commandDispatcher, commandRegistryAccess, registrationEnvironment) -> { + MobCapCommand.registerCommand(commandDispatcher); + }); + Modules.register(); } diff --git a/src/main/java/io/github/skippyall/minions/MobCapCommand.java b/src/main/java/io/github/skippyall/minions/MobCapCommand.java new file mode 100644 index 0000000..6a7fa1e --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/MobCapCommand.java @@ -0,0 +1,24 @@ +package io.github.skippyall.minions; + +import com.mojang.brigadier.CommandDispatcher; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManagerAccessor; +import io.github.skippyall.minions.mixins.antimobcap.ServerChunkManagerAccessor; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.world.ChunkTicketManager; +import net.minecraft.text.Text; + +import static net.minecraft.server.command.CommandManager.literal; + +public class MobCapCommand { + public static void registerCommand(CommandDispatcher dispatcher) { + dispatcher.register(literal("mobcapdebug") + .executes(context -> { + ChunkTicketManager ticketManager = ((ServerChunkManagerAccessor)context.getSource().getWorld().getChunkManager()).getTicketManager(); + int tickedChunkCount = ((ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor)((ChunkTicketManagerAccessor)ticketManager).getMinionless()).minions$getTickedChunkCount(); + context.getSource().sendFeedback(() -> Text.of(String.valueOf(tickedChunkCount)), false); + return 0; + }) + ); + } +} diff --git a/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor.java b/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor.java new file mode 100644 index 0000000..b4fbb8c --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor.java @@ -0,0 +1,9 @@ +package io.github.skippyall.minions.mixinhelper; + +public interface ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor { + void minions$markAsMinionless(); + void minions$markAsTarget(); + int minions$getTickedChunkCount(); + + boolean minions$isRealPlayerInChunk(long chunkPos); +} diff --git a/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManagerAccessor.java b/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManagerAccessor.java new file mode 100644 index 0000000..c6282e1 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixinhelper/ChunkTicketManagerAccessor.java @@ -0,0 +1,11 @@ +package io.github.skippyall.minions.mixinhelper; + +import it.unimi.dsi.fastutil.objects.ObjectSet; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ChunkTicketManager; + +public interface ChunkTicketManagerAccessor { + ObjectSet getPlayers(long chunkpos); + + ChunkTicketManager.DistanceFromNearestPlayerTracker getMinionless(); +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkPosDistanceLevelPropagatorMixin.java b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkPosDistanceLevelPropagatorMixin.java new file mode 100644 index 0000000..9d45022 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkPosDistanceLevelPropagatorMixin.java @@ -0,0 +1,14 @@ +package io.github.skippyall.minions.mixins.antimobcap; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(net.minecraft.world.ChunkPosDistanceLevelPropagator.class) +public class ChunkPosDistanceLevelPropagatorMixin { + @Inject(method = "updateLevel", at = @At("HEAD")) + public void minions$updateLevel(long chunkPos, int distance, boolean decrease, CallbackInfo info) { + + } +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin.java new file mode 100644 index 0000000..d540e27 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin.java @@ -0,0 +1,85 @@ +package io.github.skippyall.minions.mixins.antimobcap; + +import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManagerAccessor; +import io.github.skippyall.minions.module.MobSpawningModule; +import it.unimi.dsi.fastutil.longs.Long2ByteMap; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ChunkTicketManager; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = ChunkTicketManager.DistanceFromNearestPlayerTracker.class) +public abstract class ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin extends ChunkPosDistanceLevelPropagatorMixin implements ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor { + @Final + @Shadow + ChunkTicketManager field_17462; + + @Shadow + @Final + protected Long2ByteMap distanceFromNearestPlayer; + + @Shadow protected abstract boolean isPlayerInChunk(long chunkPos); + + @Unique + boolean minions$minionless, minions$target; + + @Inject(method = "isPlayerInChunk", at = @At("RETURN"), cancellable = true) + public void minions$filterMinions(long chunkPos, CallbackInfoReturnable cir) { + if (minions$minionless) { + cir.setReturnValue(minions$isRealPlayerInChunk(chunkPos)); + } + } + + @Override + public boolean minions$isRealPlayerInChunk(long chunkPos) { + ObjectSet players = ((ChunkTicketManagerAccessor)field_17462).getPlayers(chunkPos); + boolean contains = false; + if(players != null) { + contains = players.stream().anyMatch(player -> { + if (player instanceof MinionFakePlayer minion) { + return MobSpawningModule.canMinionSpawnMobs(minion); + } + return true; + }); + } + return contains; + } + + @Inject(method = "updateLevels", at = @At("HEAD")) + public void minions$sync(CallbackInfo info) { + if (minions$target) { + ((ChunkTicketManagerAccessor)field_17462).getMinionless().updateLevels(); + } + } + + @Override + public void minions$updateLevel(long chunkPos, int distance, boolean decrease, CallbackInfo info) { + if (minions$target && (distance == Integer.MAX_VALUE || minions$isRealPlayerInChunk(chunkPos))) { + ((ChunkTicketManagerAccessor)field_17462).getMinionless().updateLevel(chunkPos, distance, decrease); + } + } + + @Override + public void minions$markAsMinionless() { + minions$minionless = true; + } + + @Override + public void minions$markAsTarget() { + minions$target = true; + } + + @Override + public int minions$getTickedChunkCount() { + return distanceFromNearestPlayer.size(); + } +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManagerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManagerMixin.java new file mode 100644 index 0000000..6ca5362 --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ChunkTicketManagerMixin.java @@ -0,0 +1,49 @@ +package io.github.skippyall.minions.mixins.antimobcap; + +import com.llamalad7.mixinextras.sugar.Local; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManagerAccessor; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ChunkTicketManager; +import net.minecraft.util.math.ChunkSectionPos; +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.callback.CallbackInfo; + +import java.util.concurrent.Executor; + +@Mixin(ChunkTicketManager.class) +public class ChunkTicketManagerMixin implements ChunkTicketManagerAccessor { + @Shadow @Final private Long2ObjectMap> playersByChunkPos; + @Shadow @Final private ChunkTicketManager.DistanceFromNearestPlayerTracker distanceFromNearestPlayerTracker; + ChunkTicketManager.DistanceFromNearestPlayerTracker minionless; + + @Inject(method = "", at = @At("RETURN")) + public void createMinionlessClone(Executor workerExecutor, Executor mainThreadExecutor, CallbackInfo ci) { + ChunkTicketManager manager = ((ChunkTicketManager)(Object)this); + minionless = manager.new DistanceFromNearestPlayerTracker(8); + ((ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor)minionless).minions$markAsMinionless(); + ((ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor)distanceFromNearestPlayerTracker).minions$markAsTarget(); + } + + public ObjectSet getPlayers(long chunkpos) { + return playersByChunkPos.get(chunkpos); + } + + @Override + public ChunkTicketManager.DistanceFromNearestPlayerTracker getMinionless() { + return minionless; + } + + @Inject(method = "handleChunkLeave", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/objects/ObjectSet;remove(Ljava/lang/Object;)Z", shift = At.Shift.AFTER, remap = false)) + public void minion$updateMinionlessIfNoMinionInChunk(ChunkSectionPos pos, ServerPlayerEntity player, CallbackInfo ci, @Local long chunk) { + if (!((ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor)minionless).minions$isRealPlayerInChunk(chunk)) { + minionless.updateLevel(chunk, Integer.MAX_VALUE, false); + } + } +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerAccessor.java b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerAccessor.java new file mode 100644 index 0000000..b03d5fa --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerAccessor.java @@ -0,0 +1,12 @@ +package io.github.skippyall.minions.mixins.antimobcap; + +import net.minecraft.server.world.ChunkTicketManager; +import net.minecraft.server.world.ServerChunkManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ServerChunkManager.class) +public interface ServerChunkManagerAccessor { + @Accessor + ChunkTicketManager getTicketManager(); +} diff --git a/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerMixin.java b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerMixin.java new file mode 100644 index 0000000..160bceb --- /dev/null +++ b/src/main/java/io/github/skippyall/minions/mixins/antimobcap/ServerChunkManagerMixin.java @@ -0,0 +1,23 @@ +package io.github.skippyall.minions.mixins.antimobcap; + +import io.github.skippyall.minions.mixinhelper.ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor; +import io.github.skippyall.minions.mixinhelper.ChunkTicketManagerAccessor; +import net.minecraft.server.world.ChunkTicketManager; +import net.minecraft.server.world.ServerChunkManager; +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.ModifyArg; + +@Mixin(ServerChunkManager.class) +public class ServerChunkManagerMixin { + @Shadow + @Final + private ChunkTicketManager ticketManager; + + @ModifyArg(method = "tickChunks(Lnet/minecraft/util/profiler/Profiler;JLjava/util/List;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/SpawnHelper;setupSpawn(ILjava/lang/Iterable;Lnet/minecraft/world/SpawnHelper$ChunkSource;Lnet/minecraft/world/SpawnDensityCapper;)Lnet/minecraft/world/SpawnHelper$Info;")) + public int useMinionless(int spawningChunkCount) { + return ((ChunkTicketManager$DistanceFromNearestPlayerTrackerAccessor)((ChunkTicketManagerAccessor)ticketManager).getMinionless()).minions$getTickedChunkCount(); + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 49c1084..480a527 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -19,6 +19,7 @@ "mixins": [ "minions.mixins.json" ], + "accessWidener" : "minions.accesswidener", "depends": { "fabricloader": "*", "fabric": "*", diff --git a/src/main/resources/minions.accesswidener b/src/main/resources/minions.accesswidener new file mode 100644 index 0000000..0ffdeca --- /dev/null +++ b/src/main/resources/minions.accesswidener @@ -0,0 +1,4 @@ +accessWidener v2 named + +accessible class net/minecraft/server/world/ChunkTicketManager$DistanceFromNearestPlayerTracker +accessible method net/minecraft/server/world/ChunkTicketManager$DistanceFromNearestPlayerTracker (Lnet/minecraft/server/world/ChunkTicketManager;I)V \ No newline at end of file diff --git a/src/main/resources/minions.mixins.json b/src/main/resources/minions.mixins.json index a511ffc..5b22369 100644 --- a/src/main/resources/minions.mixins.json +++ b/src/main/resources/minions.mixins.json @@ -15,7 +15,12 @@ "ServerPlayerMixin", "ServerPlayNetworkHandlerMixin", "SleepManagerMixin", - "SpawnHelperMixin" + "SpawnHelperMixin", + "antimobcap.ChunkPosDistanceLevelPropagatorMixin", + "antimobcap.ChunkTicketManager$DistanceFromNearestPlayerTrackerMixin", + "antimobcap.ChunkTicketManagerMixin", + "antimobcap.ServerChunkManagerAccessor", + "antimobcap.ServerChunkManagerMixin" ], "client": [], "server": [],