9
0
mirror of https://github.com/LeavesMC/Leaves.git synced 2025-12-28 19:39:22 +00:00
* init 1.21.4, and boom!

* build change, but weight not work

* just work

* Build changes, and delete timings

* Fix API patches (#406)

* Fix API patches

* merge

---------

Co-authored-by: violetc <58360096+s-yh-china@users.noreply.github.com>

* 0006/0129

* 0009/0129

* 0011/0129

* 0018/0129

* 0030/0129

* 0035/0129

* 0043/0129

* 0048/0129

* 0049/0129

* 0057/0129

* 0065/0129

* 0086/0129 (#408)

* 0072/0129

* 0080/0129

* Readd patch infos

* 0086/0129

* Delete applied patches

* 0087/0129

* 0091/0129

* 0097/0129

* 0101/0129

* 102/129

* 0107/0129

* 0112/0129

* 0118/0129

* 0129/0129, 100% patched

* fix some

* server work

* Protocol... (#409)

* Jade v7

* Fix changed part for Jade

* Formatting imports, add Lms Paster protocol

* REI payloads 5/8

* Add REI support, remove unnecessary content in Jade

* Rename

* Make jade better

* Make action work

* fix action jar

* Fix some protocol

* Fix bot action, and entity tickCount

* Fix Warden GameEventListener register on load

* Fix extra Raider drop

* Fix grindstone overstacking

* Update Paper, and some doc

* Merge

* [ci skip] Update Action

---------

Co-authored-by: Lumine1909 <133463833+Lumine1909@users.noreply.github.com>
This commit is contained in:
violetc
2025-02-14 23:55:46 +08:00
committed by GitHub
parent 46e8b6c59c
commit d5c9306a7f
457 changed files with 34990 additions and 33902 deletions

View File

@@ -0,0 +1,2 @@
[*.java]
ij_java_use_fq_class_names = false

View File

@@ -0,0 +1,980 @@
package org.leavesmc.leaves;
import com.destroystokyo.paper.util.SneakyThrow;
import io.papermc.paper.configuration.GlobalConfiguration;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import org.bukkit.command.Command;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.command.LeavesCommand;
import org.leavesmc.leaves.config.GlobalConfig;
import org.leavesmc.leaves.config.GlobalConfigCategory;
import org.leavesmc.leaves.config.RemovedConfig;
import org.leavesmc.leaves.config.GlobalConfigManager;
import org.leavesmc.leaves.region.RegionFileFormat;
import org.leavesmc.leaves.util.MathUtils;
import org.leavesmc.leaves.config.ConfigValidatorImpl.BooleanConfigValidator;
import org.leavesmc.leaves.config.ConfigValidatorImpl.IntConfigValidator;
import org.leavesmc.leaves.config.ConfigValidatorImpl.StringConfigValidator;
import org.leavesmc.leaves.config.ConfigValidatorImpl.DoubleConfigValidator;
import org.leavesmc.leaves.config.ConfigValidatorImpl.ListConfigValidator;
import org.leavesmc.leaves.config.ConfigValidatorImpl.EnumConfigValidator;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import org.leavesmc.leaves.protocol.CarpetServerProtocol.CarpetRule;
import org.leavesmc.leaves.protocol.CarpetServerProtocol.CarpetRules;
import org.leavesmc.leaves.protocol.bladeren.BladerenProtocol.LeavesFeatureSet;
import org.leavesmc.leaves.protocol.bladeren.BladerenProtocol.LeavesFeature;
public final class LeavesConfig {
public static final String CONFIG_HEADER = "Configuration file for Leaves.";
public static final int CURRENT_CONFIG_VERSION = 6;
private static File configFile;
public static YamlConfiguration config;
public static void init(final @NotNull File file) {
LeavesConfig.configFile = file;
config = new YamlConfiguration();
config.options().setHeader(Collections.singletonList(CONFIG_HEADER));
config.options().copyDefaults(true);
if (!file.exists()) {
try {
boolean is = file.createNewFile();
if (!is) {
throw new IOException("Can't create file");
}
} catch (final Exception ex) {
LeavesLogger.LOGGER.severe("Failure to create leaves config", ex);
}
} else {
try {
config.load(file);
} catch (final Exception ex) {
LeavesLogger.LOGGER.severe("Failure to load leaves config", ex);
SneakyThrow.sneaky(ex);
throw new RuntimeException(ex);
}
}
LeavesConfig.config.set("config-version", CURRENT_CONFIG_VERSION);
GlobalConfigManager.init();
registerCommand("leaves", new LeavesCommand("leaves"));
}
public static void save() {
try {
config.save(LeavesConfig.configFile);
} catch (final Exception ex) {
LeavesLogger.LOGGER.severe("Unable to save leaves config", ex);
}
}
public static void registerCommand(String name, Command command) {
MinecraftServer.getServer().server.getCommandMap().register(name, "leaves", command);
MinecraftServer.getServer().server.syncCommands();
}
public static void unregisterCommand(String name) {
name = name.toLowerCase(Locale.ENGLISH).trim();
MinecraftServer.getServer().server.getCommandMap().getKnownCommands().remove(name);
MinecraftServer.getServer().server.getCommandMap().getKnownCommands().remove("leaves:" + name);
MinecraftServer.getServer().server.syncCommands();
}
public static ModifyConfig modify = new ModifyConfig();
@GlobalConfigCategory("modify")
public static class ModifyConfig {
public FakeplayerConfig fakeplayer = new FakeplayerConfig();
@GlobalConfigCategory("fakeplayer")
public static class FakeplayerConfig {
@RemovedConfig(name = "enable", category = "fakeplayer", transform = true)
@GlobalConfig(value = "enable", validator = FakeplayerValidator.class)
public boolean enable = true;
private static class FakeplayerValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
if (value) {
registerCommand("bot", new org.leavesmc.leaves.bot.BotCommand("bot"));
org.leavesmc.leaves.bot.agent.Actions.registerAll();
} else {
unregisterCommand("bot");
}
}
}
@RemovedConfig(name = "unable-fakeplayer-names", category = "fakeplayer", convert = ListConfigValidator.STRING.class, transform = true)
@GlobalConfig(value = "unable-fakeplayer-names", validator = ListConfigValidator.STRING.class)
public List<String> unableNames = List.of("player-name");
@GlobalConfig(value = "limit", validator = IntConfigValidator.class)
public int limit = 10;
@GlobalConfig(value = "prefix", validator = StringConfigValidator.class)
public String prefix = "";
@GlobalConfig(value = "suffix", validator = StringConfigValidator.class)
public String suffix = "";
@GlobalConfig(value = "regen-amount", validator = RegenAmountValidator.class)
public double regenAmount = 0.0;
private static class RegenAmountValidator extends DoubleConfigValidator {
@Override
public void verify(Double old, Double value) throws IllegalArgumentException {
if (value < 0.0) {
throw new IllegalArgumentException("regen-amount need >= 0.0");
}
}
}
@GlobalConfig("always-send-data")
public boolean canSendDataAlways = true;
@GlobalConfig("resident-fakeplayer")
public boolean canResident = false;
@GlobalConfig("open-fakeplayer-inventory")
public boolean canOpenInventory = false;
@GlobalConfig("skip-sleep-check")
public boolean canSkipSleep = false;
@GlobalConfig("spawn-phantom")
public boolean canSpawnPhantom = false;
@GlobalConfig("use-action")
public boolean canUseAction = true;
@GlobalConfig("modify-config")
public boolean canModifyConfig = false;
@GlobalConfig("manual-save-and-load")
public boolean canManualSaveAndLoad = false;
@GlobalConfig(value = "cache-skin", lock = true)
public boolean useSkinCache = false;
}
public MinecraftOLDConfig oldMC = new MinecraftOLDConfig();
@GlobalConfigCategory("minecraft-old")
public static class MinecraftOLDConfig {
public BlockUpdaterConfig updater = new BlockUpdaterConfig();
@GlobalConfigCategory("block-updater")
public static class BlockUpdaterConfig {
@RemovedConfig(name = "instant-block-updater-reintroduced", category = "modify", transform = true)
@RemovedConfig(name = "instant-block-updater-reintroduced", category = {"modify", "minecraft-old"}, transform = true)
@GlobalConfig(value = "instant-block-updater-reintroduced", lock = true)
public boolean instantBlockUpdaterReintroduced = false;
@RemovedConfig(name = "cce-update-suppression", category = {"modify", "minecraft-old"}, transform = true)
@GlobalConfig("cce-update-suppression")
public boolean cceUpdateSuppression = false;
@RemovedConfig(name = "redstone-wire-dont-connect-if-on-trapdoor", category = "modify", transform = true)
@RemovedConfig(name = "redstone-wire-dont-connect-if-on-trapdoor", category = {"modify", "minecraft-old"}, transform = true)
@GlobalConfig("redstone-wire-dont-connect-if-on-trapdoor")
public boolean redstoneDontCantOnTrapDoor = false;
}
@RemovedConfig(name = "shears-in-dispenser-can-zero-amount", category = {}, transform = true)
@RemovedConfig(name = "shears-in-dispenser-can-zero-amount", category = "modify", transform = true)
@GlobalConfig("shears-in-dispenser-can-zero-amount")
public boolean shearsInDispenserCanZeroAmount = false;
@GlobalConfig("armor-stand-cant-kill-by-mob-projectile")
public boolean armorStandCantKillByMobProjectile = false;
@GlobalConfig(value = "villager-infinite-discounts", validator = VillagerInfiniteDiscountsValidator.class)
public boolean villagerInfiniteDiscounts = false;
private static class VillagerInfiniteDiscountsValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
org.leavesmc.leaves.util.VillagerInfiniteDiscountHelper.doVillagerInfiniteDiscount(value);
}
}
@GlobalConfig("copper-bulb-1gt-delay")
public boolean copperBulb1gt = false;
@GlobalConfig("crafter-1gt-delay")
public boolean crafter1gt = false;
@RemovedConfig(name = "zero-tick-plants", category = "modify", transform = true)
@GlobalConfig("zero-tick-plants")
public boolean zeroTickPlants = false;
@RemovedConfig(name = "loot-world-random", category = {"modify", "minecraft-old"}, transform = true)
@GlobalConfig(value = "rng-fishing", lock = true, validator = RNGFishingValidator.class)
public boolean rngFishing = false;
private static class RNGFishingValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
LeavesFeatureSet.register(LeavesFeature.of("rng_fishing", value));
}
}
@GlobalConfig("allow-grindstone-overstacking")
public boolean allowGrindstoneOverstacking = false;
@GlobalConfig("allow-entity-portal-with-passenger")
public boolean allowEntityPortalWithPassenger = true;
@GlobalConfig("disable-gateway-portal-entity-ticking")
public boolean disableGatewayPortalEntityTicking = false;
@GlobalConfig("disable-LivingEntity-ai-step-alive-check")
public boolean disableLivingEntityAiStepAliveCheck = false;
@GlobalConfig("fix-fortress-mob-spawn")
public boolean fixFortressMobSpawn = false;
@GlobalConfig("old-block-entity-behaviour")
public boolean oldBlockEntityBehaviour = false;
@GlobalConfig("old-hopper-suck-in-behavior")
public boolean oldHopperSuckInBehavior = false;
public RaidConfig raid = new RaidConfig();
@GlobalConfigCategory("revert-raid-changes")
public static class RaidConfig {
@GlobalConfig("allow-bad-omen-trigger-raid")
public boolean allowBadOmenTriggerRaid = false;
@GlobalConfig("give-bad-omen-when-kill-patrol-leader")
public boolean giveBadOmenWhenKillPatrolLeader = false;
}
@GlobalConfig("allow-anvil-destroy-item-entities")
public boolean allowAnvilDestroyItemEntities = false;
@GlobalConfig("string-tripwire-hook-duplicate")
public boolean stringTripwireHookDuplicate = false;
}
public ElytraAeronauticsConfig elytraAeronautics = new ElytraAeronauticsConfig();
@GlobalConfigCategory("elytra-aeronautics")
public static class ElytraAeronauticsConfig {
@GlobalConfig("no-chunk-load")
public boolean noChunk = false;
@GlobalConfig(value = "no-chunk-height", validator = DoubleConfigValidator.class)
public double noChunkHeight = 500.0D;
@GlobalConfig(value = "no-chunk-speed", validator = DoubleConfigValidator.class)
public double noChunkSpeed = -1.0D;
@GlobalConfig("message")
public boolean noChunkMes = true;
@GlobalConfig(value = "message-start", validator = StringConfigValidator.class)
public String noChunkStartMes = "Flight enter cruise mode";
@GlobalConfig(value = "message-end", validator = StringConfigValidator.class)
public String noChunkEndMes = "Flight exit cruise mode";
}
@RemovedConfig(name = "redstone-shears-wrench", category = {}, transform = true)
@GlobalConfig("redstone-shears-wrench")
public boolean redstoneShearsWrench = true;
@RemovedConfig(name = "budding-amethyst-can-push-by-piston", category = {}, transform = true)
@GlobalConfig("budding-amethyst-can-push-by-piston")
public boolean buddingAmethystCanPushByPiston = false;
@RemovedConfig(name = "spectator-dont-get-advancement", category = {}, transform = true)
@GlobalConfig("spectator-dont-get-advancement")
public boolean spectatorDontGetAdvancement = false;
@RemovedConfig(name = "stick-change-armorstand-arm-status", category = {}, transform = true)
@GlobalConfig("stick-change-armorstand-arm-status")
public boolean stickChangeArmorStandArmStatus = true;
@RemovedConfig(name = "snowball-and-egg-can-knockback-player", category = {}, transform = true)
@GlobalConfig("snowball-and-egg-can-knockback-player")
public boolean snowballAndEggCanKnockback = true;
@GlobalConfig("flatten-triangular-distribution")
public boolean flattenTriangularDistribution = false;
@GlobalConfig("player-operation-limiter")
public boolean playerOperationLimiter = false;
@GlobalConfig(value = "renewable-elytra", validator = RenewableElytraValidator.class)
public double renewableElytra = -1.0F;
private static class RenewableElytraValidator extends DoubleConfigValidator {
@Override
public void verify(Double old, Double value) throws IllegalArgumentException {
if (value > 1.0) {
throw new IllegalArgumentException("renewable-elytra need <= 1.0f");
}
}
}
public int shulkerBoxStackSize = 1;
@GlobalConfig(value = "stackable-shulker-boxes", validator = StackableShulkerValidator.class)
private String stackableShulkerBoxes = "false";
private static class StackableShulkerValidator extends StringConfigValidator {
@Override
public void verify(String old, String value) throws IllegalArgumentException {
String realValue = MathUtils.isNumeric(value) ? value : value.equals("true") ? "2" : "1";
LeavesConfig.modify.shulkerBoxStackSize = Integer.parseInt(realValue);
}
}
@GlobalConfig("force-void-trade")
public boolean forceVoidTrade = false;
@GlobalConfig(value = "mc-technical-survival-mode", validator = McTechnicalModeValidator.class, lock = true)
public boolean mcTechnicalMode = true;
private static class McTechnicalModeValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
if (value) {
org.leavesmc.leaves.util.McTechnicalModeHelper.doMcTechnicalMode();
}
}
}
@GlobalConfig("return-nether-portal-fix")
public boolean netherPortalFix = false;
@GlobalConfig(value = "use-vanilla-random", lock = true, validator = UseVanillaRandomValidator.class)
public boolean useVanillaRandom = false;
private static class UseVanillaRandomValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
LeavesFeatureSet.register(LeavesFeature.of("use_vanilla_random", value));
}
}
@GlobalConfig("fix-update-suppression-crash")
public boolean updateSuppressionCrashFix = true;
@GlobalConfig(value = "bedrock-break-list", lock = true)
public boolean bedrockBreakList = false;
@GlobalConfig(value = "disable-distance-check-for-use-item", validator = DisableDistanceCheckForUseItemValidator.class)
public boolean disableDistanceCheckForUseItem = false;
private static class DisableDistanceCheckForUseItemValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
if (!value && old != null && LeavesConfig.protocol.alternativeBlockPlacement != ProtocolConfig.AlternativePlaceType.NONE) {
throw new IllegalArgumentException("alternative-block-placement is enable, disable-distance-check-for-use-item always need true");
}
}
}
@GlobalConfig("no-feather-falling-trample")
public boolean noFeatherFallingTrample = false;
@GlobalConfig("shared-villager-discounts")
public boolean sharedVillagerDiscounts = false;
@GlobalConfig("disable-check-out-of-order-command")
public boolean disableCheckOutOfOrderCommand = false;
@GlobalConfig("despawn-enderman-with-block")
public boolean despawnEndermanWithBlock = false;
@GlobalConfig(value = "creative-no-clip", validator = CreativeNoClipValidator.class)
public boolean creativeNoClip = false;
private static class CreativeNoClipValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
CarpetRules.register(CarpetRule.of("carpet", "creativeNoClip", value));
}
}
@GlobalConfig("shave-snow-layers")
public boolean shaveSnowLayers = true;
@GlobalConfig("ignore-lc")
public boolean ignoreLC = false;
@GlobalConfig("disable-packet-limit")
public boolean disablePacketLimit = false;
@GlobalConfig(value = "lava-riptide", validator = LavaRiptideValidator.class)
public boolean lavaRiptide = false;
private static class LavaRiptideValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
LeavesFeatureSet.register(LeavesFeature.of("lava_riptide", value));
}
}
@GlobalConfig(value = "no-block-update-command", validator = NoBlockUpdateValidator.class)
public boolean noBlockUpdateCommand = false;
private static class NoBlockUpdateValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
if (value) {
registerCommand("blockupdate", new org.leavesmc.leaves.command.NoBlockUpdateCommand("blockupdate"));
} else {
unregisterCommand("blockupdate");
}
}
}
@GlobalConfig("no-tnt-place-update")
public boolean noTNTPlaceUpdate = false;
@GlobalConfig("raider-die-skip-self-raid-check")
public boolean skipSelfRaidCheck = false;
@GlobalConfig("container-passthrough")
public boolean containerPassthrough = false;
@GlobalConfig(value = "avoid-anvil-too-expensive", validator = AnvilNotExpensiveValidator.class)
public boolean avoidAnvilTooExpensive = false;
private static class AnvilNotExpensiveValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
CarpetRules.register(CarpetRule.of("pca", "avoidAnvilTooExpensive", value));
}
}
@GlobalConfig("bow-infinity-fix")
public boolean bowInfinityFix = false;
@GlobalConfig("hopper-counter")
public boolean hopperCounter = false;
@GlobalConfig(value = "spider-jockeys-drop-gapples", validator = JockeysDropGAppleValidator.class)
public double spiderJockeysDropGapples = -1.0;
private static class JockeysDropGAppleValidator extends DoubleConfigValidator {
@Override
public void verify(Double old, Double value) throws IllegalArgumentException {
if (value > 1.0) {
throw new IllegalArgumentException("spider-jockeys-drop-gapples need <= 1.0f");
}
}
}
@GlobalConfig("renewable-deepslate")
public boolean renewableDeepslate = false;
@GlobalConfig("renewable-sponges")
public boolean renewableSponges = false;
@GlobalConfig(value = "renewable-coral", validator = RenewableCoralValidator.class)
public RenewableCoralType renewableCoral = RenewableCoralType.FALSE;
public enum RenewableCoralType {
FALSE, TRUE, EXPANDED
}
private static class RenewableCoralValidator extends EnumConfigValidator<RenewableCoralType> {
@Override
public void verify(RenewableCoralType old, RenewableCoralType value) throws IllegalArgumentException {
CarpetRules.register(CarpetRule.of("carpet", "renewableCoral", value));
}
}
@GlobalConfig("fast-resume")
public boolean fastResume = false;
@GlobalConfig(value = "force-peaceful-mode", validator = ForcePeacefulModeValidator.class)
public int forcePeacefulMode = -1;
private static class ForcePeacefulModeValidator extends IntConfigValidator {
@Override
public void verify(Integer old, Integer value) throws IllegalArgumentException {
for (ServerLevel level : MinecraftServer.getServer().getAllLevels()) {
level.chunkSource.peacefulModeSwitchTick = value;
}
}
}
@GlobalConfig("disable-vault-blacklist")
public boolean disableVaultBlacklist = false;
@RemovedConfig(name = "tick-command", category = "modify")
@RemovedConfig(name = "player-can-edit-sign", category = "modify")
@RemovedConfig(name = "mending-compatibility-infinity", category = {"modify", "minecraft-old"})
@RemovedConfig(name = "protection-stacking", category = {"modify", "minecraft-old"})
@RemovedConfig(name = "disable-moved-wrongly-threshold", category = {"modify"})
private final boolean removed = false;
}
public static PerformanceConfig performance = new PerformanceConfig();
@GlobalConfigCategory("performance")
public static class PerformanceConfig {
public PerformanceRemoveConfig remove = new PerformanceRemoveConfig();
@GlobalConfigCategory("remove")
public static class PerformanceRemoveConfig {
@GlobalConfig("tick-guard-lambda")
public boolean tickGuardLambda = true;
@GlobalConfig("damage-lambda")
public boolean damageLambda = true;
}
@GlobalConfig("optimized-dragon-respawn")
public boolean optimizedDragonRespawn = false;
@GlobalConfig("dont-send-useless-entity-packets")
public boolean dontSendUselessEntityPackets = true;
@GlobalConfig("enable-suffocation-optimization")
public boolean enableSuffocationOptimization = true;
@GlobalConfig("check-spooky-season-once-an-hour")
public boolean checkSpookySeasonOnceAnHour = true;
@GlobalConfig("inactive-goal-selector-disable")
public boolean throttleInactiveGoalSelectorTick = false;
@GlobalConfig("reduce-entity-allocations")
public boolean reduceEntityAllocations = true;
@GlobalConfig("cache-climb-check")
public boolean cacheClimbCheck = true;
@GlobalConfig(value = "biome-temperatures-use-aging-cache", lock = true)
public boolean biomeTemperaturesUseAgingCache = true;
@GlobalConfig("reduce-chuck-load-and-lookup")
public boolean reduceChuckLoadAndLookup = true;
@GlobalConfig("cache-ignite-odds")
public boolean cacheIgniteOdds = true;
@GlobalConfig("faster-chunk-serialization")
public boolean fasterChunkSerialization = true;
@GlobalConfig("skip-secondary-POI-sensor-if-absent")
public boolean skipSecondaryPOISensorIfAbsent = true;
@GlobalConfig("store-mob-counts-in-array")
public boolean storeMobCountsInArray = true;
@GlobalConfig("optimize-noise-generation")
public boolean optimizeNoiseGeneration = false;
@GlobalConfig("optimize-sun-burn-tick")
public boolean optimizeSunBurnTick = true;
@GlobalConfig("optimized-CubePointRange")
public boolean optimizedCubePointRange = true;
@GlobalConfig("check-frozen-ticks-before-landing-block")
public boolean checkFrozenTicksBeforeLandingBlock = true;
@GlobalConfig("skip-entity-move-if-movement-is-zero")
public boolean skipEntityMoveIfMovementIsZero = true;
@GlobalConfig("skip-cloning-advancement-criteria")
public boolean skipCloningAdvancementCriteria = false;
@GlobalConfig("skip-negligible-planar-movement-multiplication")
public boolean skipNegligiblePlanarMovementMultiplication = true;
@GlobalConfig("fix-villagers-dont-release-memory")
public boolean villagersDontReleaseMemoryFix = false;
@RemovedConfig(name = "cache-world-generator-sea-level", category = "performance")
@RemovedConfig(name = "cache-ominous-banner-item", category = "performance")
@RemovedConfig(name = "use-optimized-collection", category = "performance")
@RemovedConfig(name = "async-pathfinding", category = "performance")
@RemovedConfig(name = "async-mob-spawning", category = "performance")
@RemovedConfig(name = "async-entity-tracker", category = "performance")
@RemovedConfig(name = "fix-paper-6045", category = {"performance", "fix"})
@RemovedConfig(name = "fix-paper-9372", category = {"performance", "fix"})
@RemovedConfig(name = "skip-clone-loot-parameters", category = "performance")
@RemovedConfig(name = "skip-poi-find-in-vehicle", category = "performance")
@RemovedConfig(name = "strip-raytracing-for-entity", category = "performance")
@RemovedConfig(name = "get-nearby-players-streams", category = {"performance", "remove"})
@RemovedConfig(name = "optimize-world-generation-and-block-access", category = "performance")
@RemovedConfig(name = "cache-CubeVoxelShape-shape-array", category = "performance")
@RemovedConfig(name = "reduce-entity-fluid-lookup", category = "performance")
@RemovedConfig(name = "optimize-entity-coordinate-key", category = "performance")
@RemovedConfig(name = "entity-target-find-optimization", category = "performance")
@RemovedConfig(name = "use-more-thread-unsafe-random", category = "performance")
@RemovedConfig(name = "range-check-streams-and-iterators", category = {"performance", "remove"})
@RemovedConfig(name = "improve-fluid-direction-caching", category = "performance")
@RemovedConfig(name = "cache-BlockStatePairKey-hash", category = "performance")
@RemovedConfig(name = "optimize-chunk-ticking", category = "performance")
@RemovedConfig(name = "inventory-contains-iterators", category = {"performance", "remove"})
private final boolean removedPerformance = true;
}
public static ProtocolConfig protocol = new ProtocolConfig();
@GlobalConfigCategory("protocol")
public static class ProtocolConfig {
public BladerenConfig bladeren = new BladerenConfig();
@GlobalConfigCategory("bladeren")
public static class BladerenConfig {
@GlobalConfig("protocol")
public boolean enable = true;
@GlobalConfig(value = "mspt-sync-protocol", validator = MSPTSyncValidator.class)
public boolean msptSyncProtocol = false;
private static class MSPTSyncValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
LeavesFeatureSet.register(LeavesFeature.of("mspt_sync", value));
}
}
@GlobalConfig(value = "mspt-sync-tick-interval", validator = MSPTSyncIntervalValidator.class)
public int msptSyncTickInterval = 20;
private static class MSPTSyncIntervalValidator extends IntConfigValidator {
@Override
public void verify(Integer old, Integer value) throws IllegalArgumentException {
if (value <= 0) {
throw new IllegalArgumentException("mspt-sync-tick-interval need > 0");
}
}
}
}
public SyncmaticaConfig syncmatica = new SyncmaticaConfig();
@GlobalConfigCategory("syncmatica")
public static class SyncmaticaConfig {
@GlobalConfig(value = "enable", validator = SyncmaticaValidator.class)
public boolean enable = false;
public static class SyncmaticaValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
if (value) {
org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol.init();
}
}
}
@GlobalConfig("quota")
public boolean useQuota = false;
@GlobalConfig(value = "quota-limit", validator = IntConfigValidator.class)
public int quotaLimit = 40000000;
}
public PCAConfig pca = new PCAConfig();
@GlobalConfigCategory("pca")
public static class PCAConfig {
@RemovedConfig(name = "pca-sync-protocol", category = "protocol", transform = true)
@GlobalConfig(value = "pca-sync-protocol", validator = PcaValidator.class)
public boolean enable = false;
public static class PcaValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
if (old != null && old != value) {
org.leavesmc.leaves.protocol.PcaSyncProtocol.onConfigModify(value);
}
}
}
@RemovedConfig(name = "pca-sync-player-entity", category = "protocol", convert = PcaPlayerEntityValidator.class, transform = true)
@GlobalConfig(value = "pca-sync-player-entity", validator = PcaPlayerEntityValidator.class)
public PcaPlayerEntityType syncPlayerEntity = PcaPlayerEntityType.OPS;
public enum PcaPlayerEntityType {
NOBODY, BOT, OPS, OPS_AND_SELF, EVERYONE
}
private static class PcaPlayerEntityValidator extends EnumConfigValidator<PcaPlayerEntityType> {
}
}
public AppleSkinConfig appleskin = new AppleSkinConfig();
@GlobalConfigCategory("appleskin")
public static class AppleSkinConfig {
@RemovedConfig(name = "appleskin-protocol", category = "protocol")
@GlobalConfig("protocol")
public boolean enable = false;
@GlobalConfig("sync-tick-interval")
public int syncTickInterval = 20;
}
public ServuxConfig servux = new ServuxConfig();
@GlobalConfigCategory("servux")
public static class ServuxConfig {
@RemovedConfig(name = "servux-protocol", category = "protocol", transform = true)
@GlobalConfig("structure-protocol")
public boolean structureProtocol = false;
@GlobalConfig("entity-protocol")
public boolean entityProtocol = false;
}
@GlobalConfig("bbor-protocol")
public boolean bborProtocol = false;
@GlobalConfig("jade-protocol")
public boolean jadeProtocol = false;
@GlobalConfig(value = "alternative-block-placement", validator = AlternativePlaceValidator.class)
public AlternativePlaceType alternativeBlockPlacement = AlternativePlaceType.NONE;
public enum AlternativePlaceType {
NONE, CARPET, CARPET_FIX, LITEMATICA
}
private static class AlternativePlaceValidator extends EnumConfigValidator<AlternativePlaceType> {
@Override
public void runAfterLoader(AlternativePlaceType value, boolean firstLoad) {
if (value != AlternativePlaceType.NONE) {
LeavesConfig.modify.disableDistanceCheckForUseItem = true;
}
}
}
@GlobalConfig("xaero-map-protocol")
public boolean xaeroMapProtocol = false;
@GlobalConfig(value = "xaero-map-server-id", validator = IntConfigValidator.class)
public int xaeroMapServerID = new Random().nextInt();
@GlobalConfig("leaves-carpet-support")
public boolean leavesCarpetSupport = false;
@GlobalConfig("lms-paster-protocol")
public boolean lmsPasterProtocol = false;
@GlobalConfig("rei-server-protocol")
public boolean reiServerProtocol = false;
}
public static MiscConfig mics = new MiscConfig();
@GlobalConfigCategory("misc")
public static class MiscConfig {
public AutoUpdateConfig autoUpdate = new AutoUpdateConfig();
@GlobalConfigCategory("auto-update")
public static class AutoUpdateConfig {
@GlobalConfig(value = "enable", lock = true, validator = AutoUpdateValidator.class)
public boolean enable = false;
private static class AutoUpdateValidator extends BooleanConfigValidator {
@Override
public void runAfterLoader(Boolean value, boolean firstLoad) {
if (firstLoad) {
org.leavesmc.leaves.util.LeavesUpdateHelper.init();
if (value) {
LeavesLogger.LOGGER.warning("Auto-Update is not completely safe. Enabling it may cause data security problems!");
}
}
}
}
@GlobalConfig(value = "download-source", lock = true, validator = DownloadSourceValidator.class)
public String source = "application";
public static class DownloadSourceValidator extends StringConfigValidator {
private static final List<String> suggestSourceList = List.of("application", "ghproxy", "cloud");
@Override
public List<String> valueSuggest() {
return suggestSourceList;
}
}
@GlobalConfig("allow-experimental")
public Boolean allowExperimental = false;
@GlobalConfig(value = "time", lock = true, validator = ListConfigValidator.STRING.class)
public List<String> updateTime = List.of("14:00", "2:00");
}
public ExtraYggdrasilConfig yggdrasil = new ExtraYggdrasilConfig();
@GlobalConfigCategory("extra-yggdrasil-service")
public static class ExtraYggdrasilConfig {
@GlobalConfig(value = "enable", validator = ExtraYggdrasilValidator.class)
public boolean enable = false;
public static class ExtraYggdrasilValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
if (value) {
LeavesLogger.LOGGER.warning("extra-yggdrasil-service is an unofficial support. Enabling it may cause data security problems!");
GlobalConfiguration.get().unsupportedSettings.performUsernameValidation = true; // always check username
}
}
}
@GlobalConfig("login-protect")
public boolean loginProtect = false;
@GlobalConfig(value = "urls", lock = true, validator = ExtraYggdrasilUrlsValidator.class)
public List<String> serviceList = List.of("https://url.with.authlib-injector-yggdrasil");
public static class ExtraYggdrasilUrlsValidator extends ListConfigValidator.STRING {
@Override
public void verify(List<String> old, List<String> value) throws IllegalArgumentException {
org.leavesmc.leaves.profile.LeavesMinecraftSessionService.initExtraYggdrasilList(value);
}
}
}
@GlobalConfig("disable-method-profiler")
public boolean disableMethodProfiler = true;
@RemovedConfig(name = "no-chat-sign", category = {}, transform = true)
@GlobalConfig("no-chat-sign")
public boolean noChatSign = true;
@GlobalConfig("dont-respond-ping-before-start-fully")
public boolean dontRespondPingBeforeStart = true;
@GlobalConfig(value = "server-lang", lock = true, validator = ServerLangValidator.class)
public String serverLang = "en_us";
private static class ServerLangValidator extends StringConfigValidator {
private static final List<String> supportLang = List.of("en_us", "zh_cn");
@Override
public void verify(String old, String value) throws IllegalArgumentException {
if (!supportLang.contains(value)) {
throw new IllegalArgumentException("lang " + value + " not supported");
}
}
@Override
public List<String> valueSuggest() {
return supportLang;
}
}
@GlobalConfig(value = "server-mod-name", validator = StringConfigValidator.class)
public String serverModName = "Leaves";
@GlobalConfig("bstats-privacy-mode")
public boolean bstatsPrivacyMode = false;
@GlobalConfig("force-minecraft-command")
public boolean forceMinecraftCommand = false;
@GlobalConfig("leaves-packet-event")
public boolean leavesPacketEvent = true;
}
public static RegionConfig region = new RegionConfig();
@GlobalConfigCategory("region")
public static class RegionConfig {
@GlobalConfig(value = "format", lock = true, validator = RegionFormatValidator.class)
public org.leavesmc.leaves.region.RegionFileFormat format = org.leavesmc.leaves.region.RegionFileFormat.ANVIL;
private static class RegionFormatValidator extends EnumConfigValidator<org.leavesmc.leaves.region.RegionFileFormat> {
@Override
public void verify(RegionFileFormat old, RegionFileFormat value) throws IllegalArgumentException {
org.leavesmc.leaves.region.IRegionFileFactory.initFirstRegion(value);
}
}
public LinearConfig linear = new LinearConfig();
@GlobalConfigCategory("linear")
public static class LinearConfig {
@GlobalConfig(value = "version", lock = true, validator = LinearVersionValidator.class)
public org.leavesmc.leaves.region.linear.LinearVersion version = org.leavesmc.leaves.region.linear.LinearVersion.V2;
private static class LinearVersionValidator extends EnumConfigValidator<org.leavesmc.leaves.region.linear.LinearVersion> {
}
@GlobalConfig(value = "auto-convert-anvil-to-linear", lock = true)
public boolean autoConvertAnvilToLinear = false;
@GlobalConfig(value = "flush-max-threads", lock = true, validator = IntConfigValidator.class)
public int flushThreads = 6;
public int getLinearFlushThreads() {
if (flushThreads <= 0) {
return Math.max(Runtime.getRuntime().availableProcessors() + flushThreads, 1);
} else {
return flushThreads;
}
}
@GlobalConfig(value = "flush-delay-ms", lock = true, validator = IntConfigValidator.class)
public int flushDelayMs = 100;
@GlobalConfig(value = "use-virtual-thread", lock = true)
public boolean useVirtualThread = true;
@GlobalConfig(value = "compression-level", lock = true, validator = LinearCompressValidator.class)
public int compressionLevel = 1;
private static class LinearCompressValidator extends IntConfigValidator {
@Override
public void verify(Integer old, Integer value) throws IllegalArgumentException {
if (value < 1 || value > 22) {
throw new IllegalArgumentException("linear.compression-level need between 1 and 22");
}
}
}
@RemovedConfig(name = "flush-frequency", category = {"region", "linear"})
@RemovedConfig(name = "crash-on-broken-symlink", category = {"region", "linear"})
private final boolean linearCrashOnBrokenSymlink = true;
}
}
public static FixConfig fix = new FixConfig();
@GlobalConfigCategory("fix")
public static class FixConfig {
@GlobalConfig("vanilla-hopper")
public boolean vanillaHopper = false;
@RemovedConfig(name = "spigot-EndPlatform-destroy", category = "fix")
private final boolean spigotEndPlatformDestroy = false;
}
}

View File

@@ -0,0 +1,24 @@
package org.leavesmc.leaves;
import org.bukkit.Bukkit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LeavesLogger extends Logger {
public static final LeavesLogger LOGGER = new LeavesLogger();
private LeavesLogger() {
super("Leaves", null);
setParent(Bukkit.getLogger());
setLevel(Level.ALL);
}
public void severe(String msg, Exception exception) {
this.log(Level.SEVERE, msg, exception);
}
public void warning(String msg, Exception exception) {
this.log(Level.WARNING, msg, exception);
}
}

View File

