Skip to content

Commit 5f0ca88

Browse files
authored
bugfix: Fixed crashing and bugs on 1.9, 1.9.1, 1.9.2, 1.16.2, 1.21.10 and Player Inventory
bugfix: Fixed crashing and bugs on 1.9, 1.9.1, 1.9.2, 1.16.2, 1.21.10 and Player Inventory
2 parents c4df73a + f25fe53 commit 5f0ca88

13 files changed

Lines changed: 905 additions & 62 deletions

File tree

.skills/mcc-integration-testing/SKILL.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,37 @@ Use this for TPS, movement-cadence, or packet-cadence work:
115115

116116
Run them against a real server with a temp config and summarize counts from the captured logs.
117117

118+
### 4. Full inventory regression sweep
119+
120+
Use this when touching inventory snapshots, player/container slot sync, creative inventory, item-slot serialization, packet palettes, game-mode updates, or block-use paths that open containers:
121+
122+
```bash
123+
tools/run-inventory-full-sweep.sh --versions "1.21.10 1.21.11"
124+
```
125+
126+
Default coverage includes:
127+
128+
- player inventory listing and inventory discovery
129+
- creative give/delete
130+
- inventory search
131+
- player right/left click stack split and merge
132+
- player drop one and drop all
133+
- chest open via `useblock`
134+
- container listing and close
135+
- mirrored player slots in container windows
136+
- shift-click and shift-right-click transfer
137+
- container right/left click, cursor stack, drop one, and drop all
138+
- creative middle-click command path
139+
- log scan for packet parse failures, queue-empty crashes, unhandled exceptions, and disconnects
140+
141+
The script writes `summary.tsv` under `RUN_ROOT` and per-version logs under `/tmp/mcc-debug/inventory-full-<version>/mcc-debug.log`.
142+
143+
When a matrix has existing PASS rows, do not rerun them unless a later code change affects that row or the user asks for a full rerun. Derive remaining rows from summaries:
144+
145+
```bash
146+
awk 'FNR>1 && $2=="PASS" {print $1}' /tmp/mcc-inventory-full-sweep/*/summary.tsv | sort -V | uniq
147+
```
148+
118149
## Preconditions
119150

120151
Before running any scenario:
@@ -165,6 +196,8 @@ Optionally override the login name with the fourth argument to the config helper
165196
- summarize the latest full-spectrum run
166197
- `tools/run-creative-e2e.sh`
167198
- ordered creative-mode E2E regression scenario
199+
- `tools/run-inventory-full-sweep.sh`
200+
- full inventory command/API sweep across one or more versions
168201

169202
## Evidence Discipline
170203

@@ -224,3 +257,7 @@ Always summarize:
224257
- If a test assertion fails, inspect the real MCC output before changing the code or weakening the assertion.
225258
- If an older server behaves oddly on Linux, check `use-native-transport=false` in `server.properties`.
226259
- If a matrix row fails before producing `mcc.log` or a command transcript, treat it as a harness failure, fix the environment, and rerun that row before drawing product conclusions.
260+
- If creative inventory commands report "You must be in Creative gamemode" after RCON switched the player, inspect game-mode update parsing before assuming creative inventory is broken. Modern servers can update local game mode through game event reason `3`.
261+
- If an inventory row crashes with `Queue empty` or `Failed to process incoming packet`, inspect packet palette routing before changing inventory code. A single shifted packet ID can make a healthy inventory feature look broken.
262+
- For chest-open failures, separate product and harness causes. The player may be standing inside the chest or suffocating on older servers. Stand beside the chest, put a floor under the player, and retry `useblock`.
263+
- For shared local servers, a `Done` log line does not prove RCON is ready. Retry setup commands and verify the actual RCON port from `server.properties`.

.skills/mcc-version-adaptation/SKILL.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,43 @@ When packet changes are detected:
157157
2. Create new `PacketPaletteXXX.cs` based on the previous one, adjusting IDs
158158
3. Update `PacketType18Handler.cs` routing
159159

160+
Use scriptable comparisons instead of eyeballing long packet tables. The packet ID is the registration index in `GameProtocols.java`:
161+
162+
```bash
163+
python3 - <<'PY'
164+
import re
165+
for ver in ["1.21.10", "1.21.11", "26.1"]:
166+
path=f"MinecraftOfficial/{ver}-decompiled/net/minecraft/network/protocol/game/GameProtocols.java"
167+
text=open(path).read()
168+
start=text.index("CLIENTBOUND_TEMPLATE")
169+
names=[m.group(1) for m in re.finditer(r"\.addPacket\(([^,]+),", text[start:])]
170+
print("==", ver, len(names))
171+
for i, name in enumerate(names):
172+
print(f"0x{i:02X}", name)
173+
PY
174+
```
175+
176+
For focused diffs:
177+
178+
```bash
179+
python3 - <<'PY'
180+
import re
181+
def packets(ver, marker):
182+
text=open(f"MinecraftOfficial/{ver}-decompiled/net/minecraft/network/protocol/game/GameProtocols.java").read()
183+
start=text.index(marker)
184+
return [m.group(1) for m in re.finditer(r"\.addPacket\(([^,]+),", text[start:])]
185+
left, right = "1.21.10", "1.21.11"
186+
a, b = packets(left, "CLIENTBOUND_TEMPLATE"), packets(right, "CLIENTBOUND_TEMPLATE")
187+
for i in range(max(len(a), len(b))):
188+
x = a[i] if i < len(a) else "<none>"
189+
y = b[i] if i < len(b) else "<none>"
190+
if x != y:
191+
print(f"0x{i:02X}: {left}={x} | {right}={y}")
192+
PY
193+
```
194+
195+
Do the same for `SERVERBOUND_TEMPLATE`. Clientbound and serverbound can change independently. Do not inherit a newer palette just because one side looks similar. For example, `1.21.11` used the same play packet order as `1.21.9/1.21.10` for the tested inventory path, while `26.1` had additional shifts.
196+
160197
## Step 5: Check Variant Encoding Changes
161198

162199
For entity types that use variant serializers (Cat, Wolf, Frog, Painting), check if the codec changed between versions by inspecting:

MinecraftClient/Commands/Useblock.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using Brigadier.NET;
23
using Brigadier.NET.Builder;
34
using MinecraftClient.CommandHandler;
@@ -53,8 +54,27 @@ private int UseBlockAtLocation(CmdResult r, Location block, Hand hand)
5354
Location current = handler.GetCurrentLocation();
5455
block = block.ToAbsolute(current).ToFloor();
5556
Location blockCenter = block.ToCenter();
56-
bool res = handler.PlaceBlock(block, Direction.Down, hand, lookAtBlock: true);
57+
bool res = handler.PlaceBlock(block, GetFaceNearestPlayer(current, blockCenter), hand, lookAtBlock: true);
5758
return r.SetAndReturn(string.Format(Translations.cmd_useblock_use, blockCenter.X, blockCenter.Y, blockCenter.Z, res ? "succeeded" : "failed"), res);
5859
}
60+
61+
private static Direction GetFaceNearestPlayer(Location playerLocation, Location blockCenter)
62+
{
63+
double dx = playerLocation.X - blockCenter.X;
64+
double dy = playerLocation.Y - blockCenter.Y;
65+
double dz = playerLocation.Z - blockCenter.Z;
66+
67+
double absX = Math.Abs(dx);
68+
double absY = Math.Abs(dy);
69+
double absZ = Math.Abs(dz);
70+
71+
if (absX >= absY && absX >= absZ)
72+
return dx >= 0 ? Direction.East : Direction.West;
73+
74+
if (absY >= absZ)
75+
return dy >= 0 ? Direction.Up : Direction.Down;
76+
77+
return dz >= 0 ? Direction.South : Direction.North;
78+
}
5979
}
6080
}

MinecraftClient/Mapping/World.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,16 @@ public class World
6969
/// <param name="registryCodec">Registry Codec nbt data</param>
7070
public static void StoreDimensionList(Dictionary<string, object> registryCodec)
7171
{
72-
var dimensionListNbt = (object[])(((Dictionary<string, object>)registryCodec["minecraft:dimension_type"])["value"]);
72+
const string namespacedDimensionTypeKey = "minecraft:dimension_type";
73+
const string legacyDimensionTypeKey = "dimension_type";
74+
75+
if (!registryCodec.TryGetValue(namespacedDimensionTypeKey, out var dimensionTypeRegistry)
76+
&& !registryCodec.TryGetValue(legacyDimensionTypeKey, out dimensionTypeRegistry))
77+
{
78+
return;
79+
}
80+
81+
var dimensionListNbt = (object[])(((Dictionary<string, object>)dimensionTypeRegistry)["value"]);
7382
foreach (var (dimensionName, dimensionType) in from Dictionary<string, object> dimensionNbt in dimensionListNbt
7483
let dimensionName = (string)dimensionNbt["name"]
7584
let dimensionType = (Dictionary<string, object>)dimensionNbt["element"]
@@ -333,11 +342,40 @@ public static void SetDimension(string name)
333342
return; // Dimension found with prefixed name
334343
}
335344
}
345+
else
346+
{
347+
string unprefixedName = name["minecraft:".Length..];
348+
if (dimensionList.TryGetValue(unprefixedName, out dimension))
349+
{
350+
curDimension = dimension;
351+
return;
352+
}
353+
}
354+
355+
if (TryStoreDefaultVanillaDimension(name)
356+
&& dimensionList.TryGetValue(name, out dimension))
357+
{
358+
curDimension = dimension;
359+
return;
360+
}
336361

337362
// If still not found, dimension does not exist
338363
throw new KeyNotFoundException($"Dimension '{name}' not found in dimensions dictionary.");
339364
}
340365

366+
private static bool TryStoreDefaultVanillaDimension(string name)
367+
{
368+
var normalizedName = name.StartsWith("minecraft:")
369+
? name
370+
: "minecraft:" + name;
371+
372+
if (normalizedName is not ("minecraft:overworld" or "minecraft:the_nether" or "minecraft:the_end"))
373+
return false;
374+
375+
StoreOneDimension(name, new Dictionary<string, object>());
376+
return true;
377+
}
378+
341379

342380

343381

MinecraftClient/McClient.cs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2022,6 +2022,90 @@ private static bool IsServerManagedOutputSlot(Container inventory, int slotId)
20222022
};
20232023
}
20242024

