- Поддерживаемые версии
- 1.19
Предисловие
(Данный руководство является моим личным "творением" и был написан на англоязычный форум. Я решил перевести его на русский язык и поделиться с ним с моими собратьями из СНГ.)
[Протестировано и работает на spigot-1.19.4 и ProtocolLib 5.1.0]
Недавно я начал учиться работать с ProtocolLib. И первое, что я хотел сделать, это npc. Хорошего руководства я не нашел, использую информацию из вики и что-то с форума. Именно поэтому я делаю этот ресурс.
Как мы можем создать простого NPC
Чтобы создать NPC, нам нужно отправить два пакета: пакет с информацией об игроке и именованный объект. Сначала мы отправляем пакет с информацией об игроке, потому что, если мы не отправим этот пакет первым, NPC не появится.
Пакет информации об игроке содержит информацию об игровом профиле игрока, задержке, игровом режиме и компоненте чата.
Насколько нам известно, игровой профиль игрока содержит отображаемое имя игрока, uuid и скин.
Давайте начнем отправлять пакет с информацией об игроке.
(Извините, если я упустил что-то важное или не ясно выразился, напишите мне и я все исправлю при необходимости)
Создание нашего НПС
Теперь нам нужно создать метод для создания NPC.
Создаём новый класс, который будет отвечать за спавн нашего НПС.
Укладываем весь процесс создания НПС в один единый класс, для удобства.
Спавним НПС, когда игрок заходит на сервер:
НПС всегда смотрит на вас
Вероятнее всего, вы видели, как на большинстве серверов, нпс не отводит от вас взгляда. Сейчас, я покажу вам, как реализовать данную функцию с нашим нпс.
Но ясное дело, это не всё. Нам необходимо доделать начатое в PlayerMoveEvent.
Нажатие на НПС
А что если, мы хотим иметь возможность нажать на нашего игрока и запускать к примеру SkyWars или открывать игроку GUI? Сейчас покажу, как это сделать.
В данном классе, мы создаём прослушиватель, который отслеживает все взаимодействия игрока с сущностью.
На всякий, дам вам так же свой основной Main.class и весь проект который я залил на Github.
Я надеюсь, что объяснил всё понятно и ничего не пропустил. Я уверен, что кому-то данный ресурс среди русскоязычной аудитории Spigot будет полезен. В случае, если я допустил какие-то ошибки или оплошности - пишите, я всё исправлю и помогу в вашей проблеме. Спасибо за внимание! (Админам/модерам - пожизненный запас чая и тортиков)
(Данный руководство является моим личным "творением" и был написан на англоязычный форум. Я решил перевести его на русский язык и поделиться с ним с моими собратьями из СНГ.)
[Протестировано и работает на spigot-1.19.4 и ProtocolLib 5.1.0]
Недавно я начал учиться работать с ProtocolLib. И первое, что я хотел сделать, это npc. Хорошего руководства я не нашел, использую информацию из вики и что-то с форума. Именно поэтому я делаю этот ресурс.
Как мы можем создать простого NPC
Чтобы создать NPC, нам нужно отправить два пакета: пакет с информацией об игроке и именованный объект. Сначала мы отправляем пакет с информацией об игроке, потому что, если мы не отправим этот пакет первым, NPC не появится.
Пакет информации об игроке содержит информацию об игровом профиле игрока, задержке, игровом режиме и компоненте чата.
Насколько нам известно, игровой профиль игрока содержит отображаемое имя игрока, uuid и скин.
Давайте начнем отправлять пакет с информацией об игроке.
(Извините, если я упустил что-то важное или не ясно выразился, напишите мне и я все исправлю при необходимости)
Java:
public class EntityInfoUpdate {
private ProtocolManager protocolManager;
private UUID uuid;
public EntityInfoUpdate(
ProtocolManager protocolManager,
UUID uuid) {
this.protocolManager = protocolManager;
this.uuid = uuid;
}
public void playerInfoUpdate(Player player) {
PacketContainer npc = protocolManager.createPacket(PacketType.Play.Server.PLAYER_INFO); // Создание пакет PLAYER_INFO
Set<EnumWrappers.PlayerInfoAction> playerInfoActionSet = new HashSet<>(); //Коллекция наборов, содержащая действие с информацией об игроке, очень важно
/*
Создайте новый пользовательский игровой профиль для нашего NPC.
В первых аргументах укажите уникальный uuid, сгенерированный с помощью UUID.randomUUID();
Во втором аргументе поместите имя NPC над головой.
*/
WrappedGameProfile wrappedGameProfile = new WrappedGameProfile(uuid, "NPC");
/*
Создайте новое свойство скина игрока.
В первом оставь как есть и не трогай, иначе ничего не работает.
Во втором и третьем укажите те данные, которые получите следующим способом:
Перейдите на https://minecraftuuid.com/ и найдите нужный скин.
Если вы нашли скин, скопируйте его UUID игрока.
Введите поиск в браузере, но не нажимайте Enter: вам нужно будет отредактировать этот URL-адрес;
https://sessionserver.mojang.com/session/minecraft/profile/uuid?unsigned=false
Удалите слово «uuid» и вставьте скопированный вами uuid игрока.
И теперь вам нужно нажать Enter и вы получите результат
Скопируйте значение и вставьте во вторые аргументы конструктора.
Скопируйте подпись и вставьте ее в аргументы третьего конструктора.
*/
WrappedSignedProperty property = new WrappedSignedProperty(
"textures",
"ewogICJ0aW1lc3RhbXAiIDogMTcwOTEzODQ2ODQwOSwKICAicHJvZmlsZUlkIiA6ICIwNjlhNzlmNDQ0ZTk0NzI2YTViZWZjYTkwZTM4YWFmNSIsCiAgInByb2ZpbGVOYW1lIiA6ICJOb3RjaCIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8yOTIwMDlhNDkyNWI1OGYwMmM3N2RhZGMzZWNlZjA3ZWE0Yzc0NzJmNjRlMGZkYzMyY2U1NTIyNDg5MzYyNjgwIgogICAgfQogIH0KfQ==",
"ESOeT3Fo19IDBmQS231ZSm3XKCq5SVEbu74LArJoi9Ker1RXDsP3XV8mPgfeWCmm89v2RXLp7bcmmCtJI6JgKPZb3wCasSaM1SxwJ4WEaoByQvJ0FhRyeG126d9GEujQgOxmjErvKGLOKgRbF3ORoroSua8csiNn8+ADSyYrUud0lzczOMD9hx+qov+wSYb+zJ/9bR4aXFAnLxMbEAOYOyJCSJ+ZE4KQmvebgyNGHLSmC/Vm9zZIFAZrg62wV3SXBAuWGXv46jDPlaoimZv1/zmOMDj8dGwQ89WWR1zcR8ZBn9Ysgi+yfTvR3w/AHeuMa2IdQKxXw8QJl0kZp0c36RIIfKB25KcbED2HReuA/Nmg5S+HXju9mrK+M4YDvz34oRgBvnVjUTWtg6b8eW3OtaKlIkEgjlUGPZYU4OhoeoQaEylcjISyT4nS36iGumSSgXAW75GuiPI4b8KZ5iy0yXlWWSLTJr0axShFuvuv4Vd70qufnusnEGceMyOXBUCC5lcJ06RrIwUNdLWkwGh8OWdjXffvyhyAAbrq8oFNKxRzjf8E6XEAvClf5L6BSH8kd7OwxpEFzUMiJNHZ9tQ9j8EwsYv3cLUNhBV/yf2O96AqDmyCD3wTblQj8k4pJRyi+/Kj1476yVOfuc3LJ4jYcDX2pJv3LemUxLJ+JDMdJh0=" );
//Теперь нам нужно добавить данные о скине нашего игрового профиля.
wrappedGameProfile.getProperties().clear();
wrappedGameProfile.getProperties()
.put("textures", property);
/*
В первый аргумент конструктора помещаем наш игровой профиль
Во втором аргументе конструктора указывается ваша задержка, которая вам нужна. (речь о пинге, если не ошибаюсь)
В третий аргумент конструктора поставил режим игры нпс, я поставил креатив.
На тему четвёртого аргумента, я не очень то понял:
Я не вру и говорю правду – я не знаю, что это такое. Я думаю, это как-то связано с чатом,
возможно, это имя игрока, которое отображается, если игрок что-то пишет в чате. Я не знаю, извините.
*/
PlayerInfoData playerInfoData = new PlayerInfoData(
wrappedGameProfile,
0,
EnumWrappers.NativeGameMode.CREATIVE,
WrappedChatComponent.fromText("name"));
List<PlayerInfoData> playerInfoDataList = Arrays.asList(playerInfoData); //Добавляем информацию об игроке в список, как здесь.
playerInfoActionSet.add(EnumWrappers.PlayerInfoAction.ADD_PLAYER); //Добавляет действия игрока, которые должен выполнить сервер, в нашем случае это добавление игрока
npc.getPlayerInfoActions()
.write(0, playerInfoActionSet); //Добавляем действие игрока в наш пакет
npc.getPlayerInfoDataLists().write(1, playerInfoDataList); //Добавляем список данных об игроке в пакет
/*
В аргументах первого метода нам нужно указать игрока, которому отправляется пакет.
В аргументах второго метода нам нужно поместить отправляемый пакет.
*/
protocolManager.sendServerPacket(player, npc);
}
}
Создание нашего НПС
Теперь нам нужно создать метод для создания NPC.
Создаём новый класс, который будет отвечать за спавн нашего НПС.
Java:
public class EntitySpawn {
private ProtocolManager protocolManager;
private UUID uuid;
public EntitySpawn(
ProtocolManager protocolManager,
UUID uuid) {
this.protocolManager = protocolManager;
this.uuid = uuid;
}
public void spawnEntity(Player player, Location location) {
/*
Создание пакет спавна нпс.
*/
PacketContainer npc = protocolManager.createPacket(PacketType.Play.Server.NAMED_ENTITY_SPAWN);
npc.getIntegers()
.write(0, -1) //Указываем Entity Id
.writeSafely(1, 122); //Id нашего нпс (игрок в 1.19.4 имеет идентификатор 122, получен с вики ProtocolLib)
npc.getUUIDs()
.write(0, uuid); //UUID должен быть такой же, как и тот который мы указывали в PLAYER_INFO
npc.getEntityTypeModifier()
.writeSafely(0, EntityType.PLAYER); //Указываем, что нам нужен именно НПС!
npc.getDoubles()
.write(0, location.getX())
.write(1, location.getY())
.write(2, location.getZ()); //Координаты в которых будет спавнится НПС
npc.getBytes()
.write(0, (byte) (0))
.write(1, (byte) (0)); //Направление в которое смотрит НПС
/*
В аргументах первого метода нам нужно указать игрока, которому отправляется пакет.
В аргументах второго метода нам нужно поместить отправляемый пакет.
*/
protocolManager.sendServerPacket(player, npc);
}
}
Укладываем весь процесс создания НПС в один единый класс, для удобства.
Java:
public class NPC {
private ProtocolManager protocolManager;
private UUID uuid;
public NPC(
ProtocolManager protocolManager,
UUID uuid) {
this.protocolManager = protocolManager;
this.uuid = uuid;
}
public void spawn(Player player, Location location) {
EntityInfoUpdate updateInfo = new EntityInfoUpdate(protocolManager, uuid);
EntitySpawn spawnEntity = new EntitySpawn(protocolManager, uuid);
updateInfo.playerInfoUpdate(player);
spawnEntity.spawnEntity(player, location);
}
}
Спавним НПС, когда игрок заходит на сервер:
Java:
public class PlayerJoin implements Listener {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
NPC npc = new NPC(
ProtocolLibrary.getProtocolManager(),
UUID.randomUUID());
Location location = new Location(Bukkit.getServer().getWorld("world"), -1.0, 2.0, 0.0);
npc.spawn(event.getPlayer(), location);
}
}
НПС всегда смотрит на вас
Вероятнее всего, вы видели, как на большинстве серверов, нпс не отводит от вас взгляда. Сейчас, я покажу вам, как реализовать данную функцию с нашим нпс.
Java:
public class EntityHeadAndBodyRotationUpdate {
private ProtocolManager protocolManager;
public EntityHeadAndBodyRotationUpdate(
ProtocolManager protocolManager) {
this.protocolManager = protocolManager;
}
public void updateRotation(Player player, Location location) {
/*
Как можно понять по названию пакета,
первый пакет отвечает за вращение головы,
второй пакет отвечает за вращение тела.
*/
PacketContainer rotateHead = protocolManager.createPacket(PacketType.Play.Server.ENTITY_HEAD_ROTATION);
PacketContainer rotateBody = protocolManager.createPacket(PacketType.Play.Server.ENTITY_LOOK);
rotateHead.getIntegers()
.write(0, -1); //Указываем EntityId игрока, который указывали ранее.
rotateBody.getIntegers()
.write(0, -1); //Указываем EntityId игрока, который указывали ранее.
location.setDirection(player.getLocation().subtract(location).toVector()); //Устанавливаем ветро между игроком и НПС, да бы Нпс всегда смотрел на игрока
float yaw = location.getYaw();
float pitch = location.getPitch();
rotateHead.getBytes()
.write(0, (byte) ((yaw % 360) * 256 / 360)); //Направление головы
rotateBody.getBytes()
.write(0, (byte) ((yaw % 360) * 256 / 360)) //Направление головы
.write(1, (byte) ((pitch % 360) * 256 / 360)); //Направление тела игрока
/*
В аргументах первого метода нам нужно указать игрока, которому отправляется пакет.
В аргументах второго метода нам нужно поместить отправляемый пакет.
*/
protocolManager.sendServerPacket(player, rotateHead);
protocolManager.sendServerPacket(player, rotateBody);
}
}
Но ясное дело, это не всё. Нам необходимо доделать начатое в PlayerMoveEvent.
Java:
public class PlayerMoving implements Listener {
private ProtocolManager protocolManager;
private Location location;
private UUID uuid;
public PlayerMoving(
ProtocolManager protocolManager,
Location location,
UUID uuid) {
this.protocolManager = protocolManager;
this.location = location;
this.uuid = uuid;
}
@EventHandler
public void onMove(PlayerMoveEvent event) {
Player player = event.getPlayer();
EntityHeadAndBodyRotationUpdate updateHeadAndBodyRotation = new EntityHeadAndBodyRotationUpdate(protocolManager);
/*
Чтобы не создавать дикую нагрузку на сервере, мы сделаем так, чтобы НПС смотрел на игрока только если тот на расстоянии 5 и меньше блоков.
Представьте, если мы это не сделаем и на вашем сервере 100 игроков, НПС всегда будет смотреть на каждого игрока, даже если тот находится на 1000 блоков. Данный эффект не будет виден игроку, но будет виден в качестве нагрузки на сервере. :) Нам оно не надо.
*/
if (player.getLocation().distance(location) <= 5) {
updateHeadAndBodyRotation.updateRotation(player, location);
}
}
}
Нажатие на НПС
А что если, мы хотим иметь возможность нажать на нашего игрока и запускать к примеру SkyWars или открывать игроку GUI? Сейчас покажу, как это сделать.
В данном классе, мы создаём прослушиватель, который отслеживает все взаимодействия игрока с сущностью.
Java:
public class NPCClickelableEvent {
private Protocolliblearn protocolliblearn;
private ProtocolManager protocolManager;
public NPCClickelableEvent(
Protocolliblearn protocolliblearn,
ProtocolManager protocolManager) {
this.protocolliblearn = protocolliblearn;
this.protocolManager = protocolManager;
}
public void registerEvent() {
protocolManager.addPacketListener(
new PacketAdapter(
protocolliblearn,
ListenerPriority.NORMAL,
PacketType.Play.Client.USE_ENTITY) {
@Override
public void onPacketReceiving(PacketEvent packetEvent) {
Player player = packetEvent.getPlayer();
PacketContainer packetContainer = packetEvent.getPacket();
EnumWrappers.EntityUseAction action = packetContainer.getEnumEntityUseActions().read(0).getAction();
int entityId = packetContainer.getIntegers().read(0);
if (entityId == -1 && action == EnumWrappers.EntityUseAction.ATTACK) {
player.sendMessage("Fuck You");
}
}
});
}
}
На всякий, дам вам так же свой основной Main.class и весь проект который я залил на Github.
Java:
public final class Protocolliblearn extends JavaPlugin {
@Override
public void onEnable() {
getServer().getPluginManager().registerEvents(new PlayerJoin(), this);
NPCClickelableEvent npcClickelableEvent = new NPCClickelableEvent(this, ProtocolLibrary.getProtocolManager());
npcClickelableEvent.registerEvent();
UUID uuid = UUID.randomUUID();
Bukkit.getPluginManager().registerEvents(new PlayerMoving(
ProtocolLibrary.getProtocolManager(),
new Location(
Bukkit.getServer().getWorld("world"),
-1.0,
2.0,
0.0),
uuid
), this);
}
@Override
public void onDisable() {
// Plugin shutdown logic
}
}
Я надеюсь, что объяснил всё понятно и ничего не пропустил. Я уверен, что кому-то данный ресурс среди русскоязычной аудитории Spigot будет полезен. В случае, если я допустил какие-то ошибки или оплошности - пишите, я всё исправлю и помогу в вашей проблеме. Спасибо за внимание! (Админам/модерам - пожизненный запас чая и тортиков)
GitHub - August1251/NPCWithProtocolLib
Contribute to August1251/NPCWithProtocolLib development by creating an account on GitHub.
github.com