@@ -0,0 +1,544 @@
package org.leavesmc.leaves.bot;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.bukkit.generator.WorldInfo;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.LeavesLogger;
import org.leavesmc.leaves.bot.agent.Actions;
import org.leavesmc.leaves.bot.agent.BotAction;
import org.leavesmc.leaves.bot.agent.BotConfig;
import org.leavesmc.leaves.bot.agent.Configs;
import org.leavesmc.leaves.bot.agent.actions.CraftCustomBotAction;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.LeavesCommandUtil;
import org.leavesmc.leaves.entity.Bot;
import org.leavesmc.leaves.event.bot.BotActionStopEvent;
import org.leavesmc.leaves.event.bot.BotConfigModifyEvent;
import org.leavesmc.leaves.event.bot.BotCreateEvent;
import org.leavesmc.leaves.event.bot.BotRemoveEvent;
import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
import java.util.*;
import static net.kyori.adventure.text.Component.text;
public class BotCommand extends Command {
private final Component unknownMessage;
public BotCommand(String name) {
super(name);
this.description = "FakePlayer Command";
this.usageMessage = "/bot [create | remove | action | list | config]";
this.unknownMessage = text("Usage: " + usageMessage, NamedTextColor.RED);
this.setPermission("bukkit.command.bot");
final PluginManager pluginManager = Bukkit.getServer().getPluginManager();
if (pluginManager.getPermission("bukkit.command.bot") == null) {
pluginManager.addPermission(new Permission("bukkit.command.bot", PermissionDefault.OP));
}
}
@Override
public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, String @NotNull [] args, Location location) throws IllegalArgumentException {
List<String> list = new ArrayList<>();
BotList botList = BotList.INSTANCE;
if (args.length <= 1) {
list.add("create");
list.add("remove");
if (LeavesConfig.modify.fakeplayer.canUseAction) {
list.add("action");
}
if (LeavesConfig.modify.fakeplayer.canModifyConfig) {
list.add("config");
}
if (LeavesConfig.modify.fakeplayer.canManualSaveAndLoad) {
list.add("save");
list.add("load");
}
list.add("list");
}
if (args.length == 2) {
switch (args[0]) {
case "create" -> list.add("<BotName>");
case "remove", "action", "config", "save" -> list.addAll(botList.bots.stream().map(e -> e.getName().getString()).toList());
case "list" -> list.addAll(Bukkit.getWorlds().stream().map(WorldInfo::getName).toList());
case "load" -> list.addAll(botList.getSavedBotList().getAllKeys());
}
}
if (args.length == 3) {
switch (args[0]) {
case "action" -> {
list.add("list");
list.add("stop");
list.addAll(Actions.getNames());
}
case "create" -> list.add("<BotSkinName>");
case "config" -> list.addAll(acceptConfig);
case "remove" -> list.addAll(List.of("cancel", "[hour]"));
}
}
if (args[0].equals("remove") && args.length >= 3) {
if (!Objects.equals(args[3], "cancel")) {
switch (args.length) {
case 4 -> list.add("[minute]");
case 5 -> list.add("[second]");
}
}
}
if (args.length >= 4 && args[0].equals("action")) {
ServerBot bot = botList.getBotByName(args[1]);
if (bot == null) {
return Collections.singletonList("<" + args[1] + " not found>");
}
if (args[2].equals("stop")) {
list.add("all");
for (int i = 0; i < bot.getBotActions().size(); i++) {
list.add(String.valueOf(i));
}
} else {
BotAction<?> action = Actions.getForName(args[2]);
if (action != null) {
list.addAll(action.getArgument().tabComplete(args.length - 4));
}
}
}
if (args.length >= 4 && args[0].equals("config")) {
Configs<?> config = Configs.getConfig(args[2]);
if (config != null) {
list.addAll(config.config.getArgument().tabComplete(args.length - 4));
}
}
return LeavesCommandUtil.getListMatchingLast(sender, args, list, "bukkit.command.bot.", "bukkit.command.bot");
}
@Override
public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, String[] args) {
if (!testPermission(sender) || !LeavesConfig.modify.fakeplayer.enable) return true;
if (args.length == 0) {
sender.sendMessage(unknownMessage);
return false;
}
switch (args[0]) {
case "create" -> this.onCreate(sender, args);
case "remove" -> this.onRemove(sender, args);
case "action" -> this.onAction(sender, args);
case "config" -> this.onConfig(sender, args);
case "list" -> this.onList(sender, args);
case "save" -> this.onSave(sender, args);
case "load" -> this.onLoad(sender, args);
default -> {
sender.sendMessage(unknownMessage);
return false;
}
}
return true;
}
private void onCreate(CommandSender sender, String @NotNull [] args) {
if (args.length < 2) {
sender.sendMessage(text("Use /bot create <name> [skin_name] to create a fakeplayer", NamedTextColor.RED));
return;
}
String botName = args[1];
if (this.canCreate(sender, botName)) {
BotCreateState.Builder builder = BotCreateState.builder(botName, Bukkit.getWorlds().getFirst().getSpawnLocation()).createReason(BotCreateEvent.CreateReason.COMMAND).creator(sender);
if (args.length >= 3) {
builder.skinName(args[2]);
}
if (sender instanceof Player player) {
builder.location(player.getLocation());
} else if (sender instanceof ConsoleCommandSender) {
if (args.length >= 7) {
try {
World world = Bukkit.getWorld(args[3]);
double x = Double.parseDouble(args[4]);
double y = Double.parseDouble(args[5]);
double z = Double.parseDouble(args[6]);
if (world != null) {
builder.location(new Location(world, x, y, z));
}
} catch (Exception e) {
LeavesLogger.LOGGER.warning("Can't build location", e);
}
}
}
builder.spawnWithSkin(null);
}
}
private boolean canCreate(CommandSender sender, @NotNull String name) {
BotList botList = BotList.INSTANCE;
if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) {
sender.sendMessage(text("This name is illegal", NamedTextColor.RED));
return false;
}
if (Bukkit.getPlayerExact(name) != null || botList.getBotByName(name) != null) {
sender.sendMessage(text("This player is in server", NamedTextColor.RED));
return false;
}
if (LeavesConfig.modify.fakeplayer.unableNames.contains(name)) {
sender.sendMessage(text("This name is not allowed", NamedTextColor.RED));
return false;
}
if (botList.bots.size() >= LeavesConfig.modify.fakeplayer.limit) {
sender.sendMessage(text("Fakeplayer limit is full", NamedTextColor.RED));
return false;
}
return true;
}
private void onRemove(CommandSender sender, String @NotNull [] args) {
if (args.length < 2 || args.length > 5) {
sender.sendMessage(text("Use /bot remove <name> [hour] [minute] [second] to remove a fakeplayer", NamedTextColor.RED));
return;
}
BotList botList = BotList.INSTANCE;
ServerBot bot = botList.getBotByName(args[1]);
if (bot == null) {
sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED));
return;
}
if (args.length > 2) {
if (args[2].equals("cancel")) {
if (bot.removeTaskId == -1) {
sender.sendMessage(text("This fakeplayer is not scheduled to be removed", NamedTextColor.RED));
return;
}
Bukkit.getScheduler().cancelTask(bot.removeTaskId);
bot.removeTaskId = -1;
sender.sendMessage(text("Remove cancel"));
return;
}
long time = 0;
int h; // Preventing out-of-range
long s = 0;
long m = 0;
try {
h = Integer.parseInt(args[2]);
if (h < 0) {
throw new NumberFormatException();
}
time += ((long) h) * 3600 * 20;
if (args.length > 3) {
m = Long.parseLong(args[3]);
if (m > 59 || m < 0) {
throw new NumberFormatException();
}
time += m * 60 * 20;
}
if (args.length > 4) {
s = Long.parseLong(args[4]);
if (s > 59 || s < 0) {
throw new NumberFormatException();
}
time += s * 20;
}
} catch (NumberFormatException e) {
sender.sendMessage(text("This fakeplayer is not scheduled to be removed", NamedTextColor.RED));
return;
}
boolean isReschedule = bot.removeTaskId != -1;
if (isReschedule) {
Bukkit.getScheduler().cancelTask(bot.removeTaskId);
}
bot.removeTaskId = Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> {
bot.removeTaskId = -1;
botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false);
}, time).getTaskId();
sender.sendMessage("This fakeplayer will be removed in " + h + "h " + m + "m " + s + "s" + (isReschedule ? " (rescheduled)" : ""));
return;
}
botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, false);
}
private void onAction(CommandSender sender, String @NotNull [] args) {
if (!LeavesConfig.modify.fakeplayer.canUseAction) {
return;
}
if (args.length < 3) {
sender.sendMessage(text("Use /bot action <name> <action> to make fakeplayer do action", NamedTextColor.RED));
return;
}
ServerBot bot = BotList.INSTANCE.getBotByName(args[1]);
if (bot == null) {
sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED));
return;
}
if (args[2].equals("list")) {
sender.sendMessage(bot.getScoreboardName() + "'s action list:");
for (int i = 0; i < bot.getBotActions().size(); i++) {
sender.sendMessage(i + " " + bot.getBotActions().get(i).getName());
}
return;
}
if (args[2].equals("stop")) {
if (args.length < 4) {
sender.sendMessage(text("Invalid index", NamedTextColor.RED));
return;
}
String index = args[3];
if (index.equals("all")) {
Set<BotAction<?>> forRemoval = new HashSet<>();
for (int i = 0; i < bot.getBotActions().size(); i++) {
BotAction<?> action = bot.getBotActions().get(i);
BotActionStopEvent event = new BotActionStopEvent(
bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender
);
event.callEvent();
if (!event.isCancelled()) {
forRemoval.add(action);
}
}
bot.getBotActions().removeAll(forRemoval);
sender.sendMessage(bot.getScoreboardName() + "'s action list cleared.");
} else {
try {
int i = Integer.parseInt(index);
if (i < 0 || i >= bot.getBotActions().size()) {
sender.sendMessage(text("Invalid index", NamedTextColor.RED));
return;
}
BotAction<?> action = bot.getBotActions().get(i);
BotActionStopEvent event = new BotActionStopEvent(
bot.getBukkitEntity(), action.getName(), action.getUUID(), BotActionStopEvent.Reason.COMMAND, sender
);
event.callEvent();
if (!event.isCancelled()) {
bot.getBotActions().remove(i);
sender.sendMessage(bot.getScoreboardName() + "'s " + action.getName() + " stopped.");
}
} catch (NumberFormatException e) {
sender.sendMessage(text("Invalid index", NamedTextColor.RED));
}
}
return;
}
BotAction<?> action = Actions.getForName(args[2]);
if (action == null) {
sender.sendMessage(text("Invalid action", NamedTextColor.RED));
return;
}
CraftPlayer player;
if (sender instanceof CraftPlayer) {
player = (CraftPlayer) sender;
} else {
player = bot.getBukkitEntity();
}
String[] realArgs = new String[args.length - 3];
if (realArgs.length != 0) {
System.arraycopy(args, 3, realArgs, 0, realArgs.length);
}
BotAction<?> newAction;
try {
if (action instanceof CraftCustomBotAction customBotAction) {
newAction = customBotAction.createCraft(player, realArgs);
} else {
newAction = action.create();
newAction.loadCommand(player.getHandle(), action.getArgument().parse(0, realArgs));
}
} catch (IllegalArgumentException e) {
sender.sendMessage(text("Action create error, please check your arguments, " + e.getMessage(), NamedTextColor.RED));
return;
}
if (newAction == null) {
return;
}
if (bot.addBotAction(newAction, sender)) {
sender.sendMessage("Action " + action.getName() + " has been issued to " + bot.getName().getString());
}
}
private static final List<String> acceptConfig = Configs.getConfigs().stream().map(config -> config.config.getName()).toList();
private void onConfig(CommandSender sender, String @NotNull [] args) {
if (!LeavesConfig.modify.fakeplayer.canModifyConfig) {
return;
}
if (args.length < 3) {
sender.sendMessage(text("Use /bot config <name> <config> to modify fakeplayer's config", NamedTextColor.RED));
return;
}
ServerBot bot = BotList.INSTANCE.getBotByName(args[1]);
if (bot == null) {
sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED));
return;
}
if (!acceptConfig.contains(args[2])) {
sender.sendMessage(text("This config is not accept", NamedTextColor.RED));
return;
}
BotConfig<?> config = Objects.requireNonNull(Configs.getConfig(args[2])).config;
if (args.length < 4) {
config.getMessage().forEach(sender::sendMessage);
} else {
String[] realArgs = new String[args.length - 3];
System.arraycopy(args, 3, realArgs, 0, realArgs.length);
BotConfigModifyEvent event = new BotConfigModifyEvent(bot.getBukkitEntity(), config.getName(), realArgs, sender);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
CommandArgumentResult result = config.getArgument().parse(0, realArgs);
try {
config.setValue(result);
config.getChangeMessage().forEach(sender::sendMessage);
} catch (IllegalArgumentException e) {
sender.sendMessage(text(e.getMessage(), NamedTextColor.RED));
}
}
}
private void onSave(CommandSender sender, String @NotNull [] args) {
if (!LeavesConfig.modify.fakeplayer.canManualSaveAndLoad) {
return;
}
if (args.length < 2) {
sender.sendMessage(text("Use /bot save <name> to save a fakeplayer", NamedTextColor.RED));
return;
}
BotList botList = BotList.INSTANCE;
ServerBot bot = botList.getBotByName(args[1]);
if (bot == null) {
sender.sendMessage(text("This fakeplayer is not in server", NamedTextColor.RED));
return;
}
if (botList.removeBot(bot, BotRemoveEvent.RemoveReason.COMMAND, sender, true)) {
sender.sendMessage(bot.getScoreboardName() + " saved to " + bot.createState.realName());
}
}
private void onLoad(CommandSender sender, String @NotNull [] args) {
if (!LeavesConfig.modify.fakeplayer.canManualSaveAndLoad) {
return;
}
if (args.length < 2) {
sender.sendMessage(text("Use /bot save <name> to save a fakeplayer", NamedTextColor.RED));
return;
}
String realName = args[1];
BotList botList = BotList.INSTANCE;
if (!botList.getSavedBotList().contains(realName)) {
sender.sendMessage(text("This fakeplayer is not saved", NamedTextColor.RED));
return;
}
if (botList.loadNewBot(realName) == null) {
sender.sendMessage(text("Can't load bot, please check", NamedTextColor.RED));
}
}
private void onList(CommandSender sender, String @NotNull [] args) {
BotList botList = BotList.INSTANCE;
if (args.length < 2) {
Map<World, List<String>> botMap = new HashMap<>();
for (World world : Bukkit.getWorlds()) {
botMap.put(world, new ArrayList<>());
}
for (ServerBot bot : botList.bots) {
Bot bukkitBot = bot.getBukkitEntity();
botMap.get(bukkitBot.getWorld()).add(bukkitBot.getName());
}
sender.sendMessage("Total number: (" + botList.bots.size() + "/" + LeavesConfig.modify.fakeplayer.limit + ")");
for (World world : botMap.keySet()) {
sender.sendMessage(world.getName() + "(" + botMap.get(world).size() + "): " + formatPlayerNameList(botMap.get(world)));
}
} else {
World world = Bukkit.getWorld(args[1]);
if (world == null) {
sender.sendMessage(text("Unknown world", NamedTextColor.RED));
return;
}
List<String> snowBotList = new ArrayList<>();
for (ServerBot bot : botList.bots) {
Bot bukkitBot = bot.getBukkitEntity();
if (bukkitBot.getWorld() == world) {
snowBotList.add(bukkitBot.getName());
}
}
sender.sendMessage(world.getName() + "(" + botList.bots.size() + "): " + formatPlayerNameList(snowBotList));
}
}
@NotNull
private static String formatPlayerNameList(@NotNull List<String> list) {
if (list.isEmpty()) {
return "";
}
String string = list.toString();
return string.substring(1, string.length() - 1);
}
}

View File

@@ -0,0 +1,120 @@
package org.leavesmc.leaves.bot;
import net.minecraft.server.MinecraftServer;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.entity.Bot;
import org.leavesmc.leaves.entity.BotCreator;
import org.leavesmc.leaves.entity.CraftBot;
import org.leavesmc.leaves.event.bot.BotCreateEvent;
import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
import java.util.Objects;
import java.util.function.Consumer;
public record BotCreateState(String realName, String name, String skinName, String[] skin, Location location, BotCreateEvent.CreateReason createReason, CommandSender creator) {
private static final MinecraftServer server = MinecraftServer.getServer();
public ServerBot createNow() {
return server.getBotList().createNewBot(this);
}
@NotNull
public static Builder builder(@NotNull String realName, @Nullable Location location) {
return new Builder(realName, location);
}
public static class Builder implements BotCreator {
private final String realName;
private String name;
private Location location;
private String skinName;
private String[] skin;
private BotCreateEvent.CreateReason createReason;
private CommandSender creator;
private Builder(@NotNull String realName, @Nullable Location location) {
Objects.requireNonNull(realName);
this.realName = realName;
this.location = location;
this.name = LeavesConfig.modify.fakeplayer.prefix + realName + LeavesConfig.modify.fakeplayer.suffix;
this.skinName = this.realName;
this.skin = null;
this.createReason = BotCreateEvent.CreateReason.UNKNOWN;
this.creator = null;
}
public Builder name(@NotNull String name) {
Objects.requireNonNull(name);
this.name = name;
return this;
}
public Builder skinName(@Nullable String skinName) {
this.skinName = skinName;
return this;
}
public Builder skin(@Nullable String[] skin) {
this.skin = skin;
return this;
}
public Builder mojangAPISkin() {
if (this.skinName != null) {
this.skin = MojangAPI.getSkin(this.skinName);
}
return this;
}
public Builder location(@NotNull Location location) {
this.location = location;
return this;
}
public Builder createReason(@NotNull BotCreateEvent.CreateReason createReason) {
Objects.requireNonNull(createReason);
this.createReason = createReason;
return this;
}
public Builder creator(CommandSender creator) {
this.creator = creator;
return this;
}
public BotCreateState build() {
return new BotCreateState(realName, name, skinName, skin, location, createReason, creator);
}
public void spawnWithSkin(Consumer<Bot> consumer) {
Bukkit.getScheduler().runTaskAsynchronously(MinecraftInternalPlugin.INSTANCE, () -> {
this.mojangAPISkin();
Bukkit.getScheduler().runTask(MinecraftInternalPlugin.INSTANCE, () -> {
CraftBot bot = this.spawn();
if (bot != null && consumer != null) {
consumer.accept(bot);
}
});
});
}
@Nullable
public CraftBot spawn() {
Objects.requireNonNull(this.location);
ServerBot bot = this.build().createNow();
return bot != null ? bot.getBukkitEntity() : null;
}
}
}

View File

@@ -0,0 +1,121 @@
package org.leavesmc.leaves.bot;
import com.mojang.logging.LogUtils;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtAccounter;
import net.minecraft.nbt.NbtIo;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.storage.LevelResource;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
public class BotDataStorage implements IPlayerDataStorage {
private static final LevelResource BOT_DATA_DIR = new LevelResource("fakeplayerdata");
private static final LevelResource BOT_LIST_FILE = new LevelResource("fakeplayer.dat");
private static final Logger LOGGER = LogUtils.getLogger();
private final File botDir;
private final File botListFile;
private CompoundTag savedBotList;
public BotDataStorage(LevelStorageSource.@NotNull LevelStorageAccess session) {
this.botDir = session.getLevelPath(BOT_DATA_DIR).toFile();
this.botListFile = session.getLevelPath(BOT_LIST_FILE).toFile();
this.botDir.mkdirs();
this.savedBotList = new CompoundTag();
if (this.botListFile.exists() && this.botListFile.isFile()) {
try {
Optional.of(NbtIo.readCompressed(this.botListFile.toPath(), NbtAccounter.unlimitedHeap())).ifPresent(tag -> this.savedBotList = tag);
} catch (Exception exception) {
BotDataStorage.LOGGER.warn("Failed to load player data list");
}
}
}
@Override
public void save(Player player) {
boolean flag = true;
try {
CompoundTag nbt = player.saveWithoutId(new CompoundTag());
File file = new File(this.botDir, player.getStringUUID() + ".dat");
if (file.exists() && file.isFile()) {
if (!file.delete()) {
throw new IOException("Failed to delete file: " + file);
}
}
if (!file.createNewFile()) {
throw new IOException("Failed to create nbt file: " + file);
}
NbtIo.writeCompressed(nbt, file.toPath());
} catch (Exception exception) {
BotDataStorage.LOGGER.warn("Failed to save fakeplayer data for {}", player.getScoreboardName(), exception);
flag = false;
}
if (flag && player instanceof ServerBot bot) {
CompoundTag nbt = new CompoundTag();
nbt.putString("name", bot.createState.name());
nbt.putUUID("uuid", bot.getUUID());
nbt.putBoolean("resume", bot.resume);
this.savedBotList.put(bot.createState.realName(), nbt);
this.saveBotList();
}
}
@Override
public Optional<CompoundTag> load(Player player) {
return this.load(player.getScoreboardName(), player.getStringUUID()).map((nbt) -> {
player.load(nbt);
return nbt;
});
}
private Optional<CompoundTag> load(String name, String uuid) {
File file = new File(this.botDir, uuid + ".dat");
if (file.exists() && file.isFile()) {
try {
Optional<CompoundTag> optional = Optional.of(NbtIo.readCompressed(file.toPath(), NbtAccounter.unlimitedHeap()));
if (!file.delete()) {
throw new IOException("Failed to delete fakeplayer data");
}
this.savedBotList.remove(name);
this.saveBotList();
return optional;
} catch (Exception exception) {
BotDataStorage.LOGGER.warn("Failed to load fakeplayer data for {}", name);
}
}
return Optional.empty();
}
private void saveBotList() {
try {
if (this.botListFile.exists() && this.botListFile.isFile()) {
if (!this.botListFile.delete()) {
throw new IOException("Failed to delete file: " + this.botListFile);
}
}
if (!this.botListFile.createNewFile()) {
throw new IOException("Failed to create nbt file: " + this.botListFile);
}
NbtIo.writeCompressed(this.savedBotList, this.botListFile.toPath());
} catch (Exception exception) {
BotDataStorage.LOGGER.warn("Failed to save player data list");
}
}
public CompoundTag getSavedBotList() {
return savedBotList;
}
}

View File

@@ -0,0 +1,191 @@
package org.leavesmc.leaves.bot;
import com.google.common.collect.ImmutableList;
import com.mojang.datafixers.util.Pair;
import net.minecraft.core.NonNullList;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.CustomData;
import javax.annotation.Nonnull;
import java.util.List;
// Power by gugle-carpet-addition(https://github.com/Gu-ZT/gugle-carpet-addition)
public class BotInventoryContainer extends SimpleContainer {
public final NonNullList<ItemStack> items;
public final NonNullList<ItemStack> armor;
public final NonNullList<ItemStack> offhand;
private final List<NonNullList<ItemStack>> compartments;
private final NonNullList<ItemStack> buttons = NonNullList.withSize(13, ItemStack.EMPTY);
private final ServerBot player;
public BotInventoryContainer(ServerBot player) {
this.player = player;
this.items = this.player.getInventory().items;
this.armor = this.player.getInventory().armor;
this.offhand = this.player.getInventory().offhand;
this.compartments = ImmutableList.of(this.items, this.armor, this.offhand, this.buttons);
createButton();
}
@Override
public int getContainerSize() {
return this.items.size() + this.armor.size() + this.offhand.size() + this.buttons.size();
}
@Override
public boolean isEmpty() {
for (ItemStack itemStack : this.items) {
if (itemStack.isEmpty()) {
continue;
}
return false;
}
for (ItemStack itemStack : this.armor) {
if (itemStack.isEmpty()) {
continue;
}
return false;
}
for (ItemStack itemStack : this.offhand) {
if (itemStack.isEmpty()) {
continue;
}
return false;
}
return true;
}
@Override
@Nonnull
public ItemStack getItem(int slot) {
Pair<NonNullList<ItemStack>, Integer> pair = getItemSlot(slot);
if (pair != null) {
return pair.getFirst().get(pair.getSecond());
} else {
return ItemStack.EMPTY;
}
}
public Pair<NonNullList<ItemStack>, Integer> getItemSlot(int slot) {
switch (slot) {
case 0 -> {
return new Pair<>(buttons, 0);
}
case 1, 2, 3, 4 -> {
return new Pair<>(armor, 4 - slot);
}
case 5, 6 -> {
return new Pair<>(buttons, slot - 4);
}
case 7 -> {
return new Pair<>(offhand, 0);
}
case 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 -> {
return new Pair<>(buttons, slot - 5);
}
case 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44 -> {
return new Pair<>(items, slot - 9);
}
case 45, 46, 47, 48, 49, 50, 51, 52, 53 -> {
return new Pair<>(items, slot - 45);
}
default -> {
return null;
}
}
}
@Override
@Nonnull
public ItemStack removeItem(int slot, int amount) {
Pair<NonNullList<ItemStack>, Integer> pair = getItemSlot(slot);
NonNullList<ItemStack> list = null;
ItemStack itemStack = ItemStack.EMPTY;
if (pair != null) {
list = pair.getFirst();
slot = pair.getSecond();
}
if (list != null && !list.get(slot).isEmpty()) {
itemStack = ContainerHelper.removeItem(list, slot, amount);
player.detectEquipmentUpdatesPublic();
}
return itemStack;
}
@Override
@Nonnull
public ItemStack removeItemNoUpdate(int slot) {
Pair<NonNullList<ItemStack>, Integer> pair = getItemSlot(slot);
NonNullList<ItemStack> list = null;
if (pair != null) {
list = pair.getFirst();
slot = pair.getSecond();
}
if (list != null && !list.get(slot).isEmpty()) {
ItemStack itemStack = list.get(slot);
list.set(slot, ItemStack.EMPTY);
return itemStack;
}
return ItemStack.EMPTY;
}
@Override
public void setItem(int slot, @Nonnull ItemStack stack) {
Pair<NonNullList<ItemStack>, Integer> pair = getItemSlot(slot);
NonNullList<ItemStack> list = null;
if (pair != null) {
list = pair.getFirst();
slot = pair.getSecond();
}
if (list != null) {
list.set(slot, stack);
player.detectEquipmentUpdatesPublic();
}
}
@Override
public void setChanged() {
}
@Override
public boolean stillValid(@Nonnull Player player) {
if (this.player.isRemoved()) {
return false;
}
return !(player.distanceToSqr(this.player) > 64.0);
}
@Override
public void clearContent() {
for (List<ItemStack> list : this.compartments) {
list.clear();
}
}
private void createButton() {
CompoundTag customData = new CompoundTag();
customData.putBoolean("Leaves.Gui.Placeholder", true);
DataComponentPatch patch = DataComponentPatch.builder()
.set(DataComponents.CUSTOM_NAME, Component.empty())
.set(DataComponents.CUSTOM_DATA, CustomData.of(customData))
.build();
for (int i = 0; i < 13; i++) {
ItemStack button = new ItemStack(Items.STRUCTURE_VOID);
button.applyComponents(patch);
buttons.set(i, button);
}
}
}

View File

@@ -0,0 +1,297 @@
package org.leavesmc.leaves.bot;
import com.google.common.collect.Maps;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.logging.LogUtils;
import io.papermc.paper.adventure.PaperAdventure;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
import net.minecraft.resources.ResourceKey;
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.level.Level;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.CraftWorld;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.event.bot.BotCreateEvent;
import org.leavesmc.leaves.event.bot.BotJoinEvent;
import org.leavesmc.leaves.event.bot.BotLoadEvent;
import org.leavesmc.leaves.event.bot.BotRemoveEvent;
import org.leavesmc.leaves.event.bot.BotSpawnLocationEvent;
import org.slf4j.Logger;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
public class BotList {
public static BotList INSTANCE;
private static final Logger LOGGER = LogUtils.getLogger();
private final MinecraftServer server;
public final List<ServerBot> bots = new CopyOnWriteArrayList<>();
private final BotDataStorage dataStorage;
private final Map<UUID, ServerBot> botsByUUID = Maps.newHashMap();
private final Map<String, ServerBot> botsByName = Maps.newHashMap();
public BotList(MinecraftServer server) {
this.server = server;
this.dataStorage = new BotDataStorage(server.storageSource);
INSTANCE = this;
}
public ServerBot createNewBot(BotCreateState state) {
BotCreateEvent event = new BotCreateEvent(state.name(), state.skinName(), state.location(), state.createReason(), state.creator());
event.setCancelled(!isCreateLegal(state.name()));
this.server.server.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return null;
}
Location location = event.getCreateLocation();
ServerLevel world = ((CraftWorld) location.getWorld()).getHandle();
CustomGameProfile profile = new CustomGameProfile(BotUtil.getBotUUID(state), state.name(), state.skin());
ServerBot bot = new ServerBot(this.server, world, profile);
bot.createState = state;
if (event.getCreator() instanceof org.bukkit.entity.Player player) {
bot.createPlayer = player.getUniqueId();
}
return this.placeNewBot(bot, world, location, null);
}
public ServerBot loadNewBot(String realName) {
return this.loadNewBot(realName, this.dataStorage);
}
public ServerBot loadNewBot(String realName, IPlayerDataStorage playerIO) {
UUID uuid = BotUtil.getBotUUID(realName);
BotLoadEvent event = new BotLoadEvent(realName, uuid);
this.server.server.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return null;
}
ServerBot bot = new ServerBot(this.server, this.server.getLevel(Level.OVERWORLD), new GameProfile(uuid, realName));
bot.connection = new ServerBotPacketListenerImpl(this.server, bot);
Optional<CompoundTag> optional = playerIO.load(bot);
if (optional.isEmpty()) {
return null;
}
ResourceKey<Level> resourcekey = null;
if (optional.get().contains("WorldUUIDMost") && optional.get().contains("WorldUUIDLeast")) {
org.bukkit.World bWorld = Bukkit.getServer().getWorld(new UUID(optional.get().getLong("WorldUUIDMost"), optional.get().getLong("WorldUUIDLeast")));
if (bWorld != null) {
resourcekey = ((CraftWorld) bWorld).getHandle().dimension();
}
}
if (resourcekey == null) {
return null;
}
ServerLevel world = this.server.getLevel(resourcekey);
return this.placeNewBot(bot, world, bot.getLocation(), optional.get());
}
public ServerBot placeNewBot(ServerBot bot, ServerLevel world, Location location, @Nullable CompoundTag nbt) {
Optional<CompoundTag> optional = Optional.ofNullable(nbt);
bot.isRealPlayer = true;
bot.loginTime = System.currentTimeMillis();
bot.connection = new ServerBotPacketListenerImpl(this.server, bot);
bot.setServerLevel(world);
BotSpawnLocationEvent event = new BotSpawnLocationEvent(bot.getBukkitEntity(), location);
this.server.server.getPluginManager().callEvent(event);
location = event.getSpawnLocation();
bot.spawnIn(world);
bot.gameMode.setLevel(bot.serverLevel());
bot.setPosRaw(location.getX(), location.getY(), location.getZ());
bot.setRot(location.getYaw(), location.getPitch());
bot.connection.teleport(bot.getX(), bot.getY(), bot.getZ(), bot.getYRot(), bot.getXRot());
this.bots.add(bot);
this.botsByName.put(bot.getScoreboardName().toLowerCase(Locale.ROOT), bot);
this.botsByUUID.put(bot.getUUID(), bot);
bot.supressTrackerForLogin = true;
world.addNewPlayer(bot);
bot.loadAndSpawnEnderpearls(optional);
bot.loadAndSpawnParentVehicle(optional);
BotJoinEvent event1 = new BotJoinEvent(bot.getBukkitEntity(), PaperAdventure.asAdventure(Component.translatable("multiplayer.player.joined", bot.getDisplayName())).style(Style.style(NamedTextColor.YELLOW)));
this.server.server.getPluginManager().callEvent(event1);
net.kyori.adventure.text.Component joinMessage = event1.joinMessage();
if (joinMessage != null && !joinMessage.equals(net.kyori.adventure.text.Component.empty())) {
this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(joinMessage), false);
}
bot.renderAll();
bot.supressTrackerForLogin = false;
bot.serverLevel().getChunkSource().chunkMap.addEntity(bot);
BotList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", bot.getName().getString(), "Local", bot.getId(), bot.serverLevel().serverLevelData.getLevelName(), bot.getX(), bot.getY(), bot.getZ());
return bot;
}
public boolean removeBot(@NotNull ServerBot bot, @NotNull BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover, boolean saved) {
return this.removeBot(bot, reason, remover, saved, this.dataStorage);
}
public boolean removeBot(@NotNull ServerBot bot, @NotNull BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover, boolean saved, IPlayerDataStorage playerIO) {
BotRemoveEvent event = new BotRemoveEvent(bot.getBukkitEntity(), reason, remover, PaperAdventure.asAdventure(Component.translatable("multiplayer.player.left", bot.getDisplayName())).style(Style.style(NamedTextColor.YELLOW)), saved);
this.server.server.getPluginManager().callEvent(event);
if (event.isCancelled() && event.getReason() != BotRemoveEvent.RemoveReason.INTERNAL) {
return event.isCancelled();
}
if (bot.removeTaskId != -1) {
Bukkit.getScheduler().cancelTask(bot.removeTaskId);
bot.removeTaskId = -1;
}
if (this.server.isSameThread()) {
bot.doTick();
}
if (event.shouldSave()) {
playerIO.save(bot);
} else {
bot.dropAll();
}
if (bot.isPassenger()) {
Entity entity = bot.getRootVehicle();
if (entity.hasExactlyOnePlayerPassenger()) {
bot.stopRiding();
entity.getPassengersAndSelf().forEach((entity1) -> {
if (entity1 instanceof net.minecraft.world.entity.npc.AbstractVillager villager) {
final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer();
if (human != null) {
villager.setTradingPlayer(null);
}
}
entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER);
});
}
}
bot.unRide();
bot.serverLevel().removePlayerImmediately(bot, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
this.bots.remove(bot);
this.botsByName.remove(bot.getScoreboardName().toLowerCase(Locale.ROOT));
UUID uuid = bot.getUUID();
ServerBot bot1 = this.botsByUUID.get(uuid);
if (bot1 == bot) {
this.botsByUUID.remove(uuid);
}
bot.removeTab();
for (ServerPlayer player : bot.serverLevel().players()) {
if (!(player instanceof ServerBot) && !bot.needSendFakeData(player)) {
player.connection.send(new ClientboundRemoveEntitiesPacket(bot.getId()));
}
}
net.kyori.adventure.text.Component removeMessage = event.removeMessage();
if (removeMessage != null && !removeMessage.equals(net.kyori.adventure.text.Component.empty())) {
this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(removeMessage), false);
}
return true;
}
public void removeAll() {
for (ServerBot bot : this.bots) {
bot.resume = LeavesConfig.modify.fakeplayer.canResident;
this.removeBot(bot, BotRemoveEvent.RemoveReason.INTERNAL, null, LeavesConfig.modify.fakeplayer.canResident);
}
}
public void loadResume() {
if (LeavesConfig.modify.fakeplayer.enable && LeavesConfig.modify.fakeplayer.canResident) {
CompoundTag savedBotList = this.getSavedBotList().copy();
for (String realName : savedBotList.getAllKeys()) {
CompoundTag nbt = savedBotList.getCompound(realName);
if (nbt.getBoolean("resume")) {
this.loadNewBot(realName);
}
}
}
}
public void networkTick() {
this.bots.forEach(ServerBot::doTick);
}
@Nullable
public ServerBot getBot(@NotNull UUID uuid) {
return this.botsByUUID.get(uuid);
}
@Nullable
public ServerBot getBotByName(@NotNull String name) {
return this.botsByName.get(name.toLowerCase(Locale.ROOT));
}
public CompoundTag getSavedBotList() {
return this.dataStorage.getSavedBotList();
}
public boolean isCreateLegal(@NotNull String name) {
if (!name.matches("^[a-zA-Z0-9_]{4,16}$")) {
return false;
}
if (Bukkit.getPlayerExact(name) != null || this.getBotByName(name) != null) {
return false;
}
if (LeavesConfig.modify.fakeplayer.unableNames.contains(name)) {
return false;
}
return this.bots.size() < LeavesConfig.modify.fakeplayer.limit;
}
public static class CustomGameProfile extends GameProfile {
public CustomGameProfile(UUID uuid, String name, String[] skin) {
super(uuid, name);
this.setSkin(skin);
}
public void setSkin(String[] skin) {
if (skin != null) {
this.getProperties().put("textures", new Property("textures", skin[0], skin[1]));
}
}
}
}

View File