2025+
private static bool TryGetMirroredPlayerInventoryRange(Container inventory, out int firstWindowSlot, out int lastWindowSlot)
2026+
{
2027+
firstWindowSlot = -1;
2028+
lastWindowSlot = -1;
2029+
2030+
if (inventory.Type == ContainerType.PlayerInventory)
2031+
return false;
2032+
2033+
const int mirroredPlayerInventorySlotCount = 36;
2034+
int slotCount = inventory.Type.SlotCount();
2035+
if (slotCount < mirroredPlayerInventorySlotCount)
2036+
return false;
2037+
2038+
firstWindowSlot = slotCount - mirroredPlayerInventorySlotCount;
2039+
lastWindowSlot = slotCount - 1;
2040+
return true;
2041+
}
2042+
2043+
private static bool TryGetMirroredPlayerInventorySlot(Container inventory, int windowSlot, out int playerInventorySlot)
2044+
{
2045+
playerInventorySlot = -1;
2046+
2047+
if (!TryGetMirroredPlayerInventoryRange(inventory, out int firstWindowSlot, out int lastWindowSlot))
2048+
return false;
2049+
2050+
if (windowSlot < firstWindowSlot || windowSlot > lastWindowSlot)
2051+
return false;
2052+
2053+
playerInventorySlot = windowSlot - firstWindowSlot + 9;
2054+
return true;
2055+
}
2056+
2057+
private static bool AreSameInventorySlot(Item? left, Item? right)
2058+
{
2059+
if (left is null || left.IsEmpty)
2060+
return right is null || right.IsEmpty;
2061+
if (right is null || right.IsEmpty)
2062+
return false;
2063+
2064+
return left.Type == right.Type
2065+
&& left.Count == right.Count
2066+
&& left.Data == right.Data
2067+
&& ReferenceEquals(left.NBT, right.NBT)
2068+
&& ReferenceEquals(left.Components, right.Components);
2069+
}
2070+
2071+
private bool SetPlayerInventorySlot(int playerInventorySlot, Item? item)
2072+
{
2073+
if (!inventories.TryGetValue(0, out Container? playerInventory))
2074+
return false;
2075+
2076+
playerInventory.Items.TryGetValue(playerInventorySlot, out Item? previousItem);
2077+
if (AreSameInventorySlot(previousItem, item))
2078+
return false;
2079+
2080+
if (item is null || item.IsEmpty)
2081+
playerInventory.Items.Remove(playerInventorySlot);
2082+
else
2083+
playerInventory.Items[playerInventorySlot] = item;
2084+
2085+
return true;
2086+
}
2087+
2088+
private bool SyncPlayerInventorySlotFromWindow(Container inventory, int windowSlot)
2089+
{
2090+
if (!TryGetMirroredPlayerInventorySlot(inventory, windowSlot, out int playerInventorySlot))
2091+
return false;
2092+
2093+
inventory.Items.TryGetValue(windowSlot, out Item? item);
2094+
return SetPlayerInventorySlot(playerInventorySlot, item);
2095+
}
2096+
2097+
private bool SyncPlayerInventorySlotsFromWindow(Container inventory)
2098+
{
2099+
if (!TryGetMirroredPlayerInventoryRange(inventory, out int firstWindowSlot, out int lastWindowSlot))
2100+
return false;
2101+
2102+
bool changed = false;
2103+
for (int windowSlot = firstWindowSlot; windowSlot <= lastWindowSlot; windowSlot++)
2104+
changed |= SyncPlayerInventorySlotFromWindow(inventory, windowSlot);
2105+
2106+
return changed;
2107+
}
2108+
20252109
/// <summary>
20262110
/// Click a slot in the specified window
20272111
/// </summary>
@@ -2748,6 +2832,8 @@ public bool DoWindowAction(int windowId, int slotId, WindowActionType action)
27482832
changedSlots.Add(new Tuple<short, Item?>((short)slotId, null));
27492833
break;
27502834
}
2835+
2836+
SyncPlayerInventorySlotsFromWindow(inventory);
27512837
}
27522838

27532839
return handler.SendWindowAction(windowId, slotId, action, item, changedSlots, inventories[windowId].StateID);
@@ -2764,7 +2850,16 @@ public bool DoWindowAction(int windowId, int slotId, WindowActionType action)
27642850
/// <returns>TRUE if item given successfully</returns>
27652851
public bool DoCreativeGive(int slot, ItemType itemType, int count, Dictionary<string, object>? nbt = null)
27662852
{
2767-
return InvokeOnMainThread(() => handler.SendCreativeInventoryAction(slot, itemType, count, nbt));
2853+
return InvokeOnMainThread(() =>
2854+
{
2855+
if (!handler.SendCreativeInventoryAction(slot, itemType, count, nbt))
2856+
return false;
2857+
2858+
if (slot is >= 1 and <= 45)
2859+
SetPlayerInventorySlot(slot, new Item(itemType, count, nbt));
2860+
2861+
return true;
2862+
});
27682863
}
27692864

