Мережа в Minecraft використовується для того, щоб клієнт і сервер могли спілкуватися один з одним. Мережа– це широка тема, тому ця сторінка поділена на кілька категорій.
Чому мережа важлива?
Пакети - це основна концепція мережі в Minecraft. Пакети складаються з довільних даних, які можуть надсилатися від сервера до клієнта і навпаки. Ознайомтеся з наведеною нижче схемою, яка наочно демонструє мережеву архітектуру в Fabric:

Зверніть увагу, що пакети є мостом між сервером і клієнтом; це тому, що майже все, що ви робите в грі, певним чином включає мережу, знаєте ви це чи ні. Наприклад, коли ви надсилаєте повідомлення чату, на сервер надсилається пакет із вмістом. Потім сервер надсилає ще один пакет усім іншим клієнтам із вашим повідомленням.
Пам’ятайте про одну важливу річ: сервер завжди працює, навіть у грі наодинці та LAN. Пакети все ще використовуються для обміну даними клієнту і сервера, навіть якщо ніхто інший не грає з вами. Коли говорять про сторони в мережі, використовуються терміни «логічний клієнт» і «логічний сервер». Інтегрований сервер для гри наодинці/LAN та виділений сервер є логічними серверами, але лише виділений сервер можна вважати фізичним сервером.
Коли стан між клієнтом і сервером не синхронізовано, ви можете зіткнутися з проблемами, через які сервер або інші клієнти не погоджуються з тим, що робить інший клієнт. Це часто називають «десинхронізацією». Під час написання власного моду вам може знадобитися надіслати пакет даних, щоб синхронізувати стан сервера та всіх клієнтів.
Вступ до мережі
Визначення корисного навантаження
INFO
Корисне навантаження — це дані, які надсилаються в пакеті.
Це можна зробити, створивши Java Record з параметром BlockPos, який реалізує CustomPayload.
java
public record SummonLightningS2CPayload(BlockPos pos) implements CustomPayload {
public static final Identifier SUMMON_LIGHTNING_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "summon_lightning");
public static final CustomPayload.Id<SummonLightningS2CPayload> ID = new CustomPayload.Id<>(SUMMON_LIGHTNING_PAYLOAD_ID);
public static final PacketCodec<RegistryByteBuf, SummonLightningS2CPayload> CODEC = PacketCodec.tuple(BlockPos.PACKET_CODEC, SummonLightningS2CPayload::pos, SummonLightningS2CPayload::new);
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Водночас ми визначили:
Ідентифікатор, який використовується для ідентифікації корисного навантаження нашого пакета. Для цього прикладу нашим ідентифікатором будеfabric-docs-reference:summon_lightning.
java
public static final Identifier SUMMON_LIGHTNING_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "summon_lightning");1
- Загальнодоступний статичний екземпляр
CustomPayload.Idдля унікальної ідентифікації цього спеціального корисного навантаження. Ми будемо посилатися на це ID як у нашому загальному, так і в клієнтському коді.
java
public static final CustomPayload.Id<SummonLightningS2CPayload> ID = new CustomPayload.Id<>(SUMMON_LIGHTNING_PAYLOAD_ID);1
- Загальнодоступний статичний екземпляр
PacketCodec, щоб гра знала, як серіалізувати/десеріалізувати вміст пакету.
java
public static final PacketCodec<RegistryByteBuf, SummonLightningS2CPayload> CODEC = PacketCodec.tuple(BlockPos.PACKET_CODEC, SummonLightningS2CPayload::pos, SummonLightningS2CPayload::new);1
Ми також замінили getId, щоб повернути наш ідентифікатор корисного навантаження.
java
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}1
2
3
4
2
3
4
Реєстрація корисного навантаження
Перш ніж надіслати пакет із нашим власним корисним навантаженням, нам потрібно його зареєструвати.
INFO
S2C і C2S — два загальні суфікси, які означають Server-to-Client і Client-to-Server відповідно.
Це можна зробити в нашому загальному ініціалізаторі за допомогою PayloadTypeRegistry.playS2C().register, який приймає CustomPayload.Id і PacketCodec.
java
PayloadTypeRegistry.playS2C().register(SummonLightningS2CPayload.ID, SummonLightningS2CPayload.CODEC);1
Подібний метод існує для реєстрації корисних даних клієнт-сервер: PayloadTypeRegistry.playC2S().register.
Надсилання пакета клієнту
Щоб надіслати пакет із нашим власним корисним навантаженням, ми можемо використати ServerPlayNetworking.send, який приймає ServerPlayerEntity і CustomPayload.
Почнемо зі створення нашого предмета, блискавичного татера. Ви можете замінити use, щоб активувати дію під час використання елемента. У цьому випадку надішлімо пакети гравцям у світі серверів.
java
public class LightningTaterItem extends Item {
public LightningTaterItem(Settings settings) {
super(settings);
}
@Override
public ActionResult use(World world, PlayerEntity user, Hand hand) {
if (world.isClient()) {
return ActionResult.PASS;
}
SummonLightningS2CPayload payload = new SummonLightningS2CPayload(user.getBlockPos());
for (ServerPlayerEntity player : PlayerLookup.world((ServerWorld) world)) {
ServerPlayNetworking.send(player, payload);
}
return ActionResult.SUCCESS;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Розгляньмо код вище.
Ми надсилаємо пакети лише тоді, коли дію розпочато на сервері, повертаючись раніше з перевіркою isClient:
java
if (world.isClient()) {
return ActionResult.PASS;
}1
2
3
2
3
Ми створюємо екземпляр корисного навантаження з позицією користувача:
java
SummonLightningS2CPayload payload = new SummonLightningS2CPayload(user.getBlockPos());1
Нарешті, ми отримуємо гравців у світі сервера через PlayerLookup і надсилаємо пакет кожному гравцеві.
java
for (ServerPlayerEntity player : PlayerLookup.world((ServerWorld) world)) {
ServerPlayNetworking.send(player, payload);
}1
2
3
2
3
INFO
API Fabric надає PlayerLookup, набір допоміжних функцій, які шукатимуть гравців на сервері.
Для опису функціональності цих методів часто використовується термін «відстеження». Це означає, що сутність або блок на сервері відомі клієнту гравця (в межах їх видимості), і сутність або блокова сутність повинні повідомити відстеження змін клієнтів.
Відстеження є важливою концепцією для ефективної роботи в мережі, щоб лише необхідні гравці отримували повідомлення про зміни шляхом надсилання пакетів.
Отримання пакета на клієнті
Щоб отримати пакет, надісланий із сервера на клієнті, вам потрібно вказати, як ви будете обробляти вхідний пакет.
Це можна зробити в ініціалізаторі клієнта, викликавши ClientPlayNetworking.registerGlobalReceiver і передавши CustomPayload.Id і PlayPayloadHandler, який є функціональним інтерфейсом.
У цьому випадку ми визначимо дію, яка буде запускатися в реалізації реалізації PlayPayloadHandler (як лямбда-вираз).
java
ClientPlayNetworking.registerGlobalReceiver(SummonLightningS2CPayload.ID, (payload, context) -> {
ClientWorld world = context.client().world;
if (world == null) {
return;
}
BlockPos lightningPos = payload.pos();
LightningEntity entity = EntityType.LIGHTNING_BOLT.create(world, SpawnReason.TRIGGERED);
if (entity != null) {
entity.setPosition(lightningPos.getX(), lightningPos.getY(), lightningPos.getZ());
world.addEntity(entity);
}
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Розгляньмо код вище.
Ми можемо отримати доступ до даних із нашого корисного навантаження, викликавши методи отримання запису. У цьому випадку payload.pos(). Який потім можна використовувати для отримання позицій "x", "y" і "z".
java
BlockPos lightningPos = payload.pos();1
Нарешті ми створюємо LightningEntity і додаємо його до світу.
java
LightningEntity entity = EntityType.LIGHTNING_BOLT.create(world, SpawnReason.TRIGGERED);
if (entity != null) {
entity.setPosition(lightningPos.getX(), lightningPos.getY(), lightningPos.getZ());
world.addEntity(entity);
}1
2
3
4
5
6
2
3
4
5
6
Тепер, якщо ви додасте цей мод на сервер і коли гравець використовує наш блискавичний татер, кожен гравець побачить блискавку вражаючи позицію користувача.
Надсилання пакета на сервер
Подібно до надсилання пакета клієнту, ми починаємо зі створення спеціального корисного навантаження. Цього разу, коли гравець використовує отруйну картоплю на живій істоті, ми просимо сервер застосувати до неї ефект сяяння.
java
public record GiveGlowingEffectC2SPayload(int entityId) implements CustomPayload {
public static final Identifier GIVE_GLOWING_EFFECT_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "give_glowing_effect");
public static final CustomPayload.Id<GiveGlowingEffectC2SPayload> ID = new CustomPayload.Id<>(GIVE_GLOWING_EFFECT_PAYLOAD_ID);
public static final PacketCodec<RegistryByteBuf, GiveGlowingEffectC2SPayload> CODEC = PacketCodec.tuple(PacketCodecs.INTEGER, GiveGlowingEffectC2SPayload::entityId, GiveGlowingEffectC2SPayload::new);
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Ми передаємо відповідний кодек разом із посиланням на метод, щоб отримати значення із запису для створення цього кодека.
Потім ми реєструємо наше корисне навантаження в нашому загальному ініціалізаторі. Однак цього разу як корисне навантаження клієнт-сервер за допомогою PayloadTypeRegistry.playC2S().register.
java
PayloadTypeRegistry.playC2S().register(GiveGlowingEffectC2SPayload.ID, GiveGlowingEffectC2SPayload.CODEC);1
Щоб надіслати пакет, додаймо дію, коли гравець використовує отруйну картоплю. Ми будемо використовувати UseEntityCallback подія, щоб зберегти речі стисло.
Ми реєструємо подію в нашому ініціалізаторі клієнта та використаємо isClient(), щоб гарантувати, що дія лише запускається на логічному клієнті.
java
UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
if (!world.isClient()) {
return ActionResult.PASS;
}
ItemStack usedItemStack = player.getStackInHand(hand);
if (entity instanceof LivingEntity && usedItemStack.isOf(Items.POISONOUS_POTATO) && hand == Hand.MAIN_HAND) {
GiveGlowingEffectC2SPayload payload = new GiveGlowingEffectC2SPayload(hitResult.getEntity().getId());
ClientPlayNetworking.send(payload);
return ActionResult.SUCCESS;
}
return ActionResult.PASS;
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Ми створимо екземпляр нашого GiveGlowingEffectC2SPayload з необхідними аргументами. У цьому випадку ідентифікатор мережі з цільової сутності.
java
GiveGlowingEffectC2SPayload payload = new GiveGlowingEffectC2SPayload(hitResult.getEntity().getId());1
Нарешті, ми надсилаємо пакет на сервер, викликаючи ClientPlayNetworking.send з примірником нашого GiveGlowingEffectC2SPayload.
java
ClientPlayNetworking.send(payload);1
Отримання пакета на сервері
Це можна зробити в загальному ініціалізаторі, викликавши ServerPlayNetworking.registerGlobalReceiver і передавши CustomPayload.Id і PlayPayloadHandler.
java
ServerPlayNetworking.registerGlobalReceiver(GiveGlowingEffectC2SPayload.ID, (payload, context) -> {
Entity entity = context.player().getWorld().getEntityById(payload.entityId());
if (entity instanceof LivingEntity livingEntity && livingEntity.isInRange(context.player(), 5)) {
livingEntity.addStatusEffect(new StatusEffectInstance(StatusEffects.GLOWING, 100));
}
});1
2
3
4
5
6
7
2
3
4
5
6
7
INFO
Важливо, щоб ви перевірили вміст пакета на стороні сервера.
У цьому випадку ми перевіряємо, чи існує сутність, на основі її ідентифікатора мережі.
java
Entity entity = context.player().getWorld().getEntityById(payload.entityId());1
Крім того, цільова сутність має бути живою істотою, і ми обмежуємо діапазон цільової сутності від гравця до 5.
java
if (entity instanceof LivingEntity livingEntity && livingEntity.isInRange(context.player(), 5)) {1
Тепер, коли будь-який гравець намагатиметься використати отруйну картоплю на живій істоті, до нього буде застосовано ефект світіння.
