@@ -0,0 +1,36 @@
package org.leavesmc.leaves.bot;
import com.mojang.datafixers.DataFixer;
import net.minecraft.server.MinecraftServer;
import net.minecraft.stats.ServerStatsCounter;
import net.minecraft.stats.Stat;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.NotNull;
import java.io.File;
public class BotStatsCounter extends ServerStatsCounter {
private static final File UNKOWN_FILE = new File("BOT_STATS_REMOVE_THIS");
public BotStatsCounter(MinecraftServer server) {
super(server, UNKOWN_FILE);
}
@Override
public void save() {
}
@Override
public void setValue(@NotNull Player player, @NotNull Stat<?> stat, int value) {
}
@Override
public void parseLocal(@NotNull DataFixer dataFixer, @NotNull String json) {
}
@Override
public int getValue(@NotNull Stat<?> stat) {
return 0;
}
}

View File

@@ -0,0 +1,73 @@
package org.leavesmc.leaves.bot;
import com.google.common.base.Charsets;
import net.minecraft.core.NonNullList;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class BotUtil {
public static void replenishment(@NotNull ItemStack itemStack, NonNullList<ItemStack> itemStackList) {
int count = itemStack.getMaxStackSize() / 2;
if (itemStack.getCount() <= 8 && count > 8) {
for (ItemStack itemStack1 : itemStackList) {
if (itemStack1 == ItemStack.EMPTY || itemStack1 == itemStack) {
continue;
}
if (ItemStack.isSameItemSameComponents(itemStack1, itemStack)) {
if (itemStack1.getCount() > count) {
itemStack.setCount(itemStack.getCount() + count);
itemStack1.setCount(itemStack1.getCount() - count);
} else {
itemStack.setCount(itemStack.getCount() + itemStack1.getCount());
itemStack1.setCount(0);
}
break;
}
}
}
}
public static void replaceTool(@NotNull EquipmentSlot slot, @NotNull ServerBot bot) {
ItemStack itemStack = bot.getItemBySlot(slot);
for (int i = 0; i < 36; i++) {
ItemStack itemStack1 = bot.getInventory().getItem(i);
if (itemStack1 == ItemStack.EMPTY || itemStack1 == itemStack) {
continue;
}
if (itemStack1.getItem().getClass() == itemStack.getItem().getClass() && !isDamage(itemStack1, 10)) {
ItemStack itemStack2 = itemStack1.copy();
bot.getInventory().setItem(i, itemStack);
bot.setItemSlot(slot, itemStack2);
return;
}
}
for (int i = 0; i < 36; i++) {
ItemStack itemStack1 = bot.getInventory().getItem(i);
if (itemStack1 == ItemStack.EMPTY && itemStack1 != itemStack) {
bot.getInventory().setItem(i, itemStack);
bot.setItemSlot(slot, ItemStack.EMPTY);
return;
}
}
}
public static boolean isDamage(@NotNull ItemStack item, int minDamage) {
return item.isDamageableItem() && (item.getMaxDamage() - item.getDamageValue()) <= minDamage;
}
@NotNull
public static UUID getBotUUID(@NotNull BotCreateState state) {
return getBotUUID(state.realName());
}
public static UUID getBotUUID(@NotNull String realName) {
return UUID.nameUUIDFromBytes(("Fakeplayer:" + realName).getBytes(Charsets.UTF_8));
}
}

View File

@@ -0,0 +1,13 @@
package org.leavesmc.leaves.bot;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.player.Player;
import java.util.Optional;
public interface IPlayerDataStorage {
void save(Player player);
Optional<CompoundTag> load(Player player);
}

View File

@@ -0,0 +1,39 @@
package org.leavesmc.leaves.bot;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.leavesmc.leaves.LeavesConfig;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
public class MojangAPI {
private static final Map<String, String[]> CACHE = new HashMap<>();
public static String[] getSkin(String name) {
if (LeavesConfig.modify.fakeplayer.useSkinCache && CACHE.containsKey(name)) {
return CACHE.get(name);
}
String[] values = pullFromAPI(name);
CACHE.put(name, values);
return values;
}
// Laggggggggggggggggggggggggggggggggggggggggg
public static String[] pullFromAPI(String name) {
try {
String uuid = JsonParser.parseReader(new InputStreamReader(URI.create("https://api.mojang.com/users/profiles/minecraft/" + name).toURL().openStream()))
.getAsJsonObject().get("id").getAsString();
JsonObject property = JsonParser.parseReader(new InputStreamReader(URI.create("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false").toURL().openStream()))
.getAsJsonObject().get("properties").getAsJsonArray().get(0).getAsJsonObject();
return new String[]{property.get("value").getAsString(), property.get("signature").getAsString()};
} catch (IOException | IllegalStateException | IllegalArgumentException e) {
return null;
}
}
}

View File

@@ -0,0 +1,553 @@
package org.leavesmc.leaves.bot;
import com.google.common.collect.ImmutableMap;
import com.mojang.authlib.GameProfile;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.event.entity.EntityKnockbackEvent;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.BlockParticleOption;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerPlayerConnection;
import net.minecraft.stats.ServerStatsCounter;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.PositionMoveRotation;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.portal.TeleportTransition;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.LeavesLogger;
import org.leavesmc.leaves.bot.agent.Actions;
import org.leavesmc.leaves.bot.agent.BotAction;
import org.leavesmc.leaves.bot.agent.BotConfig;
import org.leavesmc.leaves.bot.agent.Configs;
import org.leavesmc.leaves.entity.CraftBot;
import org.leavesmc.leaves.event.bot.BotActionScheduleEvent;
import org.leavesmc.leaves.event.bot.BotCreateEvent;
import org.leavesmc.leaves.event.bot.BotDeathEvent;
import org.leavesmc.leaves.event.bot.BotInventoryOpenEvent;
import org.leavesmc.leaves.event.bot.BotRemoveEvent;
import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
import org.leavesmc.leaves.util.MathUtils;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Predicate;
// TODO test
public class ServerBot extends ServerPlayer {
private final Map<Configs<?>, BotConfig<?>> configs;
private final List<BotAction<?>> actions;
public boolean resume = false;
public BotCreateState createState;
public UUID createPlayer;
private final int tracingRange;
private final ServerStatsCounter stats;
private final BotInventoryContainer container;
public int notSleepTicks;
public int removeTaskId = -1;
private Vec3 knockback = Vec3.ZERO;
public ServerBot(MinecraftServer server, ServerLevel world, GameProfile profile) {
super(server, world, profile, ClientInformation.createDefault());
this.entityData.set(Player.DATA_PLAYER_MODE_CUSTOMISATION, (byte) -2);
this.gameMode = new ServerBotGameMode(this);
this.actions = new ArrayList<>();
ImmutableMap.Builder<Configs<?>, BotConfig<?>> configBuilder = ImmutableMap.builder();
for (Configs<?> config : Configs.getConfigs()) {
configBuilder.put(config, config.config.create(this));
}
this.configs = configBuilder.build();
this.stats = new BotStatsCounter(server);
this.container = new BotInventoryContainer(this);
this.tracingRange = world.spigotConfig.playerTrackingRange * world.spigotConfig.playerTrackingRange;
this.notSleepTicks = 0;
this.fauxSleeping = LeavesConfig.modify.fakeplayer.canSkipSleep;
}
public void sendPlayerInfo(ServerPlayer player) {
player.connection.send(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME), List.of(this)));
}
public boolean needSendFakeData(ServerPlayer player) {
return this.getConfigValue(Configs.ALWAYS_SEND_DATA) && (player.level() == this.level() && player.position().distanceToSqr(this.position()) > this.tracingRange);
}
public void sendFakeDataIfNeed(ServerPlayer player, boolean login) {
if (needSendFakeData(player)) {
this.sendFakeData(player.connection, login);
}
}
public void sendFakeData(ServerPlayerConnection playerConnection, boolean login) {
ChunkMap.TrackedEntity entityTracker = ((ServerLevel) this.level()).getChunkSource().chunkMap.entityMap.get(this.getId());
if (entityTracker == null) {
LeavesLogger.LOGGER.warning("Fakeplayer cant get entity tracker for " + this.getId());
return;
}
playerConnection.send(this.getAddEntityPacket(entityTracker.serverEntity));
if (login) {
Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> playerConnection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f))), 10);
} else {
playerConnection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f)));
}
}
public void renderAll() {
this.server.getPlayerList().getPlayers().forEach(
player -> {
this.sendPlayerInfo(player);
this.sendFakeDataIfNeed(player, false);
}
);
}
private void sendPacket(Packet<?> packet) {
this.server.getPlayerList().getPlayers().forEach(player -> player.connection.send(packet));
}
@Override
public void die(@NotNull DamageSource damageSource) {
boolean flag = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES);
Component defaultMessage = this.getCombatTracker().getDeathMessage();
BotDeathEvent event = new BotDeathEvent(this.getBukkitEntity(), PaperAdventure.asAdventure(defaultMessage), flag);
this.server.server.getPluginManager().callEvent(event);
if (event.isCancelled()) {
if (this.getHealth() <= 0) {
this.setHealth(0.1f);
}
return;
}
this.gameEvent(GameEvent.ENTITY_DIE);
net.kyori.adventure.text.Component deathMessage = event.deathMessage();
if (event.isSendDeathMessage() && deathMessage != null && !deathMessage.equals(net.kyori.adventure.text.Component.empty())) {
this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(deathMessage), false);
}
this.server.getBotList().removeBot(this, BotRemoveEvent.RemoveReason.DEATH, null, false);
}
public void removeTab() {
this.sendPacket(new ClientboundPlayerInfoRemovePacket(List.of(this.getUUID())));
}
@Override
public @Nullable ServerBot teleport(@NotNull TeleportTransition teleportTarget) {
if (this.isSleeping() || this.isRemoved()) {
return null;
}
if (teleportTarget.newLevel().dimension() != this.serverLevel().dimension()) {
return null;
} else {
if (!teleportTarget.asPassenger()) {
this.stopRiding();
}
this.connection.internalTeleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives());
this.connection.resetPosition();
teleportTarget.postTeleportTransition().onTransition(this);
return this;
}
}
@Override
public void handlePortal() {
}
@Override
public void tick() {
if (!this.isAlive()) {
return;
}
super.tick();
if (this.getConfigValue(Configs.SPAWN_PHANTOM)) {
notSleepTicks++;
}
if (LeavesConfig.modify.fakeplayer.regenAmount > 0.0 && server.getTickCount() % 20 == 0) {
float health = getHealth();
float maxHealth = getMaxHealth();
float regenAmount = (float) (LeavesConfig.modify.fakeplayer.regenAmount * 20);
float amount;
if (health < maxHealth - regenAmount) {
amount = health + regenAmount;
} else {
amount = maxHealth;
}
this.setHealth(amount);
}
}
@Override
public void onItemPickup(@NotNull ItemEntity item) {
super.onItemPickup(item);
this.updateItemInHand(InteractionHand.MAIN_HAND);
}
public void updateItemInHand(InteractionHand hand) {
ItemStack item = this.getItemInHand(hand);
if (!item.isEmpty()) {
BotUtil.replenishment(item, getInventory().items);
if (BotUtil.isDamage(item, 10)) {
BotUtil.replaceTool(hand == InteractionHand.MAIN_HAND ? EquipmentSlot.MAINHAND : EquipmentSlot.OFFHAND, this);
}
}
this.detectEquipmentUpdatesPublic();
}
@Override
public @NotNull InteractionResult interact(@NotNull Player player, @NotNull InteractionHand hand) {
if (LeavesConfig.modify.fakeplayer.canOpenInventory) {
if (player instanceof ServerPlayer player1 && player.getMainHandItem().isEmpty()) {
BotInventoryOpenEvent event = new BotInventoryOpenEvent(this.getBukkitEntity(), player1.getBukkitEntity());
this.server.server.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
player.openMenu(new SimpleMenuProvider((i, inventory, p) -> ChestMenu.sixRows(i, inventory, this.container), this.getDisplayName()));
return InteractionResult.SUCCESS;
}
}
}
return super.interact(player, hand);
}
@Override
public void checkFallDamage(double y, boolean onGround, @NotNull BlockState state, @NotNull BlockPos pos) {
ServerLevel serverLevel = this.serverLevel();
if (onGround && this.fallDistance > 0.0F) {
this.onChangedBlock(serverLevel, pos);
double attributeValue = this.getAttributeValue(Attributes.SAFE_FALL_DISTANCE);
if (this.fallDistance > attributeValue && !state.isAir()) {
double x = this.getX();
double y1 = this.getY();
double z = this.getZ();
BlockPos blockPos = this.blockPosition();
if (pos.getX() != blockPos.getX() || pos.getZ() != blockPos.getZ()) {
double d = x - pos.getX() - 0.5;
double d1 = z - pos.getZ() - 0.5;
double max = Math.max(Math.abs(d), Math.abs(d1));
x = pos.getX() + 0.5 + d / max * 0.5;
z = pos.getZ() + 0.5 + d1 / max * 0.5;
}
float f = Mth.ceil(this.fallDistance - attributeValue);
double min = Math.min(0.2F + f / 15.0F, 2.5);
int i = (int) (150.0 * min);
serverLevel.sendParticlesSource(this, new BlockParticleOption(ParticleTypes.BLOCK, state), false, false, x, y1, z, i, 0.0, 0.0, 0.0, 0.15F);
}
}
if (onGround) {
if (this.fallDistance > 0.0F) {
state.getBlock().fallOn(serverLevel, state, pos, this, this.fallDistance);
serverLevel.gameEvent(GameEvent.HIT_GROUND, this.position(),
GameEvent.Context.of(this, this.mainSupportingBlockPos.map(supportingPos -> this.level().getBlockState(supportingPos)).orElse(state))
);
}
this.resetFallDistance();
} else if (y < 0.0D) {
this.fallDistance -= (float) y;
}
}
@Override
public void doTick() {
this.absMoveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
if (this.takeXpDelay > 0) {
--this.takeXpDelay;
}
if (this.isSleeping()) {
++this.sleepCounter;
if (this.sleepCounter > 100) {
this.sleepCounter = 100;
this.notSleepTicks = 0;
}
if (!this.level().isClientSide && this.level().isDay()) {
this.stopSleepInBed(false, true);
}
} else if (this.sleepCounter > 0) {
++this.sleepCounter;
if (this.sleepCounter >= 110) {
this.sleepCounter = 0;
}
}
this.updateIsUnderwater();
this.addDeltaMovement(knockback);
this.knockback = Vec3.ZERO;
this.server.scheduleOnMain(this::runAction);
this.livingEntityTick();
this.foodData.tick(this);
++this.attackStrengthTicker;
ItemStack itemstack = this.getMainHandItem();
if (!ItemStack.matches(this.lastItemInMainHand, itemstack)) {
if (!ItemStack.isSameItem(this.lastItemInMainHand, itemstack)) {
this.resetAttackStrengthTicker();
}
this.lastItemInMainHand = itemstack.copy();
}
this.getCooldowns().tick();
this.updatePlayerPose();
if (this.hurtTime > 0) {
this.hurtTime -= 1;
}
}
@Override
public void knockback(double strength, double x, double z, @Nullable Entity attacker, @NotNull EntityKnockbackEvent.Cause cause) {
strength *= 1.0D - this.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE);
if (strength > 0.0D) {
Vec3 vec3d = this.getDeltaMovement();
Vec3 vec3d1 = (new Vec3(x, 0.0D, z)).normalize().scale(strength);
this.hasImpulse = true;
this.knockback = new Vec3(vec3d.x / 2.0D - vec3d1.x, this.onGround() ? Math.min(0.4D, vec3d.y / 2.0D + strength) : vec3d.y, vec3d.z / 2.0D - vec3d1.z).subtract(vec3d);
}
}
@Override
public void attack(@NotNull Entity target) {
super.attack(target);
this.swing(InteractionHand.MAIN_HAND);
}
@Override
public void addAdditionalSaveData(@NotNull CompoundTag nbt) {
super.addAdditionalSaveData(nbt);
nbt.putBoolean("isShiftKeyDown", this.isShiftKeyDown());
CompoundTag createNbt = new CompoundTag();
createNbt.putString("realName", this.createState.realName());
createNbt.putString("name", this.createState.name());
createNbt.putString("skinName", this.createState.skinName());
if (this.createState.skin() != null) {
ListTag skin = new ListTag();
for (String s : this.createState.skin()) {
skin.add(StringTag.valueOf(s));
}
createNbt.put("skin", skin);
}
nbt.put("createStatus", createNbt);
if (!this.actions.isEmpty()) {
ListTag actionNbt = new ListTag();
for (BotAction<?> action : this.actions) {
actionNbt.add(action.save(new CompoundTag()));
}
nbt.put("actions", actionNbt);
}
if (!this.configs.isEmpty()) {
ListTag configNbt = new ListTag();
for (BotConfig<?> config : this.configs.values()) {
configNbt.add(config.save(new CompoundTag()));
}
nbt.put("configs", configNbt);
}
}
@Override
public void readAdditionalSaveData(@NotNull CompoundTag nbt) {
super.readAdditionalSaveData(nbt);
this.setShiftKeyDown(nbt.getBoolean("isShiftKeyDown"));
CompoundTag createNbt = nbt.getCompound("createStatus");
BotCreateState.Builder createBuilder = BotCreateState.builder(createNbt.getString("realName"), null).name(createNbt.getString("name"));
String[] skin = null;
if (createNbt.contains("skin")) {
ListTag skinTag = createNbt.getList("skin", 8);
skin = new String[skinTag.size()];
for (int i = 0; i < skinTag.size(); i++) {
skin[i] = skinTag.getString(i);
}
}
createBuilder.skinName(createNbt.getString("skinName")).skin(skin);
createBuilder.createReason(BotCreateEvent.CreateReason.INTERNAL).creator(null);
this.createState = createBuilder.build();
this.gameProfile = new BotList.CustomGameProfile(this.getUUID(), this.createState.name(), this.createState.skin());
if (nbt.contains("actions")) {
ListTag actionNbt = nbt.getList("actions", 10);
for (int i = 0; i < actionNbt.size(); i++) {
CompoundTag actionTag = actionNbt.getCompound(i);
BotAction<?> action = Actions.getForName(actionTag.getString("actionName"));
if (action != null) {
BotAction<?> newAction = action.create();
newAction.load(actionTag);
this.actions.add(newAction);
}
}
}
if (nbt.contains("configs")) {
ListTag configNbt = nbt.getList("configs", 10);
for (int i = 0; i < configNbt.size(); i++) {
CompoundTag configTag = configNbt.getCompound(i);
Configs<?> configKey = Configs.getConfig(configTag.getString("configName"));
if (configKey != null) {
this.configs.get(configKey).load(configTag);
}
}
}
}
public void faceLocation(@NotNull Location loc) {
this.look(loc.toVector().subtract(getLocation().toVector()), false);
}
public void look(Vector dir, boolean keepYaw) {
float yaw, pitch;
if (keepYaw) {
yaw = this.getYHeadRot();
pitch = MathUtils.fetchPitch(dir);
} else {
float[] vals = MathUtils.fetchYawPitch(dir);
yaw = vals[0];
pitch = vals[1];
this.sendPacket(new ClientboundRotateHeadPacket(this, (byte) (yaw * 256 / 360f)));
}
this.setRot(yaw, pitch);
}
public Location getLocation() {
return this.getBukkitEntity().getLocation();
}
public Entity getTargetEntity(int maxDistance, Predicate<? super Entity> predicate) {
List<Entity> entities = this.level().getEntities((Entity) null, this.getBoundingBox(), (e -> e != this && (predicate == null || predicate.test(e))));
if (!entities.isEmpty()) {
return entities.getFirst();
} else {
EntityHitResult result = this.getBukkitEntity().rayTraceEntity(maxDistance, false);
if (result != null && (predicate == null || predicate.test(result.getEntity()))) {
return result.getEntity();
}
}
return null;
}
public void dropAll() {
this.getInventory().dropAll();
this.detectEquipmentUpdatesPublic();
}
private void runAction() {
if (LeavesConfig.modify.fakeplayer.canUseAction) {
this.actions.forEach(action -> action.tryTick(this));
this.actions.removeIf(BotAction::isCancelled);
}
}
public boolean addBotAction(BotAction<?> action, CommandSender sender) {
if (!LeavesConfig.modify.fakeplayer.canUseAction) {
return false;
}
if (!new BotActionScheduleEvent(this.getBukkitEntity(), action.getName(), action.getUUID(), sender).callEvent()) {
return false;
}
action.init();
this.actions.add(action);
return true;
}
public List<BotAction<?>> getBotActions() {
return actions;
}
@Override
public @NotNull ServerStatsCounter getStats() {
return stats;
}
@SuppressWarnings("unchecked")
public <E> BotConfig<E> getConfig(Configs<E> config) {
return (BotConfig<E>) Objects.requireNonNull(this.configs.get(config));
}
public <E> E getConfigValue(Configs<E> config) {
return this.getConfig(config).getValue();
}
@Override
@NotNull
public CraftBot getBukkitEntity() {
return (CraftBot) super.getBukkitEntity();
}
}

View File

@@ -0,0 +1,127 @@
package org.leavesmc.leaves.bot;
import net.kyori.adventure.text.Component;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import org.bukkit.event.player.PlayerGameModeChangeEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ServerBotGameMode extends ServerPlayerGameMode {
public ServerBotGameMode(ServerBot bot) {
super(bot);
super.setGameModeForPlayer(GameType.SURVIVAL, null);
}
@Override
public boolean changeGameModeForPlayer(@NotNull GameType gameMode) {
return false;
}
@Nullable
@Override
public PlayerGameModeChangeEvent changeGameModeForPlayer(@NotNull GameType gameMode, PlayerGameModeChangeEvent.@NotNull Cause cause, @Nullable Component cancelMessage) {
return null;
}
@Override
protected void setGameModeForPlayer(@NotNull GameType gameMode, @Nullable GameType previousGameMode) {
}
@Override
public void tick() {
}
@Override
public void destroyAndAck(@NotNull BlockPos pos, int sequence, @NotNull String reason) {
this.destroyBlock(pos);
}
@Override
public boolean destroyBlock(@NotNull BlockPos pos) {
BlockState iblockdata = this.level.getBlockState(pos);
BlockEntity tileentity = this.level.getBlockEntity(pos);
Block block = iblockdata.getBlock();
if (this.player.blockActionRestricted(this.level, pos, this.getGameModeForPlayer())) {
return false;
} else {
this.level.captureDrops = null;
BlockState iblockdata1 = block.playerWillDestroy(this.level, pos, iblockdata, this.player);
boolean flag = this.level.removeBlock(pos, false);
if (flag) {
block.destroy(this.level, pos, iblockdata1);
}
ItemStack itemstack = this.player.getMainHandItem();
ItemStack itemstack1 = itemstack.copy();
boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1);
itemstack.mineBlock(this.level, iblockdata1, pos, this.player);
if (flag && flag1) {
Block.dropResources(iblockdata1, this.level, pos, tileentity, this.player, itemstack1, true);
}
if (flag) {
iblockdata.getBlock().popExperience(this.level, pos, block.getExpDrop(iblockdata, this.level, pos, itemstack, true), this.player);
}
return true;
}
}
@NotNull
@Override
public InteractionResult useItemOn(@NotNull ServerPlayer player, Level level, @NotNull ItemStack stack, @NotNull InteractionHand hand, BlockHitResult hitResult) {
BlockPos blockPos = hitResult.getBlockPos();
BlockState blockState = level.getBlockState(blockPos);
if (!blockState.getBlock().isEnabled(level.enabledFeatures())) {
return InteractionResult.FAIL;
}
boolean flag = !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty();
boolean flag1 = player.isSecondaryUseActive() && flag;
if (!flag1) {
InteractionResult iteminteractionresult = blockState.useItemOn(player.getItemInHand(hand), level, player, hand, hitResult);
if (iteminteractionresult.consumesAction()) {
return iteminteractionresult;
}
if (iteminteractionresult instanceof InteractionResult.TryEmptyHandInteraction && hand == InteractionHand.MAIN_HAND) {
InteractionResult interactionResult = blockState.useWithoutItem(level, player, hitResult);
if (interactionResult.consumesAction()) {
return interactionResult;
}
}
}
if (!stack.isEmpty() && !player.getCooldowns().isOnCooldown(stack)) {
UseOnContext itemactioncontext = new UseOnContext(player, hand, hitResult);
return stack.useOn(itemactioncontext);
} else {
return InteractionResult.PASS;
}
}
@Override
public void setLevel(@NotNull ServerLevel world) {
}
}

View File

@@ -0,0 +1,85 @@
package org.leavesmc.leaves.bot;
import net.minecraft.network.Connection;
import net.minecraft.network.DisconnectionDetails;
import net.minecraft.network.PacketSendListener;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.CommonListenerCookie;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.bukkit.event.player.PlayerKickEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ServerBotPacketListenerImpl extends ServerGamePacketListenerImpl {
public ServerBotPacketListenerImpl(MinecraftServer server, ServerBot bot) {
super(server, BotConnection.INSTANCE, bot, CommonListenerCookie.createInitial(bot.gameProfile, false));
}
@Override
public void sendPacket(@NotNull Packet<?> packet) {
}
@Override
public void send(@NotNull Packet<?> packet) {
}
@Override
public void send(@NotNull Packet<?> packet, @Nullable PacketSendListener callbacks) {
}
@Override
public void disconnect(@NotNull DisconnectionDetails disconnectionInfo, PlayerKickEvent.@NotNull Cause cause) {
}
@Override
public boolean isAcceptingMessages() {
return true;
}
@Override
public void tick() {
}
public static class BotConnection extends Connection {
private static final BotConnection INSTANCE = new BotConnection();
public BotConnection() {
super(PacketFlow.SERVERBOUND);
}
@Override
public void tick() {
}
@Override
public boolean isConnected() {
return true;
}
@Override
public boolean isConnecting() {
return false;
}
@Override
public boolean isMemoryConnection() {
return false;
}
@Override
public void send(@NotNull Packet<?> packet) {
}
@Override
public void send(@NotNull Packet<?> packet, @Nullable PacketSendListener packetsendlistener) {
}
@Override
public void send(@NotNull Packet<?> packet, @Nullable PacketSendListener callbacks, boolean flush) {
}
}
}

View File

@@ -0,0 +1,67 @@
package org.leavesmc.leaves.bot.agent;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.agent.actions.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Actions {
private static final Map<String, BotAction<?>> actions = new HashMap<>();
public static void registerAll() {
register(new AttackAction());
register(new BreakBlockAction());
register(new DropAction());
register(new JumpAction());
register(new RotateAction());
register(new SneakAction());
register(new UseItemAction());
register(new UseItemOnAction());
register(new UseItemToAction());
register(new LookAction());
register(new FishAction());
register(new SwimAction());
register(new UseItemOffHandAction());
register(new UseItemOnOffhandAction());
register(new UseItemToOffhandAction());
register(new RotationAction());
}
public static boolean register(@NotNull BotAction<?> action) {
if (!actions.containsKey(action.getName())) {
actions.put(action.getName(), action);
return true;
}
return false;
}
public static boolean unregister(@NotNull String name) {
if (actions.containsKey(name)) {
actions.remove(name);
return true;
}
return false;
}
@NotNull
@Contract(pure = true)
public static Collection<BotAction<?>> getAll() {
return actions.values();
}
@NotNull
public static Set<String> getNames() {
return actions.keySet();
}
@Nullable
public static BotAction<?> getForName(String name) {
return actions.get(name);
}
}

View File

@@ -0,0 +1,163 @@
package org.leavesmc.leaves.bot.agent;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.event.bot.BotActionExecuteEvent;
import org.leavesmc.leaves.event.bot.BotActionStopEvent;
import java.util.List;
import java.util.UUID;
import java.util.function.Supplier;
public abstract class BotAction<E extends BotAction<E>> {
private final String name;
private final CommandArgument argument;
private final Supplier<E> creator;
private boolean cancel;
private int tickDelay;
private int number;
private UUID uuid;
private int needWaitTick;
private int canDoNumber;
public BotAction(String name, CommandArgument argument, Supplier<E> creator) {
this.name = name;
this.argument = argument;
this.uuid = UUID.randomUUID();
this.creator = creator;
this.cancel = false;
this.tickDelay = 20;
this.number = -1;
}
public void init() {
this.needWaitTick = 0;
this.canDoNumber = this.getNumber();
this.setCancelled(false);
}
public String getName() {
return this.name;
}
public UUID getUUID() {
return uuid;
}
public int getTickDelay() {
return this.tickDelay;
}
@SuppressWarnings("unchecked")
public E setTickDelay(int tickDelay) {
this.tickDelay = Math.max(0, tickDelay);
return (E) this;
}
public int getNumber() {
return this.number;
}
@SuppressWarnings("unchecked")
public E setNumber(int number) {
this.number = Math.max(-1, number);
return (E) this;
}
public int getCanDoNumber() {
return this.canDoNumber;
}
public boolean isCancelled() {
return cancel;
}
public void setCancelled(boolean cancel) {
this.cancel = cancel;
}
public void stop(@NotNull ServerBot bot, BotActionStopEvent.Reason reason) {
new BotActionStopEvent(bot.getBukkitEntity(), this.name, this.uuid, reason, null).callEvent();
this.setCancelled(true);
}
public CommandArgument getArgument() {
return this.argument;
}
@SuppressWarnings("unchecked")
public E setTabComplete(int index, List<String> list) {
this.argument.setTabComplete(index, list);
return (E) this;
}
public void tryTick(ServerBot bot) {
if (this.canDoNumber == 0) {
this.stop(bot, BotActionStopEvent.Reason.DONE);
return;
}
if (this.needWaitTick <= 0) {
BotActionExecuteEvent event = new BotActionExecuteEvent(bot.getBukkitEntity(), name, uuid);
event.callEvent();
if (event.getResult() == BotActionExecuteEvent.Result.SOFT_CANCEL) {
this.needWaitTick = this.getTickDelay();
return;
} else if (event.getResult() == BotActionExecuteEvent.Result.HARD_CANCEL) {
if (this.canDoNumber > 0) {
this.canDoNumber--;
}
this.needWaitTick = this.getTickDelay();
return;
}
if (this.doTick(bot)) {
if (this.canDoNumber > 0) {
this.canDoNumber--;
}
this.needWaitTick = this.getTickDelay();
}
} else {
this.needWaitTick--;
}
}
@NotNull
public E create() {
return this.creator.get();
}
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
if (!this.cancel) {
nbt.putString("actionName", this.name);
nbt.putUUID("actionUUID", this.uuid);
nbt.putInt("canDoNumber", this.canDoNumber);
nbt.putInt("needWaitTick", this.needWaitTick);
nbt.putInt("tickDelay", this.tickDelay);
}
return nbt;
}
public void load(@NotNull CompoundTag nbt) {
this.tickDelay = nbt.getInt("tickDelay");
this.needWaitTick = nbt.getInt("needWaitTick");
this.canDoNumber = nbt.getInt("canDoNumber");
this.uuid = nbt.getUUID("actionUUID");
}
public abstract void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result);
public abstract boolean doTick(@NotNull ServerBot bot);
}

View File

@@ -0,0 +1,62 @@
package org.leavesmc.leaves.bot.agent;
import net.minecraft.nbt.CompoundTag;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import java.util.List;
import java.util.function.Supplier;
public abstract class BotConfig<E> {
private final String name;
private final CommandArgument argument;
private final Supplier<BotConfig<E>> creator;
protected ServerBot bot;
public BotConfig(String name, CommandArgument argument, Supplier<BotConfig<E>> creator) {
this.name = name;
this.argument = argument;
this.creator = creator;
}
public BotConfig<E> setBot(ServerBot bot) {
this.bot = bot;
return this;
}
public abstract E getValue();
public abstract void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException;
public List<String> getMessage() {
return List.of(this.bot.getScoreboardName() + "'s " + this.getName() + ": " + this.getValue());
}
public List<String> getChangeMessage() {
return List.of(this.bot.getScoreboardName() + "'s " + this.getName() + " changed: " + this.getValue());
}
public String getName() {
return name;
}
public CommandArgument getArgument() {
return argument;
}
@NotNull
public BotConfig<E> create(ServerBot bot) {
return this.creator.get().setBot(bot);
}
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
nbt.putString("configName", this.name);
return nbt;
}
public abstract void load(@NotNull CompoundTag nbt);
}

View File

@@ -0,0 +1,44 @@
package org.leavesmc.leaves.bot.agent;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.agent.configs.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class Configs<E> {
private static final Map<String, Configs<?>> configs = new HashMap<>();
public static final Configs<Boolean> SKIP_SLEEP = register(new SkipSleepConfig());
public static final Configs<Boolean> ALWAYS_SEND_DATA = register(new AlwaysSendDataConfig());
public static final Configs<Boolean> SPAWN_PHANTOM = register(new SpawnPhantomConfig());
public static final Configs<Integer> SIMULATION_DISTANCE = register(new SimulationDistanceConfig());
public final BotConfig<E> config;
private Configs(BotConfig<E> config) {
this.config = config;
}
@NotNull
@Contract(pure = true)
public static Collection<Configs<?>> getConfigs() {
return configs.values();
}
@Nullable
public static Configs<?> getConfig(String name) {
return configs.get(name);
}
@NotNull
private static <T> Configs<T> register(BotConfig<T> botConfig) {
Configs<T> config = new Configs<>(botConfig);
configs.put(botConfig.getName(), config);
return config;
}
}

View File

@@ -0,0 +1,25 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.agent.BotAction;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandArgumentType;
import java.util.List;
import java.util.function.Supplier;
public abstract class AbstractTimerAction<E extends AbstractTimerAction<E>> extends BotAction<E> {
public AbstractTimerAction(String name, Supplier<E> creator) {
super(name, CommandArgument.of(CommandArgumentType.INTEGER, CommandArgumentType.INTEGER), creator);
this.setTabComplete(0, List.of("[TickDelay]")).setTabComplete(1, List.of("[DoNumber]"));
}
@Override
public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) {
this.setTickDelay(result.readInt(20)).setNumber(result.readInt(-1));
}
}

View File