27702865
/// <summary>
@@ -3780,6 +3875,9 @@ public void OnWindowItems(byte inventoryID, Dictionary<int, Inventory.Item> item
37803875
{
37813876
inventories[inventoryID].Items = itemList;
37823877
inventories[inventoryID].StateID = stateId;
3878+
bool playerInventoryChanged = SyncPlayerInventorySlotsFromWindow(inventories[inventoryID]);
3879+
if (playerInventoryChanged)
3880+
DispatchBotEvent(bot => bot.OnInventoryUpdate(0));
37833881
DispatchBotEvent(bot => bot.OnInventoryUpdate(inventoryID));
37843882
}
37853883
}
@@ -3820,6 +3918,9 @@ public void OnSetSlot(byte inventoryID, short slotID, Item? item, int stateId)
38203918
inventories[inventoryID].Items.Remove(slotID);
38213919
}
38223920
else inventories[inventoryID].Items[slotID] = item;
3921+
3922+
if (SyncPlayerInventorySlotFromWindow(inventories[inventoryID], slotID))
3923+
DispatchBotEvent(bot => bot.OnInventoryUpdate(0));
38233924
}
38243925
}
38253926
DispatchBotEvent(bot => bot.OnInventoryUpdate(inventoryID));
@@ -4676,6 +4777,9 @@ public void OnGameEvent(byte reason, float value)
46764777
{
46774778
switch (reason)
46784779
{
4780+
case 3:
4781+
OnGamemodeUpdate(Guid.Empty, (int)value);
4782+
break;
46794783
case 7:
46804784
DispatchBotEvent(bot => bot.OnRainLevelChange(value));
46814785
break;

MinecraftClient/MinecraftClient.csproj

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -59,32 +59,7 @@
5959
<PackageReference Include="Telegram.Bot" Version="22.9.5.3" />
6060
</ItemGroup>
6161
<ItemGroup>
62-
<Compile Remove="config\ChatBots\AutoLeaveOnLowHp.cs" />
63-
<Compile Remove="config\ChatBots\AutoLook.cs" />
64-
<Compile Remove="config\ChatBots\AutoTree.cs" />
65-
<Compile Remove="config\ChatBots\CobblestoneMiner.cs" />
66-
<Compile Remove="config\ChatBots\DiscordWebhook.cs" />
67-
<Compile Remove="config\ChatBots\EntityCount.cs" />
68-
<Compile Remove="config\ChatBots\OreMiner.cs" />
69-
<Compile Remove="config\ChatBots\PayKassa.cs" />
70-
<Compile Remove="config\ChatBots\QIWIAPI.cs" />
71-
<Compile Remove="config\ChatBots\SugarCaneMiner.cs" />
72-
<Compile Remove="config\ChatBots\TreeFarmer.cs" />
73-
<Compile Remove="config\ChatBots\VkMessager.cs" />
74-
<Compile Remove="config\ChatBots\WebSocketBot.cs" />
75-
<Compile Remove="config\sample-script-extended.cs" />
76-
<Compile Remove="config\sample-script-packet-capture.cs" />
77-
<Compile Remove="config\sample-script-pm-forwarder.cs" />
78-
<Compile Remove="config\sample-script-random-command.cs" />
79-
<Compile Remove="config\sample-script-tick-counter.cs" />
80-
<Compile Remove="config\sample-script-with-chatbot.cs" />
81-
<Compile Remove="config\sample-script-with-http-request.cs" />
82-
<Compile Remove="config\sample-script-with-task.cs" />
83-
<Compile Remove="config\sample-script-with-world-access.cs" />
84-
<Compile Remove="config\sample-script-packet-capture.cs" />
85-
<Compile Remove="config\sample-script.cs" />
86-
<Compile Remove="config\ChatBots\MineCube.cs" />
87-
<Compile Remove="config\ChatBots\SugarCaneFarmer.cs" />
62+
<Compile Remove="config\**\*.cs" />
8863
<Compile Remove="Mapping\VillagerInfo.cs" />
8964
</ItemGroup>
9065
<ItemGroup>

0 commit comments

Comments
 (0)