Skip to content

Instantly share code, notes, and snippets.

@DanielTheDev
Last active December 16, 2023 07:43
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save DanielTheDev/cb51c11bd2551fd9a6700c582d12ff48 to your computer and use it in GitHub Desktop.
Save DanielTheDev/cb51c11bd2551fd9a6700c582d12ff48 to your computer and use it in GitHub Desktop.
Bukkit/Spigot NPC Util 1.12
package com.plugin.npc;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import net.minecraft.server.v1_12_R1.*;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_12_R1.util.CraftChatMessage;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class NPC {
private int entityId;
private Location location;
private GameProfile gameprofile;
private List<Player> recipients;
private Recipient recipient_type = Recipient.ALL;
private String display_name;
private String tablist_name;
private DataWatcher dataWatcher;
private DataWatcherObject<Byte> object_entity_state;
private DataWatcherObject<String> object_customName;
private DataWatcherObject<Boolean> object_isSilent;
private DataWatcherObject<Boolean> object_hasGravity;
private DataWatcherObject<Boolean> object_isCustomNameVisible;
private boolean isDestroyed;
private Plugin plugin;
/*
* initialize all the npc default settings
*/
public NPC(String name, Location location, Plugin plugin) {
this.plugin = plugin;
this.display_name = name;
this.tablist_name = name;
this.recipient_type = Recipient.ALL;
this.recipients = new ArrayList<Player>();
this.entityId = (int) Math.ceil(Math.random() * 1000);
this.gameprofile = new GameProfile(UUID.randomUUID(), display_name);
this.location = location;
this.dataWatcher = new DataWatcher(null);
this.dataWatcher.register(object_entity_state = new DataWatcherObject<>(0, DataWatcherRegistry.a), (byte) 0);
this.dataWatcher.register(new DataWatcherObject<>(1, DataWatcherRegistry.b), 300);
this.dataWatcher.register(object_customName = new DataWatcherObject<>(2, DataWatcherRegistry.d), "");
this.dataWatcher.register(object_isCustomNameVisible = new DataWatcherObject<>(3, DataWatcherRegistry.h),false);
this.dataWatcher.register(object_isSilent = new DataWatcherObject<>(4, DataWatcherRegistry.h), false);
this.dataWatcher.register(object_hasGravity = new DataWatcherObject<>(5, DataWatcherRegistry.h), false);
this.dataWatcher.register(new DataWatcherObject<>(6, DataWatcherRegistry.a), (byte) 0);
this.dataWatcher.register(new DataWatcherObject<>(7, DataWatcherRegistry.c), 20.0F);
this.dataWatcher.register(new DataWatcherObject<>(8, DataWatcherRegistry.b), 0);
this.dataWatcher.register(new DataWatcherObject<>(9, DataWatcherRegistry.h), false);
this.dataWatcher.register(new DataWatcherObject<>(10, DataWatcherRegistry.b), 0);
this.dataWatcher.register(new DataWatcherObject<>(11, DataWatcherRegistry.c), 0.0F);
this.dataWatcher.register(new DataWatcherObject<>(12, DataWatcherRegistry.b), 20);
this.dataWatcher.register(new DataWatcherObject<>(13, DataWatcherRegistry.a), (byte) 127);
this.dataWatcher.register(new DataWatcherObject<>(14, DataWatcherRegistry.a), (byte) 1);
this.dataWatcher.register(new DataWatcherObject<>(15, DataWatcherRegistry.n), new NBTTagCompound());
this.dataWatcher.register(new DataWatcherObject<>(16, DataWatcherRegistry.n), new NBTTagCompound());
}
/*
* view all the packet receivers
*/
public List<Player> getRecipients() {
return this.recipients;
}
/*
* get the signature and skin from the gameprofile
*/
public Property getSkin() {
if (this.gameprofile.getProperties().isEmpty())
return null;
return (Property) this.gameprofile.getProperties().get("textures").toArray()[0];
}
/*
* get npc id
*/
public int getEntityId() {
return entityId;
}
/*
* get npc location
*/
public Location getLocation() {
return location;
}
/*
* get npc gameprofile
*/
public GameProfile getGameprofile() {
return gameprofile;
}
/*
* get all the packet receivers
*/
public Recipient getRecipient_type() {
return recipient_type;
}
/*
* get npc displayname above head
*/
public String getDisplay_name() {
return display_name;
}
/*
* get npc displayname in tablist
*/
public String getTablist_name() {
return tablist_name;
}
/*
* check if npc is deleted
*/
public boolean isDestroyed() {
return isDestroyed;
}
/*
* get plugin
*/
public Plugin getPlugin() {
return plugin;
}
/*
* add Player so it receives the update packets
*/
public void addRecipient(Player p) {
this.recipients.add(p);
}
/*
* Remove Player that receives the update packets
*/
public void removeRecipient(Player p) {
this.recipients.remove(p);
}
/*
* toggle if the packet receivers are all the players online or a specicif group.
* If set to ALL (online) then the set/get recipient will have no effect
*/
public void setRecipientType(Recipient recipient_type) {
this.recipient_type = recipient_type;
}
/*
* spawn the npc, this should be the last function after init.
*/
public void spawn(boolean tablist, boolean fix_head) {
PacketPlayOutNamedEntitySpawn packet = new PacketPlayOutNamedEntitySpawn();
this.setField(packet, "a", this.entityId);
this.setField(packet, "b", this.gameprofile.getId());
this.setField(packet, "c", location.getX());
this.setField(packet, "d", location.getY());
this.setField(packet, "e", location.getZ());
this.setField(packet, "f", fix_head ? (byte) ((int) location.getYaw() * 256.0F / 360.0F) : 0);
this.setField(packet, "g", fix_head ? (byte) ((int) location.getPitch() * 256.0F / 360.0F) : 0);
this.setField(packet, "h", this.dataWatcher);
this.addToTabList();
this.sendPacket(packet);
this.isDestroyed = false;
//Delay is required to commit the tablist changes
new BukkitRunnable() {
@Override
public void run() {
if (!tablist) removeFromTabList();
}
}.runTaskLater(this.plugin, 5);
}
/*
* set the name above the player
*/
public void setDisplayNameAboveHead(String name) throws IOException {
if(name.length() > 16) throw new IOException("Name cannot be longer than 16 chatacters.");
this.display_name = name;
this.reloadNpc();
}
/*
* set the name above the player and tablist.
*/
public void setDisplayName(String name) throws IOException {
this.setDisplayNameAboveHead(name);
this.setTablistName(name);
}
/*
* set custom name in tablist
*/
public void setTablistName(String name) {
this.tablist_name = name;
this.updateToTabList();
}
/*
* respawn the npc and refresh all comitted changes
*/
public void reloadNpc() {
this.updateProfile();
if(!this.isDestroyed) {
PacketPlayOutEntityDestroy packet = new PacketPlayOutEntityDestroy(new int[] { this.entityId });
this.sendPacket(packet);
this.spawn(true, true);
}
}
/*
* Update/Refresh the gameprofile that contains UUID, Name, Skin.
*/
private void updateProfile() {
Property skin = this.getSkin();
this.gameprofile = new GameProfile(this.gameprofile.getId(), this.display_name);
if (skin != null)
this.setSkin(skin.getValue(), skin.getSignature());
}
/*
* set the texture and signature in the gameprofile, to submit it you must reload player.
*/
public void setSkin(String texture, String signature) {
this.gameprofile.getProperties().put("textures", new Property("textures", texture, signature));
}
/*
* remove npc from the recipient's tablist
*/
public void removeFromTabList() {
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo();
PacketPlayOutPlayerInfo.PlayerInfoData data = packet.new PlayerInfoData(this.gameprofile, 0,
EnumGamemode.NOT_SET, CraftChatMessage.fromString(tablist_name)[0]);
List<PacketPlayOutPlayerInfo.PlayerInfoData> players = (List<PacketPlayOutPlayerInfo.PlayerInfoData>) getField(packet, "b");
players.add(data);
this.setField(packet, "a", PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER);
this.setField(packet, "b", players);
this.sendPacket(packet);
}
/*
* update npc to the recipient's tablist
*/
public void updateToTabList() {
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo();
PacketPlayOutPlayerInfo.PlayerInfoData data = packet.new PlayerInfoData(this.gameprofile, 0,
EnumGamemode.NOT_SET, CraftChatMessage.fromString(tablist_name)[0]);
List<PacketPlayOutPlayerInfo.PlayerInfoData> players = (List<PacketPlayOutPlayerInfo.PlayerInfoData>) getField(packet, "b");
players.add(data);
this.setField(packet, "a", PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_DISPLAY_NAME);
this.setField(packet, "b", players);
this.sendPacket(packet);
}
/*
* add npc from the recipient's tablist
*/
public void addToTabList() {
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo();
PacketPlayOutPlayerInfo.PlayerInfoData data = packet.new PlayerInfoData(this.gameprofile, 0,
EnumGamemode.NOT_SET, CraftChatMessage.fromString(tablist_name)[0]);
List<PacketPlayOutPlayerInfo.PlayerInfoData> players = (List<PacketPlayOutPlayerInfo.PlayerInfoData>) getField(packet, "b");
players.add(data);
this.setField(packet, "a", PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER);
this.setField(packet, "b", players);
this.sendPacket(packet);
}
/*
* put items in inventory, see https://www.google.nl/search?q=bukkit+inventory+slots&source=lnms&tbm=isch&sa=X&ved=0ahUKEwjr9v_FxvjaAhUFMuwKHQi7ALUQ_AUICigB&biw=1920&bih=974#imgrc=QUECAbUohgZxbM:
* for more info
*/
public void setEquipment(EnumItemSlot slot, ItemStack item) {
PacketPlayOutEntityEquipment packet = new PacketPlayOutEntityEquipment();
this.setField(packet, "a", this.entityId);
this.setField(packet, "b", slot);
this.setField(packet, "c", item);
this.sendPacket(packet);
}
/*
* set player action, such as shift, onfire, ect recommending to use 'public void setAction(Action action)'.
*/
public void setAction(byte action) {
this.setMetaData(action);
}
/*
* set player action, such as shift, onfire, ect.
*/
public void setAction(Action action) {
this.setMetaData(action.build());
}
/*
* make sure the npc is near a bed or on it.
*/
public void setSleep(boolean state) {
if (state) {
Location bed = new Location(this.location.getWorld(), 0, 0, 0);
PacketPlayOutBed packet = new PacketPlayOutBed();
this.setField(packet, "a", this.entityId);
this.setField(packet, "b", new BlockPosition(bed.getX(), bed.getY(), bed.getZ()));
for (Player p : (this.recipient_type == Recipient.ALL ? Bukkit.getOnlinePlayers() : this.recipients)) {
((CraftPlayer) p).sendBlockChange(bed, Material.BED_BLOCK, (byte) 0);
((CraftPlayer) p).sendBlockChange(bed.add(0, 0, 1), Material.BED_BLOCK, (byte) 2);
}
this.sendPacket(packet);
this.teleport(location.clone().add(0, 0.3, 0), false);
} else {
this.setAnimation(NPCAnimation.LEAVE_BED);
this.teleport(location.clone().subtract(0, 0.3, 0), true);
}
}
/*
* delete the npc from the server.
*/
public void destroy() {
PacketPlayOutEntityDestroy packet = new PacketPlayOutEntityDestroy(new int[] { this.entityId });
this.removeFromTabList();
this.sendPacket(packet);
this.isDestroyed = true;
}
/*
* set npc status such as die or hurt. I recommond to use method 'public void setStatus(NPCStatus status)'
*/
public void setStatus(byte status) {
PacketPlayOutEntityStatus packet = new PacketPlayOutEntityStatus();
this.setField(packet, "a", this.entityId);
this.setField(packet, "b", status);
this.sendPacket(packet);
}
/*
* set npc status such as die or hurt.
*/
public void setStatus(NPCStatus status) {
this.setStatus((byte) status.getId());
}
/*
* set npc effect such as Night Vision or something else.
*/
public void setEffect(MobEffect effect) {
this.sendPacket(new PacketPlayOutEntityEffect(this.entityId, effect));
}
/*
* set npc animation such as Swing arm ect. I recommend using method 'public void setAnimation(NPCAnimation animation)'
*/
public void setAnimation(byte animation) {
PacketPlayOutAnimation packet = new PacketPlayOutAnimation();
this.setField(packet, "a", this.entityId);
this.setField(packet, "b", animation);
this.sendPacket(packet);
}
/*
* set npc animation such as Swing arm ect.
*/
public void setAnimation(NPCAnimation animation) {
this.setAnimation((byte) animation.getId());
}
/*
* teleport npc to different location
*/
public void teleport(Location location, Boolean onGround) {
PacketPlayOutEntityTeleport packet = new PacketPlayOutEntityTeleport();
this.setField(packet, "a", this.entityId);
this.setField(packet, "b", location.getX());
this.setField(packet, "c", location.getY());
this.setField(packet, "d", location.getZ());
this.setField(packet, "e", (byte) location.getYaw());
this.setField(packet, "f", (byte) location.getPitch());
this.setField(packet, "g", onGround);
this.sendPacket(packet);
this.rotateHead(location.getPitch(), location.getYaw());
this.location = location;
}
/*
* rotate npc head to pitch and yaw.
*/
public void rotateHead(float pitch, float yaw) {
PacketPlayOutEntity.PacketPlayOutEntityLook packet = new PacketPlayOutEntity.PacketPlayOutEntityLook(this.entityId, getFixRotation(yaw), (byte) pitch, true);
PacketPlayOutEntityHeadRotation packet_1 = new PacketPlayOutEntityHeadRotation();
this.setField(packet_1, "a", this.entityId);
this.setField(packet_1, "b", getFixRotation(yaw));
this.sendPacket(packet);
this.sendPacket(packet_1);
}
/*
* These methods below are not usefull.
*/
private <T> void setDataWatcherObject(DataWatcherObject<T> datawatcherobject, Object t0) {
try {
Method m = this.dataWatcher.getClass().getDeclaredMethod("registerObject", DataWatcherObject.class,
Object.class);
m.setAccessible(true);
m.invoke(this.dataWatcher, datawatcherobject, t0);
} catch (Exception e) {
e.printStackTrace();
}
}
private void setMetaData(byte data) {
this.setDataWatcherObject(this.object_entity_state, data);
PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(this.entityId, this.dataWatcher, true);
sendPacket(packet);
}
private byte getFixRotation(float yawpitch) {
return (byte) ((int) (this.location.getYaw() * 256.0F / 360.0F));
}
private Object getField(Object obj, String field_name) {
try {
Field field = obj.getClass().getDeclaredField(field_name);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private void setField(Object obj, String field_name, Object value) {
try {
Field field = obj.getClass().getDeclaredField(field_name);
field.setAccessible(true);
field.set(obj, value);
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendPacket(Packet<?> packet, Player player) {
((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet);
}
private void sendPacket(Packet<?> packet) {
for (Player p : (this.recipient_type == Recipient.ALL ? Bukkit.getOnlinePlayers() : this.recipients)) {
this.sendPacket(packet, p);
}
}
enum Recipient {
ALL, LISTED_RECIPIENTS;
}
enum NPCAnimation {
SWING_MAIN_HAND(0),
TAKE_DAMAGE(1),
LEAVE_BED(2),
SWING_OFFHAND(3),
CRITICAL_EFFECT(4),
MAGIC_CRITICAL_EFFECT(5);
private int id;
private NPCAnimation(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
enum NPCStatus {
HURT(2), DIE(3);
private int id;
private NPCStatus(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
public static class Action {
private boolean on_fire, crouched, sprinting, invisible, glowing, flying_elytra;
private byte result = 0;
public Action(boolean on_fire, boolean crouched, boolean sprinting, boolean invisible, boolean glowing,
boolean flying_elytra) {
this.on_fire = on_fire;
this.crouched = crouched;
this.sprinting = sprinting;
this.invisible = invisible;
this.glowing = glowing;
this.flying_elytra = flying_elytra;
}
public Action() {
}
public boolean isOn_fire() {
return on_fire;
}
public Action setOn_fire(boolean on_fire) {
this.on_fire = on_fire;
return this;
}
public boolean isCrouched() {
return crouched;
}
public Action setCrouched(boolean crouched) {
this.crouched = crouched;
return this;
}
public boolean isSprinting() {
return sprinting;
}
public Action setSprinting(boolean sprinting) {
this.sprinting = sprinting;
return this;
}
public boolean isInvisible() {
return invisible;
}
public Action setInvisible(boolean invisible) {
this.invisible = invisible;
return this;
}
public boolean isGlowing() {
return glowing;
}
public Action setGlowing(boolean glowing) {
this.glowing = glowing;
return this;
}
public boolean isFlying_elytra() {
return flying_elytra;
}
public Action setFlying_elytra(boolean flying_elytra) {
this.flying_elytra = flying_elytra;
return this;
}
public byte build() {
result = 0;
result = add(this.on_fire, (byte) 0x01);
result = add(this.crouched, (byte) 0x02);
result = add(this.sprinting, (byte) 0x08);
result = add(this.invisible, (byte) 0x20);
result = add(this.glowing, (byte) 0x40);
result = add(this.flying_elytra, (byte) 0x80);
return result;
}
private byte add(boolean condition, byte amount) {
return (byte) (result += (condition ? amount : 0x00));
}
}
}
@didi955
Copy link

didi955 commented May 10, 2020

Amazing work ! thx
Can you updating this file to support 1.15.2 ?

@DanielTheDev
Copy link
Author

Amazing work ! thx
Can you updating this file to support 1.15.2 ?

Hey, thanks for the positive feedback.
Also thanks for reminding me to update the source.
I will let you know if I finnished the 1.15.2 version

@Traolian
Copy link

Can you please make this for 1.8 too?

@DanielTheDev
Copy link
Author

DanielTheDev commented May 25, 2020 via email

@DanielTheDev
Copy link
Author

@Traolian
Copy link

i saw this video, im german soo.. but nothing in this video worked

@thevalleyy
Copy link

i saw this video, im german soo.. but nothing in this video worked

Wait, bist du nicht der Coding Area Typ?
wenn ja, so klein ist die welt :)

@KxmischesDomi
Copy link

i saw this video, im german soo.. but nothing in this video worked

Wait, bist du nicht der Coding Area Typ?
wenn ja, so klein ist die welt :)

Das ist mein alter GitHub Account, och man ist diese Diskussion alt..

@DanielTheDev
Copy link
Author

I am updating the util to a stable 1.17 version at the moment.
This should not take me too long but since the security of 1.17 version is upgraded, it makes it harder for me to bypass the packet system.
But for every problem is a sulotion.

Please be patient!

@exceptionpilot
Copy link

How can i do Interaction with the NPC for GUI or something?

@DanielTheDev
Copy link
Author

How can i do Interaction with the NPC for GUI or something?

You simply add a ChannelDuplexHandler before the packet_handler inside the pipeline of the EntityPlayer's Networkmanager.
Then check for the packet that corresponds with the actions performed, and then check if the entityIDs of both the packet and the NPC match. Then you do whatever you want with it.

@GhostAndry
Copy link

Plsss can you do it for 1.8.8?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment