mirror of
https://github.com/LeavesMC/Leaves.git
synced 2025-12-28 19:39:22 +00:00
1.21.4 (#413)
* 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:
@@ -0,0 +1,2 @@
|
||||
[*.java]
|
||||
ij_java_use_fq_class_names = false
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
297
leaves-server/src/main/java/org/leavesmc/leaves/bot/BotList.java
Normal file
297
leaves-server/src/main/java/org/leavesmc/leaves/bot/BotList.java
Normal 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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}}));
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}}));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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() + '}';
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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() + '}';
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user