Шаг A: предметы и инвентарь

Фундамент предметов по данным плана (срез 11.2025) и серверный инвентарь.

В протоколе (общий словарь для клиента и сервера):

- ToolClass, ResourceClass - что чем ломается и что где лежит.
- ItemType - определение предмета: класс инструмента, урон по четырем
  частям тела, прочность, Gather Rates по каждому классу ресурса
  (сколько снимает удар, разброс, сколько уходит прочности инструмента),
  bench - требуемый уровень верстака, knownByDefault - открыт ли рецепт
  с начала игры, admin - только ли админы сервера.
- ItemRegistry - таблица предметов. Числа из плана, каждое в одном
  месте, чтобы игра и план не разъехались.
- Recipe - ингредиенты, количество, базовое время крафта.
- ItemStack - тип, количество, остаток прочности и свои данные на стеке
  (как теги у плагина в Paper): стек - это то, что путешествует.

Пакеты: Inventory (весь инвентарь при входе) и SlotChange (один слот).

На сервере: Inventory - 30 слотов, хотбар 6, сервер владеет инвентарем и
сообщает клиенту, а не спрашивает. Стартовый набор - камень в первом
слоте и в руке.

Тесты: предметы закреплены числами из плана (камень бьёт в голову на 20,
снимает 10 дерева и 5 руды; кирка не рубит дерево; меч не ломает вообще),
инвентарь переживает провод, сумка наполняется и опустошается, полная
сумма говорит об этом.

Замечание: прогрессия крафта в Rust упирается в лутбоксы - скрап падает
только из них, а чертёж требует найденного предмета. Это отдельный
вопрос (п. 5.2 плана), он не блокирует предметы и инвентарь.
This commit is contained in:
sasheg dev
2026-10-21 17:43:30 +03:00
parent 56f0341373
commit 8488a34c80
15 changed files with 1324 additions and 1 deletions
@@ -102,6 +102,52 @@ public final class Packets {
*/
public static final int LAUNCHER_AUTH = 0x1A;
/**
* The whole inventory, sent when the player arrives.
*
* The server is the only one who decides what is in the slots, so the
* client is told rather than asked.
*/
public static final int INVENTORY = 0x1C;
/** One slot of the inventory, when it changes. */
public static final int INVENTORY_SLOT = 0x1D;
/**
* One slot of the inventory and what is in it now.
*
* The slot number is here because the client has to know which one to
* redraw, and the contents because it has no way of guessing them.
*/
public static final class SlotChange implements Packet {
public final int slot;
public final int item;
public final int count;
public final int condition;
public SlotChange(int slot, int item, int count, int condition) {
this.slot = slot;
this.item = item;
this.count = count;
this.condition = condition;
}
public Buf write() {
Buf buf = new Buf(24);
buf.writeVarInt(this.slot);
buf.writeVarInt(this.item);
buf.writeVarInt(this.count);
buf.writeVarInt(this.condition);
return buf;
}
public static SlotChange read(Buf in) {
return new SlotChange(in.readVarInt(), in.readVarInt(), in.readVarInt(),
in.readVarInt());
}
}
/**
* The player's game mode, changed while the player is in the world.
*
@@ -796,6 +842,80 @@ public final class Packets {
}
}
/**
* One slot of an inventory.
*
* The empty slot is a stack of nothing, so there is no special case: an
* empty slot is one with the id of nothing and a count of zero.
*/
public static final class Slot {
public final int item;
public final int count;
public final int condition;
public Slot(int item, int count, int condition) {
this.item = item;
this.count = count;
this.condition = condition;
}
public static Slot of(ItemStack stack) {
if (stack == null || stack.isEmpty()) {
return new Slot(ItemRegistry.NOTHING, 0, -1);
}
return new Slot(stack.item, stack.count, stack.condition);
}
public void write(Buf buf) {
buf.writeVarInt(this.item);
buf.writeVarInt(this.count);
buf.writeVarInt(this.condition);
}
public static Slot read(Buf in) {
return new Slot(in.readVarInt(), in.readVarInt(), in.readVarInt());
}
}
/** The whole inventory of a player. */
public static final class Inventory implements Packet {
public final Slot[] slots;
/** The slot the player is holding. */
public final int held;
public Inventory(Slot[] slots, int held) {
this.slots = slots;
this.held = held;
}
public Buf write() {
Buf buf = new Buf(16 + this.slots.length * 12);
buf.writeVarInt(this.held);
buf.writeVarInt(this.slots.length);
for (int i = 0; i < this.slots.length; i++) {
Slot slot = this.slots[i] == null ? Slot.of(null) : this.slots[i];
slot.write(buf);
}
return buf;
}
public static Inventory read(Buf in) {
int held = in.readVarInt();
int count = in.readVarInt();
if (count < 0 || count > 1024) {
throw new ProtocolException("an inventory of " + count + " slots is not real");
}
Slot[] slots = new Slot[count];
for (int i = 0; i < count; i++) {
slots[i] = Slot.read(in);
}
return new Inventory(slots, held);
}
}
/**
* The launcher's session, as the game server sees it.
*