Шаг 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:
@@ -82,6 +82,12 @@ public final class ProtocolTest {
|
||||
run("every packet has a name", new Runnable() {
|
||||
public void run() { testPacketNames(); }
|
||||
});
|
||||
run("the items match the original game", new Runnable() {
|
||||
public void run() { testItems(); }
|
||||
});
|
||||
run("an inventory survives the wire", new Runnable() {
|
||||
public void run() { testInventory(); }
|
||||
});
|
||||
run("a lying length cannot stall the heap", new Runnable() {
|
||||
public void run() { testLyingLength(); }
|
||||
});
|
||||
@@ -550,6 +556,98 @@ public final class ProtocolTest {
|
||||
Integer.valueOf(names.size()));
|
||||
}
|
||||
|
||||
private static void testItems() {
|
||||
// The numbers the game is built on. If these move, the game stops
|
||||
// matching its own rules, so they are pinned here.
|
||||
ItemType rock = ItemRegistry.require(ItemRegistry.ROCK);
|
||||
eq("the rock is called a rock", "Rock", rock.name);
|
||||
eq("the rock hits the head for twenty", Integer.valueOf(20),
|
||||
Integer.valueOf(rock.damage(ItemType.HEAD)));
|
||||
eq("and the torso for ten", Integer.valueOf(10),
|
||||
Integer.valueOf(rock.damage(ItemType.TORSO)));
|
||||
eq("the rock takes ten off a tree", Integer.valueOf(10),
|
||||
Integer.valueOf(rock.gather(ResourceClass.TREE).damage));
|
||||
eq("and five off an ore", Integer.valueOf(5),
|
||||
Integer.valueOf(rock.gather(ResourceClass.METAL).damage));
|
||||
eq("the rock is known from the start", Boolean.TRUE,
|
||||
Boolean.valueOf(rock.knownByDefault));
|
||||
eq("the rock needs no workbench", Integer.valueOf(0),
|
||||
Integer.valueOf(rock.bench));
|
||||
yes("the rock has a recipe", rock.recipe != null);
|
||||
eq("the rock is made of ten stones", Integer.valueOf(10),
|
||||
Integer.valueOf(rock.recipe.needs(ItemRegistry.STONE)));
|
||||
|
||||
ItemType hatchet = ItemRegistry.require(ItemRegistry.STONE_HATCHET);
|
||||
eq("the stone hatchet is an axe", ToolClass.AXE, hatchet.tool);
|
||||
eq("it takes twenty off a tree", Integer.valueOf(20),
|
||||
Integer.valueOf(hatchet.gather(ResourceClass.TREE).damage));
|
||||
|
||||
ItemType pickaxe = ItemRegistry.require(ItemRegistry.STONE_PICKAXE);
|
||||
eq("the stone pickaxe is a pickaxe", ToolClass.PICKAXE, pickaxe.tool);
|
||||
eq("it takes sixteen off an ore", Integer.valueOf(16),
|
||||
Integer.valueOf(pickaxe.gather(ResourceClass.METAL).damage));
|
||||
yes("and it does not cut trees", pickaxe.gather(ResourceClass.TREE) == null);
|
||||
|
||||
ItemType sword = ItemRegistry.require(ItemRegistry.SALVAGED_SWORD);
|
||||
eq("the sword is a sword", ToolClass.SWORD, sword.tool);
|
||||
yes("the sword gathers nothing but flesh", sword.gather(ResourceClass.TREE) == null
|
||||
&& sword.gather(ResourceClass.METAL) == null
|
||||
&& sword.gather(ResourceClass.FLESH) != null);
|
||||
|
||||
ItemType workbench2 = ItemRegistry.require(ItemRegistry.WORKBENCH_2);
|
||||
eq("the second workbench needs the first", Integer.valueOf(1),
|
||||
Integer.valueOf(workbench2.bench));
|
||||
ItemType workbench3 = ItemRegistry.require(ItemRegistry.WORKBENCH_3);
|
||||
eq("the third needs the second", Integer.valueOf(2),
|
||||
Integer.valueOf(workbench3.bench));
|
||||
|
||||
// the resources are gathered, not made
|
||||
ItemType wood = ItemRegistry.require(ItemRegistry.WOOD);
|
||||
yes("wood has no recipe", wood.recipe == null);
|
||||
yes("every id is different", ItemRegistry.all().size() > 20);
|
||||
}
|
||||
|
||||
private static void testInventory() {
|
||||
Packets.Slot[] slots = new Packets.Slot[6];
|
||||
slots[0] = new Packets.Slot(ItemRegistry.ROCK, 1, 100);
|
||||
slots[1] = new Packets.Slot(ItemRegistry.WOOD, 12, -1);
|
||||
slots[2] = new Packets.Slot(ItemRegistry.NOTHING, 0, -1);
|
||||
Packets.Inventory inventory = new Packets.Inventory(slots, 0);
|
||||
|
||||
Packets.Inventory back =
|
||||
Packets.Inventory.read(new Buf(inventory.write().toByteArray()));
|
||||
eq("the slots survive", Integer.valueOf(6),
|
||||
Integer.valueOf(back.slots.length));
|
||||
eq("the rock is in the first slot", Integer.valueOf(ItemRegistry.ROCK),
|
||||
Integer.valueOf(back.slots[0].item));
|
||||
eq("with one of it", Integer.valueOf(1),
|
||||
Integer.valueOf(back.slots[0].count));
|
||||
eq("and its life", Integer.valueOf(100),
|
||||
Integer.valueOf(back.slots[0].condition));
|
||||
eq("wood is in the second", Integer.valueOf(12),
|
||||
Integer.valueOf(back.slots[1].count));
|
||||
eq("an empty slot stays empty", Integer.valueOf(ItemRegistry.NOTHING),
|
||||
Integer.valueOf(back.slots[2].item));
|
||||
eq("the hand survives", Integer.valueOf(0), Integer.valueOf(back.held));
|
||||
|
||||
Packets.SlotChange change = new Packets.SlotChange(3, ItemRegistry.SCRAP, 7, -1);
|
||||
Packets.SlotChange changeBack =
|
||||
Packets.SlotChange.read(new Buf(change.write().toByteArray()));
|
||||
eq("the slot number survives", Integer.valueOf(3),
|
||||
Integer.valueOf(changeBack.slot));
|
||||
eq("the item survives", Integer.valueOf(ItemRegistry.SCRAP),
|
||||
Integer.valueOf(changeBack.item));
|
||||
eq("the count survives", Integer.valueOf(7),
|
||||
Integer.valueOf(changeBack.count));
|
||||
|
||||
// a stack carries the data that belongs to it
|
||||
ItemStack stack = ItemStack.of(ItemRegistry.ROCK, 1);
|
||||
stack.set("charged", "yes");
|
||||
eq("the data travels", "yes", stack.copy().get("charged"));
|
||||
eq("the copy is separate", Boolean.FALSE,
|
||||
Boolean.valueOf(stack == stack.copy()));
|
||||
}
|
||||
|
||||
private static void testLyingLength() {
|
||||
// announces a large but legal body, sends nothing, and keeps quiet.
|
||||
// The decoder must hold it without growing and without pretending the
|
||||
|
||||
Reference in New Issue
Block a user