Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01dc9fa79c | ||
|
|
a32a784f87 | ||
|
|
0649034969 | ||
|
|
c42fffad3c | ||
|
|
21f8e92f2e | ||
|
|
9963664933 | ||
|
|
ddb8699328 | ||
|
|
ff742f0274 |
No files matched your search
@@ -7,6 +7,7 @@ public class SLSCarpetSettings {
|
||||
public static final String NEED_CLIENT = "needClient"; // 需要客户端安装SLS-Addition或实现相关支持
|
||||
public static final String FROM_AMS = "AMS";
|
||||
public static final String SLSA = "SLS";
|
||||
public static final String LEGACY = "legacy";
|
||||
|
||||
@Rule(categories = {SLSA, RuleCategory.SURVIVAL})
|
||||
public static boolean obtainableReinforcedDeepSlate = false;
|
||||
@@ -77,4 +78,32 @@ public class SLSCarpetSettings {
|
||||
|
||||
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
||||
public static boolean unbreakableDecoratedPot = false;
|
||||
// ===== [旧版] 遗留漏洞机制复刻 =====
|
||||
// 总开关:开启后,其余子规则才生效
|
||||
@Rule(categories = {LEGACY}, strict = false)
|
||||
public static boolean legacyExploitMode = false;
|
||||
|
||||
// 等价 instantTick:计划刻(scheduleTick)立即执行,不等延迟
|
||||
@Rule(categories = {LEGACY})
|
||||
public static boolean legacyInstantTick = false;
|
||||
|
||||
// 等价 instantFall:重力方块被计划刻触发时立即下落
|
||||
@Rule(categories = {LEGACY})
|
||||
public static boolean legacyInstantFall = false;
|
||||
|
||||
// 自建后台线程,模拟 1.12 信标线程并发访问 chunk
|
||||
@Rule(categories = {LEGACY})
|
||||
public static boolean legacyAsyncChunkAccess = false;
|
||||
|
||||
// 允许异步线程在 chunk 表竞态时把"已加载"当成"未加载",触发重新加载/装饰
|
||||
@Rule(categories = {LEGACY})
|
||||
public static boolean legacyChunkSwap = false;
|
||||
|
||||
// 落沙替换:在 FallingBlock 转实体窗口内把方块替换成目标方块
|
||||
@Rule(categories = {LEGACY})
|
||||
public static boolean legacyFallingBlockReplace = false;
|
||||
|
||||
// 目标方块(替换结果),默认末地传送门框架
|
||||
@Rule(categories = {LEGACY}, strict = false, options = {"#none", "end_portal_frame", "command_block", "spawner", "barrier", "nether_portal"})
|
||||
public static String legacyFallingBlockTarget = "#none";
|
||||
}
|
||||
@@ -2,9 +2,7 @@ package com.github.zly2006.carpetslsaddition;
|
||||
|
||||
import carpet.CarpetExtension;
|
||||
import carpet.CarpetServer;
|
||||
import com.github.zly2006.carpetslsaddition.command.BotAllCommand;
|
||||
import com.github.zly2006.carpetslsaddition.command.BotCommand;
|
||||
import com.github.zly2006.carpetslsaddition.command.BotListCommand;
|
||||
import com.github.zly2006.carpetslsaddition.command.HatCommand;
|
||||
import com.github.zly2006.carpetslsaddition.command.SitCommand;
|
||||
import com.google.common.collect.Maps;
|
||||
@@ -53,6 +51,34 @@ public class ServerMain implements ModInitializer, CarpetExtension {
|
||||
@Override
|
||||
public void onServerLoaded(MinecraftServer server) {
|
||||
ServerMain.server = server;
|
||||
startLegacyRuleWatcher();
|
||||
}
|
||||
|
||||
/** [旧版] 轮询规则状态,按需启停异步 chunk 线程。 */
|
||||
private void startLegacyRuleWatcher() {
|
||||
Thread watcher = new Thread(() -> {
|
||||
boolean running = false;
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
boolean want = SLSCarpetSettings.legacyExploitMode
|
||||
&& SLSCarpetSettings.legacyAsyncChunkAccess;
|
||||
if (want && !running) {
|
||||
com.github.zly2006.carpetslsaddition.util.LegacyAsyncExecutor.start();
|
||||
running = true;
|
||||
} else if (!want && running) {
|
||||
com.github.zly2006.carpetslsaddition.util.LegacyAsyncExecutor.stop();
|
||||
running = false;
|
||||
}
|
||||
Thread.sleep(1000L);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
LOGGER.debug("[SLSA] legacy rule watcher error: {}", t.toString());
|
||||
}
|
||||
}
|
||||
}, "SLSA-Legacy-RuleWatcher");
|
||||
watcher.setDaemon(true);
|
||||
watcher.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,11 +109,9 @@ public class ServerMain implements ModInitializer, CarpetExtension {
|
||||
HatCommand.register(dispatcher);
|
||||
SitCommand.register(dispatcher);
|
||||
BotCommand.register(dispatcher);
|
||||
BotAllCommand.register(dispatcher);
|
||||
BotListCommand.register(dispatcher);
|
||||
LOGGER.info("[SLSA] commands registered: hat/sit/bot");
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("[SLSA] failed to register commands", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
package com.github.zly2006.carpetslsaddition.command;
|
||||
|
||||
import carpet.fakes.ServerPlayerInterface;
|
||||
import carpet.helpers.EntityPlayerActionPack;
|
||||
import carpet.patches.EntityPlayerMPFake;
|
||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||
import com.github.zly2006.carpetslsaddition.util.access.SLSBotAccessor;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.arguments.coordinates.RotationArgument;
|
||||
import net.minecraft.commands.arguments.coordinates.Vec3Argument;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static net.minecraft.commands.Commands.argument;
|
||||
import static net.minecraft.commands.Commands.literal;
|
||||
|
||||
/**
|
||||
* /botall —— 批量控制本模组生成的所有假人。
|
||||
*
|
||||
* <p>与 {@link BotCommand} 的区别:{@code /bot <player>} 操作单个指定玩家,
|
||||
* 而 {@code /botall} 对其余命令中出现的所有本模组假人(isBot()==true)批量执行同一操作。
|
||||
*/
|
||||
public class BotAllCommand {
|
||||
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
LiteralArgumentBuilder<CommandSourceStack> command = literal("botall")
|
||||
.requires(source -> Commands.LEVEL_OWNERS.check(source.permissions()) || me.lucko.fabric.api.permissions.v0.Permissions.check(source, "slsaddition.command.bot"))
|
||||
.then(literal("stop").executes(batch(EntityPlayerActionPack::stopAll)))
|
||||
.then(actionCommand("use", EntityPlayerActionPack.ActionType.USE))
|
||||
.then(actionCommand("jump", EntityPlayerActionPack.ActionType.JUMP))
|
||||
.then(actionCommand("attack", EntityPlayerActionPack.ActionType.ATTACK))
|
||||
.then(actionCommand("drop", EntityPlayerActionPack.ActionType.DROP_ITEM))
|
||||
.then(dropCommand("dropStack", true))
|
||||
.then(actionCommand("swapHands", EntityPlayerActionPack.ActionType.SWAP_HANDS))
|
||||
.then(literal("hotbar").then(argument("slot", IntegerArgumentType.integer(1, 9))
|
||||
.executes(c -> batchAp(c, ap -> ap.setSlot(IntegerArgumentType.getInteger(c, "slot"))))))
|
||||
.then(literal("mount").executes(batch(ap -> ap.mount(true)))
|
||||
.then(literal("anything").executes(batch(ap -> ap.mount(false)))))
|
||||
.then(literal("dismount").executes(batch(EntityPlayerActionPack::dismount)))
|
||||
.then(literal("sneak").executes(batch(ap -> ap.setSneaking(true))))
|
||||
.then(literal("unsneak").executes(batch(ap -> ap.setSneaking(false))))
|
||||
.then(literal("sprint").executes(batch(ap -> ap.setSprinting(true))))
|
||||
.then(literal("unsprint").executes(batch(ap -> ap.setSprinting(false))))
|
||||
.then(literal("look")
|
||||
.then(literal("north").executes(batch(ap -> ap.look(Direction.NORTH))))
|
||||
.then(literal("south").executes(batch(ap -> ap.look(Direction.SOUTH))))
|
||||
.then(literal("east").executes(batch(ap -> ap.look(Direction.EAST))))
|
||||
.then(literal("west").executes(batch(ap -> ap.look(Direction.WEST))))
|
||||
.then(literal("up").executes(batch(ap -> ap.look(Direction.UP))))
|
||||
.then(literal("down").executes(batch(ap -> ap.look(Direction.DOWN))))
|
||||
.then(literal("at").then(argument("position", Vec3Argument.vec3())
|
||||
.executes(c -> batchAp(c, ap -> ap.lookAt(Vec3Argument.getVec3(c, "position"))))))
|
||||
.then(argument("direction", RotationArgument.rotation())
|
||||
.executes(c -> batchAp(c, ap -> ap.look(RotationArgument.getRotation(c, "direction").getRotation(c.getSource()))))))
|
||||
.then(literal("turn")
|
||||
.then(literal("left").executes(batch(ap -> ap.turn(-90, 0))))
|
||||
.then(literal("right").executes(batch(ap -> ap.turn(90, 0))))
|
||||
.then(literal("back").executes(batch(ap -> ap.turn(180, 0))))
|
||||
.then(argument("rotation", RotationArgument.rotation())
|
||||
.executes(c -> batchAp(c, ap -> ap.turn(RotationArgument.getRotation(c, "rotation").getRotation(c.getSource()))))))
|
||||
.then(literal("move").executes(batch(EntityPlayerActionPack::stopMovement))
|
||||
.then(literal("forward").executes(batch(ap -> ap.setForward(1))))
|
||||
.then(literal("backward").executes(batch(ap -> ap.setForward(-1))))
|
||||
.then(literal("left").executes(batch(ap -> ap.setStrafing(1))))
|
||||
.then(literal("right").executes(batch(ap -> ap.setStrafing(-1)))))
|
||||
.then(literal("sit").executes(BotAllCommand::sitAll))
|
||||
.then(literal("stand").executes(BotAllCommand::standAll))
|
||||
.then(literal("spawn")
|
||||
.then(literal("list").executes(BotAllCommand::spawnFromList))
|
||||
.then(argument("prefix", com.mojang.brigadier.arguments.StringArgumentType.word())
|
||||
.then(argument("count", IntegerArgumentType.integer(1, 1000))
|
||||
.executes(BotAllCommand::spawnBatch))))
|
||||
.then(literal("kill").executes(BotAllCommand::killBatch));
|
||||
dispatcher.register(command);
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> actionCommand(String name, EntityPlayerActionPack.ActionType type) {
|
||||
return literal(name)
|
||||
.executes(batch(ap -> ap.start(type, EntityPlayerActionPack.Action.once())))
|
||||
.then(literal("once").executes(batch(ap -> ap.start(type, EntityPlayerActionPack.Action.once()))))
|
||||
.then(literal("continuous").executes(batch(ap -> ap.start(type, EntityPlayerActionPack.Action.continuous()))))
|
||||
.then(literal("interval").then(argument("ticks", IntegerArgumentType.integer(1))
|
||||
.executes(c -> batchAp(c, ap -> ap.start(type, EntityPlayerActionPack.Action.interval(IntegerArgumentType.getInteger(c, "ticks")))))));
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> dropCommand(String name, boolean dropAll) {
|
||||
return literal(name)
|
||||
.then(literal("all").executes(batch(ap -> ap.drop(-2, dropAll))))
|
||||
.then(literal("mainhand").executes(batch(ap -> ap.drop(-1, dropAll))))
|
||||
.then(literal("offhand").executes(batch(ap -> ap.drop(40, dropAll))))
|
||||
.then(argument("slot", IntegerArgumentType.integer(0, 40))
|
||||
.executes(c -> batchAp(c, ap -> ap.drop(IntegerArgumentType.getInteger(c, "slot"), dropAll))));
|
||||
}
|
||||
|
||||
/** 批量执行一个 action pack 操作。 */
|
||||
private static int batchAp(CommandContext<CommandSourceStack> context, Consumer<EntityPlayerActionPack> action) {
|
||||
List<ServerPlayer> bots = collectBots(context.getSource());
|
||||
for (ServerPlayer bot : bots) {
|
||||
action.accept(((ServerPlayerInterface) bot).getActionPack());
|
||||
}
|
||||
final int n = bots.size();
|
||||
context.getSource().sendSuccess(
|
||||
() -> net.minecraft.network.chat.Component.literal("[SLSA] 已对 " + n + " 个假人执行操作"), false);
|
||||
return n;
|
||||
}
|
||||
|
||||
private static com.mojang.brigadier.Command<CommandSourceStack> batch(Consumer<EntityPlayerActionPack> action) {
|
||||
return c -> batchAp(c, action);
|
||||
}
|
||||
|
||||
/** 收集所有本模组生成的假人(受名单限制:名单非空时只取名单内的)。 */
|
||||
private static List<ServerPlayer> collectBots(CommandSourceStack source) {
|
||||
MinecraftServer server = source.getServer();
|
||||
List<ServerPlayer> bots = new ArrayList<>();
|
||||
boolean filterByList = !com.github.zly2006.carpetslsaddition.util.BotListManager.isEmpty();
|
||||
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
|
||||
if (player instanceof EntityPlayerMPFake && ((SLSBotAccessor) player).carpet_SLS_Addition$isBot()) {
|
||||
if (filterByList && !com.github.zly2006.carpetslsaddition.util.BotListManager.contains(player.getScoreboardName())) {
|
||||
continue;
|
||||
}
|
||||
bots.add(player);
|
||||
}
|
||||
}
|
||||
return bots;
|
||||
}
|
||||
private static int sitAll(CommandContext<CommandSourceStack> context) {
|
||||
if (!SLSCarpetSettings.canUseSitCommand) {
|
||||
context.getSource().sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||
"[SLSA] 坐下功能未启用(canUseSitCommand)"), false);
|
||||
return 0;
|
||||
}
|
||||
List<ServerPlayer> bots = collectBots(context.getSource());
|
||||
int ok = 0;
|
||||
for (ServerPlayer bot : bots) {
|
||||
if (SitCommand.sitPlayer(bot)) ok++;
|
||||
}
|
||||
final int done = ok;
|
||||
context.getSource().sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||
"[SLSA] 已让 " + done + "/" + bots.size() + " 个假人坐下"), false);
|
||||
return ok;
|
||||
}
|
||||
|
||||
private static int standAll(CommandContext<CommandSourceStack> context) {
|
||||
List<ServerPlayer> bots = collectBots(context.getSource());
|
||||
int ok = 0;
|
||||
for (ServerPlayer bot : bots) {
|
||||
if (bot.getVehicle() != null) {
|
||||
bot.stopRiding();
|
||||
ok++;
|
||||
}
|
||||
}
|
||||
final int done = ok;
|
||||
context.getSource().sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||
"[SLSA] 已让 " + done + "/" + bots.size() + " 个假人站起"), false);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/** 一键召唤名单里的所有假人(执行者位置)。 */
|
||||
private static int spawnFromList(CommandContext<CommandSourceStack> context) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
java.util.Set<String> names = com.github.zly2006.carpetslsaddition.util.BotListManager.getNames();
|
||||
if (names.isEmpty()) {
|
||||
source.sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||
"[SLSA] 名单为空,先用 /botlist add 添加名字"), false);
|
||||
return 0;
|
||||
}
|
||||
net.minecraft.world.phys.Vec3 pos = source.getPosition();
|
||||
net.minecraft.world.phys.Vec2 rot = source.getRotation();
|
||||
net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dim = source.getLevel().dimension();
|
||||
int ok = 0;
|
||||
for (String name : names) {
|
||||
// 已在线的跳过
|
||||
if (source.getServer().getPlayerList().getPlayerByName(name) != null) continue;
|
||||
try {
|
||||
var bot = BotCommand.createBot(name, source.getServer(), pos, rot.y, rot.x, dim);
|
||||
if (bot != null) ok++;
|
||||
} catch (Throwable t) {
|
||||
com.github.zly2006.carpetslsaddition.ServerMain.LOGGER.warn("[SLSA] spawn list bot {} failed: {}", name, t.toString());
|
||||
}
|
||||
}
|
||||
final int done = ok;
|
||||
source.sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||
"[SLSA] 已召唤名单中的 " + done + "/" + names.size() + " 个假人"), false);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/** 批量召唤:生成 前缀1 ~ 前缀N。 */
|
||||
private static int spawnBatch(CommandContext<CommandSourceStack> context) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
String prefix = com.mojang.brigadier.arguments.StringArgumentType.getString(context, "prefix");
|
||||
int count = IntegerArgumentType.getInteger(context, "count");
|
||||
net.minecraft.world.phys.Vec3 pos = source.getPosition();
|
||||
net.minecraft.world.phys.Vec2 rot = source.getRotation();
|
||||
net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> dim = source.getLevel().dimension();
|
||||
int ok = 0;
|
||||
for (int i = 1; i <= count; i++) {
|
||||
String name = prefix + i;
|
||||
try {
|
||||
var bot = BotCommand.createBot(name, source.getServer(), pos, rot.y, rot.x, dim);
|
||||
if (bot != null) ok++;
|
||||
} catch (Throwable t) {
|
||||
com.github.zly2006.carpetslsaddition.ServerMain.LOGGER.warn("[SLSA] spawn bot {} failed: {}", name, t.toString());
|
||||
}
|
||||
}
|
||||
final int done = ok;
|
||||
source.sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||
"[SLSA] 已召唤 " + done + "/" + count + " 个假人(前缀 " + prefix + ")"), false);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/** 批量移除:杀死受名单限制的假人(名单空则全部)。 */
|
||||
private static int killBatch(CommandContext<CommandSourceStack> context) {
|
||||
List<ServerPlayer> bots = collectBots(context.getSource());
|
||||
int ok = 0;
|
||||
for (ServerPlayer bot : bots) {
|
||||
bot.kill((net.minecraft.server.level.ServerLevel) bot.level());
|
||||
ok++;
|
||||
}
|
||||
final int done = ok;
|
||||
context.getSource().sendSuccess(() -> net.minecraft.network.chat.Component.literal(
|
||||
"[SLSA] 已移除 " + done + " 个假人"), false);
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
@@ -352,7 +352,7 @@ public class BotCommand {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static EntityPlayerMPFake createBot(String username, MinecraftServer server, Vec3 pos, double yaw, double pitch, ResourceKey<Level> dimensionId) {
|
||||
private static EntityPlayerMPFake createBot(String username, MinecraftServer server, Vec3 pos, double yaw, double pitch, ResourceKey<Level> dimensionId) {
|
||||
ServerLevel worldIn = server.getLevel(dimensionId);
|
||||
server.services().nameToIdCache().resolveOfflineUsers(false);
|
||||
GameProfile gameprofile;
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
package com.github.zly2006.carpetslsaddition.command;
|
||||
|
||||
import carpet.patches.EntityPlayerMPFake;
|
||||
import com.github.zly2006.carpetslsaddition.util.BotListManager;
|
||||
import com.github.zly2006.carpetslsaddition.util.access.SLSBotAccessor;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static net.minecraft.commands.Commands.argument;
|
||||
import static net.minecraft.commands.Commands.literal;
|
||||
|
||||
/**
|
||||
* /botlist —— 维护"受控假人名单",供 /botall 限定操作范围。
|
||||
*
|
||||
* <p>名单持久化在 config/slsa-bot-list.json,重启保留。
|
||||
*/
|
||||
public class BotListCommand {
|
||||
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
LiteralArgumentBuilder<CommandSourceStack> command = literal("botlist")
|
||||
.requires(source -> Commands.LEVEL_OWNERS.check(source.permissions())
|
||||
|| me.lucko.fabric.api.permissions.v0.Permissions.check(source, "slsaddition.command.bot"))
|
||||
.then(literal("show").executes(BotListCommand::show))
|
||||
.then(literal("clear").executes(c -> {
|
||||
BotListManager.clear();
|
||||
c.getSource().sendSuccess(() -> Component.literal("[SLSA] 已清空假人名单"), false);
|
||||
return 1;
|
||||
}))
|
||||
.then(literal("add")
|
||||
.then(argument("names", StringArgumentType.greedyString())
|
||||
.executes(c -> add(c.getSource(), StringArgumentType.getString(c, "names")))))
|
||||
.then(literal("remove")
|
||||
.then(argument("names", StringArgumentType.greedyString())
|
||||
.executes(c -> remove(c.getSource(), StringArgumentType.getString(c, "names")))))
|
||||
.then(literal("addall").executes(BotListCommand::addAll));
|
||||
dispatcher.register(command);
|
||||
}
|
||||
|
||||
private static int show(com.mojang.brigadier.context.CommandContext<CommandSourceStack> c) {
|
||||
Set<String> names = BotListManager.getNames();
|
||||
if (names.isEmpty()) {
|
||||
c.getSource().sendSuccess(() -> Component.literal("[SLSA] 假人名单为空(此时 /botall 操作所有假人)"), false);
|
||||
} else {
|
||||
c.getSource().sendSuccess(() -> Component.literal(
|
||||
"[SLSA] 假人名单(" + names.size() + "):" + String.join(", ", names)), false);
|
||||
}
|
||||
return names.size();
|
||||
}
|
||||
|
||||
private static int add(CommandSourceStack source, String raw) {
|
||||
String[] parts = raw.trim().split("\\s+");
|
||||
int n = 0;
|
||||
for (String p : parts) {
|
||||
if (!p.isEmpty() && BotListManager.add(p)) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
final int added = n;
|
||||
source.sendSuccess(() -> Component.literal(
|
||||
"[SLSA] 已添加 " + added + " 个名字,名单共 " + BotListManager.getNames().size() + " 个"), false);
|
||||
return n;
|
||||
}
|
||||
|
||||
private static int remove(CommandSourceStack source, String raw) {
|
||||
String[] parts = raw.trim().split("\\s+");
|
||||
int n = 0;
|
||||
for (String p : parts) {
|
||||
if (!p.isEmpty() && BotListManager.remove(p)) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
final int removed = n;
|
||||
source.sendSuccess(() -> Component.literal(
|
||||
"[SLSA] 已移除 " + removed + " 个名字,名单共 " + BotListManager.getNames().size() + " 个"), false);
|
||||
return n;
|
||||
}
|
||||
|
||||
/** 把当前所有在线 SLS 假人加入名单。 */
|
||||
private static int addAll(com.mojang.brigadier.context.CommandContext<CommandSourceStack> c) {
|
||||
int n = 0;
|
||||
for (ServerPlayer player : c.getSource().getServer().getPlayerList().getPlayers()) {
|
||||
if (player instanceof EntityPlayerMPFake && ((SLSBotAccessor) player).carpet_SLS_Addition$isBot()) {
|
||||
if (BotListManager.add(player.getScoreboardName())) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
final int added = n;
|
||||
c.getSource().sendSuccess(() -> Component.literal(
|
||||
"[SLSA] 已把 " + added + " 个在线假人加入名单,名单共 " + BotListManager.getNames().size() + " 个"), false);
|
||||
return n;
|
||||
}
|
||||
}
|
||||
@@ -20,27 +20,21 @@ public class SitCommand {
|
||||
if (player == null) {
|
||||
return 0;
|
||||
}
|
||||
sitPlayer(player);
|
||||
|
||||
if (player.getVehicle() != null || !player.onGround()) { // 防止错误的坐下行为
|
||||
return 1;
|
||||
}
|
||||
|
||||
ServerLevel world = (ServerLevel) player.level();
|
||||
|
||||
ArmorStand armorStandEntity = new ArmorStand(world, player.getX(), player.getY(), player.getZ());
|
||||
((SitEntity) armorStandEntity).setSitEntity(true);
|
||||
world.addFreshEntity(armorStandEntity);
|
||||
player.setShiftKeyDown(false);
|
||||
player.startRiding(armorStandEntity);
|
||||
|
||||
return 1;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 让指定玩家原地坐下(生成一个标记为 SitEntity 的 ArmorStand 并骑乘)。
|
||||
*
|
||||
* @return 若成功坐下返回 true,否则返回 false(如已在骑乘/不在地面)。
|
||||
*/
|
||||
public static boolean sitPlayer(ServerPlayer player) {
|
||||
if (player.getVehicle() != null || !player.onGround()) { // 防止错误的坐下行为
|
||||
return false;
|
||||
}
|
||||
ServerLevel world = (ServerLevel) player.level();
|
||||
ArmorStand armorStandEntity = new ArmorStand(world, player.getX(), player.getY(), player.getZ());
|
||||
((SitEntity) armorStandEntity).setSitEntity(true);
|
||||
world.addFreshEntity(armorStandEntity);
|
||||
player.setShiftKeyDown(false);
|
||||
player.startRiding(armorStandEntity);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.github.zly2006.carpetslsaddition.mixin.legacy;
|
||||
|
||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||
import com.github.zly2006.carpetslsaddition.ServerMain;
|
||||
import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/**
|
||||
* [旧版] 区块替换(chunk swap)——race condition 的等价实现。
|
||||
*
|
||||
* <p>1.12 的漏洞:{@code ChunkProviderServer.id2ChunkMap}(非线程安全的
|
||||
* {@code Long2ObjectOpenHashMap})被主线程 {@code remove}(卸载)与异步线程 {@code get}
|
||||
* 并发访问,导致 {@code get} 错过已加载的区块,进而触发"重新加载 + 世界装饰"。
|
||||
*
|
||||
* <p>26.1.2 已用 {@code Long2ObjectLinkedOpenHashMap} + {@code mainThreadExecutor} 约束修复。
|
||||
* 直接并发改 map 风险极高(可能崩溃/损坏存档),故本 mixin 采用<b>低风险等价方案</b>:
|
||||
* hook {@link ChunkMap#getVisibleChunkIfPresent(long)},当满足以下条件时对<b>非主线程</b>调用返回 {@code null}:
|
||||
* <ul>
|
||||
* <li>{@code legacyExploitMode} + {@code legacyChunkSwap} 均开启</li>
|
||||
* <li>当前不是服务器主线程(即异步线程在访问)</li>
|
||||
* </ul>
|
||||
* 这样异步线程会"误以为区块未加载",后续逻辑会尝试重新加载并触发世界装饰,
|
||||
* 从而在异步线程上产生方块更新——与 1.12 的效果等价,但不触碰共享数据结构。
|
||||
*
|
||||
* <p><b>⚠ 风险</b>:仍可能引起区块状态不一致。仅在明确开启时生效。
|
||||
*/
|
||||
@Mixin(ChunkMap.class)
|
||||
public abstract class ChunkMapMixin {
|
||||
|
||||
@Inject(method = "getVisibleChunkIfPresent", at = @At("RETURN"), cancellable = true)
|
||||
private void legacyChunkSwap(long chunkPos, CallbackInfoReturnable<ChunkHolder> cir) {
|
||||
if (cir.getReturnValue() == null) {
|
||||
return;
|
||||
}
|
||||
if (!SLSCarpetSettings.legacyExploitMode || !SLSCarpetSettings.legacyChunkSwap) {
|
||||
return;
|
||||
}
|
||||
// 仅在异步线程访问时伪装"区块未加载"
|
||||
if (!isServerMainThread()) {
|
||||
cir.setReturnValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
/** 判断当前是否在服务器主线程上。 */
|
||||
private static boolean isServerMainThread() {
|
||||
net.minecraft.server.MinecraftServer server = ServerMain.server;
|
||||
if (server == null) {
|
||||
return true;
|
||||
}
|
||||
return server.isSameThread();
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.github.zly2006.carpetslsaddition.mixin.legacy;
|
||||
|
||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||
import com.github.zly2006.carpetslsaddition.ServerMain;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
||||
/**
|
||||
* [旧版] 落沙替换——固定目标版。
|
||||
*
|
||||
* <p>26.1.2 单线程下 1.12 的"替换窗口"物理不存在,本 mixin 采用直接方案:
|
||||
* 当 {@code legacyFallingBlockReplace} 开启且配置了 {@code legacyFallingBlockTarget} 时,
|
||||
* 把下落实体的方块状态替换为目标方块的默认状态——沙子下落即变目标方块。
|
||||
*/
|
||||
@Mixin(net.minecraft.world.entity.item.FallingBlockEntity.class)
|
||||
public class FallingBlockEntityMixin {
|
||||
|
||||
@ModifyVariable(
|
||||
method = "fall",
|
||||
at = @At("HEAD"),
|
||||
argsOnly = true,
|
||||
index = 2
|
||||
)
|
||||
private static BlockState legacyReplaceFallingState(BlockState original, Level level, BlockPos pos, BlockState state) {
|
||||
if (!SLSCarpetSettings.legacyExploitMode || !SLSCarpetSettings.legacyFallingBlockReplace) {
|
||||
return original;
|
||||
}
|
||||
String target = SLSCarpetSettings.legacyFallingBlockTarget;
|
||||
if (target == null || target.isEmpty() || "#none".equals(target)) {
|
||||
return original;
|
||||
}
|
||||
Block block = BuiltInRegistries.BLOCK.getOptional(Identifier.withDefaultNamespace(target)).orElse(null);
|
||||
if (block == null) {
|
||||
return original;
|
||||
}
|
||||
ServerMain.LOGGER.info("[SLSA-DEBUG] >>> REPLACE at {}: {} -> {}", pos, original.getBlock(), block);
|
||||
return block.defaultBlockState();
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package com.github.zly2006.carpetslsaddition.mixin.legacy;
|
||||
|
||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||
import com.github.zly2006.carpetslsaddition.util.LegacyExploitState;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.block.FallingBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/**
|
||||
* [旧版] 瞬间下落(等价 1.12 的 instantFall flag)。
|
||||
*
|
||||
* <p>1.12:世界装饰时短暂开启 instantFall,使重力方块在被更新时【立即】转为下落实体,
|
||||
* 而不等待 2gt 的计划刻延迟。26.1.2 已移除该 flag。
|
||||
*
|
||||
* <p>实现方式(双保险):
|
||||
* <ul>
|
||||
* <li>{@link #legacyDelayAfterPlace}:把 {@code getDelayAfterPlace()} 的返回值改为 0。</li>
|
||||
* <li>{@link #legacyScheduleDelay}:直接拦截 {@code onPlace}/{@code updateShape} 里
|
||||
* 对 {@code scheduleTick} 的 delay 参数,强制改为 0(更直接可靠)。</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Mixin(FallingBlock.class)
|
||||
public abstract class FallingBlockMixin {
|
||||
|
||||
/** instantFall:把"放置后延迟"改为 0。 */
|
||||
@Inject(method = "getDelayAfterPlace", at = @At("HEAD"), cancellable = true)
|
||||
private void legacyDelayAfterPlace(CallbackInfoReturnable<Integer> cir) {
|
||||
if (SLSCarpetSettings.legacyExploitMode && SLSCarpetSettings.legacyInstantFall) {
|
||||
cir.setReturnValue(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* instantFall:拦截 onPlace / updateShape 里 scheduleTick 的 delay 参数,强制为 0。
|
||||
* 第 3 个参数(index=2)是延迟 tick 数。
|
||||
*/
|
||||
@ModifyArg(
|
||||
method = "onPlace",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/Level;scheduleTick(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/Block;I)V"),
|
||||
index = 2
|
||||
)
|
||||
private int legacyScheduleDelayLevel(int delay) {
|
||||
if (SLSCarpetSettings.legacyExploitMode && SLSCarpetSettings.legacyInstantFall) {
|
||||
return 0;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
@ModifyArg(
|
||||
method = "updateShape",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/ScheduledTickAccess;scheduleTick(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/Block;I)V"),
|
||||
index = 2
|
||||
)
|
||||
private int legacyScheduleDelayAccess(int delay) {
|
||||
if (SLSCarpetSettings.legacyExploitMode && SLSCarpetSettings.legacyInstantFall) {
|
||||
return 0;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
/** instantFall:tick 入口标记(供其他机制判断窗口)。 */
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
private void legacyTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource rand, CallbackInfo ci) {
|
||||
if (SLSCarpetSettings.legacyExploitMode && SLSCarpetSettings.legacyInstantFall) {
|
||||
LegacyExploitState.markInstantFall(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.github.zly2006.carpetslsaddition.mixin.legacy;
|
||||
|
||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||
import net.minecraft.world.ticks.ScheduledTick;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/**
|
||||
* [旧版] 计划刻立即执行(等价 1.12 的 instantTick flag)。
|
||||
*
|
||||
* <p>1.12 的 instantTick:世界装饰时会短暂开启,使计划刻(中继器、发射器、沙子等)
|
||||
* 立即执行而不等待延迟。26.1.2 已移除该 flag。
|
||||
*
|
||||
* <p>本 mixin 用 {@link Inject} 拦截 {@link ScheduledTick#triggerTick()}:
|
||||
* 规则开启时返回 0,使该计划刻在"当前时刻"被触发(等价立即执行)。
|
||||
*
|
||||
* <p><b>注意</b>:这会强制所有计划刻立即触发,可能造成大量连锁更新,
|
||||
* 仅应在 {@code legacyExploitMode} + {@code legacyInstantTick} 同时开启时使用。
|
||||
*/
|
||||
@Mixin(ScheduledTick.class)
|
||||
public abstract class ScheduledTickMixin {
|
||||
|
||||
@Inject(method = "triggerTick", at = @At("HEAD"), cancellable = true)
|
||||
private void legacyInstantTriggerTick(CallbackInfoReturnable<Long> cir) {
|
||||
if (SLSCarpetSettings.legacyExploitMode && SLSCarpetSettings.legacyInstantTick) {
|
||||
// 返回 0 表示"在当前时刻立即触发"
|
||||
cir.setReturnValue(0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package com.github.zly2006.carpetslsaddition.util;
|
||||
|
||||
import com.github.zly2006.carpetslsaddition.ServerMain;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 假人名单管理器:维护一份"受控假人名字"列表,持久化到 config/slsa-bot-list.json。
|
||||
*
|
||||
* <p>用途:{@code /botlist add cute wazi} 把名字加入名单后,{@code /botall} 的操作范围
|
||||
* 就限定为名单内的假人(名单为空时操作所有 SLS 假人)。
|
||||
*/
|
||||
public final class BotListManager {
|
||||
|
||||
private BotListManager() {}
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
/** 名字列表(保序、去重)。 */
|
||||
private static final Set<String> NAMES = new LinkedHashSet<>();
|
||||
|
||||
private static Path configPath;
|
||||
|
||||
private static Path path() {
|
||||
if (configPath == null) {
|
||||
configPath = FabricLoader.getInstance().getConfigDir().resolve("slsa-bot-list.json");
|
||||
}
|
||||
return configPath;
|
||||
}
|
||||
|
||||
/** 加载名单(服务器启动时调用)。 */
|
||||
public static void load() {
|
||||
NAMES.clear();
|
||||
Path p = path();
|
||||
if (!Files.exists(p)) {
|
||||
return;
|
||||
}
|
||||
try (Reader r = Files.newBufferedReader(p)) {
|
||||
Set<String> loaded = GSON.fromJson(r, new TypeToken<LinkedHashSet<String>>() {}.getType());
|
||||
if (loaded != null) {
|
||||
NAMES.addAll(loaded);
|
||||
}
|
||||
ServerMain.LOGGER.info("[SLSA] loaded bot list: {} entries", NAMES.size());
|
||||
} catch (Exception e) {
|
||||
ServerMain.LOGGER.error("[SLSA] failed to load bot list", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存名单。 */
|
||||
public static void save() {
|
||||
Path p = path();
|
||||
try {
|
||||
Files.createDirectories(p.getParent());
|
||||
try (Writer w = Files.newBufferedWriter(p)) {
|
||||
GSON.toJson(NAMES, w);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
ServerMain.LOGGER.error("[SLSA] failed to save bot list", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean add(String name) {
|
||||
boolean added = NAMES.add(name);
|
||||
if (added) save();
|
||||
return added;
|
||||
}
|
||||
|
||||
public static boolean remove(String name) {
|
||||
boolean removed = NAMES.remove(name);
|
||||
if (removed) save();
|
||||
return removed;
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
NAMES.clear();
|
||||
save();
|
||||
}
|
||||
|
||||
/** 返回名单快照。 */
|
||||
public static Set<String> getNames() {
|
||||
return new LinkedHashSet<>(NAMES);
|
||||
}
|
||||
|
||||
public static boolean contains(String name) {
|
||||
return NAMES.contains(name);
|
||||
}
|
||||
|
||||
public static boolean isEmpty() {
|
||||
return NAMES.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.github.zly2006.carpetslsaddition.util;
|
||||
|
||||
import com.github.zly2006.carpetslsaddition.ServerMain;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* [旧版] 自建异步线程,模拟 1.12 的"染色玻璃→信标搜索线程"。
|
||||
*
|
||||
* <p>1.12 的漏洞依赖一个独立的异步线程,与主线程并发访问区块哈希表
|
||||
* ({@code Long2ObjectOpenHashMap}),从而制造 race condition,让 chunk 被误判为"未加载",
|
||||
* 触发重新加载与世界装饰。
|
||||
*
|
||||
* <p>26.1.2 的信标已无独立线程,故本类自建一个单线程调度器:
|
||||
* 周期性在<b>主线程之外</b>调用 {@link ServerChunkCache#getChunkNow} 与读取区块,
|
||||
* 尽最大可能复现 1.12 的并发访问模式。
|
||||
*
|
||||
* <p><b>⚠ 风险提示</b>:26.1.2 的区块访问有 {@code mainThreadExecutor} 约束,
|
||||
* 跨线程访问可能触发断言或未定义行为,极端情况下可导致服务器崩溃或存档损坏。
|
||||
* 仅在 {@code legacyExploitMode} + {@code legacyAsyncChunkAccess} 同时开启时启动。
|
||||
*/
|
||||
public final class LegacyAsyncExecutor {
|
||||
|
||||
private LegacyAsyncExecutor() {}
|
||||
|
||||
private static ScheduledExecutorService executor;
|
||||
private static volatile boolean started = false;
|
||||
|
||||
/** 目标区块坐标(由玩家装置或命令设置)。 */
|
||||
private static volatile int targetChunkX = Integer.MIN_VALUE;
|
||||
private static volatile int targetChunkZ = Integer.MIN_VALUE;
|
||||
|
||||
public static void setTargetChunk(int x, int z) {
|
||||
targetChunkX = x;
|
||||
targetChunkZ = z;
|
||||
}
|
||||
|
||||
public static synchronized void start() {
|
||||
if (started) {
|
||||
return;
|
||||
}
|
||||
executor = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r, "SLSA-Legacy-AsyncChunk");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
executor.scheduleAtFixedRate(LegacyAsyncExecutor::tick, 0, 1, TimeUnit.MILLISECONDS);
|
||||
started = true;
|
||||
LegacyExploitState.setAsyncThreadRunning(true);
|
||||
ServerMain.LOGGER.info("[SLSA] legacy async chunk thread started");
|
||||
}
|
||||
|
||||
public static synchronized void stop() {
|
||||
if (!started || executor == null) {
|
||||
return;
|
||||
}
|
||||
executor.shutdownNow();
|
||||
executor = null;
|
||||
started = false;
|
||||
LegacyExploitState.setAsyncThreadRunning(false);
|
||||
ServerMain.LOGGER.info("[SLSA] legacy async chunk thread stopped");
|
||||
}
|
||||
|
||||
/** 异步线程主体:并发访问目标区块,制造与主线程的竞态。 */
|
||||
private static void tick() {
|
||||
try {
|
||||
if (!SLSCarpetSettingsBridge.shouldRun()) {
|
||||
return;
|
||||
}
|
||||
MinecraftServer server = ServerMain.server;
|
||||
if (server == null) {
|
||||
return;
|
||||
}
|
||||
for (ServerLevel level : server.getAllLevels()) {
|
||||
ServerChunkCache cache = level.getChunkSource();
|
||||
if (targetChunkX != Integer.MIN_VALUE) {
|
||||
// 故意在主线程之外读取区块表——制造 race condition
|
||||
LevelChunk chunk = cache.getChunkNow(targetChunkX, targetChunkZ);
|
||||
if (chunk != null) {
|
||||
// 触发一次方块状态读取,模拟信标向下搜索
|
||||
chunk.getBlockState(new net.minecraft.core.BlockPos(
|
||||
targetChunkX << 4, level.getMinY(), targetChunkZ << 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// 异步线程绝不能让异常冒泡导致调度器静默停止
|
||||
ServerMain.LOGGER.debug("[SLSA] legacy async tick error: {}", t.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/** 桥接类:避免直接依赖 SLSCarpetSettings 的静态初始化顺序问题。 */
|
||||
private static final class SLSCarpetSettingsBridge {
|
||||
static boolean shouldRun() {
|
||||
return com.github.zly2006.carpetslsaddition.SLSCarpetSettings.legacyExploitMode
|
||||
&& com.github.zly2006.carpetslsaddition.SLSCarpetSettings.legacyAsyncChunkAccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package com.github.zly2006.carpetslsaddition.util;
|
||||
|
||||
import com.github.zly2006.carpetslsaddition.ServerMain;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* [旧版] 方块变更记录器——用于重建 1.12 的"替换窗口"。
|
||||
*
|
||||
* <p>1.12 的窗口依赖"异步线程在主线程 tick 中途改块",26.1.2 单线程模型下物理不存在。
|
||||
* 本类改用"记录+回放":把最近发生的方块变更记录在此,
|
||||
* 当重力方块触发下落时,若该位置"刚被改过",就用记录的新方块作为下落实体的内容——
|
||||
* 等价于 1.12 的窗口替换效果(只是窗口由 mod 主动开启)。
|
||||
*
|
||||
* <p>记录以"服务器 tick 数"为界:仅保留最近 {@link #WINDOW_TICKS} 个 tick 内的变更,
|
||||
* 超过即失效,避免记录无限增长或误伤无关方块。
|
||||
*/
|
||||
public final class LegacyBlockChangeRecorder {
|
||||
|
||||
private LegacyBlockChangeRecorder() {}
|
||||
|
||||
/** 窗口大小:变更与下落之间允许的最大 tick 间隔。 */
|
||||
public static final long WINDOW_TICKS = 2L;
|
||||
|
||||
private record Change(BlockState state, long tick) {}
|
||||
|
||||
private static final Map<Long, Change> RECENT = new ConcurrentHashMap<>();
|
||||
|
||||
/** 获取当前服务器 tick(拿不到时回退为 0)。 */
|
||||
private static long now() {
|
||||
MinecraftServer server = ServerMain.server;
|
||||
return server != null ? server.getTickCount() : 0L;
|
||||
}
|
||||
|
||||
/** 记录一次方块变更。 */
|
||||
public static void record(BlockPos pos, BlockState newState) {
|
||||
RECENT.put(pos.asLong(), new Change(newState, now()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询某位置是否在窗口期内有变更记录。
|
||||
*
|
||||
* @return 若命中窗口,返回记录的新状态;否则返回 null。
|
||||
*/
|
||||
public static BlockState getRecentChange(BlockPos pos) {
|
||||
Change c = RECENT.get(pos.asLong());
|
||||
if (c == null) {
|
||||
return null;
|
||||
}
|
||||
if (now() - c.tick() > WINDOW_TICKS) {
|
||||
RECENT.remove(pos.asLong());
|
||||
return null;
|
||||
}
|
||||
return c.state();
|
||||
}
|
||||
|
||||
/** 清理过期记录。 */
|
||||
public static void cleanup() {
|
||||
long t = now();
|
||||
RECENT.entrySet().removeIf(e -> t - e.getValue().tick() > WINDOW_TICKS);
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
RECENT.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.github.zly2006.carpetslsaddition.util;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* [旧版] 漏洞机制的全局状态管理(线程安全)。
|
||||
*
|
||||
* <p>承载:
|
||||
* <ul>
|
||||
* <li>当前"落沙替换目标方块"ID(由 FallingBlockMixin 设置,FallingBlockEntityMixin 消费)</li>
|
||||
* <li>被标记为 instantFall 触发的位置(跨线程共享,故用并发容器)</li>
|
||||
* <li>异步 chunk 访问线程的运行状态</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>线程安全说明</b>:本类会被主线程与自建的异步线程同时访问,
|
||||
* 所有可变状态均使用并发容器或原子引用。
|
||||
*/
|
||||
public final class LegacyExploitState {
|
||||
|
||||
private LegacyExploitState() {}
|
||||
|
||||
/** 当前落沙替换目标(方块 ID 字符串),"#none" 表示禁用。 */
|
||||
private static final AtomicReference<String> REPLACE_TARGET = new AtomicReference<>("#none");
|
||||
|
||||
/** 被标记为 instantFall 触发的位置集合(异步线程可读)。 */
|
||||
private static final ConcurrentHashMap<Long, Boolean> INSTANT_FALL_POS = new ConcurrentHashMap<>();
|
||||
|
||||
/** 异步 chunk 访问线程是否正在运行。 */
|
||||
private static volatile boolean asyncThreadRunning = false;
|
||||
|
||||
public static void setReplaceTarget(String target) {
|
||||
REPLACE_TARGET.set(target == null ? "#none" : target);
|
||||
}
|
||||
|
||||
public static String getReplaceTarget() {
|
||||
return REPLACE_TARGET.get();
|
||||
}
|
||||
|
||||
public static boolean hasReplaceTarget() {
|
||||
String t = REPLACE_TARGET.get();
|
||||
return t != null && !"#none".equals(t) && !t.isEmpty();
|
||||
}
|
||||
|
||||
public static void markInstantFall(BlockPos pos) {
|
||||
INSTANT_FALL_POS.put(pos.asLong(), Boolean.TRUE);
|
||||
}
|
||||
|
||||
public static boolean isInstantFall(BlockPos pos) {
|
||||
return INSTANT_FALL_POS.containsKey(pos.asLong());
|
||||
}
|
||||
|
||||
public static void clearInstantFall(BlockPos pos) {
|
||||
INSTANT_FALL_POS.remove(pos.asLong());
|
||||
}
|
||||
|
||||
public static boolean isAsyncThreadRunning() {
|
||||
return asyncThreadRunning;
|
||||
}
|
||||
|
||||
public static void setAsyncThreadRunning(boolean running) {
|
||||
asyncThreadRunning = running;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"carpet.category.pca": "PCA Adaption",
|
||||
"carpet.category.needClient":"Need client support",
|
||||
"carpet.category.protocol":"Protocol",
|
||||
"carpet.category.legacy": "Legacy",
|
||||
|
||||
|
||||
"carpet.rule.obtainableReinforcedDeepSlate.name": "Obtainable reinforced deep slate",
|
||||
@@ -72,5 +73,20 @@
|
||||
"carpet.slsa.bot.respawned": "The bot %s's online time has been reset.",
|
||||
"carpet.slsa.bot.not_a_bot": "The player %s is not created by carpet sls addition, so you can't reset the online time.",
|
||||
|
||||
"carpet.slsa.block.deep_slate_core": "Deepslate Core"
|
||||
"carpet.slsa.block.deep_slate_core": "Deepslate Core",
|
||||
|
||||
"carpet.rule.legacyExploitMode.name": "Legacy exploit mode",
|
||||
"carpet.rule.legacyExploitMode.desc": "Master switch: other rules under the [Legacy] category only take effect when enabled",
|
||||
"carpet.rule.legacyInstantTick.name": "Instant scheduled tick",
|
||||
"carpet.rule.legacyInstantTick.desc": "Equivalent to 1.12 instantTick: scheduled ticks execute immediately without delay",
|
||||
"carpet.rule.legacyInstantFall.name": "Instant falling blocks",
|
||||
"carpet.rule.legacyInstantFall.desc": "Equivalent to 1.12 instantFall: falling blocks fall immediately when scheduled",
|
||||
"carpet.rule.legacyAsyncChunkAccess.name": "Async chunk access thread",
|
||||
"carpet.rule.legacyAsyncChunkAccess.desc": "Spawns a background thread mimicking 1.12 beacon thread to access chunks concurrently",
|
||||
"carpet.rule.legacyChunkSwap.name": "Chunk swap",
|
||||
"carpet.rule.legacyChunkSwap.desc": "Allows async thread to treat a loaded chunk as unloaded via race condition, triggering reload and world population",
|
||||
"carpet.rule.legacyFallingBlockReplace.name": "Falling block replace",
|
||||
"carpet.rule.legacyFallingBlockReplace.desc": "Replaces the block with a target block during the falling block to entity window",
|
||||
"carpet.rule.legacyFallingBlockTarget.name": "Falling replace target",
|
||||
"carpet.rule.legacyFallingBlockTarget.desc": "Target block ID for falling block replace, #none to disable"
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"carpet.category.pca": "PCA适配",
|
||||
"carpet.category.needClient": "需要客户端支持",
|
||||
"carpet.category.protocol": "协议",
|
||||
"carpet.category.legacy": "旧版",
|
||||
|
||||
|
||||
"carpet.rule.obtainableReinforcedDeepSlate.name": "强化深板岩可获取",
|
||||
@@ -72,5 +73,20 @@
|
||||
"carpet.slsa.bot.respawned": "假人%s的在线时长已重置!",
|
||||
"carpet.slsa.bot.not_a_bot": "假人%s不是由Carpet SLS Addition生成的,所以你不能重置它的在线时间!",
|
||||
|
||||
"carpet.slsa.block.deep_slate_core": "深板岩核心"
|
||||
"carpet.slsa.block.deep_slate_core": "深板岩核心",
|
||||
|
||||
"carpet.rule.legacyExploitMode.name": "旧版漏洞模式",
|
||||
"carpet.rule.legacyExploitMode.desc": "总开关:开启后[旧版]分类下的其余机制规则才会生效",
|
||||
"carpet.rule.legacyInstantTick.name": "计划刻立即执行",
|
||||
"carpet.rule.legacyInstantTick.desc": "等价1.12的instantTick:计划刻(scheduleTick)立即执行而不等待延迟",
|
||||
"carpet.rule.legacyInstantFall.name": "重力方块瞬间下落",
|
||||
"carpet.rule.legacyInstantFall.desc": "等价1.12的instantFall:重力方块被计划刻触发时立即下落",
|
||||
"carpet.rule.legacyAsyncChunkAccess.name": "异步区块访问线程",
|
||||
"carpet.rule.legacyAsyncChunkAccess.desc": "自建后台线程模拟1.12的信标线程,并发访问区块",
|
||||
"carpet.rule.legacyChunkSwap.name": "区块替换",
|
||||
"carpet.rule.legacyChunkSwap.desc": "允许异步线程在区块表竞态时把已加载区块当成未加载,触发重新加载与世界装饰",
|
||||
"carpet.rule.legacyFallingBlockReplace.name": "落沙替换",
|
||||
"carpet.rule.legacyFallingBlockReplace.desc": "在重力方块转为下落实体的窗口内,把方块替换为指定目标方块",
|
||||
"carpet.rule.legacyFallingBlockTarget.name": "落沙替换目标方块",
|
||||
"carpet.rule.legacyFallingBlockTarget.desc": "落沙替换的目标方块ID,#none为禁用"
|
||||
}
|
||||
@@ -16,6 +16,10 @@
|
||||
"carpet.MixinPlayerCommand",
|
||||
"elytra.ItemAccessor",
|
||||
"elytra.MixinItem",
|
||||
"legacy.FallingBlockMixin",
|
||||
"legacy.FallingBlockEntityMixin",
|
||||
"legacy.ScheduledTickMixin",
|
||||
"legacy.ChunkMapMixin",
|
||||
"entity.MixinArmadilloEntity",
|
||||
"entity.MixinArmorStandEntity",
|
||||
"entity.MixinBoatEntity",
|
||||
@@ -42,4 +46,4 @@
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user