@@ -0,0 +1,22 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.world.entity.Entity;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
public class AttackAction extends AbstractTimerAction<AttackAction> {
public AttackAction() {
super("attack", AttackAction::new);
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
Entity entity = bot.getTargetEntity(3, target -> target.isAttackable() && !target.skipAttackInteraction(bot));
if (entity != null) {
bot.attack(entity);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,75 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.block.CraftBlock;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
public class BreakBlockAction extends AbstractTimerAction<BreakBlockAction> {
public BreakBlockAction() {
super("break", BreakBlockAction::new);
}
private BlockPos lastPos = null;
private int destroyProgressTime = 0;
private int lastSentState = -1;
@Override
public boolean doTick(@NotNull ServerBot bot) {
Block block = bot.getBukkitEntity().getTargetBlockExact(5);
if (block != null) {
BlockPos pos = ((CraftBlock) block).getPosition();
if (lastPos == null || !lastPos.equals(pos)) {
lastPos = pos;
destroyProgressTime = 0;
lastSentState = -1;
}
BlockState iblockdata = bot.level().getBlockState(pos);
if (!iblockdata.isAir()) {
bot.swing(InteractionHand.MAIN_HAND);
if (iblockdata.getDestroyProgress(bot, bot.level(), pos) >= 1.0F) {
bot.gameMode.destroyAndAck(pos, 0, "insta mine");
bot.level().destroyBlockProgress(bot.getId(), pos, -1);
bot.updateItemInHand(InteractionHand.MAIN_HAND);
finalBreak();
return true;
}
float damage = this.incrementDestroyProgress(bot, iblockdata, pos);
if (damage >= 1.0F) {
bot.gameMode.destroyAndAck(pos, 0, "destroyed");
bot.level().destroyBlockProgress(bot.getId(), pos, -1);
bot.updateItemInHand(InteractionHand.MAIN_HAND);
finalBreak();
return true;
}
}
}
return false;
}
private void finalBreak() {
lastPos = null;
destroyProgressTime = 0;
lastSentState = -1;
}
private float incrementDestroyProgress(ServerBot bot, @NotNull BlockState state, BlockPos pos) {
float f = state.getDestroyProgress(bot, bot.level(), pos) * (float) (++destroyProgressTime);
int k = (int) (f * 10.0F);
if (k != lastSentState) {
bot.level().destroyBlockProgress(bot.getId(), pos, k);
lastSentState = k;
}
return f;
}
}

View File

@@ -0,0 +1,54 @@
package org.leavesmc.leaves.bot.agent.actions;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.agent.Actions;
import org.leavesmc.leaves.bot.agent.BotAction;
import org.leavesmc.leaves.entity.botaction.BotActionType;
import org.leavesmc.leaves.entity.botaction.LeavesBotAction;
public class CraftBotAction extends LeavesBotAction {
private final BotAction<?> handle;
public CraftBotAction(@NotNull BotAction<?> action) {
super(BotActionType.valueOf(action.getName()), action.getTickDelay(), action.getCanDoNumber());
this.handle = action;
}
@Contract("_ -> new")
@NotNull
public static LeavesBotAction asAPICopy(BotAction<?> action) {
return new CraftBotAction(action);
}
@NotNull
public static BotAction<?> asInternalCopy(@NotNull LeavesBotAction action) {
BotAction<?> act = Actions.getForName(action.getActionName());
if (act == null) {
throw new IllegalArgumentException("Invalid action name!");
}
BotAction<?> newAction = null;
String[] args = new String[]{String.valueOf(action.getExecuteInterval()), String.valueOf(action.getRemainingExecuteTime())};
try {
if (act instanceof CraftCustomBotAction customBotAction) {
newAction = customBotAction.createCraft(action.getActionPlayer(), args);
} else {
newAction = act.create();
newAction.loadCommand(action.getActionPlayer() == null ? null : ((CraftPlayer) action.getActionPlayer()).getHandle(), act.getArgument().parse(0, args));
}
} catch (IllegalArgumentException ignore) {
}
if (newAction == null) {
throw new IllegalArgumentException("Invalid action!"); // TODO look action
}
return newAction;
}
public BotAction<?> getHandle() {
return handle;
}
}

View File

@@ -0,0 +1,49 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.BotAction;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.entity.botaction.CustomBotAction;
public class CraftCustomBotAction extends BotAction<CraftCustomBotAction> {
private final CustomBotAction realAction;
public CraftCustomBotAction(String name, @NotNull CustomBotAction realAction) {
super(name, CommandArgument.of().setAllTabComplete(realAction.getTabComplete()), null);
this.realAction = realAction;
}
@Override
public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) {
throw new UnsupportedOperationException("Not supported.");
}
public CraftCustomBotAction createCraft(@Nullable Player player, String[] args) {
CustomBotAction newRealAction = realAction.getNew(player, args);
if (newRealAction != null) {
return new CraftCustomBotAction(this.getName(), newRealAction);
}
return null;
}
@Override
public int getNumber() {
return realAction.getNumber();
}
@Override
public int getTickDelay() {
return realAction.getTickDelay();
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
return realAction.doTick(bot.getBukkitEntity());
}
}

View File

@@ -0,0 +1,25 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.command.CommandArgumentResult;
public class DropAction extends AbstractTimerAction<DropAction> {
public DropAction() {
super("drop", DropAction::new);
}
@Override
public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) {
this.setTickDelay(result.readInt(100)).setNumber(result.readInt(1));
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
bot.dropAll();
return true;
}
}

View File

@@ -0,0 +1,73 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.projectile.FishingHook;
import net.minecraft.world.item.FishingRodItem;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
public class FishAction extends AbstractTimerAction<FishAction> {
public FishAction() {
super("fish", FishAction::new);
}
private int delay = 0;
private int nowDelay = 0;
@Override
public FishAction setTickDelay(int tickDelay) {
super.setTickDelay(0);
this.delay = tickDelay;
return this;
}
@Override
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putInt("fishDelay", this.delay);
nbt.putInt("fishNowDelay", this.nowDelay);
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
super.load(nbt);
this.delay = nbt.getInt("fishDelay");
this.nowDelay = nbt.getInt("fishNowDelay");
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
if (this.nowDelay > 0) {
this.nowDelay--;
return false;
}
ItemStack mainHand = bot.getMainHandItem();
if (mainHand == ItemStack.EMPTY || mainHand.getItem().getClass() != FishingRodItem.class) {
return false;
}
FishingHook fishingHook = bot.fishing;
if (fishingHook != null) {
if (fishingHook.currentState == FishingHook.FishHookState.HOOKED_IN_ENTITY) {
mainHand.use(bot.level(), bot, InteractionHand.MAIN_HAND);
this.nowDelay = 20;
return false;
}
if (fishingHook.nibble > 0) {
mainHand.use(bot.level(), bot, InteractionHand.MAIN_HAND);
this.nowDelay = this.delay;
return true;
}
} else {
mainHand.use(bot.level(), bot, InteractionHand.MAIN_HAND);
}
return false;
}
}

View File

@@ -0,0 +1,21 @@
package org.leavesmc.leaves.bot.agent.actions;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
public class JumpAction extends AbstractTimerAction<JumpAction> {
public JumpAction() {
super("jump", JumpAction::new);
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
if (bot.onGround()) {
bot.jumpFromGround();
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,63 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.BotAction;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandArgumentType;
import java.util.List;
public class LookAction extends BotAction<LookAction> {
public LookAction() {
super("look", CommandArgument.of(CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE, CommandArgumentType.DOUBLE), LookAction::new);
this.setTabComplete(0, List.of("<X>"));
this.setTabComplete(1, List.of("<Y>"));
this.setTabComplete(2, List.of("<Z>"));
}
private Vector pos;
@Override
public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) throws IllegalArgumentException {
Vector pos = result.readVector();
if (pos != null) {
this.setPos(pos).setTickDelay(0).setNumber(1);
} else {
throw new IllegalArgumentException("pos?");
}
}
@Override
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putDouble("x", this.pos.getX());
nbt.putDouble("y", this.pos.getY());
nbt.putDouble("z", this.pos.getZ());
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
super.load(nbt);
this.setPos(new Vector(nbt.getDouble("x"), nbt.getDouble("y"), nbt.getDouble("z")));
}
public LookAction setPos(Vector pos) {
this.pos = pos;
return this;
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
bot.look(pos.subtract(bot.getLocation().toVector()), false);
return true;
}
}

View File

@@ -0,0 +1,51 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.BotAction;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
public class RotateAction extends BotAction<RotateAction> {
public RotateAction() {
super("rotate", CommandArgument.EMPTY, RotateAction::new);
}
private ServerPlayer player;
@Override
public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) {
this.setPlayer(player).setTickDelay(0).setNumber(1);
}
public RotateAction setPlayer(ServerPlayer player) {
this.player = player;
return this;
}
@Override
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putString("actionName", "look"); // to player loc
nbt.putDouble("x", player.getX());
nbt.putDouble("y", player.getY());
nbt.putDouble("z", player.getZ());
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
bot.faceLocation(player.getBukkitEntity().getLocation());
return true;
}
}

View File

@@ -0,0 +1,65 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.BotAction;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandArgumentType;
import java.util.List;
public class RotationAction extends BotAction<RotationAction> {
public RotationAction() {
super("rotation", CommandArgument.of(CommandArgumentType.FLOAT, CommandArgumentType.FLOAT), RotationAction::new);
this.setTabComplete(0, List.of("<yaw>"));
this.setTabComplete(1, List.of("<pitch>"));
}
private float yaw;
private float pitch;
@Override
public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) {
if (player == null) {
return;
}
this.setYaw(result.readFloat(player.getYRot())).setPitch(result.readFloat(player.getXRot())).setTickDelay(0).setNumber(1);
}
public RotationAction setYaw(float yaw) {
this.yaw = yaw;
return this;
}
public RotationAction setPitch(float pitch) {
this.pitch = pitch;
return this;
}
@Override
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putFloat("yaw", this.yaw);
nbt.putFloat("pitch", this.pitch);
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
super.load(nbt);
this.setYaw(nbt.getFloat("yaw")).setPitch(nbt.getFloat("pitch"));
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
bot.setRot(yaw, pitch);
return true;
}
}

View File

@@ -0,0 +1,27 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.BotAction;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
public class SneakAction extends BotAction<SneakAction> {
public SneakAction() {
super("sneak", CommandArgument.EMPTY, SneakAction::new);
}
@Override
public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) {
this.setTickDelay(0).setNumber(1);
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
bot.setShiftKeyDown(!bot.isShiftKeyDown());
return true;
}
}

View File

@@ -0,0 +1,30 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.BotAction;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
public class SwimAction extends BotAction<SwimAction> {
public SwimAction() {
super("swim", CommandArgument.EMPTY, SwimAction::new);
}
@Override
public void loadCommand(@Nullable ServerPlayer player, @NotNull CommandArgumentResult result) {
this.setTickDelay(0).setNumber(-1);
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
if (bot.isInWater()) {
bot.addDeltaMovement(new Vec3(0, 0.03, 0));
}
return true;
}
}

View File

@@ -0,0 +1,26 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.world.InteractionHand;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
public class UseItemAction extends AbstractTimerAction<UseItemAction> {
public UseItemAction() {
super("use", UseItemAction::new);
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
if (bot.isUsingItem()) {
return false;
}
boolean flag = bot.gameMode.useItem(bot, bot.level(), bot.getItemInHand(InteractionHand.MAIN_HAND), InteractionHand.MAIN_HAND).consumesAction();
if (flag) {
bot.swing(InteractionHand.MAIN_HAND);
bot.updateItemInHand(InteractionHand.MAIN_HAND);
}
return flag;
}
}

View File

@@ -0,0 +1,26 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.world.InteractionHand;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
public class UseItemOffHandAction extends AbstractTimerAction<UseItemOffHandAction> {
public UseItemOffHandAction() {
super("use_offhand", UseItemOffHandAction::new);
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
if (bot.isUsingItem()) {
return false;
}
boolean flag = bot.gameMode.useItem(bot, bot.level(), bot.getItemInHand(InteractionHand.OFF_HAND), InteractionHand.OFF_HAND).consumesAction();
if (flag) {
bot.swing(InteractionHand.OFF_HAND);
bot.updateItemInHand(InteractionHand.OFF_HAND);
}
return flag;
}
}

View File

@@ -0,0 +1,45 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.TrappedChestBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
public class UseItemOnAction extends AbstractTimerAction<UseItemOnAction> {
public UseItemOnAction() {
super("use_on", UseItemOnAction::new);
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
HitResult result = bot.getRayTrace(5, ClipContext.Fluid.NONE);
if (result instanceof BlockHitResult blockHitResult) {
BlockState state = bot.serverLevel().getBlockState(blockHitResult.getBlockPos());
if (state.isAir()) {
return false;
}
bot.swing(InteractionHand.MAIN_HAND);
if (state.getBlock() == Blocks.TRAPPED_CHEST) {
BlockEntity entity = bot.serverLevel().getBlockEntity(blockHitResult.getBlockPos());
if (entity instanceof TrappedChestBlockEntity chestBlockEntity) {
chestBlockEntity.startOpen(bot);
Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> chestBlockEntity.stopOpen(bot), 1);
return true;
}
} else {
bot.updateItemInHand(InteractionHand.MAIN_HAND);
return bot.gameMode.useItemOn(bot, bot.level(), bot.getItemInHand(InteractionHand.MAIN_HAND), InteractionHand.MAIN_HAND, (BlockHitResult) result).consumesAction();
}
}
return false;
}
}

View File

@@ -0,0 +1,45 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.TrappedChestBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.plugin.MinecraftInternalPlugin;
public class UseItemOnOffhandAction extends AbstractTimerAction<UseItemOnOffhandAction> {
public UseItemOnOffhandAction() {
super("use_on_offhand", UseItemOnOffhandAction::new);
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
HitResult result = bot.getRayTrace(5, ClipContext.Fluid.NONE);
if (result instanceof BlockHitResult blockHitResult) {
BlockState state = bot.serverLevel().getBlockState(blockHitResult.getBlockPos());
if (state.isAir()) {
return false;
}
bot.swing(InteractionHand.OFF_HAND);
if (state.getBlock() == Blocks.TRAPPED_CHEST) {
BlockEntity entity = bot.serverLevel().getBlockEntity(blockHitResult.getBlockPos());
if (entity instanceof TrappedChestBlockEntity chestBlockEntity) {
chestBlockEntity.startOpen(bot);
Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> chestBlockEntity.stopOpen(bot), 1);
return true;
}
} else {
bot.updateItemInHand(InteractionHand.OFF_HAND);
return bot.gameMode.useItemOn(bot, bot.level(), bot.getItemInHand(InteractionHand.OFF_HAND), InteractionHand.OFF_HAND, (BlockHitResult) result).consumesAction();
}
}
return false;
}
}

View File

@@ -0,0 +1,27 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
public class UseItemToAction extends AbstractTimerAction<UseItemToAction> {
public UseItemToAction() {
super("use_to", UseItemToAction::new);
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
Entity entity = bot.getTargetEntity(3, null);
if (entity != null) {
boolean flag = bot.interactOn(entity, InteractionHand.MAIN_HAND).consumesAction();
if (flag) {
bot.swing(InteractionHand.MAIN_HAND);
bot.updateItemInHand(InteractionHand.MAIN_HAND);
}
return flag;
}
return false;
}
}

View File

@@ -0,0 +1,27 @@
package org.leavesmc.leaves.bot.agent.actions;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.ServerBot;
public class UseItemToOffhandAction extends AbstractTimerAction<UseItemToOffhandAction> {
public UseItemToOffhandAction() {
super("use_to_offhand", UseItemToOffhandAction::new);
}
@Override
public boolean doTick(@NotNull ServerBot bot) {
Entity entity = bot.getTargetEntity(3, null);
if (entity != null) {
boolean flag = bot.interactOn(entity, InteractionHand.OFF_HAND).consumesAction();
if (flag) {
bot.swing(InteractionHand.OFF_HAND);
bot.updateItemInHand(InteractionHand.OFF_HAND);
}
return flag;
}
return false;
}
}

View File

@@ -0,0 +1,45 @@
package org.leavesmc.leaves.bot.agent.configs;
import net.minecraft.nbt.CompoundTag;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.bot.agent.BotConfig;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandArgumentType;
import java.util.List;
public class AlwaysSendDataConfig extends BotConfig<Boolean> {
private boolean value;
public AlwaysSendDataConfig() {
super("always_send_data", CommandArgument.of(CommandArgumentType.BOOLEAN).setTabComplete(0, List.of("true", "false")), AlwaysSendDataConfig::new);
this.value = LeavesConfig.modify.fakeplayer.canSendDataAlways;
}
@Override
public Boolean getValue() {
return value;
}
@Override
public void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException {
this.value = result.readBoolean(this.value);
}
@Override
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putBoolean("always_send_data", this.getValue());
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
this.value = nbt.getBoolean("always_send_data");
}
}

View File

@@ -0,0 +1,47 @@
package org.leavesmc.leaves.bot.agent.configs;
import net.minecraft.nbt.CompoundTag;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.agent.BotConfig;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandArgumentType;
import java.util.ArrayList;
import java.util.List;
public class SimulationDistanceConfig extends BotConfig<Integer> {
public SimulationDistanceConfig() {
super("simulation_distance", CommandArgument.of(CommandArgumentType.INTEGER).setTabComplete(0, List.of("2", "10", "<INT 2 - 32>")), SimulationDistanceConfig::new);
}
@Override
public Integer getValue() {
return this.bot.getBukkitEntity().getSimulationDistance();
}
@Override
public void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException {
int realValue = result.readInt(this.bot.getBukkitEntity().getSimulationDistance());
if (realValue < 2 || realValue > 32) {
throw new IllegalArgumentException("simulation_distance must be a number between 2 and 32, got: " + result);
}
this.bot.getBukkitEntity().setSimulationDistance(realValue);
}
@Override
@NotNull
public CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putInt("simulation_distance", this.getValue());
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
this.setValue(new CommandArgumentResult(new ArrayList<>(){{
add(nbt.getInt("simulation_distance"));
}}));
}
}

View File

@@ -0,0 +1,42 @@
package org.leavesmc.leaves.bot.agent.configs;
import net.minecraft.nbt.CompoundTag;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.bot.agent.BotConfig;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandArgumentType;
import java.util.ArrayList;
import java.util.List;
public class SkipSleepConfig extends BotConfig<Boolean> {
public SkipSleepConfig() {
super("skip_sleep", CommandArgument.of(CommandArgumentType.BOOLEAN).setTabComplete(0, List.of("true", "false")), SkipSleepConfig::new);
}
@Override
public Boolean getValue() {
return bot.fauxSleeping;
}
@Override
public void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException {
bot.fauxSleeping = result.readBoolean(bot.fauxSleeping);
}
@Override
public @NotNull CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putBoolean("skip_sleep", this.getValue());
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
this.setValue(new CommandArgumentResult(new ArrayList<>() {{
add(nbt.getBoolean("skip_sleep"));
}}));
}
}

View File

@@ -0,0 +1,51 @@
package org.leavesmc.leaves.bot.agent.configs;
import net.minecraft.nbt.CompoundTag;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.bot.agent.BotConfig;
import org.leavesmc.leaves.command.CommandArgument;
import org.leavesmc.leaves.command.CommandArgumentResult;
import org.leavesmc.leaves.command.CommandArgumentType;
import java.util.List;
public class SpawnPhantomConfig extends BotConfig<Boolean> {
private boolean value;
public SpawnPhantomConfig() {
super("spawn_phantom", CommandArgument.of(CommandArgumentType.BOOLEAN).setTabComplete(0, List.of("true", "false")), SpawnPhantomConfig::new);
this.value = LeavesConfig.modify.fakeplayer.canSpawnPhantom;
}
@Override
public Boolean getValue() {
return value;
}
@Override
public void setValue(@NotNull CommandArgumentResult result) throws IllegalArgumentException {
this.value = result.readBoolean(this.value);
}
@Override
public List<String> getMessage() {
return List.of(
bot.getScoreboardName() + "'s spawn_phantom: " + this.getValue(),
bot.getScoreboardName() + "'s not_sleeping_ticks: " + bot.notSleepTicks
);
}
@Override
public @NotNull CompoundTag save(@NotNull CompoundTag nbt) {
super.save(nbt);
nbt.putBoolean("spawn_phantom", this.getValue());
return nbt;
}
@Override
public void load(@NotNull CompoundTag nbt) {
this.value = nbt.getBoolean("spawn_phantom");
}
}

View File

@@ -0,0 +1,35 @@
package org.leavesmc.leaves.bytebuf;
import io.netty.buffer.Unpooled;
import org.bukkit.plugin.Plugin;
import org.leavesmc.leaves.bytebuf.internal.InternalBytebufHandler;
import org.leavesmc.leaves.bytebuf.packet.PacketListener;
public class SimpleBytebufManager implements BytebufManager {
private final InternalBytebufHandler internal;
public SimpleBytebufManager(InternalBytebufHandler internal) {
this.internal = internal;
}
@Override
public void registerListener(Plugin plugin, PacketListener listener) {
internal.listenerMap.put(listener, plugin);
}
@Override
public void unregisterListener(Plugin plugin, PacketListener listener) {
internal.listenerMap.remove(listener);
}
@Override
public Bytebuf newBytebuf(int size) {
return new WrappedBytebuf(Unpooled.buffer(size));
}
@Override
public Bytebuf toBytebuf(byte[] bytes) {
return new WrappedBytebuf(Unpooled.wrappedBuffer(bytes));
}
}

View File

@@ -0,0 +1,277 @@
package org.leavesmc.leaves.bytebuf;
import com.google.gson.JsonElement;
import io.netty.buffer.ByteBuf;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentSerialization;
import net.minecraft.server.MinecraftServer;
import org.bukkit.inventory.ItemStack;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import java.util.List;
import java.util.UUID;
public class WrappedBytebuf implements Bytebuf {
private final FriendlyByteBuf buf;
private final RegistryFriendlyByteBuf registryBuf;
public WrappedBytebuf(ByteBuf buf) {
if (buf instanceof RegistryFriendlyByteBuf) {
this.buf = (FriendlyByteBuf) buf;
this.registryBuf = (RegistryFriendlyByteBuf) buf;
} else {
this.buf = new FriendlyByteBuf(buf);
this.registryBuf = new RegistryFriendlyByteBuf(this.buf, MinecraftServer.getServer().registryAccess());
}
}
public RegistryFriendlyByteBuf getRegistryBuf() {
return registryBuf;
}
@Override
public byte[] toArray() {
int length = buf.readableBytes();
byte[] data = new byte[length];
buf.getBytes(buf.readerIndex(), data);
return data;
}
@Override
public Bytebuf skipBytes(int i) {
buf.skipBytes(i);
return this;
}
@Override
public int readerIndex() {
return buf.readerIndex();
}
@Override
public Bytebuf readerIndex(int i) {
buf.readerIndex(i);
return this;
}
@Override
public int writerIndex() {
return buf.writerIndex();
}
@Override
public Bytebuf writerIndex(int i) {
buf.writerIndex(i);
return this;
}
@Override
public Bytebuf resetReaderIndex() {
buf.resetReaderIndex();
return this;
}
@Override
public Bytebuf resetWriterIndex() {
buf.resetWriterIndex();
return this;
}
@Override
public Bytebuf writeByte(int i) {
buf.writeByte(i);
return this;
}
@Override
public byte readByte() {
return buf.readByte();
}
@Override
public Bytebuf writeBoolean(boolean b) {
buf.writeBoolean(b);
return this;
}
@Override
public boolean readBoolean() {
return buf.readBoolean();
}
@Override
public Bytebuf writeFloat(float f) {
buf.writeFloat(f);
return this;
}
@Override
public float readFloat() {
return buf.readFloat();
}
@Override
public Bytebuf writeDouble(double d) {
buf.writeDouble(d);
return this;
}
@Override
public double readDouble() {
return buf.readDouble();
}
@Override
public Bytebuf writeShort(int i) {
buf.writeShort(i);
return this;
}
@Override
public short readShort() {
return buf.readShort();
}
@Override
public Bytebuf writeInt(int i) {
buf.writeShort(i);
return this;
}
@Override
public int readInt() {
return buf.readInt();
}
@Override
public Bytebuf writeLong(long i) {
buf.writeLong(i);
return this;
}
@Override
public long readLong() {
return buf.readLong();
}
@Override
public Bytebuf writeVarInt(int i) {
this.buf.writeVarInt(i);
return this;
}
@Override
public int readVarInt() {
return this.buf.readVarInt();
}
@Override
public Bytebuf writeVarLong(long i) {
this.buf.writeVarLong(i);
return this;
}
@Override
public long readVarLong() {
return this.buf.readVarLong();
}
@Override
public Bytebuf writeUUID(UUID uuid) {
this.buf.writeUUID(uuid);
return this;
}
@Override
public UUID readUUID() {
return this.buf.readUUID();
}
@Override
public Bytebuf writeEnum(Enum<?> instance) {
this.buf.writeEnum(instance);
return this;
}
@Override
public <T extends Enum<T>> T readEnum(Class<T> enumClass) {
return this.buf.readEnum(enumClass);
}
@Override
public Bytebuf writeUTFString(String utf) {
buf.writeUtf(utf);
return this;
}
@Override
public String readUTFString() {
return buf.readUtf();
}
@Override
public Bytebuf writeComponentPlain(String str) {
ComponentSerialization.STREAM_CODEC.encode(new RegistryFriendlyByteBuf(this.buf, RegistryAccess.EMPTY), Component.literal(str));
return this;
}
@Override
public String readComponentPlain() {
return ComponentSerialization.STREAM_CODEC.decode(new RegistryFriendlyByteBuf(buf, RegistryAccess.EMPTY)).getString();
}
@Override
public Bytebuf writeComponentJson(JsonElement json) {
Component component = Component.Serializer.fromJson(json, RegistryAccess.EMPTY);
if (component == null) {
throw new IllegalArgumentException("Null can not be serialize to Minecraft chat component");
}
ComponentSerialization.STREAM_CODEC.encode(new RegistryFriendlyByteBuf(buf, RegistryAccess.EMPTY), component);
return this;
}
@Override
public JsonElement readComponentJson() {
return Component.Serializer.serialize(ComponentSerialization.STREAM_CODEC.decode(new RegistryFriendlyByteBuf(buf, RegistryAccess.EMPTY)), RegistryAccess.EMPTY);
}
@Override
public Bytebuf writeItemStack(ItemStack itemStack) {
net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.unwrap(itemStack);
net.minecraft.world.item.ItemStack.OPTIONAL_STREAM_CODEC.encode(this.registryBuf, nmsItem);
return this;
}
@Override
public ItemStack readItemStack() {
net.minecraft.world.item.ItemStack nmsItem = net.minecraft.world.item.ItemStack.OPTIONAL_STREAM_CODEC.decode(this.registryBuf);
return nmsItem.asBukkitMirror();
}
@Override
public Bytebuf writeItemStackList(List<ItemStack> itemStacks) {
List<net.minecraft.world.item.ItemStack> nmsItemList = itemStacks.stream().map(CraftItemStack::unwrap).toList();
net.minecraft.world.item.ItemStack.OPTIONAL_LIST_STREAM_CODEC.encode(this.registryBuf, nmsItemList);
return this;
}
@Override
public List<ItemStack> readItemStackList() {
List<net.minecraft.world.item.ItemStack> nmsItemList = net.minecraft.world.item.ItemStack.OPTIONAL_LIST_STREAM_CODEC.decode(this.registryBuf);
return nmsItemList.stream().map(net.minecraft.world.item.ItemStack::asBukkitMirror).toList();
}
@Override
public Bytebuf copy() {
return new WrappedBytebuf(this.buf.copy());
}
@Override
public boolean release() {
return this.buf.release();
}
}

View File

@@ -0,0 +1,234 @@
package org.leavesmc.leaves.bytebuf.internal;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import net.minecraft.network.Connection;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.BundleDelimiterPacket;
import net.minecraft.network.protocol.BundlePacket;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
import net.minecraft.network.protocol.game.*;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.bytebuf.BytebufManager;
import org.leavesmc.leaves.bytebuf.SimpleBytebufManager;
import org.leavesmc.leaves.bytebuf.WrappedBytebuf;
import org.leavesmc.leaves.bytebuf.packet.Packet;
import org.leavesmc.leaves.bytebuf.packet.PacketListener;
import org.leavesmc.leaves.bytebuf.packet.PacketType;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import static org.leavesmc.leaves.bytebuf.packet.PacketType.*;
public class InternalBytebufHandler {
private class PacketHandler extends ChannelDuplexHandler {
private final static String handlerName = "leaves-bytebuf-handler";
private final Player player;
public PacketHandler(Player player) {
this.player = player;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof BundlePacket<?> || msg instanceof BundleDelimiterPacket<?>) {
super.channelRead(ctx, msg);
return;
}
if (msg instanceof net.minecraft.network.protocol.Packet<?> nmsPacket) {
try {
msg = callPacketInEvent(player, nmsPacket);
} catch (Exception e) {
MinecraftServer.LOGGER.error("Error on PacketInEvent.", e);
}
}
if (msg != null) {
super.channelRead(ctx, msg);
}
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof BundlePacket<?> || msg instanceof BundleDelimiterPacket<?>) {
super.write(ctx, msg, promise);
return;
}
if (msg instanceof net.minecraft.network.protocol.Packet<?> nmsPacket) {
try {
msg = callPacketOutEvent(player, nmsPacket);
} catch (Exception e) {
MinecraftServer.LOGGER.error("Error on PacketInEvent.", e);
}
}
if (msg != null) {
super.write(ctx, msg, promise);
}
}
}
public final Map<PacketListener, Plugin> listenerMap = new HashMap<>();
private final BytebufManager manager = new SimpleBytebufManager(this);
private final ImmutableMap<PacketType, StreamCodec> type2CodecMap;
private final Cache<net.minecraft.network.protocol.PacketType<?>, PacketType> resultCache = CacheBuilder.newBuilder().build();
public InternalBytebufHandler() {
ImmutableMap.Builder<PacketType, StreamCodec> builder = ImmutableMap.builder();
for (PacketType packet : PacketType.values()) {
Class<?> packetClass;
try {
packetClass = Class.forName("net.minecraft.network.protocol.game." + packet.name() + "Packet");
} catch (ClassNotFoundException e) {
try {
packetClass = Class.forName("net.minecraft.network.protocol.common." + packet.name() + "Packet");
} catch (ClassNotFoundException e2) {
try {
packetClass = Class.forName("net.minecraft.network.protocol.ping." + packet.name() + "Packet");
} catch (ClassNotFoundException ignored) {
continue;
}
}
}
try {
Field field = packetClass.getDeclaredField("STREAM_CODEC");
builder.put(packet, (StreamCodec<FriendlyByteBuf, net.minecraft.network.protocol.Packet<?>>) field.get(null));
} catch (Exception ignored) {
}
}
builder.put(ClientboundMoveEntityPos, ClientboundMoveEntityPacket.Pos.STREAM_CODEC);
builder.put(ClientboundMoveEntityPosRot, ClientboundMoveEntityPacket.PosRot.STREAM_CODEC);
builder.put(ClientboundMoveEntityRot, ClientboundMoveEntityPacket.Rot.STREAM_CODEC);
builder.put(ServerboundMovePlayerPos, ServerboundMovePlayerPacket.Pos.STREAM_CODEC);
builder.put(ServerboundMovePlayerPosRot, ServerboundMovePlayerPacket.PosRot.STREAM_CODEC);
builder.put(ServerboundMovePlayerRot, ServerboundMovePlayerPacket.Rot.STREAM_CODEC);
builder.put(ServerboundMovePlayerStatusOnly, ServerboundMovePlayerPacket.StatusOnly.STREAM_CODEC);
builder.put(ClientboundCustomPayload, ClientboundCustomPayloadPacket.GAMEPLAY_STREAM_CODEC);
type2CodecMap = builder.build();
}
public void injectPlayer(ServerPlayer player) {
if (LeavesConfig.mics.leavesPacketEvent) {
player.connection.connection.channel.pipeline().addBefore("packet_handler", PacketHandler.handlerName, new PacketHandler(player.getBukkitEntity()));
}
}
public BytebufManager getManager() {
return manager;
}
public net.minecraft.network.protocol.Packet<?> callPacketInEvent(Player player, net.minecraft.network.protocol.Packet<?> nmsPacket) {
if (listenerMap.isEmpty()) {
return nmsPacket;
}
PacketType type = toEnumType(nmsPacket.type());
if (type == null) {
return nmsPacket;
}
Packet packet = createBytebufPacket(type, nmsPacket);
for (PacketListener listener : listenerMap.keySet()) {
if (listenerMap.get(listener).isEnabled()) {
packet = listener.onPacketIn(player, packet);
packet.bytebuf().resetReaderIndex();
} else {
listenerMap.remove(listener);
}
}
return createNMSPacket(packet);
}
public net.minecraft.network.protocol.Packet<?> callPacketOutEvent(Player player, net.minecraft.network.protocol.Packet<?> nmsPacket) {
if (listenerMap.isEmpty()) {
return nmsPacket;
}
PacketType type = toEnumType(nmsPacket.type());
if (type == null) {
return nmsPacket;
}
Packet packet = createBytebufPacket(type, nmsPacket);
for (PacketListener listener : listenerMap.keySet()) {
if (listenerMap.get(listener).isEnabled()) {
packet = listener.onPacketOut(player, packet);
packet.bytebuf().resetReaderIndex();
} else {
listenerMap.remove(listener);
}
}
return createNMSPacket(packet);
}
public void applyPacketToPlayer(ServerPlayer player, Packet packet) {
Connection sp = player.connection.connection;
sp.send(createNMSPacket(packet));
}
public net.minecraft.network.protocol.Packet<?> createNMSPacket(Packet packet) {
StreamCodec<FriendlyByteBuf, net.minecraft.network.protocol.Packet<?>> codec = type2CodecMap.get(packet.type());
if (codec == null) {
throw new UnsupportedOperationException("This feature is not completely finished yet, packet type " + packet.type() + " is not supported temporary.");
}
return codec.decode(((WrappedBytebuf) packet.bytebuf()).getRegistryBuf());
}
@Nullable
private PacketType toEnumType(net.minecraft.network.protocol.PacketType<?> type) {
try {
return this.resultCache.get(type, () -> {
StringBuilder builder = new StringBuilder();
String bound = type.toString().split("/")[0];
String name = type.toString().split(":")[1];
builder.append(bound.substring(0, 1).toUpperCase()).append(bound.substring(1));
boolean flag = true;
for (int i = 0; i < name.length(); i++) {
if (flag) {
builder.append(name.substring(i, i + 1).toUpperCase());
flag = false;
continue;
}
if (name.charAt(i) == '_') {
flag = true;
} else {
builder.append(name.charAt(i));
}
}
try {
return PacketType.valueOf(builder.toString());
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
}
});
} catch (Exception ignore) {
return null;
}
}
public Packet createBytebufPacket(PacketType type, net.minecraft.network.protocol.Packet<?> nmsPacket) {
RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.buffer(), MinecraftServer.getServer().registryAccess());
StreamCodec<FriendlyByteBuf, net.minecraft.network.protocol.Packet<?>> codec = type2CodecMap.get(type);
if (codec == null) {
throw new UnsupportedOperationException("This feature is not completely finished yet, packet type " + type + " is not supported temporary.");
}
codec.encode(buf, nmsPacket);
return new Packet(type, new WrappedBytebuf(buf));
}
}

View File

@@ -0,0 +1,55 @@
package org.leavesmc.leaves.command;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CommandArgument {
public static final CommandArgument EMPTY = new CommandArgument();
private final List<CommandArgumentType<?>> argumentTypes;
private final List<List<String>> tabComplete;
private CommandArgument(CommandArgumentType<?>... argumentTypes) {
this.argumentTypes = List.of(argumentTypes);
this.tabComplete = new ArrayList<>();
for (int i = 0; i < argumentTypes.length; i++) {
tabComplete.add(new ArrayList<>());
}
}
public static CommandArgument of(CommandArgumentType<?>... argumentTypes) {
return new CommandArgument(argumentTypes);
}
public List<String> tabComplete(int n) {
if (tabComplete.size() > n) {
return tabComplete.get(n);
} else {
return List.of();
}
}
public CommandArgument setTabComplete(int index, List<String> list) {
tabComplete.set(index, list);
return this;
}
public CommandArgument setAllTabComplete(List<List<String>> tabComplete) {
this.tabComplete.clear();
this.tabComplete.addAll(tabComplete);
return this;
}
public CommandArgumentResult parse(int index, String @NotNull [] args) {
Object[] result = new Object[argumentTypes.size()];
Arrays.fill(result, null);
for (int i = index, j = 0; i < args.length && j < result.length; i++, j++) {
result[j] = argumentTypes.get(j).pasre(args[i]);
}
return new CommandArgumentResult(new ArrayList<>(Arrays.asList(result)));
}
}

View File

@@ -0,0 +1,69 @@
package org.leavesmc.leaves.command;
import net.minecraft.core.BlockPos;
import org.bukkit.util.Vector;
import java.util.List;
import java.util.Objects;
public class CommandArgumentResult {
private final List<Object> result;
public CommandArgumentResult(List<Object> result) {
this.result = result;
}
public int readInt(int def) {
return Objects.requireNonNullElse(read(Integer.class), def);
}
public double readDouble(double def) {
return Objects.requireNonNullElse(read(Double.class), def);
}
public float readFloat(float def) {
return Objects.requireNonNullElse(read(Float.class), def);
}
public String readString(String def) {
return Objects.requireNonNullElse(read(String.class), def);
}
public boolean readBoolean(boolean def) {
return Objects.requireNonNullElse(read(Boolean.class), def);
}
public BlockPos readPos() {
Integer[] pos = {read(Integer.class), read(Integer.class), read(Integer.class)};
for (Integer po : pos) {
if (po == null) {
return null;
}
}
return new BlockPos(pos[0], pos[1], pos[2]);
}
public Vector readVector() {
Double[] pos = {read(Double.class), read(Double.class), read(Double.class)};
for (Double po : pos) {
if (po == null) {
return null;
}
}
return new Vector(pos[0], pos[1], pos[2]);
}
public <T> T read(Class<T> tClass) {
if (result.isEmpty()) {
return null;
}
Object obj = result.remove(0);
if (tClass.isInstance(obj)) {
return tClass.cast(obj);
} else {
return null;
}
}
}

View File

@@ -0,0 +1,55 @@
package org.leavesmc.leaves.command;
import org.jetbrains.annotations.NotNull;
public abstract class CommandArgumentType<E> {
public static final CommandArgumentType<Integer> INTEGER = new CommandArgumentType<>() {
@Override
public Integer pasre(@NotNull String arg) {
try {
return Integer.parseInt(arg);
} catch (NumberFormatException e) {
return null;
}
}
};
public static final CommandArgumentType<Double> DOUBLE = new CommandArgumentType<>() {
@Override
public Double pasre(@NotNull String arg) {
try {
return Double.parseDouble(arg);
} catch (NumberFormatException e) {
return null;
}
}
};
public static final CommandArgumentType<Float> FLOAT = new CommandArgumentType<>() {
@Override
public Float pasre(@NotNull String arg) {
try {
return Float.parseFloat(arg);
} catch (NumberFormatException e) {
return null;
}
}
};
public static final CommandArgumentType<String> STRING = new CommandArgumentType<>() {
@Override
public String pasre(@NotNull String arg) {
return arg;
}
};
public static final CommandArgumentType<Boolean> BOOLEAN = new CommandArgumentType<>() {
@Override
public Boolean pasre(@NotNull String arg) {
return Boolean.parseBoolean(arg);
}
};
public abstract E pasre(@NotNull String arg);
}

