Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f985a22d8a | ||
|
|
61ce9891b9 | ||
|
|
94fafe972c | ||
|
|
f9a198a892 | ||
|
|
0df145927f | ||
|
|
5cfe2f1694 | ||
|
|
ea7720b7cd | ||
|
|
1c77347dc2 | ||
|
|
35b05eb044 | ||
|
|
a434f12749 | ||
|
|
93e7416056 | ||
|
|
62397479be | ||
|
|
5001b2f5a2 | ||
|
|
d3bd73f7c5 | ||
|
|
d44ea0f4a7 | ||
|
|
de966ac511 | ||
|
|
33542f9678 | ||
|
|
870223bc1e | ||
|
|
2097a241aa | ||
|
|
8576395126 | ||
|
|
72fbc95874 | ||
|
|
1ee529eb5b | ||
|
|
bdac8233a6 | ||
|
|
4cc1b64fc3 | ||
|
|
664d2a39cf | ||
|
|
99b431145f | ||
|
|
3fde7e2c1b | ||
|
|
7366a8b244 | ||
|
|
9e32c4dbfa | ||
|
|
fa526b0839 | ||
|
|
ac6cd5c1b5 | ||
|
|
f63a59495e | ||
|
|
fba85271f4 | ||
|
|
993001c4ef | ||
|
|
9e03c076ae | ||
|
|
c647189f77 | ||
|
|
022b2144d3 |
No files matched your search
+6
-6
@@ -5,13 +5,13 @@ org.gradle.configuration-cache=true
|
|||||||
|
|
||||||
# Fabric Properties
|
# Fabric Properties
|
||||||
# check these on https://fabricmc.net/develop
|
# check these on https://fabricmc.net/develop
|
||||||
minecraft_version=26.1.2
|
minecraft_version=26.4-snapshot-4
|
||||||
loader_version=0.19.5
|
loader_version=0.20.0
|
||||||
loom_version=1.18-SNAPSHOT
|
loom_version=1.19-SNAPSHOT
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version = 0.23
|
mod_version = 0.26.0-26.4
|
||||||
maven_group = com.github.zly2006
|
maven_group = com.github.zly2006
|
||||||
archives_base_name = carpet-sls-addition
|
archives_base_name = carpet-sls-addition
|
||||||
# Dependencies
|
# Dependencies
|
||||||
fabric_version=0.155.3+26.1.2
|
fabric_version=0.163.0+26.4
|
||||||
carpet_core_version=26.1+v260401
|
carpet_core_version=26.4-beta-3+v261013
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
retries=0
|
retries=0
|
||||||
retryBackOffMs=500
|
retryBackOffMs=500
|
||||||
|
|||||||
@@ -77,4 +77,13 @@ public class SLSCarpetSettings {
|
|||||||
|
|
||||||
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
||||||
public static boolean unbreakableDecoratedPot = false;
|
public static boolean unbreakableDecoratedPot = false;
|
||||||
|
|
||||||
|
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
||||||
|
public static boolean stackableVehicles = false;
|
||||||
|
|
||||||
|
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
||||||
|
public static boolean slsBoardEnabled = false;
|
||||||
|
|
||||||
|
@Rule(categories = {SLSA, RuleCategory.FEATURE})
|
||||||
|
public static boolean noBrittleIceFromFlowingWater = false;
|
||||||
}
|
}
|
||||||
@@ -43,6 +43,13 @@ public class ServerMain implements ModInitializer, CarpetExtension {
|
|||||||
LOGGER.info("[SLSA] onInitialize: managing Carpet extension");
|
LOGGER.info("[SLSA] onInitialize: managing Carpet extension");
|
||||||
INSTANCE = this;
|
INSTANCE = this;
|
||||||
CarpetServer.manageExtension(this);
|
CarpetServer.manageExtension(this);
|
||||||
|
com.github.zly2006.carpetslsaddition.net.BoardRequestPayload.register();
|
||||||
|
// 客户端请求显示榜单 → 给该玩家单独发 display 包
|
||||||
|
net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.registerGlobalReceiver(
|
||||||
|
com.github.zly2006.carpetslsaddition.net.BoardRequestPayload.TYPE,
|
||||||
|
(payload, context) -> context.server().execute(() ->
|
||||||
|
com.github.zly2006.carpetslsaddition.util.BoardManager.onClientRequest(
|
||||||
|
context.player(), payload.objectiveName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -53,6 +60,32 @@ public class ServerMain implements ModInitializer, CarpetExtension {
|
|||||||
@Override
|
@Override
|
||||||
public void onServerLoaded(MinecraftServer server) {
|
public void onServerLoaded(MinecraftServer server) {
|
||||||
ServerMain.server = server;
|
ServerMain.server = server;
|
||||||
|
com.github.zly2006.carpetslsaddition.util.BotListManager.load();
|
||||||
|
startBoardWatcher(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 服务器完全启动后创建 objective(此时 scoreboard 已 load 完,避免重名崩溃)。 */
|
||||||
|
static {
|
||||||
|
net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents.SERVER_STARTED.register(server ->
|
||||||
|
com.github.zly2006.carpetslsaddition.util.BoardManager.ensureObjectives(server));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 每秒检测 slsBoardEnabled 规则变化并同步给客户端。 */
|
||||||
|
private void startBoardWatcher(MinecraftServer server) {
|
||||||
|
Thread t = new Thread(() -> {
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
try {
|
||||||
|
com.github.zly2006.carpetslsaddition.util.BoardManager.syncScores(server);
|
||||||
|
Thread.sleep(1000L);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
LOGGER.debug("[SLSA] board watcher error: {}", ex.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "SLSA-BoardWatcher");
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.client;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.net.BoardRequestPayload;
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||||
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
|
import net.fabricmc.api.EnvType;
|
||||||
|
import net.fabricmc.api.Environment;
|
||||||
|
import net.fabricmc.fabric.api.client.command.v2.ClientCommands;
|
||||||
|
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
|
||||||
|
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||||
|
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||||
|
import net.minecraft.commands.SharedSuggestionProvider;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端入口:/slsboard 命令,请求服务端给自己显示某个榜单。
|
||||||
|
*
|
||||||
|
* <p>服务端收到请求后,给该玩家单独发 display 包,实现各玩家各自显示、互不影响。
|
||||||
|
* 数据由服务端从 stats 同步到 scoreboard objective(见 BoardManager)。
|
||||||
|
*/
|
||||||
|
@Environment(EnvType.CLIENT)
|
||||||
|
public class BoardClient implements ClientModInitializer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeClient() {
|
||||||
|
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
|
||||||
|
dispatcher.register(
|
||||||
|
ClientCommands.literal("slsboard")
|
||||||
|
.then(ClientCommands.literal("deaths").executes(c -> request(c.getSource(), "sls_deaths")))
|
||||||
|
.then(ClientCommands.literal("mined").executes(c -> request(c.getSource(), "sls_mined")))
|
||||||
|
.then(ClientCommands.literal("off").executes(c -> request(c.getSource(), "")))
|
||||||
|
.then(ClientCommands.argument("board", StringArgumentType.word())
|
||||||
|
.suggests((c, b) -> SharedSuggestionProvider.suggest(
|
||||||
|
new String[]{"deaths", "mined", "off"}, b))
|
||||||
|
.executes(c -> {
|
||||||
|
String v = StringArgumentType.getString(c, "board");
|
||||||
|
return request(c.getSource(), "off".equals(v) ? "" : "sls_" + v);
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int request(FabricClientCommandSource source, String objectiveName) {
|
||||||
|
ClientPlayNetworking.send(new BoardRequestPayload(objectiveName));
|
||||||
|
source.sendFeedback(Component.literal(
|
||||||
|
objectiveName.isEmpty() ? "[SLSA] 榜单已关闭" : "[SLSA] 请求显示:" + objectiveName));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@ import net.minecraft.server.level.ClientInformation;
|
|||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.server.network.CommonListenerCookie;
|
import net.minecraft.server.network.CommonListenerCookie;
|
||||||
|
import net.minecraft.server.network.ClientDebugInfo;
|
||||||
import net.minecraft.server.players.NameAndId;
|
import net.minecraft.server.players.NameAndId;
|
||||||
import net.minecraft.server.players.PlayerList;
|
import net.minecraft.server.players.PlayerList;
|
||||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||||
@@ -386,7 +387,7 @@ public class BotCommand {
|
|||||||
EntityPlayerMPFake bot = constructor.newInstance(server, worldIn, gameprofile, ClientInformation.createDefault(), false);
|
EntityPlayerMPFake bot = constructor.newInstance(server, worldIn, gameprofile, ClientInformation.createDefault(), false);
|
||||||
|
|
||||||
bot.fixStartingPosition = () -> bot.snapTo(pos.x, pos.y, pos.z, (float) yaw, (float) pitch);
|
bot.fixStartingPosition = () -> bot.snapTo(pos.x, pos.y, pos.z, (float) yaw, (float) pitch);
|
||||||
server.getPlayerList().placeNewPlayer(new FakeClientConnection(PacketFlow.SERVERBOUND), bot, new CommonListenerCookie(gameprofile, 0, bot.clientInformation(), false));
|
server.getPlayerList().placeNewPlayer(new FakeClientConnection(PacketFlow.SERVERBOUND), bot, new CommonListenerCookie(gameprofile, 0, bot.clientInformation(), false, new ClientDebugInfo()));
|
||||||
|
|
||||||
bot.teleportTo(worldIn, pos.x, pos.y, pos.z, Set.of(), (float) yaw, (float) pitch, true);
|
bot.teleportTo(worldIn, pos.x, pos.y, pos.z, Set.of(), (float) yaw, (float) pitch, true);
|
||||||
bot.setHealth(20.0F);
|
bot.setHealth(20.0F);
|
||||||
|
|||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.mixin.block;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
|
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||||
|
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.LiquidBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.material.FluidState;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 26.4 快照加入的“碎冰”(Brittle Ice)机制:
|
||||||
|
* LiquidBlock#shouldSpreadLiquid 中,水流方块任意水平方向的邻居是蓝冰时,
|
||||||
|
* 会把当前水流方块替换为碎冰。
|
||||||
|
*
|
||||||
|
* 开启 noBrittleIceFromFlowingWater 规则后:
|
||||||
|
* 只有「水源方块」贴蓝冰时照常变碎冰;所有「流动的水」(横向、下落)都不再变碎冰,
|
||||||
|
* 且水流扩散不受影响。
|
||||||
|
*
|
||||||
|
* 实现:原版判断邻居是不是蓝冰用 BlockState#is(Blocks.BLUE_ICE),一旦命中就
|
||||||
|
* 变碎冰并 return false(中断扩散)。本 mixin 让流动水贴蓝冰时这次 is() 返回 false,
|
||||||
|
* 整个“变碎冰”分支被跳过,shouldSpreadLiquid 正常返回 true,水流照常扩散。
|
||||||
|
* 位置信息通过 ThreadLocal 从方法入口传入。
|
||||||
|
*/
|
||||||
|
@Mixin(LiquidBlock.class)
|
||||||
|
public class MixinFlowingWaterBrittleIce {
|
||||||
|
@Unique
|
||||||
|
private static final ThreadLocal<Level> CURRENT_LEVEL = new ThreadLocal<>();
|
||||||
|
@Unique
|
||||||
|
private static final ThreadLocal<BlockPos> CURRENT_POS = new ThreadLocal<>();
|
||||||
|
|
||||||
|
@Inject(method = "shouldSpreadLiquid", at = @At("HEAD"))
|
||||||
|
private void captureArgs(Level level, BlockPos pos, BlockState state, CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
CURRENT_LEVEL.set(level);
|
||||||
|
CURRENT_POS.set(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@WrapOperation(
|
||||||
|
method = "shouldSpreadLiquid",
|
||||||
|
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;is(Ljava/lang/Object;)Z")
|
||||||
|
)
|
||||||
|
private boolean wrapIsBlueIce(BlockState instance, Object target, Operation<Boolean> original) {
|
||||||
|
Boolean result = original.call(instance, target);
|
||||||
|
if (!result) return false;
|
||||||
|
// 只有判断目标确实是 BLUE_ICE 时才可能干预
|
||||||
|
if (!(target instanceof net.minecraft.world.level.block.Block b) || !b.equals(Blocks.BLUE_ICE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!SLSCarpetSettings.noBrittleIceFromFlowingWater) return true;
|
||||||
|
|
||||||
|
Level level = CURRENT_LEVEL.get();
|
||||||
|
BlockPos pos = CURRENT_POS.get();
|
||||||
|
if (level == null || pos == null) return true;
|
||||||
|
|
||||||
|
FluidState current = level.getFluidState(pos);
|
||||||
|
// 流动水(非水源)不让它触发碎冰替换
|
||||||
|
if (!current.isSource()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -3,13 +3,13 @@ package com.github.zly2006.carpetslsaddition.mixin.block;
|
|||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.block.Blocks;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.RedStoneWireBlock;
|
import net.minecraft.world.level.block.RedstoneWireBlock;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
@Mixin(value = RedStoneWireBlock.class, priority = 1)
|
@Mixin(value = RedstoneWireBlock.class, priority = 1)
|
||||||
public class MixinRedstoneWireBlock {
|
public class MixinRedstoneWireBlock {
|
||||||
// oldRedstoneConnectionLogic:让红石能连到活板门上的红石线。
|
// oldRedstoneConnectionLogic:让红石能连到活板门上的红石线。
|
||||||
// 新版在 getConnectingSide 里用 `blockState.getBlock() instanceof TrapDoorBlock` 判断。
|
// 新版在 getConnectingSide 里用 `blockState.getBlock() instanceof TrapDoorBlock` 判断。
|
||||||
|
|||||||
+7
-4
@@ -15,7 +15,7 @@ import net.minecraft.world.level.block.state.pattern.BlockInWorld;
|
|||||||
import net.minecraft.world.level.block.state.pattern.BlockPattern;
|
import net.minecraft.world.level.block.state.pattern.BlockPattern;
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
import net.minecraft.world.level.levelgen.Heightmap;
|
import net.minecraft.world.level.levelgen.Heightmap;
|
||||||
import net.minecraft.world.level.levelgen.feature.EndPodiumFeature;
|
import net.minecraft.world.level.dimension.end.EnderDragonFight;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.spongepowered.asm.mixin.*;
|
import org.spongepowered.asm.mixin.*;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
@@ -37,6 +37,9 @@ public class MixinEnderDragonFight {
|
|||||||
@Shadow
|
@Shadow
|
||||||
private BlockPos exitPortalLocation;
|
private BlockPos exitPortalLocation;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private BlockPos origin;
|
||||||
|
|
||||||
@Unique
|
@Unique
|
||||||
private int defaultChunkX = -8;
|
private int defaultChunkX = -8;
|
||||||
@Unique
|
@Unique
|
||||||
@@ -79,13 +82,13 @@ public class MixinEnderDragonFight {
|
|||||||
if (SLSCarpetSettings.optimizedOnDragonRespawn && defaultOriginY != -1) {
|
if (SLSCarpetSettings.optimizedOnDragonRespawn && defaultOriginY != -1) {
|
||||||
i = defaultOriginY;
|
i = defaultOriginY;
|
||||||
} else {
|
} else {
|
||||||
i = this.level.getHeight(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(BlockPos.ZERO).getX(), EndPodiumFeature.getLocation(BlockPos.ZERO).getZ());
|
i = this.level.getHeight(Heightmap.Types.MOTION_BLOCKING, EnderDragonFight.getPodiumLocation(this.origin).getX(), EnderDragonFight.getPodiumLocation(this.origin).getZ());
|
||||||
}
|
}
|
||||||
boolean notFirstSearch = false;
|
boolean notFirstSearch = false;
|
||||||
for (j = i; j >= 0; --j) {
|
for (j = i; j >= 0; --j) {
|
||||||
BlockPattern.BlockPatternMatch result2;
|
BlockPattern.BlockPatternMatch result2;
|
||||||
BlockPos origin = EndPodiumFeature.getLocation(BlockPos.ZERO);
|
BlockPos podiumPos = EnderDragonFight.getPodiumLocation(this.origin);
|
||||||
BlockPos searchPos = new BlockPos(origin.getX(), j, origin.getZ());
|
BlockPos searchPos = new BlockPos(podiumPos.getX(), j, podiumPos.getZ());
|
||||||
if (SLSCarpetSettings.optimizedOnDragonRespawn && notFirstSearch) {
|
if (SLSCarpetSettings.optimizedOnDragonRespawn && notFirstSearch) {
|
||||||
result2 = partialSearchAround(this.exitPortalPattern, this.level, searchPos);
|
result2 = partialSearchAround(this.exitPortalPattern, this.level, searchPos);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.mixin.item;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
|
import net.minecraft.world.inventory.Slot;
|
||||||
|
import net.minecraft.world.item.BoatItem;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.MinecartItem;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 载具堆叠(仅玩家背包):矿车/船在【玩家背包】内可堆叠到 64,其他容器不堆叠。
|
||||||
|
*
|
||||||
|
* <p>原理:{@code Slot.getMaxStackSize(ItemStack)} = min(容器上限, 物品上限)。
|
||||||
|
* 载具的物品上限全局为 1(默认不叠)。本 mixin 在【容器是玩家背包 Inventory】且物品是载具时,
|
||||||
|
* 直接返回 64,绕过 min 限制。
|
||||||
|
*
|
||||||
|
* <p>这样只有玩家背包(物品栏 36 格)内能叠,箱子/漏斗/潜影盒等其他容器不受影响。
|
||||||
|
*/
|
||||||
|
@Mixin(Slot.class)
|
||||||
|
public abstract class SlotMixin {
|
||||||
|
|
||||||
|
@Inject(method = "getMaxStackSize(Lnet/minecraft/world/item/ItemStack;)I", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void stackableVehiclesInPlayerInventory(ItemStack stack, CallbackInfoReturnable<Integer> cir) {
|
||||||
|
if (!SLSCarpetSettings.stackableVehicles) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stack == null || stack.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Slot self = (Slot) (Object) this;
|
||||||
|
// 仅玩家背包
|
||||||
|
if (!(self.container instanceof Inventory)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Item item = stack.getItem();
|
||||||
|
if (item instanceof MinecartItem || item instanceof BoatItem) {
|
||||||
|
cir.setReturnValue(64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -3,6 +3,7 @@ package com.github.zly2006.carpetslsaddition.mixin.world;
|
|||||||
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.world.entity.EntityType;
|
import net.minecraft.world.entity.EntityType;
|
||||||
|
import net.minecraft.world.entity.EntityTypes;
|
||||||
import net.minecraft.world.entity.Mob;
|
import net.minecraft.world.entity.Mob;
|
||||||
import net.minecraft.world.level.NaturalSpawner;
|
import net.minecraft.world.level.NaturalSpawner;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
@@ -16,7 +17,7 @@ public class MixinSpawnHelper {
|
|||||||
// 改为在 getMobForSpawn 返回处拦截。
|
// 改为在 getMobForSpawn 返回处拦截。
|
||||||
@Inject(method = "getMobForSpawn", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "getMobForSpawn", at = @At("HEAD"), cancellable = true)
|
||||||
private static void onCreateMob(ServerLevel level, EntityType<?> type, CallbackInfoReturnable<Mob> cir) {
|
private static void onCreateMob(ServerLevel level, EntityType<?> type, CallbackInfoReturnable<Mob> cir) {
|
||||||
if (SLSCarpetSettings.noBatSpawning && type == EntityType.BAT) {
|
if (SLSCarpetSettings.noBatSpawning && type == EntityTypes.BAT) {
|
||||||
cir.setReturnValue(null);
|
cir.setReturnValue(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.net;
|
||||||
|
|
||||||
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
|
import net.minecraft.network.codec.ByteBufCodecs;
|
||||||
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端 → 服务端:请求显示某个榜单(或关闭)。
|
||||||
|
*
|
||||||
|
* <p>服务端收到后,给该玩家单独发送 {@code ClientboundSetDisplayObjectivePacket},
|
||||||
|
* 实现"各玩家各自显示、互不影响"。
|
||||||
|
*
|
||||||
|
* <p>{@code objectiveName} 为空字符串表示关闭。
|
||||||
|
*/
|
||||||
|
public record BoardRequestPayload(String objectiveName) implements CustomPacketPayload {
|
||||||
|
|
||||||
|
public static final CustomPacketPayload.Type<BoardRequestPayload> TYPE =
|
||||||
|
new CustomPacketPayload.Type<>(Identifier.fromNamespaceAndPath("slsa", "board_request"));
|
||||||
|
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, BoardRequestPayload> CODEC = StreamCodec.composite(
|
||||||
|
ByteBufCodecs.STRING_UTF8, BoardRequestPayload::objectiveName,
|
||||||
|
BoardRequestPayload::new
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomPacketPayload.Type<? extends CustomPacketPayload> type() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void register() {
|
||||||
|
net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry.serverboundPlay().register(TYPE, CODEC);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package com.github.zly2006.carpetslsaddition.util;
|
||||||
|
|
||||||
|
import com.github.zly2006.carpetslsaddition.ServerMain;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.stats.ServerStatsCounter;
|
||||||
|
import net.minecraft.stats.Stat;
|
||||||
|
import net.minecraft.stats.Stats;
|
||||||
|
import net.minecraft.world.scores.Objective;
|
||||||
|
import net.minecraft.world.scores.ScoreAccess;
|
||||||
|
import net.minecraft.world.scores.ScoreHolder;
|
||||||
|
import net.minecraft.world.scores.Scoreboard;
|
||||||
|
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 死亡榜 / 挖掘榜的 scoreboard 管理器(服务端)。
|
||||||
|
*
|
||||||
|
* <p>数据源是 vanilla stats,但 stats 不会自动同步给其他客户端。
|
||||||
|
* 因此本类定期把 stats 的聚合值写入 scoreboard objective,
|
||||||
|
* 客户端再用原版计分板侧边栏显示(scoreboard 会自动同步给所有客户端)。
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code sls_deaths}:死亡次数(DUMMY 判据,从 stats 的 {@link Stats#DEATHS} 写入)。</li>
|
||||||
|
* <li>{@code sls_mined}:挖掘总数(DUMMY 判据,从 stats 的 {@link Stats#BLOCK_MINED} 聚合)。</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class BoardManager {
|
||||||
|
|
||||||
|
private BoardManager() {}
|
||||||
|
|
||||||
|
public static final String OBJ_DEATHS = "sls_deaths";
|
||||||
|
public static final String OBJ_MINED = "sls_mined";
|
||||||
|
|
||||||
|
/** 确保两个 objective 存在(服务器启动/重载时调用)。 */
|
||||||
|
public static void ensureObjectives(MinecraftServer server) {
|
||||||
|
Scoreboard sb = server.getScoreboard();
|
||||||
|
ServerMain.LOGGER.info("[SLSA] ensureObjectives called, deaths={} mined={}",
|
||||||
|
sb.getObjective(OBJ_DEATHS) != null, sb.getObjective(OBJ_MINED) != null);
|
||||||
|
if (sb.getObjective(OBJ_DEATHS) == null) {
|
||||||
|
sb.addObjective(OBJ_DEATHS, ObjectiveCriteria.DUMMY,
|
||||||
|
Component.literal("死亡榜"), ObjectiveCriteria.RenderType.INTEGER, true, null);
|
||||||
|
ServerMain.LOGGER.info("[SLSA] created scoreboard objective {}", OBJ_DEATHS);
|
||||||
|
}
|
||||||
|
if (sb.getObjective(OBJ_MINED) == null) {
|
||||||
|
sb.addObjective(OBJ_MINED, ObjectiveCriteria.DUMMY,
|
||||||
|
Component.literal("挖掘榜"), ObjectiveCriteria.RenderType.INTEGER, true, null);
|
||||||
|
ServerMain.LOGGER.info("[SLSA] created scoreboard objective {}", OBJ_MINED);
|
||||||
|
}
|
||||||
|
// 让客户端能收到这两个 objective(load 进来的 objective 可能未被追踪)
|
||||||
|
if (sb instanceof net.minecraft.server.ServerScoreboard ssb) {
|
||||||
|
Objective d = sb.getObjective(OBJ_DEATHS);
|
||||||
|
if (d != null) ssb.startTrackingObjective(d);
|
||||||
|
Objective m = sb.getObjective(OBJ_MINED);
|
||||||
|
if (m != null) ssb.startTrackingObjective(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 读取某玩家的挖掘总数(遍历 stats map 中 BLOCK_MINED 类型条目)。 */
|
||||||
|
private static int totalMined(ServerStatsCounter counter) {
|
||||||
|
int sum = 0;
|
||||||
|
for (Stat<?> stat : counter.stats.keySet()) {
|
||||||
|
if (stat.getType() == Stats.BLOCK_MINED) {
|
||||||
|
sum += counter.getValue(stat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 读取某玩家的死亡次数。 */
|
||||||
|
private static int totalDeaths(ServerStatsCounter counter) {
|
||||||
|
return counter.getValue(Stats.CUSTOM, Stats.DEATHS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 把一个分数写入 objective(若不存在则创建/更新)。 */
|
||||||
|
private static void setScore(Scoreboard sb, Objective obj, ServerPlayer player, int value) {
|
||||||
|
if (obj == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ScoreAccess access = sb.getOrCreatePlayerScore((ScoreHolder) player, obj);
|
||||||
|
access.set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从 stats 同步所有在线玩家的分数到 scoreboard(定期调用)。 */
|
||||||
|
public static void syncScores(MinecraftServer server) {
|
||||||
|
Scoreboard sb = server.getScoreboard();
|
||||||
|
Objective deaths = sb.getObjective(OBJ_DEATHS);
|
||||||
|
Objective mined = sb.getObjective(OBJ_MINED);
|
||||||
|
if (deaths == null && mined == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
|
||||||
|
ServerStatsCounter counter = player.getStats();
|
||||||
|
setScore(sb, deaths, player, totalDeaths(counter));
|
||||||
|
setScore(sb, mined, player, totalMined(counter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 客户端请求显示某榜单 → 给该玩家单独发 display 包(不影响其他玩家)。 */
|
||||||
|
public static void onClientRequest(ServerPlayer player, String objectiveName) {
|
||||||
|
MinecraftServer server = ServerMain.server;
|
||||||
|
if (server == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Objective obj = objectiveName == null || objectiveName.isEmpty()
|
||||||
|
? null
|
||||||
|
: server.getScoreboard().getObjective(objectiveName);
|
||||||
|
// 干净重建客户端该 objective:先 REMOVE(客户端没有则忽略),再 ADD + 分数包
|
||||||
|
if (obj != null) {
|
||||||
|
player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetObjectivePacket(
|
||||||
|
obj, net.minecraft.network.protocol.game.ClientboundSetObjectivePacket.METHOD_REMOVE));
|
||||||
|
player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetObjectivePacket(
|
||||||
|
obj, net.minecraft.network.protocol.game.ClientboundSetObjectivePacket.METHOD_ADD));
|
||||||
|
if (server.getScoreboard() instanceof net.minecraft.server.ServerScoreboard ssb) {
|
||||||
|
for (var pkt : ssb.getStartTrackingPackets(obj)) {
|
||||||
|
if (pkt instanceof net.minecraft.network.protocol.game.ClientboundSetScorePacket) {
|
||||||
|
player.connection.send(pkt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 发 display 包(slot=SIDEBAR,objective 可为 null 表示清除)
|
||||||
|
player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket(
|
||||||
|
net.minecraft.world.scores.DisplaySlot.SIDEBAR, obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ public class ShulkerBoxItemUtil {
|
|||||||
if (itemStack.getItem() instanceof BlockItem blockItem
|
if (itemStack.getItem() instanceof BlockItem blockItem
|
||||||
&& blockItem.getBlock() instanceof ShulkerBoxBlock) {
|
&& blockItem.getBlock() instanceof ShulkerBoxBlock) {
|
||||||
return itemStack.getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY)
|
return itemStack.getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY)
|
||||||
.allItemsCopyStream().findAny().isEmpty();
|
.nonEmptyItemCopyStream().findAny().isEmpty();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
"carpet.rule.elytraCraftable.desc": "Make elytra craftable for Minecraft 1.20.6+ (eltra wont disappear when crafting)",
|
"carpet.rule.elytraCraftable.desc": "Make elytra craftable for Minecraft 1.20.6+ (eltra wont disappear when crafting)",
|
||||||
"carpet.rule.botMaxOnlineTime.desc": "Set the maximum living time of the bot which create by carpet sls addition.",
|
"carpet.rule.botMaxOnlineTime.desc": "Set the maximum living time of the bot which create by carpet sls addition.",
|
||||||
"carpet.rule.spectatorCannotUseLeash.desc": "If a player is a spectator, they cannot use the leash.",
|
"carpet.rule.spectatorCannotUseLeash.desc": "If a player is a spectator, they cannot use the leash.",
|
||||||
"carpet.rule.offlineFakePlayers.desc": "假人强制离线uuid",
|
"carpet.rule.offlineFakePlayers.desc": "Force fake players to use offline UUIDs",
|
||||||
"carpet.rule.armadilloImmediateDespawns.desc": "Armadillos will despawn immediately if they are 128 blocks away from the player.",
|
"carpet.rule.armadilloImmediateDespawns.desc": "Armadillos will despawn immediately if they are 128 blocks away from the player.",
|
||||||
"carpet.rule.turtleLocalNesting.name": "Turtle Local Nesting",
|
"carpet.rule.turtleLocalNesting.name": "Turtle Local Nesting",
|
||||||
"carpet.rule.turtleLocalNesting.desc": "Turtles carrying eggs will lay them on reachable sand near the closest local water source instead of returning to their birthplace.",
|
"carpet.rule.turtleLocalNesting.desc": "Turtles carrying eggs will lay them on reachable sand near the closest local water source instead of returning to their birthplace.",
|
||||||
@@ -72,5 +72,16 @@
|
|||||||
"carpet.slsa.bot.respawned": "The bot %s's online time has been reset.",
|
"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.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.stackableVehicles.name": "Stackable vehicles",
|
||||||
|
"carpet.rule.stackableVehicles.desc": "Minecarts and boats can stack up to 64, auto-merging on pickup",
|
||||||
|
"carpet.rule.slsBoardEnabled.name": "Enable boards",
|
||||||
|
"carpet.rule.slsBoardEnabled.desc": "Allow players to use /slsboard to show death/mining leaderboards",
|
||||||
|
"carpet.rule.noBrittleIceFromFlowingWater.name": "No brittle ice from flowing water",
|
||||||
|
"carpet.rule.noBrittleIceFromFlowingWater.desc": "Only water flowing straight down onto blue ice forms brittle ice. Horizontally flowing water will not.",
|
||||||
|
"carpet.rule.elytraCraftable.name": "Craftable elytra",
|
||||||
|
"carpet.rule.armadilloImmediateDespawns.name": "Armadillos despawn immediately",
|
||||||
|
"carpet.rule.offlineFakePlayers.name": "Force offline UUID for fake players",
|
||||||
|
"carpet.rule.netherPortalSize.name": "Max nether portal size",
|
||||||
|
"carpet.rule.trialSpawnerCD.name": "Trial spawner cooldown"
|
||||||
}
|
}
|
||||||
@@ -72,5 +72,16 @@
|
|||||||
"carpet.slsa.bot.respawned": "假人%s的在线时长已重置!",
|
"carpet.slsa.bot.respawned": "假人%s的在线时长已重置!",
|
||||||
"carpet.slsa.bot.not_a_bot": "假人%s不是由Carpet SLS Addition生成的,所以你不能重置它的在线时间!",
|
"carpet.slsa.bot.not_a_bot": "假人%s不是由Carpet SLS Addition生成的,所以你不能重置它的在线时间!",
|
||||||
|
|
||||||
"carpet.slsa.block.deep_slate_core": "深板岩核心"
|
"carpet.slsa.block.deep_slate_core": "深板岩核心",
|
||||||
|
"carpet.rule.stackableVehicles.name": "载具可堆叠",
|
||||||
|
"carpet.rule.stackableVehicles.desc": "矿车和船的物品可以堆叠到64,拾取时自动合并",
|
||||||
|
"carpet.rule.slsBoardEnabled.name": "启用榜单",
|
||||||
|
"carpet.rule.slsBoardEnabled.desc": "允许玩家用 /slsboard 显示死亡榜/挖掘榜",
|
||||||
|
"carpet.rule.noBrittleIceFromFlowingWater.name": "横向流动水不产生碎冰",
|
||||||
|
"carpet.rule.noBrittleIceFromFlowingWater.desc": "只有竖直向下的水流接触蓝冰时才会产生碎冰,横向流动的水不再转化",
|
||||||
|
"carpet.rule.elytraCraftable.name": "鞘翅可合成",
|
||||||
|
"carpet.rule.armadilloImmediateDespawns.name": "犰狳立即消失",
|
||||||
|
"carpet.rule.offlineFakePlayers.name": "假人强制离线UUID",
|
||||||
|
"carpet.rule.netherPortalSize.name": "地狱门最大尺寸",
|
||||||
|
"carpet.rule.trialSpawnerCD.name": "试炼刷怪笼冷却"
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,9 @@
|
|||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
"main": [
|
"main": [
|
||||||
"com.github.zly2006.carpetslsaddition.ServerMain"
|
"com.github.zly2006.carpetslsaddition.ServerMain"
|
||||||
|
],
|
||||||
|
"client": [
|
||||||
|
"com.github.zly2006.carpetslsaddition.client.BoardClient"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"accessWidener": "slsa.accesswidener",
|
"accessWidener": "slsa.accesswidener",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
accessWidener v1 official
|
accessWidener v1 official
|
||||||
|
|
||||||
accessible class net/minecraft/world/entity/monster/EnderMan$EndermanTakeBlockGoal
|
accessible class net/minecraft/world/entity/monster/Enderman$EndermanTakeBlockGoal
|
||||||
accessible method net/minecraft/world/entity/Entity unsetRemoved ()V
|
accessible method net/minecraft/world/entity/Entity unsetRemoved ()V
|
||||||
accessible field net/minecraft/world/entity/Entity entityData Lnet/minecraft/network/syncher/SynchedEntityData;
|
accessible field net/minecraft/world/entity/Entity entityData Lnet/minecraft/network/syncher/SynchedEntityData;
|
||||||
accessible field net/minecraft/world/entity/Avatar DATA_PLAYER_MODE_CUSTOMISATION Lnet/minecraft/network/syncher/EntityDataAccessor;
|
accessible field net/minecraft/world/entity/Avatar DATA_PLAYER_MODE_CUSTOMISATION Lnet/minecraft/network/syncher/EntityDataAccessor;
|
||||||
|
|
||||||
|
accessible field net/minecraft/stats/StatsCounter stats Lit/unimi/dsi/fastutil/objects/Object2IntMap;
|
||||||
@@ -12,6 +12,8 @@
|
|||||||
"block.MixinDecoratedPotBlock",
|
"block.MixinDecoratedPotBlock",
|
||||||
"block.MixinTrialSpawnerData",
|
"block.MixinTrialSpawnerData",
|
||||||
"block.MixinTrialSpawnerState",
|
"block.MixinTrialSpawnerState",
|
||||||
|
"block.MixinFlowingWaterBrittleIce",
|
||||||
|
"item.SlotMixin",
|
||||||
"carpet.MixinEntityPlayerMPFake",
|
"carpet.MixinEntityPlayerMPFake",
|
||||||
"carpet.MixinPlayerCommand",
|
"carpet.MixinPlayerCommand",
|
||||||
"elytra.ItemAccessor",
|
"elytra.ItemAccessor",
|
||||||
|
|||||||
Reference in new issue
Block a user