Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9ab0883
feat: implement ItemStack request processing system and inventory imp…
lt-name Apr 20, 2026
aa35ac0
Merge branch 'master' into feat/ItemStack
lt-name Apr 21, 2026
d5c2ebe
fix: improve item stack request and inventory transaction handling
lt-name Apr 24, 2026
7d7415e
fix: plugin event compatibility under SAI inventory mode
lt-name Apr 24, 2026
ebb395a
remove debug logs
lt-name Apr 24, 2026
6a46be1
fix: SA inventory validation holes
lt-name Apr 24, 2026
ebd1ea9
fix
lt-name Apr 25, 2026
0d5eaeb
Merge branch 'master' into feat/ItemStack
lt-name Apr 26, 2026
615ade8
fix
lt-name Apr 26, 2026
1b2ac70
merge: merge master into feat/ItemStack
lt-name Apr 30, 2026
634685c
fix: add missing CrafterInventory mapping in NetworkMapping.getSlotTy…
lt-name May 1, 2026
e323fe5
Merge remote-tracking branch 'origin/master' into feat/ItemStack
lt-name May 14, 2026
0f210fd
fix
lt-name May 16, 2026
499e9ff
Merge branch 'master' into feat/ItemStack
lt-name May 28, 2026
e893b6e
feat(inventory): implement bundle interactions, offhand item restrict…
lt-name May 30, 2026
c3b3e4f
Merge branch 'master' into feat/ItemStack
lt-name May 31, 2026
0561518
fix: CraftResultsDeprecated item instance decoding
lt-name May 31, 2026
3e4b885
fix: restore inventory compatibility for SAI horse armor and offhand
lt-name May 31, 2026
d70cfef
Merge branch 'master' into feat/ItemStack
lt-name Jun 13, 2026
d5f9384
test: align multi-recipe test output with strict firework canExecute …
lt-name Jun 13, 2026
effbef6
fix: validate topWindow type for typed container slots in NetworkMapping
lt-name Jun 13, 2026
2a92493
fix(inventory): skip deprecated/unimplemented item stack request acti…
lt-name Jun 13, 2026
d1cf689
fix(inventory): prevent item duping via force-restore rollback and ha…
lt-name Jun 13, 2026
e9b1d10
fix(inventory): creative take in SAI mode rolls back cursor due to co…
lt-name Jun 13, 2026
dd60dda
fix(inventory): null dynamicId for dynamic containers; add cartograph…
lt-name Jun 14, 2026
b0d1aee
Merge branch 'master' into feat/ItemStack
lt-name Jun 14, 2026
706caad
fix(inventory): correct failed window open handling and cartography n…
lt-name Jun 14, 2026
01c6c09
Merge branch 'master' into feat/ItemStack
lt-name Jun 14, 2026
a9a2b21
fix(inventory): allow custom items with allowOffHand in off-hand slot
lt-name Jun 14, 2026
c8d5a02
fix(customitem): make custom armor and tools fully functional server-…
lt-name Jun 15, 2026
15d50ab
fix(customitem): prevent StackOverflow recursion and fix armor durabi…
lt-name Jun 15, 2026
34017c3
docs(customitem): fix misleading builder javadocs about fallback beha…
lt-name Jun 15, 2026
fb6c3ff
fix(customitem): write digger for tagless tools and default armor dur…
lt-name Jun 16, 2026
7d05042
fix(customitem): resolve dig speed per block from destroy_speeds
lt-name Jun 16, 2026
7728e65
fix(customitem): allow legacy items in off-hand slot
lt-name Jun 16, 2026
868286f
Merge remote-tracking branch 'origin/master' into feat/ItemStack
lt-name Jun 17, 2026
1763ec0
Merge remote-tracking branch 'origin/master' into feat/ItemStack
lt-name Jun 20, 2026
1000206
fix(inventory): resolve review issues in ItemStack SAI system
lt-name Jun 20, 2026
c70c382
fix(customitem): allow custom item subclasses into off-hand slot
lt-name Jun 20, 2026
ded75b1
Merge branch 'master' into feat/ItemStack
lt-name Jun 21, 2026
e4112ac
Merge remote-tracking branch 'origin/master' into feat/ItemStack
lt-name Jun 23, 2026
b58cc4a
fix: restore legacy override contract for custom items and harden LEV…
lt-name Jun 24, 2026
294706a
Merge remote-tracking branch 'origin/master' into feat/ItemStack
lt-name Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,460 changes: 807 additions & 653 deletions src/main/java/cn/nukkit/Player.java

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/main/java/cn/nukkit/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,12 @@ public Level remove(@NotNull Object key) {
* Server authority block destruction
*/
public boolean serverAuthoritativeBlockBreaking;
/**
* Server authoritative inventory mode
* When enabled, server has final authority over inventory changes
* @since v1.16.100 (protocol 407+)
*/
public boolean serverAuthoritativeInventory;
/**
* Network encryption
*/
Expand Down Expand Up @@ -3358,6 +3364,7 @@ private void loadSettings() {
default -> this.serverAuthoritativeMovementMode = 1; // server-auth
}
this.serverAuthoritativeBlockBreaking = this.getPropertyBoolean("server-authoritative-block-breaking", true);
this.serverAuthoritativeInventory = this.getPropertyBoolean("server-authoritative-inventory", true);

// === Advanced MOT settings from nukkit-mot.yml ===
ServerConfig config = this.serverConfig;
Expand Down Expand Up @@ -3577,6 +3584,7 @@ private static class ServerProperties extends ConfigSection {

put("server-authoritative-movement", "server-auth");
put("server-authoritative-block-breaking", true);
put("server-authoritative-inventory", true);
}
}