View File

@@ -0,0 +1,121 @@
package org.leavesmc.leaves.command;
import it.unimi.dsi.fastutil.Pair;
import net.minecraft.Util;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.command.subcommands.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
public final class LeavesCommand extends Command {
static final String BASE_PERM = "bukkit.command.leaves.";
// subcommand label -> subcommand
private static final Map<String, LeavesSubcommand> SUBCOMMANDS = Util.make(() -> {
final Map<Set<String>, LeavesSubcommand> commands = new HashMap<>();
commands.put(Set.of("config"), new ConfigCommand());
commands.put(Set.of("update"), new UpdateCommand());
commands.put(Set.of("peaceful"), new PeacefulModeSwitchCommand());
commands.put(Set.of("counter"), new CounterCommand());
commands.put(Set.of("reload"), new ReloadCommand());
return commands.entrySet().stream()
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
});
private static final Set<String> COMPLETABLE_SUBCOMMANDS = SUBCOMMANDS.entrySet().stream().filter(entry -> entry.getValue().tabCompletes()).map(Map.Entry::getKey).collect(Collectors.toSet());
public LeavesCommand(final String name) {
super(name);
this.description = "Leaves related commands";
this.usageMessage = "/leaves [" + String.join(" | ", SUBCOMMANDS.keySet()) + "]";
final List<String> permissions = new ArrayList<>();
permissions.add("bukkit.command.leaves");
permissions.addAll(SUBCOMMANDS.keySet().stream().map(s -> BASE_PERM + s).toList());
this.setPermission(String.join(";", permissions));
final PluginManager pluginManager = Bukkit.getServer().getPluginManager();
for (final String perm : permissions) {
if (pluginManager.getPermission(perm) == null) {
pluginManager.addPermission(new Permission(perm, PermissionDefault.OP));
}
}
}
private static boolean testPermission(final CommandSender sender, final String permission) {
if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.leaves")) {
return true;
}
sender.sendMessage(Bukkit.permissionMessage());
return false;
}
@NotNull
@Override
public List<String> tabComplete(final @NotNull CommandSender sender, final @NotNull String alias, final String[] args, final @Nullable Location location) throws IllegalArgumentException {
if (args.length <= 1) {
return LeavesCommandUtil.getListMatchingLast(sender, args, COMPLETABLE_SUBCOMMANDS);
}
final @Nullable Pair<String, LeavesSubcommand> subCommand = resolveCommand(args[0]);
if (subCommand != null) {
return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length));
}
return Collections.emptyList();
}
@Override
public boolean execute(final @NotNull CommandSender sender, final @NotNull String commandLabel, final String[] args) {
if (!testPermission(sender)) {
return true;
}
if (args.length == 0) {
sender.sendMessage(text("Usage: " + this.usageMessage, RED));
return false;
}
final Pair<String, LeavesSubcommand> subCommand = resolveCommand(args[0]);
if (subCommand == null) {
sender.sendMessage(text("Usage: " + this.usageMessage, RED));
return false;
}
if (!testPermission(sender, subCommand.first())) {
return true;
}
final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length);
return subCommand.second().execute(sender, subCommand.first(), choppedArgs);
}
@Nullable
private static Pair<String, LeavesSubcommand> resolveCommand(String label) {
label = label.toLowerCase(Locale.ENGLISH);
LeavesSubcommand subCommand = SUBCOMMANDS.get(label);
if (subCommand != null) {
return Pair.of(label, subCommand);
}
return null;
}
}

View File

@@ -0,0 +1,81 @@
package org.leavesmc.leaves.command;
import com.google.common.base.Functions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import net.minecraft.resources.ResourceLocation;
import org.bukkit.command.CommandSender;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@DefaultQualifier(NonNull.class)
public class LeavesCommandUtil {
private LeavesCommandUtil() {
}
// Code from Mojang - copyright them
public static List<String> getListMatchingLast(
final CommandSender sender,
final String[] args,
final String... matches
) {
return getListMatchingLast(sender, args, Arrays.asList(matches));
}
public static boolean matches(final String s, final String s1) {
return s1.regionMatches(true, 0, s, 0, s.length());
}
public static List<String> getListMatchingLast(
final CommandSender sender,
final String[] strings,
final Collection<?> collection
) {
return getListMatchingLast(sender, strings, collection, LeavesCommand.BASE_PERM, "bukkit.command.leaves");
}
public static List<String> getListMatchingLast(
final CommandSender sender,
final String[] strings,
final Collection<?> collection,
final String basePermission,
final String overridePermission
) {
String last = strings[strings.length - 1];
ArrayList<String> results = Lists.newArrayList();
if (!collection.isEmpty()) {
Iterator iterator = Iterables.transform(collection, Functions.toStringFunction()).iterator();
while (iterator.hasNext()) {
String s1 = (String) iterator.next();
if (matches(last, s1) && (sender.hasPermission(basePermission + s1) || sender.hasPermission(overridePermission))) {
results.add(s1);
}
}
if (results.isEmpty()) {
iterator = collection.iterator();
while (iterator.hasNext()) {
Object object = iterator.next();
if (object instanceof ResourceLocation && matches(last, ((ResourceLocation) object).getPath())) {
results.add(String.valueOf(object));
}
}
}
}
return results;
}
// end copy stuff
}

View File

@@ -0,0 +1,18 @@
package org.leavesmc.leaves.command;
import org.bukkit.command.CommandSender;
import java.util.Collections;
import java.util.List;
public interface LeavesSubcommand {
boolean execute(CommandSender sender, String subCommand, String[] args);
default List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
return Collections.emptyList();
}
default boolean tabCompletes() {
return true;
}
}

View File

@@ -0,0 +1,52 @@
package org.leavesmc.leaves.command;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesConfig;
import java.util.List;
public class NoBlockUpdateCommand extends Command {
private static boolean noBlockUpdate = false;
public NoBlockUpdateCommand(@NotNull String name) {
super(name);
this.description = "No Block Update Command";
this.usageMessage = "/blockupdate";
this.setPermission("bukkit.command.blockupdate");
final PluginManager pluginManager = Bukkit.getServer().getPluginManager();
if (pluginManager.getPermission("bukkit.command.blockupdate") == null) {
pluginManager.addPermission(new Permission("bukkit.command.blockupdate", PermissionDefault.OP));
}
}
@Override
public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, String @NotNull [] args) throws IllegalArgumentException {
return List.of();
}
@Override
public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, String @NotNull [] args) {
if (!testPermission(sender)) return true;
noBlockUpdate = !noBlockUpdate;
Bukkit.broadcast(Component.join(JoinConfiguration.noSeparators(),
Component.text("Block update status: ", NamedTextColor.GRAY),
Component.text(!noBlockUpdate, noBlockUpdate ? NamedTextColor.AQUA : NamedTextColor.GRAY)
), "bukkit.command.blockupdate");
return true;
}
public static boolean isNoBlockUpdate() {
return LeavesConfig.modify.noBlockUpdateCommand && noBlockUpdate;
}
}

View File

@@ -0,0 +1,83 @@
package org.leavesmc.leaves.command.subcommands;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import org.leavesmc.leaves.command.LeavesCommandUtil;
import org.leavesmc.leaves.command.LeavesSubcommand;
import org.leavesmc.leaves.config.GlobalConfigManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ConfigCommand implements LeavesSubcommand {
@Override
public boolean execute(CommandSender sender, String subCommand, String[] args) {
if (args.length < 1) {
sender.sendMessage(Component.text("Leaves Config", NamedTextColor.GRAY));
return true;
}
GlobalConfigManager.VerifiedConfig verifiedConfig = GlobalConfigManager.getVerifiedConfig(args[0]);
if (verifiedConfig == null) {
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Config ", NamedTextColor.GRAY),
Component.text(args[0], NamedTextColor.RED),
Component.text(" is Not Found.", NamedTextColor.GRAY)
));
return true;
}
if (args.length > 1) {
try {
verifiedConfig.set(args[1]);
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Config ", NamedTextColor.GRAY),
Component.text(args[0], NamedTextColor.AQUA),
Component.text(" changed to ", NamedTextColor.GRAY),
Component.text(verifiedConfig.getString(), NamedTextColor.AQUA)
));
} catch (IllegalArgumentException exception) {
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Config ", NamedTextColor.GRAY),
Component.text(args[0], NamedTextColor.RED),
Component.text(" modify error by ", NamedTextColor.GRAY),
Component.text(exception.getMessage(), NamedTextColor.RED)
));
}
} else {
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Config ", NamedTextColor.GRAY),
Component.text(args[0], NamedTextColor.AQUA),
Component.text(" value is ", NamedTextColor.GRAY),
Component.text(verifiedConfig.getString(), NamedTextColor.AQUA)
));
}
return true;
}
@Override
public List<String> tabComplete(CommandSender sender, String subCommand, String[] args) {
switch (args.length) {
case 1 -> {
List<String> list = new ArrayList<>(GlobalConfigManager.getVerifiedConfigPaths());
return LeavesCommandUtil.getListMatchingLast(sender, args, list);
}
case 2 -> {
GlobalConfigManager.VerifiedConfig verifiedConfig = GlobalConfigManager.getVerifiedConfig(args[0]);
if (verifiedConfig != null) {
return LeavesCommandUtil.getListMatchingLast(sender, args, verifiedConfig.validator().valueSuggest());
} else {
return Collections.singletonList("<ERROR CONFIG>");
}
}
}
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,121 @@
package org.leavesmc.leaves.command.subcommands;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.DyeColor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.command.LeavesCommandUtil;
import org.leavesmc.leaves.command.LeavesSubcommand;
import org.leavesmc.leaves.util.HopperCounter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class CounterCommand implements LeavesSubcommand {
@Override
public boolean execute(CommandSender sender, String subCommand, String[] args) {
if (!LeavesConfig.modify.hopperCounter) {
return false;
}
if (args.length < 1) {
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Hopper Counter: ", NamedTextColor.GRAY),
Component.text(HopperCounter.isEnabled(), HopperCounter.isEnabled() ? NamedTextColor.AQUA : NamedTextColor.GRAY)
));
return true;
}
if (!HopperCounter.isEnabled()) {
if (args[0].equals("enable")) {
HopperCounter.setEnabled(true);
sender.sendMessage(Component.text("Hopper Counter now is enabled", NamedTextColor.AQUA));
} else {
sender.sendMessage(Component.text("Hopper Counter is not enabled", NamedTextColor.RED));
}
return true;
}
DyeColor color = DyeColor.byName(args[0], null);
if (color != null) {
HopperCounter counter = HopperCounter.getCounter(color);
if (args.length < 2) {
displayCounter(sender, counter, false);
} else {
switch (args[1]) {
case "reset" -> {
counter.reset(MinecraftServer.getServer());
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Restarted "),
Component.text(color.getName(), TextColor.color(color.getTextColor())),
Component.text(" counter")
));
}
case "realtime" -> displayCounter(sender, counter, true);
}
}
return true;
}
switch (args[0]) {
case "reset" -> {
HopperCounter.resetAll(MinecraftServer.getServer(), false);
sender.sendMessage(Component.text("Restarted all counters"));
}
case "disable" -> {
HopperCounter.setEnabled(false);
HopperCounter.resetAll(MinecraftServer.getServer(), true);
sender.sendMessage(Component.text("Hopper Counter now is disabled", NamedTextColor.GRAY));
}
}
return true;
}
private void displayCounter(CommandSender sender, @NotNull HopperCounter counter, boolean realTime) {
for (Component component : counter.format(MinecraftServer.getServer(), realTime)) {
sender.sendMessage(component);
}
}
@Override
public List<String> tabComplete(CommandSender sender, String subCommand, String[] args) {
if (!LeavesConfig.modify.hopperCounter) {
return Collections.emptyList();
}
switch (args.length) {
case 1 -> {
if (!HopperCounter.isEnabled()) {
return Collections.singletonList("enable");
}
List<String> list = new ArrayList<>(Arrays.stream(DyeColor.values()).map(DyeColor::getName).toList());
list.add("reset");
list.add("disable");
return LeavesCommandUtil.getListMatchingLast(sender, args, list);
}
case 2 -> {
if (DyeColor.byName(args[0], null) != null) {
return LeavesCommandUtil.getListMatchingLast(sender, args, "reset", "realtime");
}
}
}
return Collections.emptyList();
}
@Override
public boolean tabCompletes() {
return LeavesConfig.modify.hopperCounter;
}
}

View File

@@ -0,0 +1,87 @@
package org.leavesmc.leaves.command.subcommands;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.NaturalSpawner;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Player;
import org.leavesmc.leaves.command.LeavesCommandUtil;
import org.leavesmc.leaves.command.LeavesSubcommand;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PeacefulModeSwitchCommand implements LeavesSubcommand {
@Override
public boolean execute(CommandSender sender, String subCommand, String[] args) {
World world;
if (args.length == 0) {
if (sender instanceof Player player) {
world = player.getWorld();
} else {
sender.sendMessage(Component.text("Must specify a world! ex: '/leaves peaceful world'", NamedTextColor.RED));
return true;
}
} else {
final String input = args[0];
final World inputWorld = Bukkit.getWorld(input);
if (inputWorld == null) {
sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED));
return true;
} else {
world = inputWorld;
}
}
final ServerLevel level = ((CraftWorld) world).getHandle();
int chunks = 0;
if (level.chunkSource.getLastSpawnState() != null) {
chunks = level.chunkSource.getLastSpawnState().getSpawnableChunkCount();
}
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Peaceful Mode Switch for world: "),
Component.text(world.getName(), NamedTextColor.AQUA)
));
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Refuses per "),
Component.text(level.chunkSource.peacefulModeSwitchTick, level.chunkSource.peacefulModeSwitchTick > 0 ? NamedTextColor.AQUA : NamedTextColor.GRAY),
Component.text(" tick")
));
int count = level.chunkSource.peacefulModeSwitchCount;
int limit = NaturalSpawner.globalLimitForCategory(level, MobCategory.MONSTER, chunks);
NamedTextColor color = count >= limit ? NamedTextColor.AQUA : NamedTextColor.GRAY;
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Now count "),
Component.text(count, color),
Component.text("/", color),
Component.text(limit, color)
));
return true;
}
@Override
public List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
return LeavesCommandUtil.getListMatchingLast(sender, args, this.suggestPeacefulModeSwitch(args));
}
private List<String> suggestPeacefulModeSwitch(final String[] args) {
if (args.length == 1) {
return new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList());
}
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,22 @@
package org.leavesmc.leaves.command.subcommands;
import net.minecraft.server.MinecraftServer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.command.LeavesSubcommand;
import java.io.File;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
public class ReloadCommand implements LeavesSubcommand {
@Override
public boolean execute(CommandSender sender, String subCommand, String[] args) {
MinecraftServer server = MinecraftServer.getServer();
LeavesConfig.init((File) server.options.valueOf("leaves-settings"));
Command.broadcastCommandMessage(sender, text("Leaves config reload complete.", GREEN));
return false;
}
}

View File

@@ -0,0 +1,21 @@
package org.leavesmc.leaves.command.subcommands;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.leavesmc.leaves.command.LeavesSubcommand;
import org.leavesmc.leaves.util.LeavesUpdateHelper;
public class UpdateCommand implements LeavesSubcommand {
@Override
public boolean execute(CommandSender sender, String subCommand, String[] args) {
sender.sendMessage(ChatColor.GRAY + "Trying to update Leaves, see the console for more info.");
LeavesUpdateHelper.tryUpdateLeaves();
return true;
}
@Override
public boolean tabCompletes() {
return false;
}
}

View File

@@ -0,0 +1,20 @@
package org.leavesmc.leaves.config;
public interface ConfigConverter<E> {
E convert(String value) throws IllegalArgumentException;
@SuppressWarnings("unchecked")
default E loadConvert(Object value) throws IllegalArgumentException {
try {
return (E) value;
} catch (ClassCastException e) {
throw new IllegalArgumentException(e);
}
}
default Object saveConvert(E value) {
return value;
}
Class<E> getFieldClass();
}

View File

@@ -0,0 +1,15 @@
package org.leavesmc.leaves.config;
import java.util.List;
public interface ConfigValidator<E> extends ConfigConverter<E> {
default void verify(E old, E value) throws IllegalArgumentException {
}
default List<String> valueSuggest() {
return List.of("<value>");
}
default void runAfterLoader(E value, boolean firstLoad) {
}
}

View File

@@ -0,0 +1,108 @@
package org.leavesmc.leaves.config;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public abstract class ConfigValidatorImpl<E> implements ConfigValidator<E> {
private Class<E> fieldClass;
@SuppressWarnings("unchecked")
public ConfigValidatorImpl() {
Type superClass = getClass().getGenericSuperclass();
if (superClass instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) superClass).getActualTypeArguments();
if (actualTypeArguments[0] instanceof Class) {
this.fieldClass = (Class<E>) actualTypeArguments[0];
}
}
}
@Override
public Class<E> getFieldClass() {
return fieldClass;
}
public static class BooleanConfigValidator extends ConfigValidatorImpl<Boolean> {
@Override
public Boolean convert(String value) throws IllegalArgumentException {
return Boolean.parseBoolean(value);
}
@Override
public List<String> valueSuggest() {
return List.of("false", "true");
}
}
public static class IntConfigValidator extends ConfigValidatorImpl<Integer> {
@Override
public Integer convert(String value) throws IllegalArgumentException {
return Integer.parseInt(value);
}
}
public static class StringConfigValidator extends ConfigValidatorImpl<String> {
@Override
public String convert(String value) throws IllegalArgumentException {
return value;
}
}
public static class DoubleConfigValidator extends ConfigValidatorImpl<Double> {
@Override
public Double convert(String value) throws IllegalArgumentException {
return Double.parseDouble(value);
}
}
public abstract static class ListConfigValidator<E> extends ConfigValidatorImpl<List<E>> {
public static class STRING extends ListConfigValidator<String> {
}
@Override
public List<E> convert(String value) throws IllegalArgumentException {
throw new IllegalArgumentException("not support"); // TODO
}
}
public abstract static class EnumConfigValidator<E extends Enum<E>> extends ConfigValidatorImpl<E> {
private final List<String> enumValues;
public EnumConfigValidator() {
super();
this.enumValues = new ArrayList<>() {{
for (E e : getFieldClass().getEnumConstants()) {
add(e.name().toLowerCase(Locale.ROOT));
}
}};
}
@Override
public E convert(@NotNull String value) throws IllegalArgumentException {
return Enum.valueOf(getFieldClass(), value.toUpperCase(Locale.ROOT));
}
@Override
public E loadConvert(@NotNull Object value) throws IllegalArgumentException {
return this.convert(value.toString());
}
@Override
public Object saveConvert(@NotNull E value) {
return value.toString().toUpperCase(Locale.ROOT);
}
@Override
public List<String> valueSuggest() {
return enumValues;
}
}
}

View File

@@ -0,0 +1,16 @@
package org.leavesmc.leaves.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalConfig {
String value();
boolean lock() default false;
Class<? extends ConfigValidator<?>> validator() default ConfigValidatorImpl.BooleanConfigValidator.class;
}

View File

@@ -0,0 +1,12 @@
package org.leavesmc.leaves.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalConfigCategory {
String value();
}

View File

