* Add PCA sync protocol support Introduce PCA sync protocol and integrate it into Carpet/Leaves. - Added org/leavesmc/leaves/protocol/PcaSyncProtocol.java: full implementation of the PCA sync protocol (handlers for sync requests, watcher maps, update payloads, player/block watch management, scheduling, and config-driven behavior). - Added fun/bm/lophine/config/modules/function/protocol/PcaSyncProtocolConfig.java and enum PcaPlayerEntityType.java to drive enabling and player-entity sync policy. - Integrated protocol into Carpet: register new carpet rules (pcaSyncProtocol, pcaSyncPlayerEntity) in CarpetCompatSync and added batching to avoid excessive updates when rules change. - Enhanced CarpetServerProtocol to track active players, batch rule updates, and send server rule data safely on the server thread. - Added a Minecraft patch (0046-PCA-sync-protocol.patch) to trigger syncs from setChanged() in containers, chest boats, minecarts and BlockEntity updates so watched clients receive entity/block-entity updates. This enables PCA clients to watch and receive synchronized block-entity and entity state on demand, while keeping updates gated by config and player permissions and improving rule-update performance via batching. * Add Vanilla-like experience config and updates Introduce a new VanillaLikeExperienceConfig module and a patch (0047) that gates multiple Paper/Luminol behavior changes behind this toggle to restore more vanilla-like technical behavior when enabled. The patch injects conditional checks across many server classes (entity merging, phantoms, pistons, hoppers, TNT handling, explosions, portal/piston protections, item merging, teleportation, spawn counting, etc.). Also adjust PCA sync protocol references in the existing 0046 patch to use the updated fun.bm.lophine package path. Includes the new fun.bm.lophine.config.modules.fixes.VanillaLikeExperienceConfig.java file. * Restore vanilla ender pearl loading on Folia Add patch (features/0048) that restores vanilla ender-pearl loading behavior on Folia. Reintroduces ender-pearl tracking and persistence: ServerPlayer now uses a thread-safe enderPearls set and saves pearl tags, ThrownEnderpearl maintains a lastSavedEnderPearlTag (updated on construction, owner changes, spawn and restore) and updates/clears it on tick/removal, and EnderpearlItem updates the saved tag when spawning a pearl. PlayerList and BotList now schedule pearl removal via the Bukkit taskScheduler when players/bots quit. The changes preserve Paper's legacyEnderPearlBehavior checks where applicable and add logging and safer tag handling to avoid saving invalid snapshots. * Restore vanilla ender pearl loading Restore vanilla ender-pearl loading behavior on Folia by preserving the last saved NBT for thrown pearls. ThrownEnderpearl.java: add a volatile CompoundTag lastSavedEnderPearlTag, provide a defensive-copying getter getLastSavedEnderPearlTag(), adjust @Nullable annotation placement, and update logger/comment. Minor index/format updates in ServerPlayer.java, PlayerList.java and EnderpearlItem.java. * remove extra import * Add Precise mob cap and adjust ender pearl patch Introduce a Precise mob cap feature and integrate it into Paper/Folia world/region lifecycle. Adds new patch features/0049-Precise-mob-cap.patch which modifies RegionizedWorldData, ServerChunkCache and ServerLevel to support an incremental, event-driven mob counting system that can supersede the existing GlobalEntitiesCounter. Adds PreciseMobCapConfig (toggle and behavior flags) and PreciseMobCapCounter (unique id management, area map registration, per-level global counters, spawnable-chunk tracking, spawn state creation, and cleanup on unload). Also updates the existing 0048 ender-pearl loading patch file (minor import/index adjustments). The Precise mob cap counts only relevant mobs (configurable to match vanilla or Paper-optimized behavior), hooks into entity tracking start/end, and requires per-player-mob-spawns=false to be set in Paper world defaults when enabled. * remove extra import * pre merge config * transform config * merge config * rebuild patches * fix a bug in new counter * simplify code --------- Co-authored-by: Helvetica Volubi <suisuroru@blue-millennium.fun> Co-authored-by: Helvetica Volubi <88063803+Suisuroru@users.noreply.github.com>
This commit is contained in:
committed by
Helvetica Volubi
parent
f98781b7cc
commit
9f2f53001a
@@ -4,6 +4,7 @@ import fun.bm.lophine.carpet.config.modules.FakePlayerCompatConfig;
|
||||
import fun.bm.lophine.carpet.config.modules.GeneralCompatConfig;
|
||||
import fun.bm.lophine.carpet.config.modules.WoolHopperCounterConfig;
|
||||
import fun.bm.lophine.config.modules.function.FakeplayerConfig;
|
||||
import fun.bm.lophine.config.modules.function.protocol.PcaSyncProtocolConfig;
|
||||
import fun.bm.lophine.protocol.CarpetLoggerProtocol;
|
||||
import org.leavesmc.leaves.protocol.CarpetServerProtocol;
|
||||
|
||||
@@ -24,75 +25,83 @@ public final class CarpetCompatSync {
|
||||
}
|
||||
|
||||
private static void registerProtocolRules() {
|
||||
CarpetServerProtocol.CarpetRules.clear();
|
||||
CarpetServerProtocol.CarpetRules.beginBatch();
|
||||
try {
|
||||
CarpetServerProtocol.CarpetRules.clear();
|
||||
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "language", GeneralCompatConfig.language));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "amsUpdateSuppressionCrashFix", GeneralCompatConfig.amsUpdateSuppressionCrashFix));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "yeetUpdateSuppressionCrash", GeneralCompatConfig.yeetUpdateSuppressionCrash));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "dustTrapdoorReintroduced", GeneralCompatConfig.dustTrapdoorReintroduced));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "shulkerBoxCCEReintroduced", GeneralCompatConfig.shulkerBoxCCEReintroduced));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "instantBlockUpdaterReintroduced", GeneralCompatConfig.instantBlockUpdaterReintroduced));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "commandTick", GeneralCompatConfig.commandTick));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "creativeNoClip", GeneralCompatConfig.creativeNoClip));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "optimizedDragonRespawn", GeneralCompatConfig.optimizedDragonRespawn));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "antiSpamDisabled", GeneralCompatConfig.antiSpamDisabled));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "blockPlacementIgnoreEntity", GeneralCompatConfig.blockPlacementIgnoreEntity));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "creativeOpenContainerForcibly", GeneralCompatConfig.creativeOpenContainerForcibly));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "creativeOneHitKill", GeneralCompatConfig.creativeOneHitKill));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "observerNoDetection", GeneralCompatConfig.observerNoDetection));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "bambooModelNoOffset", GeneralCompatConfig.bambooModelNoOffset));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "creativeNoItemCooldown", GeneralCompatConfig.creativeNoItemCooldown));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "ctrlQCraftingFix", GeneralCompatConfig.ctrlQCraftingFix));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "carpetAlwaysSetDefault", GeneralCompatConfig.carpetAlwaysSetDefault));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "placementRotationFix", GeneralCompatConfig.placementRotationFix));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "tntDoNotUpdate", GeneralCompatConfig.tntDoNotUpdate));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "totallyNoBlockUpdate", GeneralCompatConfig.totallyNoBlockUpdate));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "tiscmNetworkProtocol", GeneralCompatConfig.tiscmNetworkProtocol));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetorgaddition", "hopperNoItemCost", GeneralCompatConfig.hopperNoItemCost));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "explosionNoBlockDamage", GeneralCompatConfig.explosionNoBlockDamage));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "optimizedTNTHighPriority", GeneralCompatConfig.optimizedTNTHighPriority));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "tntPrimerMomentumRemoved", GeneralCompatConfig.tntPrimerMomentumRemoved));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "tntIgnoreRedstoneSignal", GeneralCompatConfig.tntIgnoreRedstoneSignal));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "tntDupingFix", GeneralCompatConfig.tntDupingFix));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "interactionUpdates", GeneralCompatConfig.interactionUpdates));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "xpNoCooldown", GeneralCompatConfig.xpNoCooldown));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "powerfulExpMending", GeneralCompatConfig.powerfulExpMending));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "clientSettingsLostOnRespawnFix", GeneralCompatConfig.clientSettingsLostOnRespawnFix));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "sensibleEnderman", GeneralCompatConfig.sensibleEnderman));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "entityInstantDeathRemoval", GeneralCompatConfig.entityInstantDeathRemoval));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "farmlandTrampledDisabled", GeneralCompatConfig.farmlandTrampledDisabled));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "shulkerGolem", GeneralCompatConfig.shulkerGolem));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "preventEndSpikeRespawn", GeneralCompatConfig.preventEndSpikeRespawn));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "yeetOutOfOrderChatKick", GeneralCompatConfig.yeetOutOfOrderChatKick));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "betterCraftableBoneBlock", GeneralCompatConfig.betterCraftableBoneBlock));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "betterCraftableDispenser", GeneralCompatConfig.betterCraftableDispenser));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "viewDistance", GeneralCompatConfig.viewDistance));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "tickCommandPermission", GeneralCompatConfig.normalizedTickCommandPermission()));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "tickFreezeCommandToggleable", GeneralCompatConfig.tickFreezeCommandToggleable));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "syncServerMsptMetricsData", GeneralCompatConfig.syncServerMsptMetricsData));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetorgaddition", "simpleInGameCalculator", GeneralCompatConfig.simpleInGameCalculator));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "microTiming", GeneralCompatConfig.microTiming));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "fastRedstoneDust", GeneralCompatConfig.fastRedstoneDust));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "lagFreeSpawning", GeneralCompatConfig.lagFreeSpawning));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "optimizedFastEntityMovement", GeneralCompatConfig.optimizedFastEntityMovement));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "optimizedHardHitBoxEntityCollision", GeneralCompatConfig.optimizedHardHitBoxEntityCollision));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "tntFuseDuration", GeneralCompatConfig.normalizedTntFuseDuration()));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "defaultLoggers", CarpetLoggerProtocol.serializeConfiguredDefaults(sanitizeDefaultLoggers(GeneralCompatConfig.defaultLoggers))));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "language", GeneralCompatConfig.language));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "amsUpdateSuppressionCrashFix", GeneralCompatConfig.amsUpdateSuppressionCrashFix));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "yeetUpdateSuppressionCrash", GeneralCompatConfig.yeetUpdateSuppressionCrash));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "dustTrapdoorReintroduced", GeneralCompatConfig.dustTrapdoorReintroduced));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "shulkerBoxCCEReintroduced", GeneralCompatConfig.shulkerBoxCCEReintroduced));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "instantBlockUpdaterReintroduced", GeneralCompatConfig.instantBlockUpdaterReintroduced));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "commandTick", GeneralCompatConfig.commandTick));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "creativeNoClip", GeneralCompatConfig.creativeNoClip));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "optimizedDragonRespawn", GeneralCompatConfig.optimizedDragonRespawn));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "antiSpamDisabled", GeneralCompatConfig.antiSpamDisabled));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "blockPlacementIgnoreEntity", GeneralCompatConfig.blockPlacementIgnoreEntity));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "creativeOpenContainerForcibly", GeneralCompatConfig.creativeOpenContainerForcibly));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "creativeOneHitKill", GeneralCompatConfig.creativeOneHitKill));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "observerNoDetection", GeneralCompatConfig.observerNoDetection));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "bambooModelNoOffset", GeneralCompatConfig.bambooModelNoOffset));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "creativeNoItemCooldown", GeneralCompatConfig.creativeNoItemCooldown));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "ctrlQCraftingFix", GeneralCompatConfig.ctrlQCraftingFix));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "carpetAlwaysSetDefault", GeneralCompatConfig.carpetAlwaysSetDefault));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "placementRotationFix", GeneralCompatConfig.placementRotationFix));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "tntDoNotUpdate", GeneralCompatConfig.tntDoNotUpdate));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "totallyNoBlockUpdate", GeneralCompatConfig.totallyNoBlockUpdate));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "tiscmNetworkProtocol", GeneralCompatConfig.tiscmNetworkProtocol));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetorgaddition", "hopperNoItemCost", GeneralCompatConfig.hopperNoItemCost));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "explosionNoBlockDamage", GeneralCompatConfig.explosionNoBlockDamage));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "optimizedTNTHighPriority", GeneralCompatConfig.optimizedTNTHighPriority));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "tntPrimerMomentumRemoved", GeneralCompatConfig.tntPrimerMomentumRemoved));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "tntIgnoreRedstoneSignal", GeneralCompatConfig.tntIgnoreRedstoneSignal));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "tntDupingFix", GeneralCompatConfig.tntDupingFix));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "interactionUpdates", GeneralCompatConfig.interactionUpdates));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "xpNoCooldown", GeneralCompatConfig.xpNoCooldown));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "powerfulExpMending", GeneralCompatConfig.powerfulExpMending));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "clientSettingsLostOnRespawnFix", GeneralCompatConfig.clientSettingsLostOnRespawnFix));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "sensibleEnderman", GeneralCompatConfig.sensibleEnderman));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "entityInstantDeathRemoval", GeneralCompatConfig.entityInstantDeathRemoval));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "farmlandTrampledDisabled", GeneralCompatConfig.farmlandTrampledDisabled));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "shulkerGolem", GeneralCompatConfig.shulkerGolem));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "preventEndSpikeRespawn", GeneralCompatConfig.preventEndSpikeRespawn));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "yeetOutOfOrderChatKick", GeneralCompatConfig.yeetOutOfOrderChatKick));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "betterCraftableBoneBlock", GeneralCompatConfig.betterCraftableBoneBlock));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "betterCraftableDispenser", GeneralCompatConfig.betterCraftableDispenser));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "viewDistance", GeneralCompatConfig.viewDistance));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "tickCommandPermission", GeneralCompatConfig.normalizedTickCommandPermission()));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "tickFreezeCommandToggleable", GeneralCompatConfig.tickFreezeCommandToggleable));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "syncServerMsptMetricsData", GeneralCompatConfig.syncServerMsptMetricsData));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetorgaddition", "simpleInGameCalculator", GeneralCompatConfig.simpleInGameCalculator));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "microTiming", GeneralCompatConfig.microTiming));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "fastRedstoneDust", GeneralCompatConfig.fastRedstoneDust));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "lagFreeSpawning", GeneralCompatConfig.lagFreeSpawning));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "optimizedFastEntityMovement", GeneralCompatConfig.optimizedFastEntityMovement));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "optimizedHardHitBoxEntityCollision", GeneralCompatConfig.optimizedHardHitBoxEntityCollision));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "tntFuseDuration", GeneralCompatConfig.normalizedTntFuseDuration()));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "defaultLoggers", CarpetLoggerProtocol.serializeConfiguredDefaults(sanitizeDefaultLoggers(GeneralCompatConfig.defaultLoggers))));
|
||||
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "commandBot", FakeplayerConfig.enable));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "commandPlayer", FakePlayerCompatConfig.commandPlayer));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "fakePlayerResident", FakePlayerCompatConfig.fakePlayerResident));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "openFakePlayerInventory", FakePlayerCompatConfig.openFakePlayerInventory));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "fakePlayerTicksLikeRealPlayer", FakePlayerCompatConfig.fakePlayerTicksLikeRealPlayer));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "fakePlayerDefaultSurvivalMode", FakePlayerCompatConfig.fakePlayerDefaultSurvivalMode));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "fakePlayerInteractLikeClient", FakePlayerCompatConfig.fakePlayerInteractLikeClient));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "fakePlayerAutoReplaceTool", FakePlayerCompatConfig.fakePlayerAutoReplaceTool));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "fakePlayerAutoReplenishment", FakePlayerCompatConfig.fakePlayerAutoReplenishment));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "fakePlayerAutoReplenishmentFormShulkerBox", FakePlayerCompatConfig.fakePlayerAutoReplenishmentFormShulkerBox));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "fakePlayerAutoFish", FakePlayerCompatConfig.fakePlayerAutoFish));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "fakePlayerReloadAction", FakePlayerCompatConfig.fakePlayerReloadAction));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "commandBot", FakeplayerConfig.enable));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "commandPlayer", FakePlayerCompatConfig.commandPlayer));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "fakePlayerResident", FakePlayerCompatConfig.fakePlayerResident));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "openFakePlayerInventory", FakePlayerCompatConfig.openFakePlayerInventory));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "fakePlayerTicksLikeRealPlayer", FakePlayerCompatConfig.fakePlayerTicksLikeRealPlayer));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "fakePlayerDefaultSurvivalMode", FakePlayerCompatConfig.fakePlayerDefaultSurvivalMode));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "fakePlayerInteractLikeClient", FakePlayerCompatConfig.fakePlayerInteractLikeClient));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "fakePlayerAutoReplaceTool", FakePlayerCompatConfig.fakePlayerAutoReplaceTool));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "fakePlayerAutoReplenishment", FakePlayerCompatConfig.fakePlayerAutoReplenishment));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpetamsaddition", "fakePlayerAutoReplenishmentFormShulkerBox", FakePlayerCompatConfig.fakePlayerAutoReplenishmentFormShulkerBox));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "fakePlayerAutoFish", FakePlayerCompatConfig.fakePlayerAutoFish));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("lophine", "fakePlayerReloadAction", FakePlayerCompatConfig.fakePlayerReloadAction));
|
||||
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "hopperCounters", WoolHopperCounterConfig.hopperCounters));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "hopperCountersUnlimitedSpeed", WoolHopperCounterConfig.hopperCountersUnlimitedSpeed));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpet", "hopperCounters", WoolHopperCounterConfig.hopperCounters));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("carpettisaddition", "hopperCountersUnlimitedSpeed", WoolHopperCounterConfig.hopperCountersUnlimitedSpeed));
|
||||
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("pca", "pcaSyncProtocol", PcaSyncProtocolConfig.enabled));
|
||||
CarpetServerProtocol.CarpetRules.register(CarpetServerProtocol.CarpetRule.of("pca", "pcaSyncPlayerEntity", PcaSyncProtocolConfig.syncPlayerEntity));
|
||||
} finally {
|
||||
CarpetServerProtocol.CarpetRules.endBatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+53
-6
@@ -1,20 +1,67 @@
|
||||
package fun.bm.lophine.config.modules.experiment;
|
||||
|
||||
import fun.bm.lophine.enums.GlobalEntitiesCounterType;
|
||||
import me.earthme.luminol.config.ConfigManager;
|
||||
import me.earthme.luminol.config.ConfigsInstance;
|
||||
import me.earthme.luminol.config.IConfigModule;
|
||||
import me.earthme.luminol.config.flags.ConfigClassInfo;
|
||||
import me.earthme.luminol.config.flags.ConfigInfo;
|
||||
import me.earthme.luminol.config.flags.DoNotLoad;
|
||||
import me.earthme.luminol.config.flags.HotReloadUnsupported;
|
||||
import me.earthme.luminol.enums.EnumConfigCategory;
|
||||
|
||||
@ConfigClassInfo(name = "global_entities_counter", category = EnumConfigCategory.EXPERIMENT)
|
||||
public class GlobalEntitiesCounter implements IConfigModule {
|
||||
@HotReloadUnsupported
|
||||
@ConfigInfo(name = "enabled", comments = """
|
||||
Enable global entities counter.
|
||||
@ConfigInfo(name = "version", comments = """
|
||||
DISABLED
|
||||
DEFAULT_SYNC: Enable global entities counter origin version with sync counter module.
|
||||
DEFAULT_ASYNC: Enable global entities counter origin version with async counter module.
|
||||
PRECISE: Enable precise mob cap calculation with incremental counting. Replaces the periodic full-scan with event-driven real-time updates.
|
||||
|
||||
You need to set per-player-mob-spawns to false on paper-world-defaults.yml or paper-world.yml""")
|
||||
public static boolean enabled = false;
|
||||
public static GlobalEntitiesCounterType type = GlobalEntitiesCounterType.DISABLED;
|
||||
|
||||
@HotReloadUnsupported
|
||||
@ConfigInfo(name = "async", comments = "Enable Asynchronous(maybe cause bugs)")
|
||||
public static boolean async = false;
|
||||
@DoNotLoad
|
||||
private static boolean enabled;
|
||||
@DoNotLoad
|
||||
private static boolean async;
|
||||
@DoNotLoad
|
||||
private static boolean defaultModule;
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public static boolean isAsync() {
|
||||
return async;
|
||||
}
|
||||
|
||||
public static boolean isDefaultModule() {
|
||||
return defaultModule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeFinalLoad() {
|
||||
ConfigsInstance configsInstance = ConfigManager.getConfigs("lophine");
|
||||
try {
|
||||
Boolean enabled0 = configsInstance.getConfigOrigin("experiment.global_entities_counter.enabled");
|
||||
Boolean async0 = configsInstance.getConfigOrigin("experiment.global_entities_counter.async");
|
||||
if (enabled0 != null) {
|
||||
if (enabled0) {
|
||||
if (async0 != null && async0) {
|
||||
type = GlobalEntitiesCounterType.DEFAULT_ASYNC;
|
||||
} else {
|
||||
type = GlobalEntitiesCounterType.DEFAULT_SYNC;
|
||||
}
|
||||
}
|
||||
}
|
||||
configsInstance.removeConfig("experiment.global_entities_counter.enabled");
|
||||
configsInstance.removeConfig("experiment.global_entities_counter.async");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
enabled = type != GlobalEntitiesCounterType.DISABLED;
|
||||
async = type == GlobalEntitiesCounterType.DEFAULT_ASYNC;
|
||||
defaultModule = type == GlobalEntitiesCounterType.DEFAULT_SYNC || type == GlobalEntitiesCounterType.DEFAULT_ASYNC;
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package fun.bm.lophine.config.modules.fixes;
|
||||
|
||||
import me.earthme.luminol.config.IConfigModule;
|
||||
import me.earthme.luminol.config.flags.ConfigClassInfo;
|
||||
import me.earthme.luminol.config.flags.ConfigInfo;
|
||||
import me.earthme.luminol.enums.EnumConfigCategory;
|
||||
|
||||
@ConfigClassInfo(category = EnumConfigCategory.FIXES, name = "vanilla-like-experience")
|
||||
public class VanillaLikeExperienceConfig implements IConfigModule {
|
||||
@ConfigInfo(name = "enabled", comments = """
|
||||
Restore a more vanilla-like technical gameplay experience by bypassing some Paper safety and behavior changes.""")
|
||||
public static boolean enabled = false;
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package fun.bm.lophine.config.modules.function.protocol;
|
||||
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import fun.bm.lophine.carpet.CarpetCompatSync;
|
||||
import fun.bm.lophine.enums.PcaPlayerEntityType;
|
||||
import me.earthme.luminol.config.IConfigModule;
|
||||
import me.earthme.luminol.config.flags.ConfigClassInfo;
|
||||
import me.earthme.luminol.config.flags.ConfigInfo;
|
||||
import me.earthme.luminol.config.flags.TransformedConfig;
|
||||
import me.earthme.luminol.enums.EnumConfigCategory;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.leavesmc.leaves.protocol.PcaSyncProtocol;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@ConfigClassInfo(category = EnumConfigCategory.FUNCTION, name = "pca", directory = {"protocol"})
|
||||
public class PcaSyncProtocolConfig implements IConfigModule {
|
||||
private static boolean lastEnabled = false;
|
||||
|
||||
@TransformedConfig(name = "pca-sync-protocol", directory = {"protocol"})
|
||||
@ConfigInfo(name = "enabled", comments = """
|
||||
Enable PCA sync protocol support""")
|
||||
public static boolean enabled = false;
|
||||
|
||||
@TransformedConfig(name = "pca-sync-player-entity", directory = {"protocol"})
|
||||
@ConfigInfo(name = "sync-player-entity", comments = """
|
||||
Controls which player entities can be watched through the PCA sync protocol.
|
||||
NOBODY: never sync player entities
|
||||
BOT: only sync Lophine fake players
|
||||
OPS: sync fake players and allow operators to sync real players
|
||||
OPS_AND_SELF: sync fake players, operators, and a player's own entity
|
||||
EVERYONE: allow all player entities""")
|
||||
public static PcaPlayerEntityType syncPlayerEntity = PcaPlayerEntityType.OPS;
|
||||
|
||||
@Override
|
||||
public void onLoaded(CommentedFileConfig configInstance, @Nullable Set<Exception> e) {
|
||||
if (lastEnabled != enabled) {
|
||||
PcaSyncProtocol.onConfigModify(enabled);
|
||||
lastEnabled = enabled;
|
||||
}
|
||||
CarpetCompatSync.apply();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package fun.bm.lophine.enums;
|
||||
|
||||
public enum GlobalEntitiesCounterType {
|
||||
DISABLED,
|
||||
DEFAULT_SYNC,
|
||||
DEFAULT_ASYNC,
|
||||
PRECISE
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package fun.bm.lophine.enums;
|
||||
|
||||
public enum PcaPlayerEntityType {
|
||||
NOBODY,
|
||||
BOT,
|
||||
OPS,
|
||||
OPS_AND_SELF,
|
||||
EVERYONE
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import fun.bm.lophine.config.modules.experiment.GlobalEntitiesCounter;
|
||||
import fun.bm.lophine.config.modules.fixes.VanillaLikeExperienceConfig;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
@@ -24,10 +25,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static net.minecraft.world.level.NaturalSpawner.getRoughBiome;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class EntitiesCounterUtil {
|
||||
// global entities counter
|
||||
private static final Map<ServerLevel, Cache<Integer, ReferenceList<Entity>>> globalLoadedEntities = new ConcurrentHashMap<>();
|
||||
private static final Map<ServerLevel, Object2IntOpenHashMap<MobCategory>> mobsMap = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private static final Map<ServerLevel, Cache<Integer, PositionCountingAreaMap<ServerPlayer>>> mobsAreaMap = new ConcurrentHashMap<>();
|
||||
@@ -35,6 +36,9 @@ public class EntitiesCounterUtil {
|
||||
private static final Map<ServerLevel, CompletableFuture<Void>> tasks = new ConcurrentHashMap<>();
|
||||
private static final Set<Integer> UniqueIds = ConcurrentHashMap.newKeySet();
|
||||
|
||||
// precise mob counter extra
|
||||
private static final Map<ServerLevel, EnumMap<MobCategory, AtomicInteger>> globalMobCounts = new ConcurrentHashMap<>();
|
||||
|
||||
private static int lastUsedId = 0;
|
||||
|
||||
private static final int CLEANUP_INTERVAL = 200; // each 200 ids used
|
||||
@@ -104,94 +108,193 @@ public class EntitiesCounterUtil {
|
||||
}
|
||||
|
||||
public static void tick(ServerLevel level) {
|
||||
Runnable task = () -> {
|
||||
try {
|
||||
Cache<Integer, ReferenceList<Entity>> data0 = globalLoadedEntities.get(level);
|
||||
if (data0 == null) return;
|
||||
if (GlobalEntitiesCounter.isDefaultModule()) {
|
||||
Runnable task = () -> {
|
||||
try {
|
||||
Cache<Integer, ReferenceList<Entity>> data0 = globalLoadedEntities.get(level);
|
||||
if (data0 == null) return;
|
||||
|
||||
Object2IntOpenHashMap<MobCategory> map = new Object2IntOpenHashMap<>();
|
||||
Collection<ReferenceList<Entity>> snapshot = data0.asMap().values();
|
||||
Object2IntOpenHashMap<MobCategory> map = new Object2IntOpenHashMap<>();
|
||||
Collection<ReferenceList<Entity>> snapshot = data0.asMap().values();
|
||||
|
||||
for (ReferenceList<Entity> data : snapshot) {
|
||||
if (data == null) continue;
|
||||
for (Entity entity : GlobalEntitiesCounter.async ? data.copy() : data) {
|
||||
if (entity == null || entity.isRemoved() || !entity.isAlive()) continue;
|
||||
// Lophine start - Copy from net/minecraft/world/level/NaturalSpawner
|
||||
MobCategory category = entity.getType().getCategory();
|
||||
if (category != MobCategory.MISC) {
|
||||
// Paper start - Only count natural spawns
|
||||
if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning &&
|
||||
!(entity.spawnReason == CreatureSpawnEvent.SpawnReason.NATURAL ||
|
||||
entity.spawnReason == CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) {
|
||||
continue;
|
||||
for (ReferenceList<Entity> data : snapshot) {
|
||||
if (data == null) continue;
|
||||
for (Entity entity : GlobalEntitiesCounter.isAsync() ? data.copy() : data) {
|
||||
if (entity == null || entity.isRemoved() || !entity.isAlive()) continue;
|
||||
// Lophine start - Copy from net/minecraft/world/level/NaturalSpawner
|
||||
MobCategory category = entity.getType().getCategory();
|
||||
if (category != MobCategory.MISC) {
|
||||
// Paper start - Only count natural spawns
|
||||
if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning &&
|
||||
!(entity.spawnReason == CreatureSpawnEvent.SpawnReason.NATURAL ||
|
||||
entity.spawnReason == CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) {
|
||||
continue;
|
||||
}
|
||||
// Paper end - Only count natural spawns
|
||||
map.addTo(category, 1);
|
||||
}
|
||||
// Paper end - Only count natural spawns
|
||||
map.addTo(category, 1);
|
||||
}
|
||||
// Lophine end - Copy from net/minecraft/world/level/NaturalSpawner
|
||||
}
|
||||
// Lophine end - Copy from net/minecraft/world/level/NaturalSpawner
|
||||
}
|
||||
mobsMap.put(level, map);
|
||||
mobsMap.put(level, map);
|
||||
|
||||
Cache<Integer, PositionCountingAreaMap<ServerPlayer>> collection = mobsAreaMap.get(level);
|
||||
if (collection != null) {
|
||||
int count = 0;
|
||||
for (PositionCountingAreaMap<ServerPlayer> areaMap : collection.asMap().values()) {
|
||||
if (areaMap != null) {
|
||||
count += areaMap.getTotalPositions();
|
||||
Cache<Integer, PositionCountingAreaMap<ServerPlayer>> collection = mobsAreaMap.get(level);
|
||||
if (collection != null) {
|
||||
int count = 0;
|
||||
for (PositionCountingAreaMap<ServerPlayer> areaMap : collection.asMap().values()) {
|
||||
if (areaMap != null) {
|
||||
count += areaMap.getTotalPositions();
|
||||
}
|
||||
}
|
||||
spawnableChunkCount.put(level, count);
|
||||
}
|
||||
spawnableChunkCount.put(level, count);
|
||||
} catch (Exception e) {
|
||||
LogUtils.getClassLogger().error("Failed to run task", e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.getClassLogger().error("Failed to run task", e);
|
||||
};
|
||||
if (GlobalEntitiesCounter.isAsync()) {
|
||||
tasks.put(level, CompletableFuture.runAsync(task).exceptionally(ex -> {
|
||||
LogUtils.getClassLogger().error("Failed to run task", ex);
|
||||
return null;
|
||||
}));
|
||||
} else {
|
||||
task.run();
|
||||
}
|
||||
};
|
||||
if (GlobalEntitiesCounter.async) {
|
||||
tasks.put(level, CompletableFuture.runAsync(task).exceptionally(ex -> {
|
||||
LogUtils.getClassLogger().error("Failed to run task", ex);
|
||||
return null;
|
||||
}));
|
||||
} else {
|
||||
task.run();
|
||||
Cache<Integer, PositionCountingAreaMap<ServerPlayer>> cache = mobsAreaMap.get(level);
|
||||
if (cache == null) return;
|
||||
|
||||
int total = 0;
|
||||
for (PositionCountingAreaMap<ServerPlayer> map : cache.asMap().values()) {
|
||||
if (map != null) {
|
||||
total += map.getTotalPositions();
|
||||
}
|
||||
}
|
||||
|
||||
spawnableChunkCount.put(level, total);
|
||||
}
|
||||
}
|
||||
|
||||
public static NaturalSpawner.SpawnState runRemainingTasks(
|
||||
public static NaturalSpawner.SpawnState createSpawnState(
|
||||
ServerLevel level, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkGetter, LocalMobCapCalculator calculator, final boolean countMobs
|
||||
) {
|
||||
Object2IntOpenHashMap<MobCategory> map = getMobsMap(level);
|
||||
if (map == null) return null; // skip if no data
|
||||
// Lophine start - Copy from net/minecraft/world/level/NaturalSpawner
|
||||
PotentialCalculator potentialCalculator = new PotentialCalculator();
|
||||
for (Entity entity : entities) {
|
||||
if (entity == null || entity.isRemoved() || !entity.isAlive()) continue;
|
||||
// Paper start - Only count natural spawns
|
||||
if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning &&
|
||||
!(entity.spawnReason == CreatureSpawnEvent.SpawnReason.NATURAL ||
|
||||
entity.spawnReason == CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) {
|
||||
continue;
|
||||
if (GlobalEntitiesCounter.isDefaultModule()) {
|
||||
Object2IntOpenHashMap<MobCategory> map = getMobsMap(level);
|
||||
if (map == null) return null; // skip if no data
|
||||
// Lophine start - Copy from net/minecraft/world/level/NaturalSpawner
|
||||
PotentialCalculator potentialCalculator = new PotentialCalculator();
|
||||
for (Entity entity : entities) {
|
||||
if (entity == null || entity.isRemoved() || !entity.isAlive()) continue;
|
||||
// Paper start - Only count natural spawns
|
||||
if (!entity.level().paperConfig().entities.spawning.countAllMobsForSpawning &&
|
||||
!(entity.spawnReason == CreatureSpawnEvent.SpawnReason.NATURAL ||
|
||||
entity.spawnReason == CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) {
|
||||
continue;
|
||||
}
|
||||
// Paper end - Only count natural spawns
|
||||
BlockPos blockPos = entity.blockPosition();
|
||||
chunkGetter.query(ChunkPos.pack(blockPos), chunk -> {
|
||||
MobSpawnSettings.MobSpawnCost mobSpawnCost = NaturalSpawner.getRoughBiome(blockPos, chunk).getMobSettings().getMobSpawnCost(entity.getType());
|
||||
if (mobSpawnCost != null) {
|
||||
potentialCalculator.addCharge(entity.blockPosition(), mobSpawnCost.charge());
|
||||
}
|
||||
|
||||
if (calculator != null && entity instanceof Mob) { // Paper - Optional per player mob spawns
|
||||
calculator.addMob(chunk.getPos(), entity.getType().getCategory());
|
||||
}
|
||||
|
||||
// Paper start - Optional per player mob spawns
|
||||
if (countMobs) {
|
||||
chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
|
||||
}
|
||||
// Paper end - Optional per player mob spawns
|
||||
});
|
||||
}
|
||||
// Paper end - Only count natural spawns
|
||||
BlockPos blockPos = entity.blockPosition();
|
||||
chunkGetter.query(ChunkPos.pack(blockPos), chunk -> {
|
||||
MobSpawnSettings.MobSpawnCost mobSpawnCost = getRoughBiome(blockPos, chunk).getMobSettings().getMobSpawnCost(entity.getType());
|
||||
if (mobSpawnCost != null) {
|
||||
potentialCalculator.addCharge(entity.blockPosition(), mobSpawnCost.charge());
|
||||
}
|
||||
return new NaturalSpawner.SpawnState(getTotalChunkCount(level), map, potentialCalculator, calculator);
|
||||
// Lophine end - Copy from net/minecraft/world/level/NaturalSpawner
|
||||
} else {
|
||||
int chunkCount = getSpawnableChunkCount(level);
|
||||
if (chunkCount == 0) return null;
|
||||
|
||||
if (calculator != null && entity instanceof Mob) { // Paper - Optional per player mob spawns
|
||||
calculator.addMob(chunk.getPos(), entity.getType().getCategory());
|
||||
}
|
||||
Object2IntOpenHashMap<MobCategory> globalCounts = getGlobalCounts(level);
|
||||
|
||||
// Paper start - Optional per player mob spawns
|
||||
if (countMobs) {
|
||||
((ServerLevel) chunk.getLevel()).getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
|
||||
}
|
||||
// Paper end - Optional per player mob spawns
|
||||
});
|
||||
PotentialCalculator potentialCalculator = new PotentialCalculator();
|
||||
for (Entity entity : entities) {
|
||||
if (entity == null || entity.isRemoved() || !entity.isAlive()) continue;
|
||||
if (!shouldCount(entity)) continue;
|
||||
|
||||
BlockPos pos = entity.blockPosition();
|
||||
chunkGetter.query(ChunkPos.pack(pos), chunk -> {
|
||||
MobSpawnSettings.MobSpawnCost mobSpawnCost = NaturalSpawner.getRoughBiome(pos, chunk).getMobSettings().getMobSpawnCost(entity.getType());
|
||||
if (mobSpawnCost != null) {
|
||||
potentialCalculator.addCharge(entity.blockPosition(), mobSpawnCost.charge());
|
||||
}
|
||||
|
||||
if (calculator != null && entity instanceof Mob) {
|
||||
calculator.addMob(chunk.getPos(), entity.getType().getCategory());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new NaturalSpawner.SpawnState(chunkCount, globalCounts, potentialCalculator, calculator);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object2IntOpenHashMap<MobCategory> getGlobalCounts(ServerLevel level) {
|
||||
EnumMap<MobCategory, AtomicInteger> counts = globalMobCounts.get(level);
|
||||
Object2IntOpenHashMap<MobCategory> result = new Object2IntOpenHashMap<>();
|
||||
if (counts != null) {
|
||||
for (Map.Entry<MobCategory, AtomicInteger> entry : counts.entrySet()) {
|
||||
result.put(entry.getKey(), entry.getValue().get());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int getSpawnableChunkCount(ServerLevel level) {
|
||||
Integer value = spawnableChunkCount.get(level);
|
||||
return value != null ? value : 0;
|
||||
}
|
||||
|
||||
public static void onWorldUnload(ServerLevel level) {
|
||||
globalMobCounts.remove(level);
|
||||
mobsAreaMap.remove(level);
|
||||
spawnableChunkCount.remove(level);
|
||||
}
|
||||
|
||||
public static void onEntityTrackingStart(ServerLevel level, Entity entity) {
|
||||
if (!shouldCount(entity)) return;
|
||||
MobCategory category = entity.getType().getCategory();
|
||||
getOrCreateCounts(level).computeIfAbsent(category, k -> new AtomicInteger(0)).incrementAndGet();
|
||||
}
|
||||
|
||||
public static boolean shouldCount(Entity entity) {
|
||||
MobCategory category = entity.getType().getCategory();
|
||||
if (category == MobCategory.MISC) return false;
|
||||
|
||||
if (entity instanceof Mob mob && (mob.isPersistenceRequired() || mob.requiresCustomPersistence())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VanillaLikeExperienceConfig.enabled || entity.level().paperConfig().entities.spawning.countAllMobsForSpawning) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return entity.spawnReason == CreatureSpawnEvent.SpawnReason.NATURAL
|
||||
|| entity.spawnReason == CreatureSpawnEvent.SpawnReason.CHUNK_GEN;
|
||||
}
|
||||
|
||||
private static EnumMap<MobCategory, AtomicInteger> getOrCreateCounts(ServerLevel level) {
|
||||
return globalMobCounts.computeIfAbsent(level, _ -> new EnumMap<>(MobCategory.class));
|
||||
}
|
||||
|
||||
public static void onEntityTrackingEnd(ServerLevel level, Entity entity) {
|
||||
if (!shouldCount(entity)) return;
|
||||
MobCategory category = entity.getType().getCategory();
|
||||
EnumMap<MobCategory, AtomicInteger> counts = getOrCreateCounts(level);
|
||||
AtomicInteger counter = counts.get(category);
|
||||
if (counter != null) {
|
||||
counter.updateAndGet(v -> Math.max(0, v - 1));
|
||||
}
|
||||
return new NaturalSpawner.SpawnState(getTotalChunkCount(level), map, potentialCalculator, calculator);
|
||||
// Lophine end - Copy from net/minecraft/world/level/NaturalSpawner
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,11 +485,13 @@ public class BotList {
|
||||
|
||||
bot.unRide();
|
||||
for (ThrownEnderpearl thrownEnderpearl : bot.getEnderPearls()) {
|
||||
if (!thrownEnderpearl.level().paperConfig().misc.legacyEnderPearlBehavior) {
|
||||
thrownEnderpearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT);
|
||||
} else {
|
||||
thrownEnderpearl.setOwner(null);
|
||||
}
|
||||
// Lophine start - restore vanilla ender pearl loading on Folia
|
||||
thrownEnderpearl.getBukkitEntity().taskScheduler.scheduleOrExecute((Entity pearl) -> {
|
||||
if (!pearl.isRemoved()) {
|
||||
pearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT);
|
||||
}
|
||||
});
|
||||
// Lophine end - restore vanilla ender pearl loading on Folia
|
||||
}
|
||||
|
||||
bot.level().getCurrentWorldData().connections.remove(bot.connection.connection);
|
||||
|
||||
+70
-6
@@ -6,18 +6,22 @@ import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@LeavesProtocol.Register(namespace = "carpet")
|
||||
public class CarpetServerProtocol implements LeavesProtocol {
|
||||
@@ -28,6 +32,9 @@ public class CarpetServerProtocol implements LeavesProtocol {
|
||||
|
||||
private static final String HI = "69";
|
||||
private static final String HELLO = "420";
|
||||
private static final Set<UUID> activePlayers = ConcurrentHashMap.newKeySet();
|
||||
private static boolean batchingRules = false;
|
||||
private static boolean rulesDirty = false;
|
||||
|
||||
@Contract("_ -> new")
|
||||
public static Identifier id(String path) {
|
||||
@@ -44,21 +51,68 @@ public class CarpetServerProtocol implements LeavesProtocol {
|
||||
@ProtocolHandler.PayloadReceiver(payload = CarpetPayload.class)
|
||||
private static void handleHello(@NotNull ServerPlayer player, @NotNull CarpetServerProtocol.CarpetPayload payload) {
|
||||
if (payload.nbt.contains(HELLO)) {
|
||||
LOGGER.info("Player {} joined with carpet {}", player.getScoreboardName(), payload.nbt.getString(HELLO).orElse("Unknown"));
|
||||
CompoundTag data = new CompoundTag();
|
||||
CarpetRules.write(data);
|
||||
ProtocolUtils.sendPayloadPacket(player, new CarpetPayload(data));
|
||||
UUID playerId = player.getUUID();
|
||||
String carpetVersion = payload.nbt.getString(HELLO).orElse("Unknown");
|
||||
player.getBukkitEntity().getScheduler().execute(MinecraftInternalPlugin.INSTANCE, () -> {
|
||||
ServerPlayer onlinePlayer = MinecraftServer.getServer().getPlayerList().getPlayer(playerId);
|
||||
if (onlinePlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info("Player {} joined with carpet {}", onlinePlayer.getScoreboardName(), carpetVersion);
|
||||
sendServerData(onlinePlayer);
|
||||
activePlayers.add(playerId);
|
||||
}, null, 1L);
|
||||
}
|
||||
}
|
||||
|
||||
@ProtocolHandler.PlayerLeave
|
||||
public static void onPlayerLeave(ServerPlayer player) {
|
||||
activePlayers.remove(player.getUUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return CarpetRules.hasRules();
|
||||
}
|
||||
|
||||
private static void sendServerData(ServerPlayer player) {
|
||||
sendServerData(player.getUUID());
|
||||
}
|
||||
|
||||
private static void sendServerData(UUID playerId) {
|
||||
ServerPlayer player = MinecraftServer.getServer().getPlayerList().getPlayer(playerId);
|
||||
if (player == null) {
|
||||
activePlayers.remove(playerId);
|
||||
return;
|
||||
}
|
||||
|
||||
CompoundTag data = new CompoundTag();
|
||||
CarpetRules.write(data);
|
||||
player.getBukkitEntity().getScheduler().execute(MinecraftInternalPlugin.INSTANCE, () -> {
|
||||
ServerPlayer onlinePlayer = MinecraftServer.getServer().getPlayerList().getPlayer(playerId);
|
||||
if (onlinePlayer != null) {
|
||||
ProtocolUtils.sendPayloadPacket(onlinePlayer, new CarpetPayload(data));
|
||||
}
|
||||
}, null, 1L);
|
||||
}
|
||||
|
||||
public static class CarpetRules {
|
||||
|
||||
private static final Map<String, CarpetRule> rules = new HashMap<>();
|
||||
private static final Map<String, CarpetRule> rules = new ConcurrentHashMap<>();
|
||||
|
||||
public static void beginBatch() {
|
||||
batchingRules = true;
|
||||
rulesDirty = false;
|
||||
}
|
||||
|
||||
public static void endBatch() {
|
||||
batchingRules = false;
|
||||
if (rulesDirty) {
|
||||
activePlayers.forEach(CarpetServerProtocol::sendServerData);
|
||||
rulesDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void write(@NotNull CompoundTag tag) {
|
||||
CompoundTag rulesNbt = new CompoundTag();
|
||||
@@ -69,15 +123,25 @@ public class CarpetServerProtocol implements LeavesProtocol {
|
||||
|
||||
public static void register(CarpetRule rule) {
|
||||
rules.put(rule.name, rule);
|
||||
markDirty();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
rules.clear();
|
||||
markDirty();
|
||||
}
|
||||
|
||||
public static boolean hasRules() {
|
||||
return !rules.isEmpty();
|
||||
}
|
||||
|
||||
private static void markDirty() {
|
||||
if (batchingRules) {
|
||||
rulesDirty = true;
|
||||
} else {
|
||||
activePlayers.forEach(CarpetServerProtocol::sendServerData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record CarpetRule(String identifier, String name, String value) {
|
||||
|
||||
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
* This file is part of Leaves (https://github.com/LeavesMC/Leaves)
|
||||
*
|
||||
* Leaves is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Leaves is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Leaves. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.leavesmc.leaves.protocol;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import fun.bm.lophine.config.modules.function.protocol.PcaSyncProtocolConfig;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.ChestBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.ChestType;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.leavesmc.leaves.bot.ServerBot;
|
||||
import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
|
||||
import org.leavesmc.leaves.util.TagUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
@LeavesProtocol.Register(namespace = "pca")
|
||||
public class PcaSyncProtocol implements LeavesProtocol {
|
||||
public static final String PROTOCOL_ID = "pca";
|
||||
|
||||
private static final Identifier ENABLE_PCA_SYNC_PROTOCOL = id("enable_pca_sync_protocol");
|
||||
private static final Identifier DISABLE_PCA_SYNC_PROTOCOL = id("disable_pca_sync_protocol");
|
||||
|
||||
private static final ConcurrentMap<UUID, Pair<Identifier, BlockPos>> playerWatchBlockPos = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentMap<UUID, Pair<Identifier, UUID>> playerWatchEntity = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentMap<Pair<Identifier, BlockPos>, Set<UUID>> blockPosWatchPlayerSet = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentMap<Pair<Identifier, UUID>, Set<UUID>> entityWatchPlayerSet = new ConcurrentHashMap<>();
|
||||
|
||||
@Contract("_ -> new")
|
||||
public static Identifier id(String path) {
|
||||
return Identifier.fromNamespaceAndPath(PROTOCOL_ID, path);
|
||||
}
|
||||
|
||||
@ProtocolHandler.PlayerJoin
|
||||
private static void onJoin(ServerPlayer player) {
|
||||
if (PcaSyncProtocolConfig.enabled) {
|
||||
enablePcaSyncProtocol(player);
|
||||
}
|
||||
}
|
||||
|
||||
@ProtocolHandler.PlayerLeave
|
||||
private static void onLeave(ServerPlayer player) {
|
||||
clearPlayerWatchData(player);
|
||||
}
|
||||
|
||||
@ProtocolHandler.BytebufReceiver(key = "cancel_sync_block_entity")
|
||||
private static void cancelSyncBlockEntityHandler(ServerPlayer player, FriendlyByteBuf buf) {
|
||||
clearPlayerWatchBlock(player);
|
||||
}
|
||||
|
||||
@ProtocolHandler.BytebufReceiver(key = "cancel_sync_entity")
|
||||
private static void cancelSyncEntityHandler(ServerPlayer player, FriendlyByteBuf buf) {
|
||||
clearPlayerWatchEntity(player);
|
||||
}
|
||||
|
||||
@ProtocolHandler.PayloadReceiver(payload = SyncBlockEntityPayload.class)
|
||||
private static void syncBlockEntityHandler(ServerPlayer player, SyncBlockEntityPayload payload) {
|
||||
if (!PcaSyncProtocolConfig.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID playerId = player.getUUID();
|
||||
player.getBukkitEntity().getScheduler().execute(MinecraftInternalPlugin.INSTANCE, () -> {
|
||||
ServerPlayer onlinePlayer = MinecraftServer.getServer().getPlayerList().getPlayer(playerId);
|
||||
if (!PcaSyncProtocolConfig.enabled || onlinePlayer == null || onlinePlayer.hasDisconnected() || !TickThread.isTickThreadFor(onlinePlayer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerLevel world = onlinePlayer.level();
|
||||
BlockPos pos = payload.pos;
|
||||
Bukkit.getRegionScheduler().execute(MinecraftInternalPlugin.INSTANCE, world.getWorld(), pos.getX() >> 4, pos.getZ() >> 4, () -> {
|
||||
if (!PcaSyncProtocolConfig.enabled || MinecraftServer.getServer().getPlayerList().getPlayer(playerId) == null || !TickThread.isTickThreadFor(world, pos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockState blockState = world.getBlockState(pos);
|
||||
clearPlayerWatchData(playerId);
|
||||
|
||||
BlockEntity blockEntityAdj = null;
|
||||
if (blockState.getBlock() instanceof ChestBlock && blockState.getValue(ChestBlock.TYPE) != ChestType.SINGLE) {
|
||||
BlockPos posAdj = pos.relative(ChestBlock.getConnectedDirection(blockState));
|
||||
if (TickThread.isTickThreadFor(world, posAdj)) {
|
||||
blockEntityAdj = getLoadedBlockEntity(world, posAdj);
|
||||
} else {
|
||||
scheduleBlockEntityUpdate(playerId, world, posAdj);
|
||||
}
|
||||
}
|
||||
|
||||
if (blockEntityAdj != null) {
|
||||
updateBlockEntity(playerId, blockEntityAdj);
|
||||
}
|
||||
|
||||
BlockEntity blockEntity = getLoadedBlockEntity(world, pos);
|
||||
if (blockEntity != null) {
|
||||
updateBlockEntity(playerId, blockEntity);
|
||||
}
|
||||
|
||||
Pair<Identifier, BlockPos> pair = new ImmutablePair<>(world.dimension().identifier(), pos);
|
||||
playerWatchBlockPos.put(playerId, pair);
|
||||
blockPosWatchPlayerSet.computeIfAbsent(pair, ignored -> ConcurrentHashMap.newKeySet()).add(playerId);
|
||||
});
|
||||
}, null, 1L);
|
||||
}
|
||||
|
||||
@ProtocolHandler.PayloadReceiver(payload = SyncEntityPayload.class)
|
||||
private static void syncEntityHandler(ServerPlayer player, SyncEntityPayload payload) {
|
||||
if (!PcaSyncProtocolConfig.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID playerId = player.getUUID();
|
||||
player.getBukkitEntity().getScheduler().execute(MinecraftInternalPlugin.INSTANCE, () -> {
|
||||
ServerPlayer onlinePlayer = MinecraftServer.getServer().getPlayerList().getPlayer(playerId);
|
||||
if (!PcaSyncProtocolConfig.enabled || onlinePlayer == null || onlinePlayer.hasDisconnected() || !TickThread.isTickThreadFor(onlinePlayer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerLevel world = onlinePlayer.level();
|
||||
Entity entity = world.getEntity(payload.entityId);
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean viewerIsOp = MinecraftServer.getServer().getPlayerList().isOp(onlinePlayer.nameAndId());
|
||||
if (TickThread.isTickThreadFor(entity)) {
|
||||
syncEntityForWatcher(playerId, viewerIsOp, entity);
|
||||
} else {
|
||||
entity.getBukkitEntity().getScheduler().execute(MinecraftInternalPlugin.INSTANCE, () -> syncEntityForWatcher(playerId, viewerIsOp, entity), null, 1L);
|
||||
}
|
||||
}, null, 1L);
|
||||
}
|
||||
|
||||
public static void onConfigModify(boolean enable) {
|
||||
if (enable) {
|
||||
enablePcaSyncProtocolGlobal();
|
||||
} else {
|
||||
disablePcaSyncProtocolGlobal();
|
||||
}
|
||||
}
|
||||
|
||||
public static void enablePcaSyncProtocol(@NotNull ServerPlayer player) {
|
||||
sendEmptyPacket(player.getUUID(), ENABLE_PCA_SYNC_PROTOCOL);
|
||||
}
|
||||
|
||||
public static void disablePcaSyncProtocol(@NotNull ServerPlayer player) {
|
||||
sendEmptyPacket(player.getUUID(), DISABLE_PCA_SYNC_PROTOCOL);
|
||||
}
|
||||
|
||||
public static void updateEntity(@NotNull ServerPlayer player, @NotNull Entity entity) {
|
||||
updateEntity(player.getUUID(), entity);
|
||||
}
|
||||
|
||||
private static void updateEntity(@NotNull UUID playerId, @NotNull Entity entity) {
|
||||
if (!TickThread.isTickThreadFor(entity) || entity.isRemoved()) {
|
||||
return;
|
||||
}
|
||||
CompoundTag nbt = TagUtil.saveEntity(entity);
|
||||
sendPayloadPacket(playerId, new UpdateEntityPayload(entity.level().dimension().identifier(), entity.getId(), nbt));
|
||||
}
|
||||
|
||||
public static void updateBlockEntity(@NotNull ServerPlayer player, @NotNull BlockEntity blockEntity) {
|
||||
updateBlockEntity(player.getUUID(), blockEntity);
|
||||
}
|
||||
|
||||
private static void updateBlockEntity(@NotNull UUID playerId, @NotNull BlockEntity blockEntity) {
|
||||
Level world = blockEntity.getLevel();
|
||||
if (world == null || world.isClientSide() || !TickThread.isTickThreadFor(world, blockEntity.getBlockPos())) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendPayloadPacket(playerId, new UpdateBlockEntityPayload(world.dimension().identifier(), blockEntity.getBlockPos(), blockEntity.saveWithoutMetadata(world.registryAccess())));
|
||||
}
|
||||
|
||||
public static boolean syncEntityToClient(@NotNull Entity entity) {
|
||||
if (!PcaSyncProtocolConfig.enabled || entity.level().isClientSide() || !TickThread.isTickThreadFor(entity) || entity.isRemoved()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Pair<Identifier, UUID> pair = new ImmutablePair<>(entity.level().dimension().identifier(), entity.getUUID());
|
||||
Set<UUID> playerIds = entityWatchPlayerSet.get(pair);
|
||||
if (playerIds == null || playerIds.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ArrayList<UUID> stalePlayers = new ArrayList<>();
|
||||
boolean ret = false;
|
||||
for (UUID playerId : playerIds) {
|
||||
if (MinecraftServer.getServer().getPlayerList().getPlayer(playerId) == null) {
|
||||
stalePlayers.add(playerId);
|
||||
continue;
|
||||
}
|
||||
updateEntity(playerId, entity);
|
||||
ret = true;
|
||||
}
|
||||
stalePlayers.forEach(playerIds::remove);
|
||||
if (playerIds.isEmpty()) {
|
||||
entityWatchPlayerSet.remove(pair, playerIds);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static boolean syncBlockEntityToClient(@NotNull BlockEntity blockEntity) {
|
||||
Level world = blockEntity.getLevel();
|
||||
if (!PcaSyncProtocolConfig.enabled || world == null || world.isClientSide() || !TickThread.isTickThreadFor(world, blockEntity.getBlockPos())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockPos pos = blockEntity.getBlockPos();
|
||||
BlockState blockState = world.getBlockState(pos);
|
||||
Set<UUID> playerIds = collectWatchers(world, pos);
|
||||
|
||||
if (blockState.getBlock() instanceof ChestBlock && blockState.getValue(ChestBlock.TYPE) != ChestType.SINGLE) {
|
||||
BlockPos posAdj = pos.relative(ChestBlock.getConnectedDirection(blockState));
|
||||
playerIds.addAll(collectWatchers(world, posAdj));
|
||||
}
|
||||
|
||||
if (playerIds.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean ret = false;
|
||||
for (UUID playerId : playerIds) {
|
||||
if (MinecraftServer.getServer().getPlayerList().getPlayer(playerId) == null) {
|
||||
removePlayerFromWatchers(playerId);
|
||||
continue;
|
||||
}
|
||||
updateBlockEntity(playerId, blockEntity);
|
||||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void disablePcaSyncProtocolGlobal() {
|
||||
playerWatchBlockPos.clear();
|
||||
playerWatchEntity.clear();
|
||||
blockPosWatchPlayerSet.clear();
|
||||
entityWatchPlayerSet.clear();
|
||||
for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) {
|
||||
disablePcaSyncProtocol(player);
|
||||
}
|
||||
}
|
||||
|
||||
public static void enablePcaSyncProtocolGlobal() {
|
||||
for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) {
|
||||
enablePcaSyncProtocol(player);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearPlayerWatchData(ServerPlayer player) {
|
||||
clearPlayerWatchData(player.getUUID());
|
||||
}
|
||||
|
||||
private static void clearPlayerWatchData(UUID playerId) {
|
||||
clearPlayerWatchBlock(playerId);
|
||||
clearPlayerWatchEntity(playerId);
|
||||
}
|
||||
|
||||
private static void clearPlayerWatchEntity(ServerPlayer player) {
|
||||
clearPlayerWatchEntity(player.getUUID());
|
||||
}
|
||||
|
||||
private static void clearPlayerWatchEntity(UUID playerId) {
|
||||
Pair<Identifier, UUID> pair = playerWatchEntity.remove(playerId);
|
||||
if (pair != null) {
|
||||
removeFromSet(entityWatchPlayerSet, pair, playerId);
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearPlayerWatchBlock(ServerPlayer player) {
|
||||
clearPlayerWatchBlock(player.getUUID());
|
||||
}
|
||||
|
||||
private static void clearPlayerWatchBlock(UUID playerId) {
|
||||
Pair<Identifier, BlockPos> pair = playerWatchBlockPos.remove(playerId);
|
||||
if (pair != null) {
|
||||
removeFromSet(blockPosWatchPlayerSet, pair, playerId);
|
||||
}
|
||||
}
|
||||
|
||||
private static void removePlayerFromWatchers(UUID playerId) {
|
||||
Pair<Identifier, BlockPos> blockPair = playerWatchBlockPos.remove(playerId);
|
||||
if (blockPair != null) {
|
||||
removeFromSet(blockPosWatchPlayerSet, blockPair, playerId);
|
||||
}
|
||||
|
||||
Pair<Identifier, UUID> entityPair = playerWatchEntity.remove(playerId);
|
||||
if (entityPair != null) {
|
||||
removeFromSet(entityWatchPlayerSet, entityPair, playerId);
|
||||
}
|
||||
}
|
||||
|
||||
private static <K> void removeFromSet(ConcurrentMap<K, Set<UUID>> map, K key, UUID playerId) {
|
||||
Set<UUID> playerSet = map.get(key);
|
||||
if (playerSet != null) {
|
||||
playerSet.remove(playerId);
|
||||
if (playerSet.isEmpty()) {
|
||||
map.remove(key, playerSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<UUID> collectWatchers(@NotNull Level world, @NotNull BlockPos blockPos) {
|
||||
Set<UUID> playerIds = blockPosWatchPlayerSet.get(new ImmutablePair<>(world.dimension().identifier(), blockPos));
|
||||
return playerIds == null ? new HashSet<>() : new HashSet<>(playerIds);
|
||||
}
|
||||
|
||||
private static @org.jetbrains.annotations.Nullable BlockEntity getLoadedBlockEntity(ServerLevel world, BlockPos pos) {
|
||||
LevelChunk chunk = world.getChunkIfLoaded(pos);
|
||||
return chunk == null ? null : chunk.getBlockEntity(pos);
|
||||
}
|
||||
|
||||
private static void scheduleBlockEntityUpdate(UUID playerId, ServerLevel world, BlockPos pos) {
|
||||
Bukkit.getRegionScheduler().execute(MinecraftInternalPlugin.INSTANCE, world.getWorld(), pos.getX() >> 4, pos.getZ() >> 4, () -> {
|
||||
if (!PcaSyncProtocolConfig.enabled || MinecraftServer.getServer().getPlayerList().getPlayer(playerId) == null || !TickThread.isTickThreadFor(world, pos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockEntity blockEntity = getLoadedBlockEntity(world, pos);
|
||||
if (blockEntity != null) {
|
||||
updateBlockEntity(playerId, blockEntity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void syncEntityForWatcher(UUID playerId, boolean viewerIsOp, Entity entity) {
|
||||
if (!PcaSyncProtocolConfig.enabled || MinecraftServer.getServer().getPlayerList().getPlayer(playerId) == null || !TickThread.isTickThreadFor(entity) || entity.isRemoved()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity instanceof Player && !maySyncPlayerEntity(playerId, viewerIsOp, entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearPlayerWatchData(playerId);
|
||||
updateEntity(playerId, entity);
|
||||
|
||||
Pair<Identifier, UUID> pair = new ImmutablePair<>(entity.level().dimension().identifier(), entity.getUUID());
|
||||
playerWatchEntity.put(playerId, pair);
|
||||
entityWatchPlayerSet.computeIfAbsent(pair, ignored -> ConcurrentHashMap.newKeySet()).add(playerId);
|
||||
}
|
||||
|
||||
private static boolean maySyncPlayerEntity(UUID viewerId, boolean viewerIsOp, Entity entity) {
|
||||
return switch (PcaSyncProtocolConfig.syncPlayerEntity) {
|
||||
case NOBODY -> false;
|
||||
case BOT -> entity instanceof ServerBot;
|
||||
case OPS -> entity instanceof ServerBot || viewerIsOp;
|
||||
case OPS_AND_SELF -> entity instanceof ServerBot || viewerIsOp || entity.getUUID().equals(viewerId);
|
||||
case EVERYONE -> true;
|
||||
};
|
||||
}
|
||||
|
||||
private static void sendEmptyPacket(UUID playerId, Identifier id) {
|
||||
ServerPlayer player = MinecraftServer.getServer().getPlayerList().getPlayer(playerId);
|
||||
if (player == null) {
|
||||
removePlayerFromWatchers(playerId);
|
||||
return;
|
||||
}
|
||||
|
||||
player.getBukkitEntity().getScheduler().execute(MinecraftInternalPlugin.INSTANCE, () -> {
|
||||
ServerPlayer onlinePlayer = MinecraftServer.getServer().getPlayerList().getPlayer(playerId);
|
||||
if (onlinePlayer != null) {
|
||||
ProtocolUtils.sendEmptyPacket(onlinePlayer, id);
|
||||
}
|
||||
}, null, 1L);
|
||||
}
|
||||
|
||||
private static void sendPayloadPacket(UUID playerId, CustomPacketPayload payload) {
|
||||
ServerPlayer player = MinecraftServer.getServer().getPlayerList().getPlayer(playerId);
|
||||
if (player == null) {
|
||||
removePlayerFromWatchers(playerId);
|
||||
return;
|
||||
}
|
||||
|
||||
player.getBukkitEntity().getScheduler().execute(MinecraftInternalPlugin.INSTANCE, () -> {
|
||||
ServerPlayer onlinePlayer = MinecraftServer.getServer().getPlayerList().getPlayer(playerId);
|
||||
if (onlinePlayer != null) {
|
||||
ProtocolUtils.sendPayloadPacket(onlinePlayer, payload);
|
||||
}
|
||||
}, null, 1L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return PcaSyncProtocolConfig.enabled;
|
||||
}
|
||||
|
||||
public record UpdateEntityPayload(Identifier dimension, int entityId,
|
||||
CompoundTag tag) implements LeavesCustomPayload {
|
||||
@ID
|
||||
public static final Identifier UPDATE_ENTITY = PcaSyncProtocol.id("update_entity");
|
||||
|
||||
@Codec
|
||||
public static final StreamCodec<FriendlyByteBuf, UpdateEntityPayload> CODEC = StreamCodec.composite(
|
||||
Identifier.STREAM_CODEC,
|
||||
UpdateEntityPayload::dimension,
|
||||
ByteBufCodecs.INT,
|
||||
UpdateEntityPayload::entityId,
|
||||
ByteBufCodecs.COMPOUND_TAG,
|
||||
UpdateEntityPayload::tag,
|
||||
UpdateEntityPayload::new
|
||||
);
|
||||
}
|
||||
|
||||
public record UpdateBlockEntityPayload(Identifier dimension, BlockPos blockPos,
|
||||
CompoundTag tag) implements LeavesCustomPayload {
|
||||
@ID
|
||||
private static final Identifier UPDATE_BLOCK_ENTITY = PcaSyncProtocol.id("update_block_entity");
|
||||
|
||||
@Codec
|
||||
private static final StreamCodec<FriendlyByteBuf, UpdateBlockEntityPayload> CODEC = StreamCodec.composite(
|
||||
Identifier.STREAM_CODEC,
|
||||
UpdateBlockEntityPayload::dimension,
|
||||
BlockPos.STREAM_CODEC,
|
||||
UpdateBlockEntityPayload::blockPos,
|
||||
ByteBufCodecs.COMPOUND_TAG,
|
||||
UpdateBlockEntityPayload::tag,
|
||||
UpdateBlockEntityPayload::new
|
||||
);
|
||||
}
|
||||
|
||||
public record SyncBlockEntityPayload(BlockPos pos) implements LeavesCustomPayload {
|
||||
@ID
|
||||
public static final Identifier SYNC_BLOCK_ENTITY = PcaSyncProtocol.id("sync_block_entity");
|
||||
|
||||
@Codec
|
||||
private static final StreamCodec<FriendlyByteBuf, SyncBlockEntityPayload> CODEC = StreamCodec.composite(
|
||||
BlockPos.STREAM_CODEC, SyncBlockEntityPayload::pos, SyncBlockEntityPayload::new
|
||||
);
|
||||
}
|
||||
|
||||
public record SyncEntityPayload(int entityId) implements LeavesCustomPayload {
|
||||
@ID
|
||||
public static final Identifier SYNC_ENTITY = PcaSyncProtocol.id("sync_entity");
|
||||
|
||||
@Codec
|
||||
private static final StreamCodec<FriendlyByteBuf, SyncEntityPayload> CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.INT, SyncEntityPayload::entityId, SyncEntityPayload::new
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user