Expand Down
35 changes: 11 additions & 24 deletions src/main/java/cn/nukkit/block/Block.java
Original file line number Diff line number Diff line change
Expand Up @@ -765,27 +765,13 @@ public Item[] getDrops(@Nullable Player player, Item item) {
return this.getDrops(item);
}

private double customToolBreakTimeBonus(int toolType, @Nullable Integer speed) {
if (speed != null) return speed;
else if (toolType == ItemTool.TYPE_SWORD) {
if (this instanceof BlockCobweb) {
return 15.0;
} else if (this instanceof BlockBamboo) {
return 30.0;
} else return 1.0;
} else if (toolType == ItemTool.TYPE_SHEARS) {
if (this instanceof BlockWool || this instanceof BlockLeaves) {
return 5.0;
} else if (this instanceof BlockCobweb) {
return 15.0;
} else return 1.0;
} else if (toolType == ItemTool.TYPE_NONE) return 1.0;
return 0;
}

private double toolBreakTimeBonus0(Item item) {
if (item instanceof ItemCustomTool itemCustomTool && itemCustomTool.getSpeed() != null) {
return customToolBreakTimeBonus(customToolType(item), itemCustomTool.getSpeed());
if (item instanceof ItemCustomTool itemCustomTool) {
//按当前方块查 destroy_speeds;未命中则回退原版逻辑(tier 查表 + sword/shears 特殊值)
Integer speed = itemCustomTool.getSpeedFor(getId());
if (speed != null) {
return speed;
}
}
return toolBreakTimeBonus0(toolType0(item, getId()), item.getTier(), this.getId());
}
Expand Down Expand Up @@ -830,10 +816,6 @@ private static double speedRateByHasteLore0(int hasteLoreLevel) {
return 1.0 + (0.2 * hasteLoreLevel);
}

private int customToolType(Item item) {
return toolType0(item, this.getId());
}

private static int toolType0(Item item, int blockId) {
if (item.isHoe()) {
switch (blockId) {
Expand All @@ -857,6 +839,11 @@ private static int toolType0(Item item, int blockId) {
}

private static boolean correctTool0(int blockToolType, Item item, int blockId) {
//自定义工具:digger 含此方块即视为正确工具(让 addExtraBlock 能挖非自身类型的方块)
if (item instanceof ItemCustomTool customTool && customTool.getSpeedFor(blockId) != null) {
return true;
}

boolean isLeaves = blockId == LEAVES || blockId == LEAVES2 || blockId == AZALEA_LEAVES
|| blockId == AZALEA_LEAVES_FLOWERED || blockId == MANGROVE_LEAVES || blockId == CHERRY_LEAVES || blockId == PALE_OAK_LEAVES;

Expand Down
22 changes: 22 additions & 0 deletions src/main/java/cn/nukkit/block/BlockCartographyTable.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package cn.nukkit.block;

import cn.nukkit.Player;
import cn.nukkit.inventory.CartographyTableInventory;
import cn.nukkit.item.Item;
import cn.nukkit.item.ItemBlock;
import cn.nukkit.item.ItemTool;
import cn.nukkit.utils.BlockColor;

Expand Down Expand Up @@ -39,4 +43,22 @@ public BlockColor getColor() {
public int getBurnChance() {
return 5;
}

@Override
public boolean canBeActivated() {
return true;
}

@Override
public boolean onActivate(Item item, Player player) {
if (player != null) {
player.addWindow(new CartographyTableInventory(player.getUIInventory(), this), Player.CARTOGRAPHY_WINDOW_ID);
}
return true;
}

@Override
public Item toItem() {
return new ItemBlock(Block.get(this.getId(), 0), 0);
}
}
2 changes: 1 addition & 1 deletion src/main/java/cn/nukkit/block/BlockChest.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public int getId() {
public Class<? extends BlockEntityChest> getBlockEntityClass() {
return BlockEntityChest.class;
}

@NotNull
@Override
public String getBlockEntityType() {
Expand Down
14 changes: 13 additions & 1 deletion src/main/java/cn/nukkit/block/BlockDropper.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

public class BlockDropper extends BlockSolidMeta implements Faceable {
public class BlockDropper extends BlockSolidMeta implements Faceable, BlockEntityHolder<BlockEntityDropper> {

protected boolean triggered = false;

Expand All @@ -42,6 +42,18 @@ public String getName() {
return "Dropper";
}

@NotNull
@Override
public Class<? extends BlockEntityDropper> getBlockEntityClass() {
return BlockEntityDropper.class;
}

@NotNull
@Override
public String getBlockEntityType() {
return BlockEntity.DROPPER;
}

@Override
public double getHardness() {
return 0.5;
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/cn/nukkit/block/BlockEntityHolder.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ default E createBlockEntity(@Nullable CompoundTag initialData, @Nullable Object.
} else {
initialData = initialData.copy();
}
BlockEntity created = BlockEntity.createBlockEntity(typeName, chunk,
BlockEntity created = BlockEntity.createBlockEntity(typeName, chunk,
initialData
.putString("id", typeName)
.putInt("x", getFloorX())
.putInt("y", getFloorY())
.putInt("z", getFloorZ()),
.putInt("z", getFloorZ()),
args);

Class<? extends E> entityClass = getBlockEntityClass();
Expand Down Expand Up @@ -123,7 +123,7 @@ static <E extends BlockEntity, H extends BlockEntityHolder<E>> E setBlockAndCrea
static <E extends BlockEntity, H extends BlockEntityHolder<E>> E setBlockAndCreateEntity(
@NotNull H holder, boolean direct, boolean update, @Nullable CompoundTag initialData,
@Nullable Object... args) {
Block block = holder.getBlock();
Block block = holder.getBlock();
Level level = block.getLevel();
Block layer0 = level.getBlock(block, 0);
Block layer1 = level.getBlock(block, 1);
Expand All @@ -137,7 +137,7 @@ static <E extends BlockEntity, H extends BlockEntityHolder<E>> E setBlockAndCrea
throw e;
}
}

return null;
}

Expand Down
16 changes: 14 additions & 2 deletions src/main/java/cn/nukkit/block/BlockShulkerBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
/**
* Created by PetteriM1
*/
public class BlockShulkerBox extends BlockTransparentMeta {
public class BlockShulkerBox extends BlockTransparentMeta implements BlockEntityHolder<BlockEntityShulkerBox> {

public BlockShulkerBox() {
this(0);
Expand All @@ -45,6 +45,18 @@ public String getName() {
return this.getDyeColor().getName() + " Shulker Box";
}

@NotNull
@Override
public Class<? extends BlockEntityShulkerBox> getBlockEntityClass() {
return BlockEntityShulkerBox.class;
}

@NotNull
@Override
public String getBlockEntityType() {
return BlockEntity.SHULKER_BOX;
}

@Override
public double getHardness() {
return 2.5;
Expand Down Expand Up @@ -196,4 +208,4 @@ public Item[] getDrops(@Nullable Player player, Item item) {
public boolean diffusesSkyLight() {
return true;
}
}
}
16 changes: 12 additions & 4 deletions src/main/java/cn/nukkit/blockentity/BlockEntityBeacon.java
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,15 @@ public void setSecondaryPower(int power) {
}

private static final IntSet ALLOWED_EFFECTS = new IntOpenHashSet(new int[]{Effect.SPEED, Effect.HASTE, Effect.DAMAGE_RESISTANCE, Effect.JUMP, Effect.STRENGTH, Effect.REGENERATION});
private static final IntSet ITEMS = new IntOpenHashSet(new int[]{Item.AIR, ItemID.NETHERITE_INGOT, ItemID.EMERALD, ItemID.DIAMOND, ItemID.GOLD_INGOT, ItemID. IRON_INGOT});
private static final IntSet PAYMENT_ITEMS = new IntOpenHashSet(new int[]{ItemID.NETHERITE_INGOT, ItemID.EMERALD, ItemID.DIAMOND, ItemID.GOLD_INGOT, ItemID.IRON_INGOT});

public static boolean isAllowedEffect(int effectId) {
return effectId == 0 || ALLOWED_EFFECTS.contains(effectId);
}

public static boolean isPaymentItem(int itemId) {
return PAYMENT_ITEMS.contains(itemId);
}

@Override
public boolean updateCompoundTag(CompoundTag nbt, Player player) {
Expand All @@ -262,7 +270,7 @@ public boolean updateCompoundTag(CompoundTag nbt, Player player) {
}

int material = beaconInventory.useMaterial();
if (!ITEMS.contains(material)) {
if (!isPaymentItem(material)) {
Server.getInstance().getLogger().debug(player.getName() + " tried to set effect but there's no payment in beacon inventory");
return false;
}
Expand All @@ -273,14 +281,14 @@ public boolean updateCompoundTag(CompoundTag nbt, Player player) {
}

int primary = nbt.getInt("primary");
if (ALLOWED_EFFECTS.contains(primary)) {
if (isAllowedEffect(primary)) {
this.setPrimaryPower(primary);
} else {
Server.getInstance().getLogger().debug(player.getName() + " tried to set an invalid primary effect to a beacon: " + primary);
}

int secondary = nbt.getInt("secondary");
if (ALLOWED_EFFECTS.contains(secondary)) {
if (isAllowedEffect(secondary)) {
this.setSecondaryPower(secondary);
} else {
Server.getInstance().getLogger().debug(player.getName() + " tried to set an invalid secondary effect to a beacon: " + secondary);
Expand Down
13 changes: 11 additions & 2 deletions src/main/java/cn/nukkit/entity/passive/EntityDonkey.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ public float getHeight() {
public void initEntity() {
this.setMaxHealth(15);

super.initEntity();

if (this.namedTag.contains("ChestedHorse")) {
this.setChested(this.namedTag.getBoolean("ChestedHorse"));
}

super.initEntity();
}

@Override
Expand Down Expand Up @@ -115,7 +115,16 @@ public boolean isChested() {
}

public void setChested(boolean chested) {
boolean changed = this.chested != chested;
this.chested = chested;
this.setDataFlag(DATA_FLAGS, DATA_FLAG_CHESTED, chested);
if (changed) {
this.syncHorseInventoryChestSize();
}
}

@Override
protected int getChestSize() {
return this.chested ? 15 : 0;
}
}
Loading