@@ -0,0 +1,84 @@
package org.leavesmc.leaves.config;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesConfig;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import static org.leavesmc.leaves.config.GlobalConfigManager.CONFIG_START;
@SuppressWarnings("CallToPrintStackTrace")
public class GlobalConfigCreator {
private static YamlConfiguration config;
@SuppressWarnings("ResultOfMethodCallIgnored")
public static void main(String[] args) {
config = new YamlConfiguration();
config.options().setHeader(Collections.singletonList(LeavesConfig.CONFIG_HEADER));
config.set("config-version", LeavesConfig.CURRENT_CONFIG_VERSION);
for (Field field : LeavesConfig.class.getDeclaredFields()) {
initField(field, null, CONFIG_START);
}
config.set("settings.protocol.xaero-map-server-id", 0);
try {
File file = new File("leaves.yml");
if (file.exists()) {
file.delete();
}
file.createNewFile();
config.save(file);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void initCategory(@NotNull Field categoryField, @NotNull GlobalConfigCategory globalCategory, @Nullable Object upstreamField, @NotNull String upstreamPath) {
try {
Object category = categoryField.get(upstreamField);
String categoryPath = upstreamPath + globalCategory.value() + ".";
for (Field field : categoryField.getType().getDeclaredFields()) {
initField(field, category, categoryPath);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void initField(@NotNull Field field, @Nullable Object upstreamField, @NotNull String upstreamPath) {
if (upstreamField != null || Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
GlobalConfig globalConfig = field.getAnnotation(GlobalConfig.class);
if (globalConfig != null) {
initConfig(field, globalConfig, upstreamField, upstreamPath);
return;
}
GlobalConfigCategory globalCategory = field.getType().getAnnotation(GlobalConfigCategory.class);
if (globalCategory != null) {
initCategory(field, globalCategory, upstreamField, upstreamPath);
}
}
}
private static void initConfig(@NotNull Field field, GlobalConfig globalConfig, @Nullable Object upstreamField, @NotNull String upstreamPath) {
try {
GlobalConfigManager.VerifiedConfig verifiedConfig = GlobalConfigManager.VerifiedConfig.build(globalConfig, field, upstreamField, upstreamPath);
config.set(verifiedConfig.path(), verifiedConfig.validator().saveConvert(field.get(upstreamField)));
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,232 @@
package org.leavesmc.leaves.config;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.LeavesLogger;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class GlobalConfigManager {
public final static String CONFIG_START = "settings.";
private static boolean firstLoad = true;
private static final Map<String, VerifiedConfig> verifiedConfigs = new HashMap<>();
public static void init() {
verifiedConfigs.clear();
for (Field field : LeavesConfig.class.getDeclaredFields()) {
initField(field, null, CONFIG_START);
}
verifiedConfigs.forEach((path, config) -> config.validator.runAfterLoader(config.get(), firstLoad));
firstLoad = false;
LeavesConfig.save();
}
private static void initCategory(@NotNull Field categoryField, @NotNull GlobalConfigCategory globalCategory, @Nullable Object upstreamField, @NotNull String upstreamPath) {
try {
Object category = categoryField.get(upstreamField);
String categoryPath = upstreamPath + globalCategory.value() + ".";
for (Field field : categoryField.getType().getDeclaredFields()) {
initField(field, category, categoryPath);
}
} catch (Exception e) {
LeavesLogger.LOGGER.severe("Failure to load leaves config" + upstreamPath, e);
}
}
private static void initField(@NotNull Field field, @Nullable Object upstreamField, @NotNull String upstreamPath) {
if (upstreamField != null || Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
for (RemovedConfig config : field.getAnnotationsByType(RemovedConfig.class)) {
RemovedVerifiedConfig.build(config, field, upstreamField).run();
}
GlobalConfig globalConfig = field.getAnnotation(GlobalConfig.class);
if (globalConfig != null) {
initConfig(field, globalConfig, upstreamField, upstreamPath);
return;
}
GlobalConfigCategory globalCategory = field.getType().getAnnotation(GlobalConfigCategory.class);
if (globalCategory != null) {
initCategory(field, globalCategory, upstreamField, upstreamPath);
}
}
}
private static void initConfig(@NotNull Field field, GlobalConfig globalConfig, @Nullable Object upstreamField, @NotNull String upstreamPath) {
try {
VerifiedConfig verifiedConfig = VerifiedConfig.build(globalConfig, field, upstreamField, upstreamPath);
if (globalConfig.lock() && !firstLoad) {
verifiedConfigs.put(verifiedConfig.path.substring(CONFIG_START.length()), verifiedConfig);
}
ConfigValidator<? super Object> validator = verifiedConfig.validator;
Object defValue = validator.saveConvert(field.get(upstreamField));
LeavesConfig.config.addDefault(verifiedConfig.path, defValue);
try {
Object savedValue = LeavesConfig.config.get(verifiedConfig.path);
if (savedValue == null) {
throw new IllegalArgumentException("?");
}
if (savedValue.getClass() != validator.getFieldClass()) {
savedValue = validator.loadConvert(savedValue);
}
validator.verify(null, savedValue);
field.set(upstreamField, savedValue);
} catch (IllegalArgumentException | ClassCastException e) {
LeavesConfig.config.set(verifiedConfig.path, defValue);
LeavesLogger.LOGGER.warning(e.getMessage() + ", reset to " + defValue);
}
verifiedConfigs.put(verifiedConfig.path.substring(CONFIG_START.length()), verifiedConfig);
} catch (Exception e) {
LeavesLogger.LOGGER.severe("Failure to load leaves config", e);
throw new RuntimeException();
}
}
public static VerifiedConfig getVerifiedConfig(String path) {
return verifiedConfigs.get(path);
}
@Contract(pure = true)
public static @NotNull Set<String> getVerifiedConfigPaths() {
return verifiedConfigs.keySet();
}
public record RemovedVerifiedConfig(ConfigConverter<?> convert, boolean transform, Field field, Object upstreamField, String path) {
public void run() {
if (transform) {
if (LeavesConfig.config.contains(path)) {
Object savedValue = LeavesConfig.config.get(path);
if (savedValue != null) {
try {
if (savedValue.getClass() != convert.getFieldClass()) {
savedValue = convert.loadConvert(savedValue);
}
field.set(upstreamField, savedValue);
} catch (IllegalAccessException | IllegalArgumentException e) {
LeavesLogger.LOGGER.warning("Failure to load leaves config" + path, e);
}
} else {
LeavesLogger.LOGGER.warning("Failed to convert saved value for " + path + ", reset to default");
}
}
}
LeavesConfig.config.set(path, null);
}
@Contract("_, _, _ -> new")
public static @NotNull RemovedVerifiedConfig build(@NotNull RemovedConfig config, @NotNull Field field, @Nullable Object upstreamField) {
StringBuilder path = new StringBuilder("settings.");
for (String category : config.category()) {
path.append(category).append(".");
}
path.append(config.name());
ConfigConverter<?> converter = null;
try {
Constructor<? extends ConfigConverter<?>> constructor = config.convert().getDeclaredConstructor();
constructor.setAccessible(true);
converter = constructor.newInstance();
} catch (Exception e) {
LeavesLogger.LOGGER.warning("Failure to load leaves config" + path, e);
}
return new RemovedVerifiedConfig(converter, config.transform(), field, upstreamField, path.toString());
}
}
public record VerifiedConfig(ConfigValidator<? super Object> validator, boolean lock, Field field, Object upstreamField, String path) {
public void set(String stringValue) throws IllegalArgumentException {
Object value;
try {
value = validator.convert(stringValue);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("value parse error: " + e.getMessage());
}
validator.verify(this.get(), value);
try {
LeavesConfig.config.set(path, validator.saveConvert(value));
LeavesConfig.save();
if (lock) {
throw new IllegalArgumentException("locked, will load after restart");
}
field.set(upstreamField, value);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
public Object get() {
try {
return field.get(upstreamField);
} catch (IllegalAccessException e) {
LeavesLogger.LOGGER.severe("Failure to get " + path + " value", e);
return "<VALUE ERROR>";
}
}
public String getString() {
Object value = this.get();
Object savedValue = LeavesConfig.config.get(path);
try {
if (savedValue != null) {
if (validator.getFieldClass() != savedValue.getClass()) {
savedValue = validator.loadConvert(savedValue);
}
if (!savedValue.equals(value)) {
return value.toString() + "(" + savedValue + " after restart)";
}
}
} catch (IllegalArgumentException e) {
LeavesLogger.LOGGER.severe("Failure to get " + path + " value", e);
}
return value.toString();
}
@SuppressWarnings("unchecked")
@NotNull
@Contract("_, _, _, _ -> new")
public static VerifiedConfig build(@NotNull GlobalConfig config, @NotNull Field field, @Nullable Object upstreamField, @NotNull String upstreamPath) {
String path = upstreamPath + config.value();
ConfigValidator<? super Object> validator;
try {
Constructor<? extends ConfigValidator<?>> constructor = config.validator().getDeclaredConstructor();
constructor.setAccessible(true);
validator = (ConfigValidator<? super Object>) constructor.newInstance();
} catch (Exception e) {
LeavesLogger.LOGGER.severe("Failure to load leaves config" + path, e);
throw new RuntimeException();
}
return new VerifiedConfig(validator, config.lock(), field, upstreamField, path);
}
}
}

View File

@@ -0,0 +1,26 @@
package org.leavesmc.leaves.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(RemovedConfig.Array.class)
public @interface RemovedConfig {
String name();
String[] category();
boolean transform() default false;
Class<? extends ConfigConverter<?>> convert() default ConfigValidatorImpl.BooleanConfigValidator.class;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Array {
RemovedConfig[] value();
}
}

View File

@@ -0,0 +1,94 @@
package org.leavesmc.leaves.entity;
import com.google.common.base.Preconditions;
import org.bukkit.Location;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.BotList;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.BotAction;
import org.leavesmc.leaves.bot.agent.actions.CraftBotAction;
import org.leavesmc.leaves.entity.botaction.LeavesBotAction;
import org.leavesmc.leaves.event.bot.BotActionStopEvent;
import org.leavesmc.leaves.event.bot.BotRemoveEvent;
import java.util.UUID;
public class CraftBot extends CraftPlayer implements Bot {
public CraftBot(CraftServer server, ServerBot entity) {
super(server, entity);
}
@Override
public String getSkinName() {
return this.getHandle().createState.skinName();
}
@Override
public @NotNull String getRealName() {
return this.getHandle().createState.realName();
}
@Override
public @Nullable UUID getCreatePlayerUUID() {
return this.getHandle().createPlayer;
}
@Override
public void addAction(@NotNull LeavesBotAction action) {
this.getHandle().addBotAction(CraftBotAction.asInternalCopy(action), null);
}
@Override
public LeavesBotAction getAction(int index) {
return CraftBotAction.asAPICopy(this.getHandle().getBotActions().get(index));
}
@Override
public int getActionSize() {
return this.getHandle().getBotActions().size();
}
@Override
public void stopAction(int index) {
this.getHandle().getBotActions().get(index).stop(this.getHandle(), BotActionStopEvent.Reason.PLUGIN);
}
@Override
public void stopAllActions() {
for (BotAction<?> action : this.getHandle().getBotActions()) {
action.stop(this.getHandle(), BotActionStopEvent.Reason.PLUGIN);
}
}
@Override
public boolean remove(boolean save) {
BotList.INSTANCE.removeBot(this.getHandle(), BotRemoveEvent.RemoveReason.PLUGIN, null, save);
return true;
}
@Override
public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) {
Preconditions.checkArgument(location != null, "location cannot be null");
Preconditions.checkState(location.getWorld().equals(this.getWorld()), "[Leaves] Fakeplayers do not support changing world, Please use leaves fakeplayer-api instead!");
return super.teleport(location, cause, flags);
}
@Override
public ServerBot getHandle() {
return (ServerBot) entity;
}
public void setHandle(final ServerBot entity) {
super.setHandle(entity);
}
@Override
public String toString() {
return "CraftBot{" + "name=" + getName() + '}';
}
}

View File

@@ -0,0 +1,80 @@
package org.leavesmc.leaves.entity;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import net.minecraft.server.MinecraftServer;
import org.bukkit.Location;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.BotCreateState;
import org.leavesmc.leaves.bot.BotList;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.bot.agent.Actions;
import org.leavesmc.leaves.bot.agent.BotAction;
import org.leavesmc.leaves.bot.agent.actions.CraftCustomBotAction;
import org.leavesmc.leaves.entity.botaction.CustomBotAction;
import org.leavesmc.leaves.event.bot.BotCreateEvent;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
public class CraftBotManager implements BotManager {
private final BotList botList;
private final Collection<Bot> botViews;
public CraftBotManager() {
this.botList = MinecraftServer.getServer().getBotList();
this.botViews = Collections.unmodifiableList(Lists.transform(botList.bots, new Function<ServerBot, CraftBot>() {
@Override
public CraftBot apply(ServerBot bot) {
return bot.getBukkitEntity();
}
}));
}
@Override
public @Nullable Bot getBot(@NotNull UUID uuid) {
ServerBot bot = botList.getBot(uuid);
if (bot != null) {
return bot.getBukkitEntity();
} else {
return null;
}
}
@Override
public @Nullable Bot getBot(@NotNull String name) {
ServerBot bot = botList.getBotByName(name);
if (bot != null) {
return bot.getBukkitEntity();
} else {
return null;
}
}
@Override
public Collection<Bot> getBots() {
return botViews;
}
@Override
public boolean registerCustomBotAction(String name, CustomBotAction action) {
return Actions.register(new CraftCustomBotAction(name, action));
}
@Override
public boolean unregisterCustomBotAction(String name) {
BotAction<?> action = Actions.getForName(name);
if (action instanceof CraftCustomBotAction) {
return Actions.unregister(name);
}
return false;
}
@Override
public BotCreator botCreator(@NotNull String realName, @NotNull Location location) {
return BotCreateState.builder(realName, location).createReason(BotCreateEvent.CreateReason.PLUGIN);
}
}

View File

@@ -0,0 +1,73 @@
package org.leavesmc.leaves.entity;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.replay.ServerPhotographer;
import java.io.File;
public class CraftPhotographer extends CraftPlayer implements Photographer {
public CraftPhotographer(CraftServer server, ServerPhotographer entity) {
super(server, entity);
}
@Override
public void stopRecording() {
this.stopRecording(true);
}
@Override
public void stopRecording(boolean async) {
this.stopRecording(async, true);
}
@Override
public void stopRecording(boolean async, boolean save) {
this.getHandle().remove(async, save);
}
@Override
public void pauseRecording() {
this.getHandle().pauseRecording();
}
@Override
public void resumeRecording() {
this.getHandle().resumeRecording();
}
@Override
public void setRecordFile(@NotNull File file) {
this.getHandle().setSaveFile(file);
}
@Override
public void setFollowPlayer(@Nullable Player player) {
ServerPlayer serverPlayer = player != null ? ((CraftPlayer) player).getHandle() : null;
this.getHandle().setFollowPlayer(serverPlayer);
}
@Override
public @NotNull String getId() {
return this.getHandle().createState.id;
}
@Override
public ServerPhotographer getHandle() {
return (ServerPhotographer) entity;
}
public void setHandle(final ServerPhotographer entity) {
super.setHandle(entity);
}
@Override
public String toString() {
return "CraftPhotographer{" + "name=" + getName() + '}';
}
}

View File

@@ -0,0 +1,84 @@
package org.leavesmc.leaves.entity;
import com.google.common.collect.Lists;
import org.bukkit.Location;
import org.bukkit.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.bot.ServerBot;
import org.leavesmc.leaves.replay.BukkitRecorderOption;
import org.leavesmc.leaves.replay.RecorderOption;
import org.leavesmc.leaves.replay.ServerPhotographer;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
public class CraftPhotographerManager implements PhotographerManager {
private final Collection<Photographer> photographerViews = Collections.unmodifiableList(Lists.transform(ServerPhotographer.getPhotographers(), ServerPhotographer::getBukkitPlayer));
@Override
public @Nullable Photographer getPhotographer(@NotNull UUID uuid) {
ServerPhotographer photographer = ServerPhotographer.getPhotographer(uuid);
if (photographer != null) {
return photographer.getBukkitPlayer();
}
return null;
}
@Override
public @Nullable Photographer getPhotographer(@NotNull String id) {
ServerPhotographer photographer = ServerPhotographer.getPhotographer(id);
if (photographer != null) {
return photographer.getBukkitPlayer();
}
return null;
}
@Override
public @Nullable Photographer createPhotographer(@NotNull String id, @NotNull Location location) {
ServerPhotographer photographer = new ServerPhotographer.PhotographerCreateState(location, id, RecorderOption.createDefaultOption()).createSync();
if (photographer != null) {
return photographer.getBukkitPlayer();
}
return null;
}
@Override
public @Nullable Photographer createPhotographer(@NotNull String id, @NotNull Location location, @NotNull BukkitRecorderOption recorderOption) {
ServerPhotographer photographer = new ServerPhotographer.PhotographerCreateState(location, id, RecorderOption.createFromBukkit(recorderOption)).createSync();
if (photographer != null) {
return photographer.getBukkitPlayer();
}
return null;
}
@Override
public void removePhotographer(@NotNull String id) {
ServerPhotographer photographer = ServerPhotographer.getPhotographer(id);
if (photographer != null) {
photographer.remove(true);
}
}
@Override
public void removePhotographer(@NotNull UUID uuid) {
ServerPhotographer photographer = ServerPhotographer.getPhotographer(uuid);
if (photographer != null) {
photographer.remove(true);
}
}
@Override
public void removeAllPhotographers() {
for (ServerPhotographer photographer : ServerPhotographer.getPhotographers()) {
photographer.remove(true);
}
}
@Override
public Collection<Photographer> getPhotographers() {
return photographerViews;
}
}

View File

@@ -0,0 +1,197 @@
package org.leavesmc.leaves.lithium.common.world.chunk;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.HashCommon;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import net.minecraft.core.IdMap;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.VarInt;
import net.minecraft.world.level.chunk.Palette;
import net.minecraft.world.level.chunk.PaletteResize;
import org.jetbrains.annotations.NotNull;
import static it.unimi.dsi.fastutil.Hash.FAST_LOAD_FACTOR;
// Powered by Gale(https://github.com/GaleMC/Gale)
/**
* Generally provides better performance over the vanilla {@link net.minecraft.world.level.chunk.HashMapPalette} when calling
* {@link LithiumHashPalette#idFor(Object)} through using a faster backing map and reducing pointer chasing.
*/
public class LithiumHashPalette<T> implements Palette<T> {
private static final int ABSENT_VALUE = -1;
private final IdMap<T> idList;
private final PaletteResize<T> resizeHandler;
private final int indexBits;
private final Reference2IntMap<T> table;
private T[] entries;
private int size = 0;
public LithiumHashPalette(IdMap<T> idList, PaletteResize<T> resizeHandler, int indexBits, T[] entries, Reference2IntMap<T> table, int size) {
this.idList = idList;
this.resizeHandler = resizeHandler;
this.indexBits = indexBits;
this.entries = entries;
this.table = table;
this.size = size;
}
public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler, @NotNull List<T> list) {
this(idList, bits, resizeHandler);
for (T t : list) {
this.addEntry(t);
}
}
@SuppressWarnings("unchecked")
public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler) {
this.idList = idList;
this.indexBits = bits;
this.resizeHandler = resizeHandler;
int capacity = 1 << bits;
this.entries = (T[]) new Object[capacity];
this.table = new Reference2IntOpenHashMap<>(capacity, FAST_LOAD_FACTOR);
this.table.defaultReturnValue(ABSENT_VALUE);
}
@Override
public int idFor(@NotNull T obj) {
int id = this.table.getInt(obj);
if (id == ABSENT_VALUE) {
id = this.computeEntry(obj);
}
return id;
}
@Override
public boolean maybeHas(@NotNull Predicate<T> predicate) {
for (int i = 0; i < this.size; ++i) {
if (predicate.test(this.entries[i])) {
return true;
}
}
return false;
}
private int computeEntry(T obj) {
int id = this.addEntry(obj);
if (id >= 1 << this.indexBits) {
if (this.resizeHandler == null) {
throw new IllegalStateException("Cannot grow");
} else {
id = this.resizeHandler.onResize(this.indexBits + 1, obj);
}
}
return id;
}
private int addEntry(T obj) {
int nextId = this.size;
if (nextId >= this.entries.length) {
this.resize(this.size);
}
this.table.put(obj, nextId);
this.entries[nextId] = obj;
this.size++;
return nextId;
}
private void resize(int neededCapacity) {
this.entries = Arrays.copyOf(this.entries, HashCommon.nextPowerOfTwo(neededCapacity + 1));
}
@Override
public T valueFor(int id) {
T[] entries = this.entries;
if (id >= 0 && id < entries.length) {
return entries[id];
}
return null;
}
@Override
public void read(FriendlyByteBuf buf) {
this.clear();
int entryCount = buf.readVarInt();
for (int i = 0; i < entryCount; ++i) {
this.addEntry(this.idList.byId(buf.readVarInt()));
}
}
@Override
public void write(FriendlyByteBuf buf) {
int size = this.size;
buf.writeVarInt(size);
for (int i = 0; i < size; ++i) {
buf.writeVarInt(this.idList.getId(this.valueFor(i)));
}
}
@Override
public int getSerializedSize() {
int size = VarInt.getByteSize(this.size);
for (int i = 0; i < this.size; ++i) {
size += VarInt.getByteSize(this.idList.getId(this.valueFor(i)));
}
return size;
}
@Override
public int getSize() {
return this.size;
}
@NotNull
@Override
public Palette<T> copy(@NotNull PaletteResize<T> resizeListener) {
return new LithiumHashPalette<>(this.idList, resizeHandler, this.indexBits, this.entries.clone(), new Reference2IntOpenHashMap<>(this.table), this.size);
}
private void clear() {
Arrays.fill(this.entries, null);
this.table.clear();
this.size = 0;
}
public List<T> getElements() {
ImmutableList.Builder<T> builder = new ImmutableList.Builder<>();
for (T entry : this.entries) {
if (entry != null) {
builder.add(entry);
}
}
return builder.build();
}
public static <A> Palette<A> create(int bits, IdMap<A> idList, PaletteResize<A> listener, List<A> list) {
return new LithiumHashPalette<>(idList, bits, listener, list);
}
}

View File

@@ -0,0 +1,151 @@
package org.leavesmc.leaves.plugin;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.PluginBase;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginLoader;
import org.bukkit.plugin.PluginLogger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.InputStream;
import java.util.List;
public class MinecraftInternalPlugin extends PluginBase {
public static final MinecraftInternalPlugin INSTANCE = new MinecraftInternalPlugin();
private boolean enabled = true;
private final PluginDescriptionFile pdf;
public MinecraftInternalPlugin() {
String pluginName = "Minecraft";
pdf = new PluginDescriptionFile(pluginName, "1.0", "nms");
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public File getDataFolder() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public PluginDescriptionFile getDescription() {
return pdf;
}
@Override
public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() {
return pdf;
}
@Override
public FileConfiguration getConfig() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public InputStream getResource(String filename) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public void saveConfig() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public void saveDefaultConfig() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public void saveResource(String resourcePath, boolean replace) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public void reloadConfig() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public PluginLogger getLogger() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public PluginLoader getPluginLoader() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public Server getServer() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public void onDisable() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public void onLoad() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public void onEnable() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public boolean isNaggable() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public void setNaggable(boolean canNag) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public @Nullable BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public @NotNull io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager<org.bukkit.plugin.Plugin> getLifecycleManager() {
throw new UnsupportedOperationException("Not supported.");
}
}

View File

@@ -0,0 +1,57 @@
package org.leavesmc.leaves.plugin.provider;
import com.destroystokyo.paper.utils.PaperPluginLogger;
import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
import io.papermc.paper.plugin.bootstrap.PluginProviderContextImpl;
import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader;
import io.papermc.paper.plugin.entrypoint.classloader.PaperSimplePluginClassLoader;
import io.papermc.paper.plugin.loader.PaperClasspathBuilder;
import io.papermc.paper.plugin.loader.PluginLoader;
import io.papermc.paper.plugin.provider.type.PluginTypeFactory;
import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent;
import io.papermc.paper.plugin.provider.util.ProviderUtil;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.plugin.provider.configuration.LeavesPluginMeta;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;
public class LeavesPluginProviderFactory implements PluginTypeFactory<PaperPluginParent, LeavesPluginMeta> {
@Override
public PaperPluginParent build(JarFile file, LeavesPluginMeta configuration, Path source) {
Logger jul = PaperPluginLogger.getLogger(configuration);
ComponentLogger logger = ComponentLogger.logger(jul.getName());
PluginProviderContext context = PluginProviderContextImpl.create(configuration, logger, source);
PaperClasspathBuilder builder = new PaperClasspathBuilder(context);
if (configuration.getLoader() != null) {
try (
PaperSimplePluginClassLoader simplePluginClassLoader = new PaperSimplePluginClassLoader(source, file, configuration, this.getClass().getClassLoader())
) {
PluginLoader loader = ProviderUtil.loadClass(configuration.getLoader(), PluginLoader.class, simplePluginClassLoader);
loader.classloader(builder);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
PaperPluginClassLoader classLoader = builder.buildClassLoader(jul, source, file, configuration);
return new PaperPluginParent(source, file, configuration, classLoader, context);
}
@Override
public LeavesPluginMeta create(@NotNull JarFile file, JarEntry config) throws IOException {
LeavesPluginMeta configuration;
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(file.getInputStream(config)))) {
configuration = LeavesPluginMeta.create(bufferedReader);
}
return configuration;
}
}

View File

@@ -0,0 +1,90 @@
package org.leavesmc.leaves.plugin.provider.configuration;
import com.google.common.collect.ImmutableList;
import io.papermc.paper.configuration.constraint.Constraint;
import io.papermc.paper.configuration.serializer.ComponentSerializer;
import io.papermc.paper.configuration.serializer.EnumValueSerializer;
import io.papermc.paper.plugin.provider.configuration.FlattenedResolver;
import io.papermc.paper.plugin.provider.configuration.LegacyPaperMeta;
import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta;
import io.papermc.paper.plugin.provider.configuration.serializer.PermissionConfigurationSerializer;
import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints;
import io.papermc.paper.plugin.provider.configuration.type.PermissionConfiguration;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.spongepowered.configurate.CommentedConfigurationNode;
import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.hocon.HoconConfigurationLoader;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
import java.io.BufferedReader;
import java.lang.reflect.Type;
import java.util.List;
import java.util.function.Predicate;
@ConfigSerializable
public class LeavesPluginMeta extends PaperPluginMeta {
private List<String> mixins;
static final ApiVersion MINIMUM = ApiVersion.getOrCreateVersion("1.21");
public static LeavesPluginMeta create(BufferedReader reader) throws ConfigurateException {
HoconConfigurationLoader loader = HoconConfigurationLoader.builder()
.prettyPrinting(true)
.emitComments(true)
.emitJsonCompatible(true)
.source(() -> reader)
.defaultOptions((options) ->
options.serializers((serializers) ->
serializers.register(new ScalarSerializer<>(ApiVersion.class) {
@Override
public ApiVersion deserialize(final Type type, final Object obj) throws SerializationException {
try {
final ApiVersion version = ApiVersion.getOrCreateVersion(obj.toString());
if (version.isOlderThan(MINIMUM)) {
throw new SerializationException(version + " is too old for a leaves plugin!");
}
return version;
} catch (final IllegalArgumentException e) {
throw new SerializationException(e);
}
}
@Override
protected Object serialize(final ApiVersion item, final Predicate<Class<?>> typeSupported) {
return item.getVersionString();
}
})
.register(new EnumValueSerializer())
.register(PermissionConfiguration.class, PermissionConfigurationSerializer.SERIALIZER)
.register(new ComponentSerializer())
.registerAnnotatedObjects(
ObjectMapper.factoryBuilder()
.addConstraint(Constraint.class, new Constraint.Factory())
.addConstraint(PluginConfigConstraints.PluginName.class, String.class, new PluginConfigConstraints.PluginName.Factory())
.addConstraint(PluginConfigConstraints.PluginNameSpace.class, String.class, new PluginConfigConstraints.PluginNameSpace.Factory())
.addNodeResolver(new FlattenedResolver.Factory())
.build()
)
)
)
.build();
CommentedConfigurationNode node = loader.load();
LegacyPaperMeta.migrate(node);
LeavesPluginMeta pluginConfiguration = node.require(LeavesPluginMeta.class);
if (!node.node("author").virtual()) {
pluginConfiguration.authors = ImmutableList.<String>builder()
.addAll(pluginConfiguration.authors)
.add(node.node("author").getString())
.build();
}
return pluginConfiguration;
}
public List<String> getMixins() {
return mixins;
}
}

View File

@@ -0,0 +1,18 @@
package org.leavesmc.leaves.profile;
import com.destroystokyo.paper.profile.PaperAuthenticationService;
import com.mojang.authlib.minecraft.MinecraftSessionService;
import java.net.Proxy;
public class LeavesAuthenticationService extends PaperAuthenticationService {
public LeavesAuthenticationService(Proxy proxy) {
super(proxy);
}
@Override
public MinecraftSessionService createMinecraftSessionService() {
return new LeavesMinecraftSessionService(this.getServicesKeySet(), this.getProxy(), this.environment);
}
}

View File

@@ -0,0 +1,102 @@
package org.leavesmc.leaves.profile;
import com.destroystokyo.paper.profile.PaperMinecraftSessionService;
import com.mojang.authlib.Environment;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.HttpAuthenticationService;
import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
import com.mojang.authlib.exceptions.MinecraftClientException;
import com.mojang.authlib.yggdrasil.ProfileActionType;
import com.mojang.authlib.yggdrasil.ProfileResult;
import com.mojang.authlib.yggdrasil.ServicesKeySet;
import com.mojang.authlib.yggdrasil.response.HasJoinedMinecraftServerResponse;
import com.mojang.authlib.yggdrasil.response.ProfileAction;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.bot.ServerBot;
import java.net.InetAddress;
import java.net.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class LeavesMinecraftSessionService extends PaperMinecraftSessionService {
protected LeavesMinecraftSessionService(ServicesKeySet keySet, Proxy authenticationService, Environment environment) {
super(keySet, authenticationService, environment);
}
private static List<URL> extraYggdrasilList = List.of();
public static void initExtraYggdrasilList(List<String> extraYggdrasilServiceList) {
List<URL> list = new ArrayList<>();
for (String str : extraYggdrasilServiceList) {
list.add(HttpAuthenticationService.constantURL(str + "/sessionserver/session/minecraft/hasJoined"));
}
extraYggdrasilList = Collections.unmodifiableList(list);
}
@Nullable
@Override
public ProfileResult hasJoinedServer(String profileName, String serverId, @Nullable InetAddress address) throws AuthenticationUnavailableException {
ProfileResult result = super.hasJoinedServer(profileName, serverId, address); // mojang
ServerPlayer player = MinecraftServer.getServer().getPlayerList().getPlayerByName(profileName);
if (player != null && !(player instanceof ServerBot)) {
return null; // if it has same name, return null
}
if (LeavesConfig.mics.yggdrasil.enable && result == null) {
final Map<String, Object> arguments = new HashMap<>();
arguments.put("username", profileName);
arguments.put("serverId", serverId);
if (address != null) {
arguments.put("ip", address.getHostAddress());
}
GameProfile cache = null;
if (LeavesConfig.mics.yggdrasil.loginProtect) {
cache = MinecraftServer.getServer().services.profileCache().getProfileIfCached(profileName);
}
for (URL checkUrl : extraYggdrasilList) {
URL url = HttpAuthenticationService.concatenateURL(checkUrl, HttpAuthenticationService.buildQuery(arguments));
try {
final HasJoinedMinecraftServerResponse response = client.get(url, HasJoinedMinecraftServerResponse.class);
if (response != null && response.id() != null) {
if (LeavesConfig.mics.yggdrasil.loginProtect && cache != null) {
if (response.id() != cache.getId()) {
continue;
}
}
final GameProfile result1 = new GameProfile(response.id(), profileName);
if (response.properties() != null) {
result1.getProperties().putAll(response.properties());
}
final Set<ProfileActionType> profileActions = response.profileActions().stream()
.map(ProfileAction::type)
.collect(Collectors.toSet());
return new ProfileResult(result1, profileActions);
}
} catch (final MinecraftClientException e) {
if (e.toAuthenticationException() instanceof final AuthenticationUnavailableException unavailable) {
throw unavailable;
}
}
}
}
return result;
}
}

View File

@@ -0,0 +1,127 @@
package org.leavesmc.leaves.protocol;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.food.FoodData;
import net.minecraft.world.level.GameRules;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@LeavesProtocol(namespace = "appleskin")
public class AppleSkinProtocol {
public static final String PROTOCOL_ID = "appleskin";
private static final ResourceLocation SATURATION_KEY = id("saturation");
private static final ResourceLocation EXHAUSTION_KEY = id("exhaustion");
private static final ResourceLocation NATURAL_REGENERATION_KEY = id("natural_regeneration");
private static final float MINIMUM_EXHAUSTION_CHANGE_THRESHOLD = 0.01F;
private static final Map<ServerPlayer, Float> previousSaturationLevels = new HashMap<>();
private static final Map<ServerPlayer, Float> previousExhaustionLevels = new HashMap<>();
private static final Map<ServerPlayer, Boolean> previousNaturalRegeneration = new HashMap<>();
private static final Map<ServerPlayer, Set<String>> subscribedChannels = new HashMap<>();
@Contract("_ -> new")
public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
}
@ProtocolHandler.PlayerJoin
public static void onPlayerLoggedIn(@NotNull ServerPlayer player) {
if (LeavesConfig.protocol.appleskin.enable) {
resetPlayerData(player);
}
}
@ProtocolHandler.PlayerLeave
public static void onPlayerLoggedOut(@NotNull ServerPlayer player) {
if (LeavesConfig.protocol.appleskin.enable) {
subscribedChannels.remove(player);
resetPlayerData(player);
}
}
@ProtocolHandler.MinecraftRegister(ignoreId = true)
public static void onPlayerSubscribed(@NotNull ServerPlayer player, String channel) {
if (LeavesConfig.protocol.appleskin.enable) {
subscribedChannels.computeIfAbsent(player, k -> new HashSet<>()).add(channel);
}
}
@ProtocolHandler.Ticker
public static void tick() {
if (LeavesConfig.protocol.appleskin.enable) {
if (MinecraftServer.getServer().getTickCount() % LeavesConfig.protocol.appleskin.syncTickInterval != 0) {
return;
}
for (Map.Entry<ServerPlayer, Set<String>> entry : subscribedChannels.entrySet()) {
ServerPlayer player = entry.getKey();
FoodData data = player.getFoodData();
for (String channel : entry.getValue()) {
switch (channel) {
case "saturation" -> {
float saturation = data.getSaturationLevel();
Float previousSaturation = previousSaturationLevels.get(player);
if (previousSaturation == null || saturation != previousSaturation) {
ProtocolUtils.sendPayloadPacket(player, SATURATION_KEY, buf -> buf.writeFloat(saturation));
previousSaturationLevels.put(player, saturation);
}
}
case "exhaustion" -> {
float exhaustion = data.exhaustionLevel;
Float previousExhaustion = previousExhaustionLevels.get(player);
if (previousExhaustion == null || Math.abs(exhaustion - previousExhaustion) >= MINIMUM_EXHAUSTION_CHANGE_THRESHOLD) {
ProtocolUtils.sendPayloadPacket(player, EXHAUSTION_KEY, buf -> buf.writeFloat(exhaustion));
previousExhaustionLevels.put(player, exhaustion);
}
}
case "natural_regeneration" -> {
boolean regeneration = player.serverLevel().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION);
Boolean previousRegeneration = previousNaturalRegeneration.get(player);
if (previousRegeneration == null || regeneration != previousRegeneration) {
ProtocolUtils.sendPayloadPacket(player, NATURAL_REGENERATION_KEY, buf -> buf.writeBoolean(regeneration));
previousNaturalRegeneration.put(player, regeneration);
}
}
}
}
}
}
}
@ProtocolHandler.ReloadServer
public static void onServerReload() {
if (!LeavesConfig.protocol.appleskin.enable) {
disableAllPlayer();
}
}
public static void disableAllPlayer() {
for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) {
onPlayerLoggedOut(player);
}
}
private static void resetPlayerData(@NotNull ServerPlayer player) {
previousExhaustionLevels.remove(player);
previousSaturationLevels.remove(player);
previousNaturalRegeneration.remove(player);
}
}

View File

@@ -0,0 +1,227 @@
package org.leavesmc.leaves.protocol;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import static org.leavesmc.leaves.protocol.core.LeavesProtocolManager.EmptyPayload;
@LeavesProtocol(namespace = "bbor")
public class BBORProtocol {
public static final String PROTOCOL_ID = "bbor";
// send
private static final ResourceLocation INITIALIZE_CLIENT = id("initialize");
private static final ResourceLocation ADD_BOUNDING_BOX = id("add_bounding_box_v2");
private static final ResourceLocation STRUCTURE_LIST_SYNC = id("structure_list_sync_v1");
// call
private static final Map<Integer, ServerPlayer> players = new ConcurrentHashMap<>();
private static final Map<Integer, Set<BBoundingBox>> playerBoundingBoxesCache = new HashMap<>();
private static final Map<ResourceLocation, Map<BBoundingBox, Set<BBoundingBox>>> dimensionCache = new ConcurrentHashMap<>();
@Contract("_ -> new")
public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
}
@ProtocolHandler.Ticker
public static void tick() {
if (LeavesConfig.protocol.bborProtocol) {
for (var playerEntry : players.entrySet()) {
sendBoundingToPlayer(playerEntry.getKey(), playerEntry.getValue());
}
}
}
@ProtocolHandler.ReloadServer
public static void onServerReload() {
if (LeavesConfig.protocol.bborProtocol) {
initAllPlayer();
} else {
loggedOutAllPlayer();
}
}
@ProtocolHandler.PlayerJoin
public static void onPlayerLoggedIn(@NotNull ServerPlayer player) {
if (LeavesConfig.protocol.bborProtocol) {
ServerLevel overworld = MinecraftServer.getServer().overworld();
ProtocolUtils.sendPayloadPacket(player, INITIALIZE_CLIENT, buf -> {
buf.writeLong(overworld.getSeed());
buf.writeInt(overworld.levelData.getSpawnPos().getX());
buf.writeInt(overworld.levelData.getSpawnPos().getZ());
});
sendStructureList(player);
}
}
@ProtocolHandler.PlayerLeave
public static void onPlayerLoggedOut(@NotNull ServerPlayer player) {
if (LeavesConfig.protocol.bborProtocol) {
players.remove(player.getId());
playerBoundingBoxesCache.remove(player.getId());
}
}
@ProtocolHandler.PayloadReceiver(payload = EmptyPayload.class, payloadId = "subscribe")
public static void onPlayerSubscribed(@NotNull ServerPlayer player, EmptyPayload payload) {
if (LeavesConfig.protocol.bborProtocol) {
players.put(player.getId(), player);
sendBoundingToPlayer(player.getId(), player);
}
}
public static void onDataPackReload() {
if (LeavesConfig.protocol.bborProtocol) {
players.values().forEach(BBORProtocol::sendStructureList);
}
}
public static void onChunkLoaded(@NotNull LevelChunk chunk) {
if (LeavesConfig.protocol.bborProtocol) {
Map<String, StructureStart> structures = new HashMap<>();
final Registry<Structure> structureFeatureRegistry = chunk.getLevel().registryAccess().lookupOrThrow(Registries.STRUCTURE);
for (var es : chunk.getAllStarts().entrySet()) {
final var optional = structureFeatureRegistry.getResourceKey(es.getKey());
optional.ifPresent(key -> structures.put(key.location().toString(), es.getValue()));
}
if (!structures.isEmpty()) {
onStructuresLoaded(chunk.getLevel().dimension().location(), structures);
}
}
}
public static void onStructuresLoaded(@NotNull ResourceLocation dimensionID, @NotNull Map<String, StructureStart> structures) {
Map<BBoundingBox, Set<BBoundingBox>> cache = getOrCreateCache(dimensionID);
for (var entry : structures.entrySet()) {
StructureStart structureStart = entry.getValue();
if (structureStart == null) {
return;
}
String type = "structure:" + entry.getKey();
BoundingBox bb = structureStart.getBoundingBox();
BBoundingBox boundingBox = buildStructure(bb, type);
if (cache.containsKey(boundingBox)) {
return;
}
Set<BBoundingBox> structureBoundingBoxes = new HashSet<>();
for (StructurePiece structureComponent : structureStart.getPieces()) {
structureBoundingBoxes.add(buildStructure(structureComponent.getBoundingBox(), type));
}
cache.put(boundingBox, structureBoundingBoxes);
}
}
private static @NotNull BBoundingBox buildStructure(@NotNull BoundingBox bb, String type) {
BlockPos min = new BlockPos(bb.minX(), bb.minY(), bb.minZ());
BlockPos max = new BlockPos(bb.maxX(), bb.maxY(), bb.maxZ());
return new BBoundingBox(type, min, max);
}
private static void sendStructureList(@NotNull ServerPlayer player) {
final Registry<Structure> structureRegistry = player.server.registryAccess().lookupOrThrow(Registries.STRUCTURE);
final Set<String> structureIds = structureRegistry.entrySet().stream()
.map(e -> e.getKey().location().toString()).collect(Collectors.toSet());
ProtocolUtils.sendPayloadPacket(player, STRUCTURE_LIST_SYNC, buf -> {
buf.writeVarInt(structureIds.size());
structureIds.forEach(buf::writeUtf);
});
}
private static void sendBoundingToPlayer(int id, ServerPlayer player) {
for (var entry : dimensionCache.entrySet()) {
if (entry.getValue() == null) {
return;
}
Set<BBoundingBox> playerBoundingBoxes = playerBoundingBoxesCache.computeIfAbsent(id, k -> new HashSet<>());
Map<BBoundingBox, Set<BBoundingBox>> boundingBoxMap = entry.getValue();
for (BBoundingBox key : boundingBoxMap.keySet()) {
if (playerBoundingBoxes.contains(key)) {
continue;
}
Set<BBoundingBox> boundingBoxes = boundingBoxMap.get(key);
ProtocolUtils.sendPayloadPacket(player, ADD_BOUNDING_BOX, buf -> {
buf.writeResourceLocation(entry.getKey());
key.serialize(buf);
if (boundingBoxes != null && boundingBoxes.size() > 1) {
for (BBoundingBox box : boundingBoxes) {
box.serialize(buf);
}
}
});
playerBoundingBoxes.add(key);
}
}
}
public static void initAllPlayer() {
for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) {
onPlayerLoggedIn(player);
}
}
public static void loggedOutAllPlayer() {
players.clear();
playerBoundingBoxesCache.clear();
for (var cache : dimensionCache.values()) {
cache.clear();
}
dimensionCache.clear();
}
private static Map<BBoundingBox, Set<BBoundingBox>> getOrCreateCache(ResourceLocation dimensionId) {
return dimensionCache.computeIfAbsent(dimensionId, dt -> new ConcurrentHashMap<>());
}
private record BBoundingBox(String type, BlockPos min, BlockPos max) {
public void serialize(@NotNull FriendlyByteBuf buf) {
buf.writeChar('S');
buf.writeInt(type.hashCode());
buf.writeVarInt(min.getX()).writeVarInt(min.getY()).writeVarInt(min.getZ());
buf.writeVarInt(max.getX()).writeVarInt(max.getY()).writeVarInt(max.getZ());
}
@Override
public int hashCode() {
return combineHashCodes(min.hashCode(), max.hashCode());
}
private static int combineHashCodes(int @NotNull ... hashCodes) {
final int prime = 31;
int result = 0;
for (int hashCode : hashCodes) {
result = prime * result + hashCode;
}
return result;
}
}
}

View File

@@ -0,0 +1,167 @@
package org.leavesmc.leaves.protocol;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BedBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ComparatorBlock;
import net.minecraft.world.level.block.DirectionalBlock;
import net.minecraft.world.level.block.DispenserBlock;
import net.minecraft.world.level.block.GlazedTerracottaBlock;
import net.minecraft.world.level.block.ObserverBlock;
import net.minecraft.world.level.block.RepeaterBlock;
import net.minecraft.world.level.block.StairBlock;
import net.minecraft.world.level.block.TrapDoorBlock;
import net.minecraft.world.level.block.piston.PistonBaseBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.ComparatorMode;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.Half;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.state.properties.SlabType;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.Objects;
public class CarpetAlternativeBlockPlacement {
@Nullable
public static BlockState alternativeBlockPlacement(@NotNull Block block, @NotNull BlockPlaceContext context) {
Vec3 hitPos = context.getClickLocation();
BlockPos blockPos = context.getClickedPos();
double relativeHitX = hitPos.x - blockPos.getX();
BlockState state = block.getStateForPlacement(context);
Player player = context.getPlayer();
if (relativeHitX < 2 || state == null || player == null) {
return null;
}
EnumProperty<Direction> directionProp = getFirstDirectionProperty(state);
int protocolValue = ((int) relativeHitX - 2) / 2;
if (directionProp != null) {
Direction origFacing = state.getValue(directionProp);
Direction facing = origFacing;
int facingIndex = protocolValue & 0xF;
if (facingIndex == 6) {
facing = facing.getOpposite();
} else if (facingIndex <= 5) {
facing = Direction.from3DDataValue(facingIndex);
}
if (!directionProp.getPossibleValues().contains(facing)) {
facing = player.getDirection().getOpposite();
}
if (facing != origFacing && directionProp.getPossibleValues().contains(facing)) {
if (state.getBlock() instanceof BedBlock) {
BlockPos headPos = blockPos.relative(facing);
if (!context.getLevel().getBlockState(headPos).canBeReplaced(context)) {
return null;
}
}
state = state.setValue(directionProp, facing);
}
} else if (state.hasProperty(BlockStateProperties.AXIS)) {
Direction.Axis axis = Direction.Axis.VALUES[protocolValue % 3];
state = state.setValue(BlockStateProperties.AXIS, axis);
}
protocolValue &= 0xFFFFFFF0;
if (protocolValue >= 16) {
if (block instanceof RepeaterBlock) {
Integer delay = (protocolValue / 16);
if (RepeaterBlock.DELAY.getPossibleValues().contains(delay)) {
state = state.setValue(RepeaterBlock.DELAY, delay);
}
} else if (protocolValue == 16) {
if (block instanceof ComparatorBlock) {
state = state.setValue(ComparatorBlock.MODE, ComparatorMode.SUBTRACT);
} else if (state.hasProperty(BlockStateProperties.HALF) && state.getValue(BlockStateProperties.HALF) == Half.BOTTOM) {
state = state.setValue(BlockStateProperties.HALF, Half.TOP);
} else if (state.hasProperty(BlockStateProperties.SLAB_TYPE) && state.getValue(BlockStateProperties.SLAB_TYPE) == SlabType.BOTTOM) {
state = state.setValue(BlockStateProperties.SLAB_TYPE, SlabType.TOP);
}
}
}
return state;
}
@Nullable
public static BlockState alternativeBlockPlacementFix(Block block, @NotNull BlockPlaceContext context) {
Direction facing;
Vec3 vec3d = context.getClickLocation();
BlockPos pos = context.getClickedPos();
double hitX = vec3d.x - pos.getX();
if (hitX < 2) {
return null;
}
int code = (int) (hitX - 2) / 2;
Player placer = Objects.requireNonNull(context.getPlayer());
Level world = context.getLevel();
if (block instanceof GlazedTerracottaBlock) {
facing = Direction.from3DDataValue(code);
if (facing == Direction.UP || facing == Direction.DOWN) {
facing = placer.getDirection().getOpposite();
}
return block.defaultBlockState().setValue(GlazedTerracottaBlock.FACING, facing);
} else if (block instanceof ObserverBlock) {
return block.defaultBlockState().setValue(ObserverBlock.FACING, Direction.from3DDataValue(code)).setValue(ObserverBlock.POWERED, true);
} else if (block instanceof RepeaterBlock) {
facing = Direction.from3DDataValue(code % 16);
if (facing == Direction.UP || facing == Direction.DOWN) {
facing = placer.getDirection().getOpposite();
}
return block.defaultBlockState().setValue(RepeaterBlock.FACING, facing).setValue(RepeaterBlock.DELAY, Mth.clamp(code / 16, 1, 4)).setValue(RepeaterBlock.LOCKED, Boolean.FALSE);
} else if (block instanceof TrapDoorBlock) {
facing = Direction.from3DDataValue(code % 16);
if (facing == Direction.UP || facing == Direction.DOWN) {
facing = placer.getDirection().getOpposite();
}
return block.defaultBlockState().setValue(TrapDoorBlock.FACING, facing).setValue(TrapDoorBlock.OPEN, Boolean.FALSE).setValue(TrapDoorBlock.HALF, (code >= 16) ? Half.TOP : Half.BOTTOM).setValue(TrapDoorBlock.OPEN, world.hasNeighborSignal(pos));
} else if (block instanceof ComparatorBlock) {
facing = Direction.from3DDataValue(code % 16);
if ((facing == Direction.UP) || (facing == Direction.DOWN)) {
facing = placer.getDirection().getOpposite();
}
ComparatorMode m = (hitX >= 16) ? ComparatorMode.SUBTRACT : ComparatorMode.COMPARE;
return block.defaultBlockState().setValue(ComparatorBlock.FACING, facing).setValue(ComparatorBlock.POWERED, Boolean.FALSE).setValue(ComparatorBlock.MODE, m);
} else if (block instanceof DispenserBlock) {
return block.defaultBlockState().setValue(DispenserBlock.FACING, Direction.from3DDataValue(code)).setValue(DispenserBlock.TRIGGERED, Boolean.FALSE);
} else if (block instanceof PistonBaseBlock) {
return block.defaultBlockState().setValue(DirectionalBlock.FACING, Direction.from3DDataValue(code)).setValue(PistonBaseBlock.EXTENDED, Boolean.FALSE);
} else if (block instanceof StairBlock) {
return Objects.requireNonNull(block.getStateForPlacement(context)).setValue(StairBlock.FACING, Direction.from3DDataValue(code % 16)).setValue(StairBlock.HALF, (hitX >= 16) ? Half.TOP : Half.BOTTOM);
}
return null;
}
@SuppressWarnings("unchecked")
@Nullable
public static EnumProperty<Direction> getFirstDirectionProperty(@NotNull BlockState state) {
for (Property<?> prop : state.getProperties()) {
if (prop instanceof EnumProperty<?> enumProperty) {
if (enumProperty.getValueClass().equals(Direction.class)) {
return (EnumProperty<Direction>) enumProperty;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,120 @@
package org.leavesmc.leaves.protocol;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.LeavesLogger;
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 java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@LeavesProtocol(namespace = "carpet")
public class CarpetServerProtocol {
public static final String PROTOCOL_ID = "carpet";
public static final String VERSION = ProtocolUtils.buildProtocolVersion(PROTOCOL_ID);
private static final ResourceLocation HELLO_ID = CarpetServerProtocol.id("hello");
private static final String HI = "69";
private static final String HELLO = "420";
@Contract("_ -> new")
public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
}
@ProtocolHandler.PlayerJoin
public static void onPlayerJoin(ServerPlayer player) {
if (LeavesConfig.protocol.leavesCarpetSupport) {
CompoundTag data = new CompoundTag();
data.putString(HI, VERSION);
ProtocolUtils.sendPayloadPacket(player, new CarpetPayload(data));
}
}
@ProtocolHandler.PayloadReceiver(payload = CarpetPayload.class, payloadId = "hello")
private static void handleHello(@NotNull ServerPlayer player, @NotNull CarpetServerProtocol.CarpetPayload payload) {
if (LeavesConfig.protocol.leavesCarpetSupport) {
if (payload.nbt.contains(HELLO)) {
LeavesLogger.LOGGER.info("Player " + player.getScoreboardName() + " joined with carpet " + payload.nbt.getString(HELLO));
CompoundTag data = new CompoundTag();
CarpetRules.write(data);
ProtocolUtils.sendPayloadPacket(player, new CarpetPayload(data));
}
}
}
public static class CarpetRules {
private static final Map<String, CarpetRule> rules = new HashMap<>();
public static void write(@NotNull CompoundTag tag) {
CompoundTag rulesNbt = new CompoundTag();
rules.values().forEach(rule -> rule.writeNBT(rulesNbt));
tag.put("Rules", rulesNbt);
}
public static void register(CarpetRule rule) {
rules.put(rule.name, rule);
}
}
public record CarpetRule(String identifier, String name, String value) {
@NotNull
@Contract("_, _, _ -> new")
public static CarpetRule of(String identifier, String name, Enum<?> value) {
return new CarpetRule(identifier, name, value.name().toLowerCase(Locale.ROOT));
}
@NotNull
@Contract("_, _, _ -> new")
public static CarpetRule of(String identifier, String name, boolean value) {
return new CarpetRule(identifier, name, Boolean.toString(value));
}
public void writeNBT(@NotNull CompoundTag rules) {
CompoundTag rule = new CompoundTag();
String key = name;
while (rules.contains(key)) {
key = key + "2";
}
rule.putString("Value", value);
rule.putString("Manager", identifier);
rule.putString("Rule", name);
rules.put(key, rule);
}
}
public record CarpetPayload(CompoundTag nbt) implements LeavesCustomPayload<CarpetPayload> {
@New
public CarpetPayload(ResourceLocation location, FriendlyByteBuf buf) {
this(buf.readNbt());
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeNbt(nbt);
}
@Override
@NotNull
public ResourceLocation id() {
return HELLO_ID;
}
}
}

View File

@@ -0,0 +1,150 @@
package org.leavesmc.leaves.protocol;
import com.google.common.collect.Sets;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.LeavesLogger;
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 java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.Consumer;
@LeavesProtocol(namespace = "litematica-server-paster")
public class LMSPasterProtocol {
public static final String MOD_ID = "litematica-server-paster";
public static final String MOD_VERSION = "1.3.5";
private static final ResourceLocation PACKET_ID = ResourceLocation.fromNamespaceAndPath(MOD_ID, "network_v2");
private static final Map<ServerGamePacketListenerImpl, StringBuilder> VERY_LONG_CHATS = new WeakHashMap<>();
@ProtocolHandler.PayloadReceiver(payload = LmsPasterPayload.class, payloadId = "network_v2")
public static void handlePackets(ServerPlayer player, LmsPasterPayload payload) {
if (!LeavesConfig.protocol.lmsPasterProtocol) {
return;
}
String playerName = player.getName().getString();
int id = payload.getPacketId();
CompoundTag nbt = payload.getNbt();
switch (id) {
case LMSPasterProtocol.C2S.HI -> {
String clientModVersion = nbt.getString("mod_version");
LeavesLogger.LOGGER.info(String.format("Player %s connected with %s @ %s", playerName, LMSPasterProtocol.MOD_ID, clientModVersion));
ProtocolUtils.sendPayloadPacket(player, LMSPasterProtocol.S2C.build(LMSPasterProtocol.S2C.HI, nbt2 -> nbt2.putString("mod_version", LMSPasterProtocol.MOD_VERSION)));
ProtocolUtils.sendPayloadPacket(player, LMSPasterProtocol.S2C.build(LMSPasterProtocol.S2C.ACCEPT_PACKETS, nbt2 -> nbt2.putIntArray("ids", C2S.ALL_PACKET_IDS)));
}
case LMSPasterProtocol.C2S.CHAT -> {
String message = nbt.getString("chat");
triggerCommand(player, playerName, message);
}
case LMSPasterProtocol.C2S.VERY_LONG_CHAT_START -> VERY_LONG_CHATS.put(player.connection, new StringBuilder());
case LMSPasterProtocol.C2S.VERY_LONG_CHAT_CONTENT -> {
String segment = nbt.getString("segment");
getVeryLongChatBuilder(player).ifPresent(builder -> builder.append(segment));
}
case LMSPasterProtocol.C2S.VERY_LONG_CHAT_END -> {
getVeryLongChatBuilder(player).ifPresent(builder -> triggerCommand(player, playerName, builder.toString()));
VERY_LONG_CHATS.remove(player.connection);
}
}
}
private static Optional<StringBuilder> getVeryLongChatBuilder(ServerPlayer player) {
return Optional.ofNullable(VERY_LONG_CHATS.get(player.connection));
}
private static void triggerCommand(ServerPlayer player, String playerName, String command) {
if (command.isEmpty()) {
LeavesLogger.LOGGER.warning(String.format("Player %s sent an empty command", playerName));
} else {
player.getBukkitEntity().performCommand(command);
}
}
private static class C2S {
public static final int HI = 0;
public static final int CHAT = 1;
public static final int VERY_LONG_CHAT_START = 2;
public static final int VERY_LONG_CHAT_CONTENT = 3;
public static final int VERY_LONG_CHAT_END = 4;
public static final int[] ALL_PACKET_IDS;
static {
Set<Integer> allPacketIds = Sets.newLinkedHashSet();
for (Field field : C2S.class.getFields()) {
if (field.getType() == int.class && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) {
try {
allPacketIds.add((int) field.get(null));
} catch (Exception e) {
LeavesLogger.LOGGER.severe("Failed to initialized Lms Paster: ", e);
}
}
}
ALL_PACKET_IDS = new int[allPacketIds.size()];
int i = 0;
for (Integer id : allPacketIds) {
ALL_PACKET_IDS[i++] = id;
}
}
}
private static class S2C {
public static final int HI = 0;
public static final int ACCEPT_PACKETS = 1;
public static CustomPacketPayload build(int packetId, Consumer<CompoundTag> payloadBuilder) {
CompoundTag nbt = new CompoundTag();
payloadBuilder.accept(nbt);
return new LmsPasterPayload(packetId, nbt);
}
}
public static class LmsPasterPayload implements LeavesCustomPayload<LmsPasterPayload> {
private final int id;
private final CompoundTag nbt;
public LmsPasterPayload(int id, CompoundTag nbt) {
this.id = id;
this.nbt = nbt;
}
@New
public LmsPasterPayload(ResourceLocation location, FriendlyByteBuf buf) {
this(buf.readVarInt(), buf.readNbt());
}
public void write(FriendlyByteBuf buf) {
buf.writeVarInt(this.id);
buf.writeNbt(this.nbt);
}
@Override
public ResourceLocation id() {
return PACKET_ID;
}
public int getPacketId() {
return this.id;
}
public CompoundTag getNbt() {
return this.nbt;
}
}
}

View File

@@ -0,0 +1,214 @@
package org.leavesmc.leaves.protocol;
import com.google.common.collect.ImmutableSet;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BedBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.state.properties.SlabType;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesLogger;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class LitematicaEasyPlaceProtocol {
public static final ImmutableSet<Property<?>> WHITELISTED_PROPERTIES = ImmutableSet.of(
BlockStateProperties.INVERTED,
BlockStateProperties.OPEN,
BlockStateProperties.BELL_ATTACHMENT,
BlockStateProperties.AXIS,
BlockStateProperties.BED_PART,
BlockStateProperties.HALF,
BlockStateProperties.ATTACH_FACE,
BlockStateProperties.CHEST_TYPE,
BlockStateProperties.MODE_COMPARATOR,
BlockStateProperties.DOOR_HINGE,
BlockStateProperties.FACING_HOPPER,
BlockStateProperties.HORIZONTAL_FACING,
BlockStateProperties.ORIENTATION,
BlockStateProperties.RAIL_SHAPE,
BlockStateProperties.RAIL_SHAPE_STRAIGHT,
BlockStateProperties.SLAB_TYPE,
BlockStateProperties.STAIRS_SHAPE,
BlockStateProperties.BITES,
BlockStateProperties.DELAY,
BlockStateProperties.NOTE,
BlockStateProperties.ROTATION_16
);
public static BlockState applyPlacementProtocol(BlockState state, BlockPlaceContext context) {
return applyPlacementProtocolV3(state, UseContext.from(context, context.getHand()));
}
@Nullable
private static <T extends Comparable<T>> BlockState applyPlacementProtocolV3(BlockState state, @NotNull UseContext context) {
int protocolValue = (int) (context.getHitVec().x - (double) context.getPos().getX()) - 2;
BlockState oldState = state;
if (protocolValue < 0) {
return oldState;
}
EnumProperty<Direction> property = CarpetAlternativeBlockPlacement.getFirstDirectionProperty(state);
if (property != null && property != BlockStateProperties.VERTICAL_DIRECTION) {
state = applyDirectionProperty(state, context, property, protocolValue);
if (state == null) {
return null;
}
if (state.canSurvive(context.getWorld(), context.getPos())) {
oldState = state;
} else {
state = oldState;
}
protocolValue >>>= 3;
}
protocolValue >>>= 1;
List<Property<?>> propList = new ArrayList<>(state.getBlock().getStateDefinition().getProperties());
propList.sort(Comparator.comparing(Property::getName));
try {
for (Property<?> p : propList) {
if (((p instanceof EnumProperty<?> ep) && !ep.getValueClass().equals(Direction.class)) && WHITELISTED_PROPERTIES.contains(p)) {
@SuppressWarnings("unchecked")
Property<T> prop = (Property<T>) p;
List<T> list = new ArrayList<>(prop.getPossibleValues());
list.sort(Comparable::compareTo);
int requiredBits = Mth.log2(Mth.smallestEncompassingPowerOfTwo(list.size()));
int bitMask = ~(0xFFFFFFFF << requiredBits);
int valueIndex = protocolValue & bitMask;
if (valueIndex < list.size()) {
T value = list.get(valueIndex);
if (!state.getValue(prop).equals(value) && value != SlabType.DOUBLE) {
state = state.setValue(prop, value);
if (state.canSurvive(context.getWorld(), context.getPos())) {
oldState = state;
} else {
state = oldState;
}
}
protocolValue >>>= requiredBits;
}
}
}
} catch (Exception e) {
LeavesLogger.LOGGER.warning("Exception trying to apply placement protocol value", e);
}
if (state.canSurvive(context.getWorld(), context.getPos())) {
return state;
} else {
return null;
}
}
private static BlockState applyDirectionProperty(BlockState state, UseContext context, EnumProperty<Direction> property, int protocolValue) {
Direction facingOrig = state.getValue(property);
Direction facing = facingOrig;
int decodedFacingIndex = (protocolValue & 0xF) >> 1;
if (decodedFacingIndex == 6) {
facing = facing.getOpposite();
} else if (decodedFacingIndex <= 5) {
facing = Direction.from3DDataValue(decodedFacingIndex);
if (!property.getPossibleValues().contains(facing)) {
facing = context.getEntity().getDirection().getOpposite();
}
}
if (facing != facingOrig && property.getPossibleValues().contains(facing)) {
if (state.getBlock() instanceof BedBlock) {
BlockPos headPos = context.pos.relative(facing);
BlockPlaceContext ctx = context.getItemPlacementContext();
if (ctx == null || !context.getWorld().getBlockState(headPos).canBeReplaced(ctx)) {
return null;
}
}
state = state.setValue(property, facing);
}
return state;
}
public static class UseContext {
private final Level world;
private final BlockPos pos;
private final Direction side;
private final Vec3 hitVec;
private final LivingEntity entity;
private final InteractionHand hand;
@Nullable
private final BlockPlaceContext itemPlacementContext;
private UseContext(Level world, BlockPos pos, Direction side, Vec3 hitVec, LivingEntity entity, InteractionHand hand, @Nullable BlockPlaceContext itemPlacementContext) {
this.world = world;
this.pos = pos;
this.side = side;
this.hitVec = hitVec;
this.entity = entity;
this.hand = hand;
this.itemPlacementContext = itemPlacementContext;
}
@NotNull
public static UseContext from(@NotNull BlockPlaceContext ctx, InteractionHand hand) {
Vec3 pos = ctx.getClickLocation();
return new UseContext(ctx.getLevel(), ctx.getClickedPos(), ctx.getClickedFace(), new Vec3(pos.x, pos.y, pos.z), ctx.getPlayer(), hand, ctx);
}
public Level getWorld() {
return this.world;
}
public BlockPos getPos() {
return this.pos;
}
public Direction getSide() {
return this.side;
}
public Vec3 getHitVec() {
return this.hitVec;
}
public LivingEntity getEntity() {
return this.entity;
}
public InteractionHand getHand() {
return this.hand;
}
@Nullable
public BlockPlaceContext getItemPlacementContext() {
return this.itemPlacementContext;
}
}
}

View File

@@ -0,0 +1,432 @@
package org.leavesmc.leaves.protocol;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
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 org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.LeavesLogger;
import org.leavesmc.leaves.bot.ServerBot;
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 java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import static org.leavesmc.leaves.protocol.core.LeavesProtocolManager.EmptyPayload;
@LeavesProtocol(namespace = "pca")
public class PcaSyncProtocol {
public static final String PROTOCOL_ID = "pca";
public static final ReentrantLock lock = new ReentrantLock(true);
public static final ReentrantLock pairLock = new ReentrantLock(true);
// send
private static final ResourceLocation ENABLE_PCA_SYNC_PROTOCOL = id("enable_pca_sync_protocol");
private static final ResourceLocation DISABLE_PCA_SYNC_PROTOCOL = id("disable_pca_sync_protocol");
private static final Map<ServerPlayer, Pair<ResourceLocation, BlockPos>> playerWatchBlockPos = new HashMap<>();
private static final Map<ServerPlayer, Pair<ResourceLocation, Entity>> playerWatchEntity = new HashMap<>();
private static final Map<Pair<ResourceLocation, BlockPos>, Set<ServerPlayer>> blockPosWatchPlayerSet = new HashMap<>();
private static final Map<Pair<ResourceLocation, Entity>, Set<ServerPlayer>> entityWatchPlayerSet = new HashMap<>();
private static final MutablePair<ResourceLocation, Entity> ResourceLocationEntityPair = new MutablePair<>();
private static final MutablePair<ResourceLocation, BlockPos> ResourceLocationBlockPosPair = new MutablePair<>();
@Contract("_ -> new")
public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
}
@ProtocolHandler.PlayerJoin
private static void onJoin(ServerPlayer player) {
if (LeavesConfig.protocol.pca.enable) {
enablePcaSyncProtocol(player);
}
}
@ProtocolHandler.PayloadReceiver(payload = EmptyPayload.class, payloadId = "cancel_sync_block_entity")
private static void cancelSyncBlockEntityHandler(ServerPlayer player, EmptyPayload payload) {
if (!LeavesConfig.protocol.pca.enable) {
return;
}
PcaSyncProtocol.clearPlayerWatchBlock(player);
}
@ProtocolHandler.PayloadReceiver(payload = EmptyPayload.class, payloadId = "cancel_sync_entity")
private static void cancelSyncEntityHandler(ServerPlayer player, EmptyPayload payload) {
if (!LeavesConfig.protocol.pca.enable) {
return;
}
PcaSyncProtocol.clearPlayerWatchEntity(player);
}
@ProtocolHandler.PayloadReceiver(payload = SyncBlockEntityPayload.class, payloadId = "sync_block_entity")
private static void syncBlockEntityHandler(ServerPlayer player, SyncBlockEntityPayload payload) {
if (!LeavesConfig.protocol.pca.enable) {
return;
}
MinecraftServer server = MinecraftServer.getServer();
BlockPos pos = payload.pos;
ServerLevel world = player.serverLevel();
server.execute(() -> {
BlockState blockState = world.getBlockState(pos);
clearPlayerWatchData(player);
BlockEntity blockEntityAdj = null;
if (blockState.getBlock() instanceof ChestBlock) {
if (blockState.getValue(ChestBlock.TYPE) != ChestType.SINGLE) {
BlockPos posAdj = pos.relative(ChestBlock.getConnectedDirection(blockState));
// The method in World now checks that the caller is from the same thread...
blockEntityAdj = world.getChunk(posAdj).getBlockEntity(posAdj);
}
}
if (blockEntityAdj != null) {
updateBlockEntity(player, blockEntityAdj);
}
// The method in World now checks that the caller is from the same thread...
BlockEntity blockEntity = world.getChunk(pos).getBlockEntity(pos);
if (blockEntity != null) {
updateBlockEntity(player, blockEntity);
}
Pair<ResourceLocation, BlockPos> pair = new ImmutablePair<>(player.level().dimension().location(), pos);
lock.lock();
playerWatchBlockPos.put(player, pair);
if (!blockPosWatchPlayerSet.containsKey(pair)) {
blockPosWatchPlayerSet.put(pair, new HashSet<>());
}
blockPosWatchPlayerSet.get(pair).add(player);
lock.unlock();
});
}
@ProtocolHandler.PayloadReceiver(payload = SyncEntityPayload.class, payloadId = "sync_entity")
private static void syncEntityHandler(ServerPlayer player, SyncEntityPayload payload) {
if (!LeavesConfig.protocol.pca.enable) {
return;
}
MinecraftServer server = MinecraftServer.getServer();
int entityId = payload.entityId;
ServerLevel world = player.serverLevel();
server.execute(() -> {
Entity entity = world.getEntity(entityId);
if (entity != null) {
clearPlayerWatchData(player);
if (entity instanceof Player) {
switch (LeavesConfig.protocol.pca.syncPlayerEntity) {
case NOBODY -> {
return;
}
case BOT -> {
if (!(entity instanceof ServerBot)) {
return;
}
}
case OPS -> {
if (!(entity instanceof ServerBot) && server.getPlayerList().isOp(player.gameProfile)) {
return;
}
}
case OPS_AND_SELF -> {
if (!(entity instanceof ServerBot) && server.getPlayerList().isOp(player.gameProfile) && entity != player) {
return;
}
}
case EVERYONE -> {}
case null -> LeavesLogger.LOGGER.warning("pcaSyncPlayerEntity wtf???");
}
}
updateEntity(player, entity);
Pair<ResourceLocation, Entity> pair = new ImmutablePair<>(entity.level().dimension().location(), entity);
lock.lock();
playerWatchEntity.put(player, pair);
if (!entityWatchPlayerSet.containsKey(pair)) {
entityWatchPlayerSet.put(pair, new HashSet<>());
}
entityWatchPlayerSet.get(pair).add(player);
lock.unlock();
}
});
}
public static void onConfigModify(boolean enable) {
if (enable) {
enablePcaSyncProtocolGlobal();
} else {
disablePcaSyncProtocolGlobal();
}
}
public static void enablePcaSyncProtocol(@NotNull ServerPlayer player) {
ProtocolUtils.sendEmptyPayloadPacket(player, ENABLE_PCA_SYNC_PROTOCOL);
lock.lock();
lock.unlock();
}
public static void disablePcaSyncProtocol(@NotNull ServerPlayer player) {
ProtocolUtils.sendEmptyPayloadPacket(player, DISABLE_PCA_SYNC_PROTOCOL);
}
public static void updateEntity(@NotNull ServerPlayer player, @NotNull Entity entity) {
CompoundTag nbt = entity.saveWithoutId(new CompoundTag());
ProtocolUtils.sendPayloadPacket(player, new UpdateEntityPayload(entity.level().dimension().location(), entity.getId(), nbt));
}
public static void updateBlockEntity(@NotNull ServerPlayer player, @NotNull BlockEntity blockEntity) {
Level world = blockEntity.getLevel();
if (world == null) {
return;
}
ProtocolUtils.sendPayloadPacket(player, new UpdateBlockEntityPayload(world.dimension().location(), blockEntity.getBlockPos(), blockEntity.saveWithoutMetadata(world.registryAccess())));
}
private static MutablePair<ResourceLocation, Entity> getResourceLocationEntityPair(ResourceLocation ResourceLocation, Entity entity) {
pairLock.lock();
ResourceLocationEntityPair.setLeft(ResourceLocation);
ResourceLocationEntityPair.setRight(entity);
pairLock.unlock();
return ResourceLocationEntityPair;
}
private static MutablePair<ResourceLocation, BlockPos> getResourceLocationBlockPosPair(ResourceLocation ResourceLocation, BlockPos pos) {
pairLock.lock();
ResourceLocationBlockPosPair.setLeft(ResourceLocation);
ResourceLocationBlockPosPair.setRight(pos);
pairLock.unlock();
return ResourceLocationBlockPosPair;
}
private static @Nullable Set<ServerPlayer> getWatchPlayerList(@NotNull Entity entity) {
return entityWatchPlayerSet.get(getResourceLocationEntityPair(entity.level().dimension().location(), entity));
}
private static @Nullable Set<ServerPlayer> getWatchPlayerList(@NotNull Level world, @NotNull BlockPos blockPos) {
return blockPosWatchPlayerSet.get(getResourceLocationBlockPosPair(world.dimension().location(), blockPos));
}
public static boolean syncEntityToClient(@NotNull Entity entity) {
if (entity.level().isClientSide()) {
return false;
}
lock.lock();
Set<ServerPlayer> playerList = getWatchPlayerList(entity);
boolean ret = false;
if (playerList != null) {
for (ServerPlayer player : playerList) {
updateEntity(player, entity);
ret = true;
}
}
lock.unlock();
return ret;
}
public static boolean syncBlockEntityToClient(@NotNull BlockEntity blockEntity) {
boolean ret = false;
Level world = blockEntity.getLevel();
BlockPos pos = blockEntity.getBlockPos();
if (world != null) {
if (world.isClientSide()) {
return false;
}
BlockState blockState = world.getBlockState(pos);
lock.lock();
Set<ServerPlayer> playerList = getWatchPlayerList(world, blockEntity.getBlockPos());
Set<ServerPlayer> playerListAdj = null;
if (blockState.getBlock() instanceof ChestBlock) {
if (blockState.getValue(ChestBlock.TYPE) != ChestType.SINGLE) {
BlockPos posAdj = pos.relative(ChestBlock.getConnectedDirection(blockState));
playerListAdj = getWatchPlayerList(world, posAdj);
}
}
if (playerListAdj != null) {
if (playerList == null) {
playerList = playerListAdj;
} else {
playerList.addAll(playerListAdj);
}
}
if (playerList != null) {
for (ServerPlayer player : playerList) {
updateBlockEntity(player, blockEntity);
ret = true;
}
}
lock.unlock();
}
return ret;
}
private static void clearPlayerWatchEntity(ServerPlayer player) {
lock.lock();
Pair<ResourceLocation, Entity> pair = playerWatchEntity.get(player);
if (pair != null) {
Set<ServerPlayer> playerSet = entityWatchPlayerSet.get(pair);
playerSet.remove(player);
if (playerSet.isEmpty()) {
entityWatchPlayerSet.remove(pair);
}
playerWatchEntity.remove(player);
}
lock.unlock();
}
private static void clearPlayerWatchBlock(ServerPlayer player) {
lock.lock();
Pair<ResourceLocation, BlockPos> pair = playerWatchBlockPos.get(player);
if (pair != null) {
Set<ServerPlayer> playerSet = blockPosWatchPlayerSet.get(pair);
playerSet.remove(player);
if (playerSet.isEmpty()) {
blockPosWatchPlayerSet.remove(pair);
}
playerWatchBlockPos.remove(player);
}
lock.unlock();
}
public static void disablePcaSyncProtocolGlobal() {
lock.lock();
playerWatchBlockPos.clear();
playerWatchEntity.clear();
blockPosWatchPlayerSet.clear();
entityWatchPlayerSet.clear();
lock.unlock();
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) {
PcaSyncProtocol.clearPlayerWatchBlock(player);
PcaSyncProtocol.clearPlayerWatchEntity(player);
}
public record UpdateEntityPayload(ResourceLocation dimension, int entityId, CompoundTag tag) implements LeavesCustomPayload<UpdateEntityPayload> {
public static final ResourceLocation UPDATE_ENTITY = PcaSyncProtocol.id("update_entity");
@New
public UpdateEntityPayload(ResourceLocation location, FriendlyByteBuf byteBuf) {
this(byteBuf.readResourceLocation(), byteBuf.readInt(), byteBuf.readNbt());
}
@Override
public void write(@NotNull FriendlyByteBuf buf) {
buf.writeResourceLocation(this.dimension);
buf.writeInt(this.entityId);
buf.writeNbt(this.tag);
}
@Override
public ResourceLocation id() {
return UPDATE_ENTITY;
}
}
public record UpdateBlockEntityPayload(ResourceLocation dimension, BlockPos blockPos, CompoundTag tag) implements LeavesCustomPayload<UpdateBlockEntityPayload> {
private static final ResourceLocation UPDATE_BLOCK_ENTITY = PcaSyncProtocol.id("update_block_entity");
@New
public UpdateBlockEntityPayload(ResourceLocation location, @NotNull FriendlyByteBuf byteBuf) {
this(byteBuf.readResourceLocation(), byteBuf.readBlockPos(), byteBuf.readNbt());
}
@Override
public void write(@NotNull FriendlyByteBuf buf) {
buf.writeResourceLocation(this.dimension);
buf.writeBlockPos(this.blockPos);
buf.writeNbt(this.tag);
}
@Override
public ResourceLocation id() {
return UPDATE_BLOCK_ENTITY;
}
}
public record SyncBlockEntityPayload(BlockPos pos) implements LeavesCustomPayload<SyncBlockEntityPayload> {
public static final ResourceLocation SYNC_BLOCK_ENTITY = PcaSyncProtocol.id("sync_block_entity");
@New
public SyncBlockEntityPayload(ResourceLocation id, @NotNull FriendlyByteBuf buf) {
this(buf.readBlockPos());
}
@Override
public void write(@NotNull FriendlyByteBuf buf) {
buf.writeBlockPos(pos);
}
@Override
public @NotNull ResourceLocation id() {
return SYNC_BLOCK_ENTITY;
}
}
public record SyncEntityPayload(int entityId) implements LeavesCustomPayload<SyncEntityPayload> {
public static final ResourceLocation SYNC_ENTITY = PcaSyncProtocol.id("sync_entity");
@New
public SyncEntityPayload(ResourceLocation id, @NotNull FriendlyByteBuf buf) {
this(buf.readInt());
}
@Override
public void write(@NotNull FriendlyByteBuf buf) {
buf.writeInt(entityId);
}
@Override
public @NotNull ResourceLocation id() {
return SYNC_ENTITY;
}
}
}

View File

@@ -0,0 +1,42 @@
package org.leavesmc.leaves.protocol;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
@LeavesProtocol(namespace = {"xaerominimap", "xaeroworldmap"})
public class XaeroMapProtocol {
public static final String PROTOCOL_ID_MINI = "xaerominimap";
public static final String PROTOCOL_ID_WORLD = "xaeroworldmap";
private static final ResourceLocation MINIMAP_KEY = idMini("main");
private static final ResourceLocation WORLDMAP_KEY = idWorld("main");
@Contract("_ -> new")
public static ResourceLocation idMini(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID_MINI, path);
}
@Contract("_ -> new")
public static ResourceLocation idWorld(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID_WORLD, path);
}
public static void onSendWorldInfo(@NotNull ServerPlayer player) {
if (LeavesConfig.protocol.xaeroMapProtocol) {
ProtocolUtils.sendPayloadPacket(player, MINIMAP_KEY, buf -> {
buf.writeByte(0);
buf.writeInt(LeavesConfig.protocol.xaeroMapServerID);
});
ProtocolUtils.sendPayloadPacket(player, WORLDMAP_KEY, buf -> {
buf.writeByte(0);
buf.writeInt(LeavesConfig.protocol.xaeroMapServerID);
});
}
}
}

View File

@@ -0,0 +1,150 @@
package org.leavesmc.leaves.protocol.bladeren;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.LeavesLogger;
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 java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
@LeavesProtocol(namespace = "bladeren")
public class BladerenProtocol {
public static final String PROTOCOL_ID = "bladeren";
public static final String PROTOCOL_VERSION = ProtocolUtils.buildProtocolVersion(PROTOCOL_ID);
private static final ResourceLocation HELLO_ID = id("hello");
private static final ResourceLocation FEATURE_MODIFY_ID = id("feature_modify");
private static final Map<String, BiConsumer<ServerPlayer, CompoundTag>> registeredFeatures = new HashMap<>();
@Contract("_ -> new")
public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
}
@ProtocolHandler.PayloadReceiver(payload = BladerenHelloPayload.class, payloadId = "hello")
private static void handleHello(@NotNull ServerPlayer player, @NotNull BladerenHelloPayload payload) {
if (LeavesConfig.protocol.bladeren.enable) {
String clientVersion = payload.version;
CompoundTag tag = payload.nbt;
LeavesLogger.LOGGER.info("Player " + player.getScoreboardName() + " joined with bladeren " + clientVersion);
if (tag != null) {
CompoundTag featureNbt = tag.getCompound("Features");
for (String name : featureNbt.getAllKeys()) {
if (registeredFeatures.containsKey(name)) {
registeredFeatures.get(name).accept(player, featureNbt.getCompound(name));
}
}
}
}
}
@ProtocolHandler.PayloadReceiver(payload = BladerenFeatureModifyPayload.class, payloadId = "feature_modify")
private static void handleModify(@NotNull ServerPlayer player, @NotNull BladerenFeatureModifyPayload payload) {
if (LeavesConfig.protocol.bladeren.enable) {
String name = payload.name;
CompoundTag tag = payload.nbt;
if (registeredFeatures.containsKey(name)) {
registeredFeatures.get(name).accept(player, tag);
}
}
}
@ProtocolHandler.PlayerJoin
public static void onPlayerJoin(@NotNull ServerPlayer player) {
if (LeavesConfig.protocol.bladeren.enable) {
CompoundTag tag = new CompoundTag();
LeavesFeatureSet.writeNBT(tag);
ProtocolUtils.sendPayloadPacket(player, new BladerenHelloPayload(PROTOCOL_VERSION, tag));
}
}
public static void registerFeature(String name, BiConsumer<ServerPlayer, CompoundTag> consumer) {
registeredFeatures.put(name, consumer);
}
public static class LeavesFeatureSet {
private static final Map<String, LeavesFeature> features = new HashMap<>();
public static void writeNBT(@NotNull CompoundTag tag) {
CompoundTag featureNbt = new CompoundTag();
features.values().forEach(feature -> feature.writeNBT(featureNbt));
tag.put("Features", featureNbt);
}
public static void register(LeavesFeature feature) {
features.put(feature.name, feature);
}
}
public record LeavesFeature(String name, String value) {
@NotNull
@Contract("_, _ -> new")
public static LeavesFeature of(String name, boolean value) {
return new LeavesFeature(name, Boolean.toString(value));
}
public void writeNBT(@NotNull CompoundTag rules) {
CompoundTag rule = new CompoundTag();
rule.putString("Feature", name);
rule.putString("Value", value);
rules.put(name, rule);
}
}
public record BladerenFeatureModifyPayload(String name, CompoundTag nbt) implements LeavesCustomPayload<BladerenFeatureModifyPayload> {
@New
public BladerenFeatureModifyPayload(ResourceLocation location, FriendlyByteBuf buf) {
this(buf.readUtf(), buf.readNbt());
}
@Override
public void write(@NotNull FriendlyByteBuf buf) {
buf.writeUtf(name);
buf.writeNbt(nbt);
}
@Override
@NotNull
public ResourceLocation id() {
return FEATURE_MODIFY_ID;
}
}
public record BladerenHelloPayload(String version, CompoundTag nbt) implements LeavesCustomPayload<BladerenHelloPayload> {
@New
public BladerenHelloPayload(ResourceLocation location, @NotNull FriendlyByteBuf buf) {
this(buf.readUtf(64), buf.readNbt());
}
@Override
public void write(@NotNull FriendlyByteBuf buf) {
buf.writeUtf(version);
buf.writeNbt(nbt);
}
@Override
@NotNull
public ResourceLocation id() {
return HELLO_ID;
}
}
}

View File

@@ -0,0 +1,77 @@
package org.leavesmc.leaves.protocol.bladeren;
import net.minecraft.resources.ResourceLocation;
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.LeavesConfig;
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.OptionalDouble;
@LeavesProtocol(namespace = "bladeren")
public class MsptSyncProtocol {
public static final String PROTOCOL_ID = "bladeren";
private static final ResourceLocation MSPT_SYNC = id("mspt_sync");
private static final List<ServerPlayer> players = new ArrayList<>();
@Contract("_ -> new")
public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
}
@ProtocolHandler.Init
public static void init() {
BladerenProtocol.registerFeature("mspt_sync", (player, compoundTag) -> {
if (compoundTag.getString("Value").equals("true")) {
onPlayerSubmit(player);
} else {
onPlayerLoggedOut(player);
}
});
}
@ProtocolHandler.PlayerLeave
public static void onPlayerLoggedOut(@NotNull ServerPlayer player) {
if (LeavesConfig.protocol.bladeren.msptSyncProtocol) {
players.remove(player);
}
}
@ProtocolHandler.Ticker
public static void tick() {
if (LeavesConfig.protocol.bladeren.msptSyncProtocol) {
if (players.isEmpty()) {
return;
}
MinecraftServer server = MinecraftServer.getServer();
if (server.getTickCount() % LeavesConfig.protocol.bladeren.msptSyncTickInterval == 0) {
OptionalDouble msptArr = Arrays.stream(server.getTickTimesNanos()).average();
if (msptArr.isPresent()) {
double mspt = msptArr.getAsDouble() * 1.0E-6D;
double tps = 1000.0D / Math.max(mspt, 50);
players.forEach(player -> ProtocolUtils.sendPayloadPacket(player, MSPT_SYNC, buf -> {
buf.writeDouble(mspt);
buf.writeDouble(tps);
}));
}
}
}
}
public static void onPlayerSubmit(@NotNull ServerPlayer player) {
if (LeavesConfig.protocol.bladeren.msptSyncProtocol) {
players.add(player);
}
}
}

View File

@@ -0,0 +1,29 @@
package org.leavesmc.leaves.protocol.core;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public interface LeavesCustomPayload<T extends LeavesCustomPayload<T>> extends CustomPacketPayload {
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
@interface New {
}
void write(FriendlyByteBuf buf);
ResourceLocation id();
@Override
@NotNull
default Type<T> type() {
return new CustomPacketPayload.Type<>(id());
}
}

View File

@@ -0,0 +1,12 @@
package org.leavesmc.leaves.protocol.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LeavesProtocol {
String[] namespace();
}

View File

@@ -0,0 +1,432 @@
package org.leavesmc.leaves.protocol.core;
import com.google.common.collect.ImmutableSet;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.apache.commons.lang.ArrayUtils;
import org.bukkit.event.player.PlayerKickEvent;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesLogger;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class LeavesProtocolManager {
private static final Class<?>[] PAYLOAD_PARAMETER_TYPES = {ResourceLocation.class, FriendlyByteBuf.class};
private static final LeavesLogger LOGGER = LeavesLogger.LOGGER;
private static final Map<LeavesProtocol, Map<ProtocolHandler.PayloadReceiver, Executable>> KNOWN_TYPES = new HashMap<>();
private static final Map<LeavesProtocol, Map<ProtocolHandler.PayloadReceiver, Method>> KNOW_RECEIVERS = new HashMap<>();
private static Set<ResourceLocation> ALL_KNOWN_ID = new HashSet<>();
private static final List<Method> TICKERS = new ArrayList<>();
private static final List<Method> PLAYER_JOIN = new ArrayList<>();
private static final List<Method> PLAYER_LEAVE = new ArrayList<>();
private static final List<Method> RELOAD_SERVER = new ArrayList<>();
private static final Map<LeavesProtocol, Map<ProtocolHandler.MinecraftRegister, Method>> MINECRAFT_REGISTER = new HashMap<>();
public static void init() {
for (Class<?> clazz : getClasses("org.leavesmc.leaves.protocol")) {
final LeavesProtocol protocol = clazz.getAnnotation(LeavesProtocol.class);
if (protocol != null) {
Set<Method> methods;
try {
Method[] publicMethods = clazz.getMethods();
Method[] privateMethods = clazz.getDeclaredMethods();
methods = new HashSet<>(publicMethods.length + privateMethods.length, 1.0f);
Collections.addAll(methods, publicMethods);
Collections.addAll(methods, privateMethods);
} catch (NoClassDefFoundError error) {
LOGGER.severe("Failed to load class " + clazz.getName() + " due to missing dependencies, " + error.getCause() + ": " + error.getMessage());
return;
}
Map<ProtocolHandler.PayloadReceiver, Executable> map = KNOWN_TYPES.getOrDefault(protocol, new HashMap<>());
for (final Method method : methods) {
if (method.isBridge() || method.isSynthetic() || !Modifier.isStatic(method.getModifiers())) {
continue;
}
method.setAccessible(true);
final ProtocolHandler.Init init = method.getAnnotation(ProtocolHandler.Init.class);
if (init != null) {
try {
method.invoke(null);
} catch (InvocationTargetException | IllegalAccessException exception) {
LOGGER.severe("Failed to invoke init method " + method.getName() + " in " + clazz.getName() + ", " + exception.getCause() + ": " + exception.getMessage());
}
continue;
}
final ProtocolHandler.PayloadReceiver receiver = method.getAnnotation(ProtocolHandler.PayloadReceiver.class);
if (receiver != null) {
try {
boolean found = false;
for (Method payloadMethod : receiver.payload().getDeclaredMethods()) {
if (payloadMethod.isAnnotationPresent(LeavesCustomPayload.New.class)) {
if (Arrays.equals(payloadMethod.getParameterTypes(), PAYLOAD_PARAMETER_TYPES) && payloadMethod.getReturnType() == receiver.payload() && Modifier.isStatic(payloadMethod.getModifiers())) {
payloadMethod.setAccessible(true);
map.put(receiver, payloadMethod);
found = true;
break;
}
}
}
if (!found) {
Constructor<? extends LeavesCustomPayload<?>> constructor = receiver.payload().getConstructor(PAYLOAD_PARAMETER_TYPES);
if (constructor.isAnnotationPresent(LeavesCustomPayload.New.class)) {
constructor.setAccessible(true);
map.put(receiver, constructor);
} else {
throw new NoSuchMethodException();
}
}
} catch (NoSuchMethodException exception) {
LOGGER.severe("Failed to find constructor for " + receiver.payload().getName() + ", " + exception.getCause() + ": " + exception.getMessage());
continue;
}
if (!KNOW_RECEIVERS.containsKey(protocol)) {
KNOW_RECEIVERS.put(protocol, new HashMap<>());
}
KNOW_RECEIVERS.get(protocol).put(receiver, method);
continue;
}
final ProtocolHandler.Ticker ticker = method.getAnnotation(ProtocolHandler.Ticker.class);
if (ticker != null) {
TICKERS.add(method);
continue;
}
final ProtocolHandler.PlayerJoin playerJoin = method.getAnnotation(ProtocolHandler.PlayerJoin.class);
if (playerJoin != null) {
PLAYER_JOIN.add(method);
continue;
}
final ProtocolHandler.PlayerLeave playerLeave = method.getAnnotation(ProtocolHandler.PlayerLeave.class);
if (playerLeave != null) {
PLAYER_LEAVE.add(method);
continue;
}
final ProtocolHandler.ReloadServer reloadServer = method.getAnnotation(ProtocolHandler.ReloadServer.class);
if (reloadServer != null) {
RELOAD_SERVER.add(method);
continue;
}
final ProtocolHandler.MinecraftRegister minecraftRegister = method.getAnnotation(ProtocolHandler.MinecraftRegister.class);
if (minecraftRegister != null) {
if (!MINECRAFT_REGISTER.containsKey(protocol)) {
MINECRAFT_REGISTER.put(protocol, new HashMap<>());
}
MINECRAFT_REGISTER.get(protocol).put(minecraftRegister, method);
}
}
KNOWN_TYPES.put(protocol, map);
}
}
for (LeavesProtocol protocol : KNOWN_TYPES.keySet()) {
Map<ProtocolHandler.PayloadReceiver, Executable> map = KNOWN_TYPES.get(protocol);
for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) {
if (receiver.sendFabricRegister() && !receiver.ignoreId()) {
for (String payloadId : receiver.payloadId()) {
for (String namespace : protocol.namespace()) {
ALL_KNOWN_ID.add(ResourceLocation.tryBuild(namespace, payloadId));
}
}
}
}
}
ALL_KNOWN_ID = ImmutableSet.copyOf(ALL_KNOWN_ID);
}
public static LeavesCustomPayload<?> decode(ResourceLocation id, FriendlyByteBuf buf) {
for (LeavesProtocol protocol : KNOWN_TYPES.keySet()) {
if (!ArrayUtils.contains(protocol.namespace(), id.getNamespace())) {
continue;
}
Map<ProtocolHandler.PayloadReceiver, Executable> map = KNOWN_TYPES.get(protocol);
for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) {
if (receiver.ignoreId() || ArrayUtils.contains(receiver.payloadId(), id.getPath())) {
try {
if (map.get(receiver) instanceof Constructor<?> constructor) {
return (LeavesCustomPayload<?>) constructor.newInstance(id, buf);
} else if (map.get(receiver) instanceof Method method) {
return (LeavesCustomPayload<?>) method.invoke(null, id, buf);
}
} catch (InvocationTargetException | InstantiationException | IllegalAccessException exception) {
LOGGER.warning("Failed to create payload for " + id + " in " + ArrayUtils.toString(protocol.namespace()) + ", " + exception.getCause() + ": " + exception.getMessage());
buf.readBytes(buf.readableBytes());
return new ErrorPayload(id, protocol.namespace(), receiver.payloadId());
}
}
}
}
return null;
}
public static void handlePayload(ServerPlayer player, LeavesCustomPayload<?> payload) {
if (payload instanceof ErrorPayload errorPayload) {
player.connection.disconnect(Component.literal("Payload " + Arrays.toString(errorPayload.packetID) + " from " + Arrays.toString(errorPayload.protocolID) + " error"), PlayerKickEvent.Cause.INVALID_PAYLOAD);
return;
}
for (LeavesProtocol protocol : KNOW_RECEIVERS.keySet()) {
if (!ArrayUtils.contains(protocol.namespace(), payload.type().id().getNamespace())) {
continue;
}
Map<ProtocolHandler.PayloadReceiver, Method> map = KNOW_RECEIVERS.get(protocol);
for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) {
if (payload.getClass() == receiver.payload()) {
if (receiver.ignoreId() || ArrayUtils.contains(receiver.payloadId(), payload.type().id().getPath())) {
try {
map.get(receiver).invoke(null, player, payload);
} catch (InvocationTargetException | IllegalAccessException exception) {
LOGGER.warning("Failed to handle payload " + payload.type().id() + " in " + ArrayUtils.toString(protocol.namespace()) + ", " + exception.getCause() + ": " + exception.getMessage());
}
}
}
}
}
}
public static void handleTick() {
if (!TICKERS.isEmpty()) {
try {
for (Method method : TICKERS) {
method.invoke(null);
}
} catch (InvocationTargetException | IllegalAccessException exception) {
LOGGER.warning("Failed to tick, " + exception.getCause() + ": " + exception.getMessage());
}
}
}
public static void handlePlayerJoin(ServerPlayer player) {
if (!PLAYER_JOIN.isEmpty()) {
try {
for (Method method : PLAYER_JOIN) {
method.invoke(null, player);
}
} catch (InvocationTargetException | IllegalAccessException exception) {
LOGGER.warning("Failed to handle player join, " + exception.getCause() + ": " + exception.getMessage());
}
}
ProtocolUtils.sendPayloadPacket(player, new FabricRegisterPayload(ALL_KNOWN_ID));
}
public static void handlePlayerLeave(ServerPlayer player) {
if (!PLAYER_LEAVE.isEmpty()) {
try {
for (Method method : PLAYER_LEAVE) {
method.invoke(null, player);
}
} catch (InvocationTargetException | IllegalAccessException exception) {
LOGGER.warning("Failed to handle player leave, " + exception.getCause() + ": " + exception.getMessage());
}
}
}
public static void handleServerReload() {
if (!RELOAD_SERVER.isEmpty()) {
try {
for (Method method : RELOAD_SERVER) {
method.invoke(null);
}
} catch (InvocationTargetException | IllegalAccessException exception) {
LOGGER.warning("Failed to handle server reload, " + exception.getCause() + ": " + exception.getMessage());
}
}
}
public static void handleMinecraftRegister(String channelId, ServerPlayer player) {
for (LeavesProtocol protocol : MINECRAFT_REGISTER.keySet()) {
String[] channel = channelId.split(":");
if (!ArrayUtils.contains(protocol.namespace(), channel[0])) {
continue;
}
Map<ProtocolHandler.MinecraftRegister, Method> map = MINECRAFT_REGISTER.get(protocol);
for (ProtocolHandler.MinecraftRegister register : map.keySet()) {
if (register.ignoreId() || ArrayUtils.contains(register.channelId(), channel[1])) {
try {
map.get(register).invoke(null, player, channel[1]);
} catch (InvocationTargetException | IllegalAccessException exception) {
LOGGER.warning("Failed to handle minecraft register, " + exception.getCause() + ": " + exception.getMessage());
}
}
}
}
}
public static Set<Class<?>> getClasses(String pack) {
Set<Class<?>> classes = new LinkedHashSet<>();
String packageDirName = pack.replace('.', '/');
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
findClassesInPackageByFile(pack, filePath, classes);
} else if ("jar".equals(protocol)) {
JarFile jar;
try {
jar = ((JarURLConnection) url.openConnection()).getJarFile();
Enumeration<JarEntry> entries = jar.entries();
findClassesInPackageByJar(pack, entries, packageDirName, classes);
} catch (IOException exception) {
LOGGER.warning("Failed to load jar file, " + exception.getCause() + ": " + exception.getMessage());
}
}
}
} catch (IOException exception) {
LOGGER.warning("Failed to load classes, " + exception.getCause() + ": " + exception.getMessage());
}
return classes;
}
private static void findClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes) {
File dir = new File(packagePath);
if (!dir.exists() || !dir.isDirectory()) {
return;
}
File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class"));
if (dirfiles != null) {
for (File file : dirfiles) {
if (file.isDirectory()) {
findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes);
} else {
String className = file.getName().substring(0, file.getName().length() - 6);
try {
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException exception) {
LOGGER.warning("Failed to load class " + className + ", " + exception.getCause() + ": " + exception.getMessage());
}
}
}
}
}
private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, Set<Class<?>> classes) {
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (name.charAt(0) == '/') {
name = name.substring(1);
}
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
if (idx != -1) {
packageName = name.substring(0, idx).replace('/', '.');
}
if (name.endsWith(".class") && !entry.isDirectory()) {
String className = name.substring(packageName.length() + 1, name.length() - 6);
try {
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException exception) {
LOGGER.warning("Failed to load class " + className + ", " + exception.getCause() + ": " + exception.getMessage());
}
}
}
}
}
public record ErrorPayload(ResourceLocation id, String[] protocolID, String[] packetID) implements LeavesCustomPayload<ErrorPayload> {
@Override
public void write(@NotNull FriendlyByteBuf buf) {
}
}
public record EmptyPayload(ResourceLocation id) implements LeavesCustomPayload<EmptyPayload> {
@New
public EmptyPayload(ResourceLocation location, FriendlyByteBuf buf) {
this(location);
}
@Override
public void write(@NotNull FriendlyByteBuf buf) {
}
}
public record LeavesPayload(FriendlyByteBuf data, ResourceLocation id) implements LeavesCustomPayload<LeavesPayload> {
@New
public LeavesPayload(ResourceLocation location, FriendlyByteBuf buf) {
this(new FriendlyByteBuf(buf.readBytes(buf.readableBytes())), location);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeBytes(data);
}
}
public record FabricRegisterPayload(Set<ResourceLocation> channels) implements LeavesCustomPayload<FabricRegisterPayload> {
public static final ResourceLocation CHANNEL = ResourceLocation.withDefaultNamespace("register");
@New
public FabricRegisterPayload(ResourceLocation location, FriendlyByteBuf buf) {
this(buf.readCollection(HashSet::new, FriendlyByteBuf::readResourceLocation));
}
@Override
public void write(FriendlyByteBuf buf) {
boolean first = true;
ResourceLocation channel;
for (Iterator<ResourceLocation> var3 = this.channels.iterator(); var3.hasNext(); buf.writeBytes(channel.toString().getBytes(StandardCharsets.US_ASCII))) {
channel = var3.next();
if (first) {
first = false;
} else {
buf.writeByte(0);
}
}
}
@Override
public ResourceLocation id() {
return CHANNEL;
}
}
}

View File

@@ -0,0 +1,55 @@
package org.leavesmc.leaves.protocol.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class ProtocolHandler {
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Init {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PayloadReceiver {
Class<? extends LeavesCustomPayload<?>> payload();
String[] payloadId() default "";
boolean ignoreId() default false;
boolean sendFabricRegister() default true;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ticker {
int delay() default 0;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PlayerJoin {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PlayerLeave {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReloadServer {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MinecraftRegister {
String[] channelId() default "";
boolean ignoreId() default false;
}
}

View File

@@ -0,0 +1,52 @@
package org.leavesmc.leaves.protocol.core;
import io.netty.buffer.ByteBuf;
import io.papermc.paper.ServerBuildInfo;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
import java.util.function.Function;
public class ProtocolUtils {
private static final Function<ByteBuf, RegistryFriendlyByteBuf> bufDecorator = RegistryFriendlyByteBuf.decorator(MinecraftServer.getServer().registryAccess());
public static String buildProtocolVersion(String protocol) {
return protocol + "-leaves-" + ServerBuildInfo.buildInfo().asString(ServerBuildInfo.StringRepresentation.VERSION_SIMPLE);
}
public static void sendEmptyPayloadPacket(ServerPlayer player, ResourceLocation id) {
player.connection.send(new ClientboundCustomPayloadPacket(new LeavesProtocolManager.EmptyPayload(id)));
}
@SuppressWarnings("all")
public static void sendPayloadPacket(@NotNull ServerPlayer player, ResourceLocation id, Consumer<FriendlyByteBuf> consumer) {
player.connection.send(new ClientboundCustomPayloadPacket(new LeavesCustomPayload() {
@Override
public void write(@NotNull FriendlyByteBuf buf) {
consumer.accept(buf);
}
@Override
@NotNull
public ResourceLocation id() {
return id;
}
}));
}
public static void sendPayloadPacket(ServerPlayer player, CustomPacketPayload payload) {
player.connection.send(new ClientboundCustomPayloadPacket(payload));
}
public static RegistryFriendlyByteBuf decorate(ByteBuf buf) {
return bufDecorator.apply(buf);
}
}

View File

@@ -0,0 +1,296 @@
package org.leavesmc.leaves.protocol.jade;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.animal.Chicken;
import net.minecraft.world.entity.animal.allay.Allay;
import net.minecraft.world.entity.animal.armadillo.Armadillo;
import net.minecraft.world.entity.animal.frog.Tadpole;
import net.minecraft.world.entity.monster.ZombieVillager;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.CampfireBlock;
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BrewingStandBlockEntity;
import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity;
import net.minecraft.world.level.block.entity.ChiseledBookShelfBlockEntity;
import net.minecraft.world.level.block.entity.CommandBlockEntity;
import net.minecraft.world.level.block.entity.ComparatorBlockEntity;
import net.minecraft.world.level.block.entity.HopperBlockEntity;
import net.minecraft.world.level.block.entity.JukeboxBlockEntity;
import net.minecraft.world.level.block.entity.LecternBlockEntity;
import net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.LeavesLogger;
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.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.accessor.EntityAccessor;
import org.leavesmc.leaves.protocol.jade.payload.ClientHandshakePayload;
import org.leavesmc.leaves.protocol.jade.payload.ReceiveDataPayload;
import org.leavesmc.leaves.protocol.jade.payload.RequestBlockPayload;
import org.leavesmc.leaves.protocol.jade.payload.RequestEntityPayload;
import org.leavesmc.leaves.protocol.jade.payload.ServerHandshakePayload;
import org.leavesmc.leaves.protocol.jade.provider.IJadeProvider;
import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider;
import org.leavesmc.leaves.protocol.jade.provider.IServerExtensionProvider;
import org.leavesmc.leaves.protocol.jade.provider.ItemStorageExtensionProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.BeehiveProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.BrewingStandProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.CampfireProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.ChiseledBookshelfProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.CommandBlockProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.FurnaceProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.HopperLockProvider;
import org.leavesmc.leaves.protocol.jade.provider.ItemStorageProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.JukeboxProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.LecternProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.MobSpawnerCooldownProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.ObjectNameProvider;
import org.leavesmc.leaves.protocol.jade.provider.block.RedstoneProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.AnimalOwnerProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.MobBreedingProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.MobGrowthProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.NextEntityDropProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.PetArmorProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.StatusEffectsProvider;
import org.leavesmc.leaves.protocol.jade.provider.entity.ZombieVillagerProvider;
import org.leavesmc.leaves.protocol.jade.util.HierarchyLookup;
import org.leavesmc.leaves.protocol.jade.util.LootTableMineableCollector;
import org.leavesmc.leaves.protocol.jade.util.PairHierarchyLookup;
import org.leavesmc.leaves.protocol.jade.util.PriorityStore;
import org.leavesmc.leaves.protocol.jade.util.WrappedHierarchyLookup;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@LeavesProtocol(namespace = "jade")
public class JadeProtocol {
public static PriorityStore<ResourceLocation, IJadeProvider> priorities;
private static List<Block> shearableBlocks = null;
public static final String PROTOCOL_ID = "jade";
public static final String PROTOCOL_VERSION = "7";
public static final HierarchyLookup<IServerDataProvider<EntityAccessor>> entityDataProviders = new HierarchyLookup<>(Entity.class);
public static final PairHierarchyLookup<IServerDataProvider<BlockAccessor>> blockDataProviders = new PairHierarchyLookup<>(new HierarchyLookup<>(Block.class), new HierarchyLookup<>(BlockEntity.class));
public static final WrappedHierarchyLookup<IServerExtensionProvider<ItemStack>> itemStorageProviders = WrappedHierarchyLookup.forAccessor();
private static final Set<ServerPlayer> enabledPlayers = new HashSet<>();
@Contract("_ -> new")
public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path);
}
@Contract("_ -> new")
public static @NotNull ResourceLocation mc_id(String path) {
return ResourceLocation.withDefaultNamespace(path);
}
@ProtocolHandler.Init
public static void init() {
priorities = new PriorityStore<>(IJadeProvider::getDefaultPriority, IJadeProvider::getUid);
// core plugin
blockDataProviders.register(BlockEntity.class, ObjectNameProvider.ForBlock.INSTANCE);
// universal plugin
entityDataProviders.register(Entity.class, ItemStorageProvider.getEntity());
blockDataProviders.register(Block.class, ItemStorageProvider.getBlock());
itemStorageProviders.register(Object.class, ItemStorageExtensionProvider.INSTANCE);
itemStorageProviders.register(Block.class, ItemStorageExtensionProvider.INSTANCE);
// vanilla plugin
entityDataProviders.register(Entity.class, AnimalOwnerProvider.INSTANCE);
entityDataProviders.register(LivingEntity.class, StatusEffectsProvider.INSTANCE);
entityDataProviders.register(AgeableMob.class, MobGrowthProvider.INSTANCE);
entityDataProviders.register(Tadpole.class, MobGrowthProvider.INSTANCE);
entityDataProviders.register(Animal.class, MobBreedingProvider.INSTANCE);
entityDataProviders.register(Allay.class, MobBreedingProvider.INSTANCE);
entityDataProviders.register(Mob.class, PetArmorProvider.INSTANCE);
entityDataProviders.register(Chicken.class, NextEntityDropProvider.INSTANCE);
entityDataProviders.register(Armadillo.class, NextEntityDropProvider.INSTANCE);
entityDataProviders.register(ZombieVillager.class, ZombieVillagerProvider.INSTANCE);
blockDataProviders.register(BrewingStandBlockEntity.class, BrewingStandProvider.INSTANCE);
blockDataProviders.register(BeehiveBlockEntity.class, BeehiveProvider.INSTANCE);
blockDataProviders.register(CommandBlockEntity.class, CommandBlockProvider.INSTANCE);
blockDataProviders.register(JukeboxBlockEntity.class, JukeboxProvider.INSTANCE);
blockDataProviders.register(LecternBlockEntity.class, LecternProvider.INSTANCE);
blockDataProviders.register(ComparatorBlockEntity.class, RedstoneProvider.INSTANCE);
blockDataProviders.register(HopperBlockEntity.class, HopperLockProvider.INSTANCE);
blockDataProviders.register(CalibratedSculkSensorBlockEntity.class, RedstoneProvider.INSTANCE);
blockDataProviders.register(AbstractFurnaceBlockEntity.class, FurnaceProvider.INSTANCE);
blockDataProviders.register(ChiseledBookShelfBlockEntity.class, ChiseledBookshelfProvider.INSTANCE);
blockDataProviders.register(TrialSpawnerBlockEntity.class, MobSpawnerCooldownProvider.INSTANCE);
blockDataProviders.idMapped();
entityDataProviders.idMapped();
itemStorageProviders.register(CampfireBlock.class, CampfireProvider.INSTANCE);
blockDataProviders.loadComplete(priorities);
entityDataProviders.loadComplete(priorities);
itemStorageProviders.loadComplete(priorities);
rebuildShearableBlocks();
}
@ProtocolHandler.PayloadReceiver(payload = ClientHandshakePayload.class, payloadId = "client_handshake")
public static void clientHandshake(ServerPlayer player, ClientHandshakePayload payload) {
if (!LeavesConfig.protocol.jadeProtocol) {
return;
}
if (!payload.protocolVersion().equals(PROTOCOL_VERSION)) {
player.sendSystemMessage(Component.literal("You are using a different version of Jade than the server. Please update Jade or report to the server operator").withColor(0xff0000));
return;
}
ProtocolUtils.sendPayloadPacket(player, new ServerHandshakePayload(Collections.emptyMap(), shearableBlocks, blockDataProviders.mappedIds(), entityDataProviders.mappedIds()));
enabledPlayers.add(player);
}
@ProtocolHandler.PlayerLeave
public static void onPlayerLeave(ServerPlayer player) {
enabledPlayers.remove(player);
}
@ProtocolHandler.PayloadReceiver(payload = RequestEntityPayload.class, payloadId = "request_entity")
public static void requestEntityData(ServerPlayer player, RequestEntityPayload payload) {
if (!LeavesConfig.protocol.jadeProtocol) {
return;
}
MinecraftServer.getServer().execute(() -> {
EntityAccessor accessor = payload.data().unpack(player);
if (accessor == null) {
return;
}
Entity entity = accessor.getEntity();
double maxDistance = Mth.square(player.entityInteractionRange() + 21);
if (entity == null || player.distanceToSqr(entity) > maxDistance) {
return;
}
List<IServerDataProvider<EntityAccessor>> providers = entityDataProviders.get(entity);
if (providers.isEmpty()) {
return;
}
CompoundTag tag = new CompoundTag();
for (IServerDataProvider<EntityAccessor> provider : providers) {
if (!payload.dataProviders().contains(provider)) {
continue;
}
try {
provider.appendServerData(tag, accessor);
} catch (Exception e) {
LeavesLogger.LOGGER.warning("Error while saving data for entity " + entity);
}
}
tag.putInt("EntityId", entity.getId());
ProtocolUtils.sendPayloadPacket(player, new ReceiveDataPayload(tag));
});
}
@ProtocolHandler.PayloadReceiver(payload = RequestBlockPayload.class, payloadId = "request_block")
public static void requestBlockData(ServerPlayer player, RequestBlockPayload payload) {
if (!LeavesConfig.protocol.jadeProtocol) {
return;
}
MinecraftServer server = MinecraftServer.getServer();
server.execute(() -> {
BlockAccessor accessor = payload.data().unpack(player);
if (accessor == null) {
return;
}
BlockPos pos = accessor.getPosition();
Block block = accessor.getBlock();
BlockEntity blockEntity = accessor.getBlockEntity();
double maxDistance = Mth.square(player.blockInteractionRange() + 21);
if (pos.distSqr(player.blockPosition()) > maxDistance || !accessor.getLevel().isLoaded(pos)) {
return;
}
List<IServerDataProvider<BlockAccessor>> providers;
if (blockEntity != null) {
providers = blockDataProviders.getMerged(block, blockEntity);
} else {
providers = blockDataProviders.first.get(block);
}
if (providers.isEmpty()) {
return;
}
CompoundTag tag = new CompoundTag();
for (IServerDataProvider<BlockAccessor> provider : providers) {
if (!payload.dataProviders().contains(provider)) {
continue;
}
try {
provider.appendServerData(tag, accessor);
} catch (Exception e) {
LeavesLogger.LOGGER.warning("Error while saving data for block " + accessor.getBlockState());
}
}
tag.putInt("x", pos.getX());
tag.putInt("y", pos.getY());
tag.putInt("z", pos.getZ());
tag.putString("BlockId", BuiltInRegistries.BLOCK.getKey(block).toString());
ProtocolUtils.sendPayloadPacket(player, new ReceiveDataPayload(tag));
});
}
@ProtocolHandler.ReloadServer
public static void onServerReload() {
if (LeavesConfig.protocol.jadeProtocol) {
rebuildShearableBlocks();
for (ServerPlayer player : enabledPlayers) {
ProtocolUtils.sendPayloadPacket(player, new ServerHandshakePayload(Collections.emptyMap(), shearableBlocks, blockDataProviders.mappedIds(), entityDataProviders.mappedIds()));
}
}
}
private static void rebuildShearableBlocks() {
try {
shearableBlocks = Collections.unmodifiableList(LootTableMineableCollector.execute(
MinecraftServer.getServer().reloadableRegistries().lookup().lookupOrThrow(Registries.LOOT_TABLE),
Items.SHEARS.getDefaultInstance()
));
} catch (Throwable ignore) {
shearableBlocks = List.of();
LeavesLogger.LOGGER.severe("Failed to collect shearable blocks");
}
}
}

View File

@@ -0,0 +1,32 @@
package org.leavesmc.leaves.protocol.jade.accessor;
import net.minecraft.nbt.Tag;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamEncoder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
import org.jetbrains.annotations.Nullable;
public interface Accessor<T extends HitResult> {
Level getLevel();
Player getPlayer();
<D> Tag encodeAsNbt(StreamEncoder<RegistryFriendlyByteBuf, D> codec, D value);
T getHitResult();
/**
* @return {@code true} if the dedicated server has Jade installed.
*/
boolean isServerConnected();
boolean showDetails();
@Nullable
Object getTarget();
float tickRate();
}

View File

@@ -0,0 +1,82 @@
package org.leavesmc.leaves.protocol.jade.accessor;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.ByteArrayTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamEncoder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
import org.apache.commons.lang3.ArrayUtils;
import java.util.function.Supplier;
public abstract class AccessorImpl<T extends HitResult> implements Accessor<T> {
private final Level level;
private final Player player;
private final Supplier<T> hit;
private final boolean serverConnected;
private final boolean showDetails;
protected boolean verify;
private RegistryFriendlyByteBuf buffer;
public AccessorImpl(Level level, Player player, Supplier<T> hit, boolean serverConnected, boolean showDetails) {
this.level = level;
this.player = player;
this.hit = hit;
this.serverConnected = serverConnected;
this.showDetails = showDetails;
}
@Override
public Level getLevel() {
return level;
}
@Override
public Player getPlayer() {
return player;
}
private RegistryFriendlyByteBuf buffer() {
if (buffer == null) {
buffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), level.registryAccess());
}
buffer.clear();
return buffer;
}
@Override
public <D> Tag encodeAsNbt(StreamEncoder<RegistryFriendlyByteBuf, D> streamCodec, D value) {
RegistryFriendlyByteBuf buffer = buffer();
streamCodec.encode(buffer, value);
ByteArrayTag tag = new ByteArrayTag(ArrayUtils.subarray(buffer.array(), 0, buffer.readableBytes()));
buffer.clear();
return tag;
}
@Override
public T getHitResult() {
return hit.get();
}
/**
* Returns true if dedicated server has Jade installed.
*/
@Override
public boolean isServerConnected() {
return serverConnected;
}
@Override
public boolean showDetails() {
return showDetails;
}
@Override
public float tickRate() {
return getLevel().tickRateManager().tickrate();
}
}

View File

@@ -0,0 +1,50 @@
package org.leavesmc.leaves.protocol.jade.accessor;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import org.jetbrains.annotations.ApiStatus;
import java.util.function.Supplier;
public interface BlockAccessor extends Accessor<BlockHitResult> {
Block getBlock();
BlockState getBlockState();
BlockEntity getBlockEntity();
BlockPos getPosition();
Direction getSide();
@ApiStatus.NonExtendable
interface Builder {
Builder level(Level level);
Builder player(Player player);
Builder showDetails(boolean showDetails);
Builder hit(BlockHitResult hit);
Builder blockState(BlockState state);
default Builder blockEntity(BlockEntity blockEntity) {
return blockEntity(() -> blockEntity);
}
Builder blockEntity(Supplier<BlockEntity> blockEntity);
Builder from(BlockAccessor accessor);
BlockAccessor build();
}
}

View File

@@ -0,0 +1,163 @@
package org.leavesmc.leaves.protocol.jade.accessor;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.google.common.base.Suppliers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
/**
* Class to get information of block target and context.
*/
public class BlockAccessorImpl extends AccessorImpl<BlockHitResult> implements BlockAccessor {
private final BlockState blockState;
@Nullable
private final Supplier<BlockEntity> blockEntity;
private BlockAccessorImpl(Builder builder) {
super(builder.level, builder.player, Suppliers.ofInstance(builder.hit), builder.connected, builder.showDetails);
blockState = builder.blockState;
blockEntity = builder.blockEntity;
}
@Override
public Block getBlock() {
return getBlockState().getBlock();
}
@Override
public BlockState getBlockState() {
return blockState;
}
@Override
public BlockEntity getBlockEntity() {
return blockEntity == null ? null : blockEntity.get();
}
@Override
public BlockPos getPosition() {
return getHitResult().getBlockPos();
}
@Override
public Direction getSide() {
return getHitResult().getDirection();
}
@Nullable
@Override
public Object getTarget() {
return getBlockEntity();
}
public static class Builder implements BlockAccessor.Builder {
private Level level;
private Player player;
private boolean connected;
private boolean showDetails;
private BlockHitResult hit;
private BlockState blockState = Blocks.AIR.defaultBlockState();
private Supplier<BlockEntity> blockEntity;
@Override
public Builder level(Level level) {
this.level = level;
return this;
}
@Override
public Builder player(Player player) {
this.player = player;
return this;
}
@Override
public Builder showDetails(boolean showDetails) {
this.showDetails = showDetails;
return this;
}
@Override
public Builder hit(BlockHitResult hit) {
this.hit = hit;
return this;
}
@Override
public Builder blockState(BlockState blockState) {
this.blockState = blockState;
return this;
}
@Override
public Builder blockEntity(Supplier<BlockEntity> blockEntity) {
this.blockEntity = blockEntity;
return this;
}
@Override
public Builder from(BlockAccessor accessor) {
level = accessor.getLevel();
player = accessor.getPlayer();
connected = accessor.isServerConnected();
showDetails = accessor.showDetails();
hit = accessor.getHitResult();
blockEntity = accessor::getBlockEntity;
blockState = accessor.getBlockState();
return this;
}
@Override
public BlockAccessor build() {
return new BlockAccessorImpl(this);
}
}
public record SyncData(boolean showDetails, BlockHitResult hit, BlockState blockState, ItemStack fakeBlock) {
public static final StreamCodec<RegistryFriendlyByteBuf, SyncData> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL,
SyncData::showDetails,
StreamCodec.of(FriendlyByteBuf::writeBlockHitResult, FriendlyByteBuf::readBlockHitResult),
SyncData::hit,
ByteBufCodecs.idMapper(Block.BLOCK_STATE_REGISTRY),
SyncData::blockState,
ItemStack.OPTIONAL_STREAM_CODEC,
SyncData::fakeBlock,
SyncData::new
);
public BlockAccessor unpack(ServerPlayer player) {
Supplier<BlockEntity> blockEntity = null;
if (blockState.hasBlockEntity()) {
blockEntity = Suppliers.memoize(() -> player.level().getBlockEntity(hit.getBlockPos()));
}
return new Builder()
.level(player.level())
.player(player)
.showDetails(showDetails)
.hit(hit)
.blockState(blockState)
.blockEntity(blockEntity)
.build();
}
}
}

View File

@@ -0,0 +1,44 @@
package org.leavesmc.leaves.protocol.jade.accessor;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.EntityHitResult;
import org.jetbrains.annotations.ApiStatus;
import java.util.function.Supplier;
public interface EntityAccessor extends Accessor<EntityHitResult> {
Entity getEntity();
/**
* For part entity like ender dragon's, getEntity() will return the parent entity.
*/
Entity getRawEntity();
@ApiStatus.NonExtendable
interface Builder {
Builder level(Level level);
Builder player(Player player);
Builder showDetails(boolean showDetails);
default Builder hit(EntityHitResult hit) {
return hit(() -> hit);
}
Builder hit(Supplier<EntityHitResult> hit);
default Builder entity(Entity entity) {
return entity(() -> entity);
}
Builder entity(Supplier<Entity> entity);
Builder from(EntityAccessor accessor);
EntityAccessor build();
}
}

View File

@@ -0,0 +1,123 @@
package org.leavesmc.leaves.protocol.jade.accessor;
import com.google.common.base.Suppliers;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
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.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.jade.util.CommonUtil;
import java.util.function.Supplier;
public class EntityAccessorImpl extends AccessorImpl<EntityHitResult> implements EntityAccessor {
private final Supplier<Entity> entity;
public EntityAccessorImpl(Builder builder) {
super(builder.level, builder.player, builder.hit, builder.connected, builder.showDetails);
entity = builder.entity;
}
@Override
public Entity getEntity() {
return CommonUtil.wrapPartEntityParent(getRawEntity());
}
@Override
public Entity getRawEntity() {
return entity.get();
}
@NotNull
@Override
public Object getTarget() {
return getEntity();
}
public static class Builder implements EntityAccessor.Builder {
public boolean showDetails;
private Level level;
private Player player;
private boolean connected;
private Supplier<EntityHitResult> hit;
private Supplier<Entity> entity;
@Override
public Builder level(Level level) {
this.level = level;
return this;
}
@Override
public Builder player(Player player) {
this.player = player;
return this;
}
@Override
public Builder showDetails(boolean showDetails) {
this.showDetails = showDetails;
return this;
}
@Override
public Builder hit(Supplier<EntityHitResult> hit) {
this.hit = hit;
return this;
}
@Override
public Builder entity(Supplier<Entity> entity) {
this.entity = entity;
return this;
}
@Override
public Builder from(EntityAccessor accessor) {
level = accessor.getLevel();
player = accessor.getPlayer();
connected = accessor.isServerConnected();
showDetails = accessor.showDetails();
hit = accessor::getHitResult;
entity = accessor::getEntity;
return this;
}
@Override
public EntityAccessor build() {
return new EntityAccessorImpl(this);
}
}
public record SyncData(boolean showDetails, int id, int partIndex, Vec3 hitVec) {
public static final StreamCodec<RegistryFriendlyByteBuf, SyncData> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL,
SyncData::showDetails,
ByteBufCodecs.VAR_INT,
SyncData::id,
ByteBufCodecs.VAR_INT,
SyncData::partIndex,
ByteBufCodecs.VECTOR3F.map(Vec3::new, Vec3::toVector3f),
SyncData::hitVec,
SyncData::new
);
public EntityAccessor unpack(ServerPlayer player) {
Supplier<Entity> entity = Suppliers.memoize(() -> CommonUtil.getPartEntity(player.level().getEntity(id), partIndex));
return new EntityAccessorImpl.Builder()
.level(player.level())
.player(player)
.showDetails(showDetails)
.entity(entity)
.hit(Suppliers.memoize(() -> new EntityHitResult(entity.get(), hitVec)))
.build();
}
}
}

View File

@@ -0,0 +1,35 @@
package org.leavesmc.leaves.protocol.jade.payload;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
public record ClientHandshakePayload(String protocolVersion) implements LeavesCustomPayload<ClientHandshakePayload> {
private static final ResourceLocation PACKET_CLIENT_HANDSHAKE = JadeProtocol.id("client_handshake");
private static final StreamCodec<RegistryFriendlyByteBuf, ClientHandshakePayload> CODEC = StreamCodec.composite(
ByteBufCodecs.STRING_UTF8,
ClientHandshakePayload::protocolVersion,
ClientHandshakePayload::new);
@Override
public void write(FriendlyByteBuf buf) {
CODEC.encode(ProtocolUtils.decorate(buf), this);
}
@Override
public ResourceLocation id() {
return PACKET_CLIENT_HANDSHAKE;
}
@New
public static ClientHandshakePayload create(ResourceLocation location, FriendlyByteBuf buf) {
return CODEC.decode(ProtocolUtils.decorate(buf));
}
}

View File

@@ -0,0 +1,28 @@
package org.leavesmc.leaves.protocol.jade.payload;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
public record ReceiveDataPayload(CompoundTag tag) implements LeavesCustomPayload<ReceiveDataPayload> {
private static final ResourceLocation PACKET_RECEIVE_DATA = JadeProtocol.id("receive_data");
@New
public ReceiveDataPayload(ResourceLocation id, FriendlyByteBuf buf) {
this(buf.readNbt());
}
@Override
public void write(@NotNull FriendlyByteBuf buf) {
buf.writeNbt(tag);
}
@Override
public ResourceLocation id() {
return PACKET_RECEIVE_DATA;
}
}

View File

@@ -0,0 +1,51 @@
package org.leavesmc.leaves.protocol.jade.payload;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import org.leavesmc.leaves.protocol.jade.JadeProtocol;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessor;
import org.leavesmc.leaves.protocol.jade.accessor.BlockAccessorImpl;
import org.leavesmc.leaves.protocol.jade.provider.IServerDataProvider;
import java.util.List;
import java.util.Objects;
import static org.leavesmc.leaves.protocol.jade.JadeProtocol.blockDataProviders;
public record RequestBlockPayload(BlockAccessorImpl.SyncData data, List<@Nullable IServerDataProvider<BlockAccessor>> dataProviders) implements LeavesCustomPayload<RequestBlockPayload> {
private static final ResourceLocation PACKET_REQUEST_BLOCK = JadeProtocol.id("request_block");
private static final StreamCodec<RegistryFriendlyByteBuf, RequestBlockPayload> CODEC = StreamCodec.composite(
BlockAccessorImpl.SyncData.STREAM_CODEC,
RequestBlockPayload::data,
ByteBufCodecs.<ByteBuf, IServerDataProvider<BlockAccessor>>list()
.apply(ByteBufCodecs.idMapper(
$ -> Objects.requireNonNull(blockDataProviders.idMapper()).byId($),
$ -> Objects.requireNonNull(blockDataProviders.idMapper()).getIdOrThrow($))),
RequestBlockPayload::dataProviders,
RequestBlockPayload::new);
@Override
public void write(FriendlyByteBuf buf) {
CODEC.encode(ProtocolUtils.decorate(buf), this);
}
@Override
@NotNull
public ResourceLocation id() {
return PACKET_REQUEST_BLOCK;
}
@New
public static RequestBlockPayload create(ResourceLocation location, FriendlyByteBuf buf) {
return CODEC.decode(ProtocolUtils.decorate(buf));
}
}

Some files were not shown because too many files have changed in this diff Show More