Skip to content

Creating NPCs with Kelp

PXAV edited this page Feb 14, 2021 · 4 revisions

This tutorial teaches you how to create NPCs with kelp-v0.2.0 and above. The old way of creating NPCs introduced in 0.0.1 is not supported anymore.

Spawning a basic NPC

First of all, you need a KelpNpc object, which can then be used to manipulate data and give your fake player some abilities. New NPC instances can be created using the KelpNpcFactory class or the static factory of KelpNpc:

KelpNpc npc = KelpNpc.create();

Before spawning the NPC, you have to provide some essential data including the spawn location or the KelpPlayer who owns the NPC:

npc.location(player.getLocation());
npc.player(player);

npc.spawn();

Manipulating NPC data

There is also more data to set, but it is not necessary to define it before the NPC spawns as there are default values for that. Here is an overview of methods to use to change NPC properties:

Name Description Example
itemInHand(KelpItem) Sets the item the NPC is currently holding in its main hand. This can be any material or block. To reset the item, simply set KelpMaterial.AIR here. npc.itemInHand(KelpItem.create().material(KelpMaterial.APPLE))
helmet(KelpItem) Sets the helmet the NPC is currently wearing. To reset the helmet, simply apply KelpMaterial.AIR here as well. Unlike in the other armor slots, you can also apply different hat types than a helmet such as blocks here. npc.helmet(KelpItem.create().material(KelpMaterial.IRON_HELMET))
chestPlate(KelpItem)
leggings(KelpItem)
boots(KelpItem)
Set the other armor slots of the npc. To reset an item, use KelpMaterial.AIR see above
customName(String) Sets the NPC's custom name, which is normally displayed above its head. If there is nothing else defined, this name will be used for the tablist as well. If no custom name is defined, a random one will be generated by Kelp. You can also hide custom names. npc.customName("Bob der Baumeister")
customNameShown(Boolean) If set to true, Kelp will display the NPC's custom name above it's head. By default this is set to false. customNameShown(true)
hideCustomName()
showCustomName()
tabListName(String) Sets the name the npc will be displayed with in the server's tablist. If this name is not set, Kelp will use the customName. tabListName("GommeHD")
showInTab(boolean) If set to true, the NPC will be shown in the server's tablist under the name set in the above method. Default value is false. showInTab(true)
uuid(UUID)
uuid(String)
Sets the NPC's uuid, which is used in its GameProfile. So it might have an impact on which default skin is chosen. But normally it is recommended to not overwrite this value as Kelp generates a default one when the NPC is spawned. npc.uuid(UUID.randomUUID())
npc.uuid("30bec34e-49a9-4286-ae77-4957abef42a3")
titleLines(Supplier<List<String>>) Sets the title lines above the NPC's head. Those have the same location as the customName, which is why it's recommended to disable the custom name when using multiple title lines. Those can be updated anytime using updateTitleLines(). npc.titleLines(() -> Lists.newArrayList( "§2KELP DEMO", "§7Welcome, " + player.getName() ));
sneak()
unSneak()
setSneaking(boolean)
Makes the NPC crouch or stand normally again. setSneaking(false)

Changing NPC skins

There are multiple ways to set the skin of your NPC.

Using GameProfile UUID

One is to set the UUID of your NPC to the UUID of the player whose skin it should take.

KelpNpc npc = npcFactory.newKelpNpc();
npc.uuid("858fdbb6-871c-48fa-b8e8-69bf66c6a102");

You can get the UUID of a player on websites like NameMC or the Mojang API. Note that the Mojang API shortens the UUID by cutting the - (dashes) away, which are needed by Kelp. So when you want to use the API, remember to convert the UUID into the conventional format.

GET https://api.mojang.com/users/profiles/minecraft/<username>

Possible output:

{"name":"pxav","id":"858fdbb6871c48fab8e869bf66c6a102"}

The major drawback of this method is that the player whose UUID you pick can change their skin at any time, so will your NPC skin. So you do not have control over your skin. To avoid this and ensure the consistency of your skin, look at the second method.

Using skin texture and signature

Every skin has a texture and a signature, which can both be queried via the Mojang API:

GET https://sessionserver.mojang.com/session/minecraft/profile/<shortenedUuid>?unsigned=false

Possible output:

{
  "id" : "858fdbb6871c48fab8e869bf66c6a102",
  "name" : "pxav",
  "properties" : [ {
    "name" : "textures",
    "value" : "ewogICJ0aW1lc3RhbXAiIDogMTU5MjQ4ODg1MDc0NSwKICAicHJvZmlsZUlkIiA6ICI4NThmZGJiNjg3MWM0OGZhYjhlODY5YmY2NmM2YTEwMiIsCiAgInByb2ZpbGVOYW1lIiA6ICJweGF2IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2YyZmVhZThkMzFlYzc0NTQ4MTE5Y2E0NThmNWRhNTc2MzU0ZmE5NzUxYWE0YWU2M2Y1NDYwODJmODA2NTFiMmIiCiAgICB9CiAgfQp9",
    "signature" : "sd/uN32SL73hcW1RN0fRATCLnp8Y4WJlYPwodVI5L1ekmlNIhXVobMkwv423QKz3rPtHdeveyFumZizLqcR66f9CGGqOaF9HDgnTAU/dv0svSVaYfuZjXAP5bFuJ78vpSQMVbsHfS4sJKstEJzIvcWhHoSJzhHVEHCan1ytK8rLRp+SqN/HzUV3ZQp+dRxEZ+hz+CF0RJo38a1asr7MAtNYxriLRgaxVUhf9f5JH3jY1bY2DnFXKdroWWrvlYhXaswK52UUQ8dbaiQj2bS8lj89EW8aXlLl4Vqg5Nm34xVpnIMSAuinnWBn3UK1Dwfi3/aBn55dUteTS4HbQcsK8GmhHTM1UoqL81V8iXSdWm/bAOr0wpXq8xbuIzBaFcwPWOt2jZJCLEROLZCg7IgY8hzbxmFI2UoUzEys3gUigLnOHG3rUhDX/mPAyWwqhTJ+TFcBv9n8sSPTrdiwVZfjJeT1t8HkPMP6CWwY41SadmgtYnrFrtLVA3ZJGO7SafzO5sg1fdBw/pllb8J/zmf6hADiEqappiAi1DSPOrjvYsQM0C9PwA8DGb7Gr85Qfz/8CJe8/ZT6tSZVI+4D9YbflAiHjJWnP5Tn3CnLn/16Ks/VsyD6RUY+tN/A/EkgHNdPNY7jcswKHj+X0W4BmOg7ErPuJLaZ07SblXHXj3JOtxQA="
  } ]
}

The values of "value" (which is equal to texture) and "signature" can be passed to the npc as follows:

KelpNpc npc = npcFactory.newKelpNpc();
npc.skinTexture("yourSkinTexture");
npc.skinSignature("yourSkinSignature");

The advantage of this technique is that your skin remains constant as you can download the skin texture once and save it in a config file for example. This makes you independent of other players changing their skin.

Please note that at the current time, NPC skins can only be manipulated before the NPC is actually spawned. It is being worked on updating skins at runtime.

Despawning/Removing an NPC

Kelp distinguished between despawning and removing an NPC. When a player walks out of range of an NPC - let's say 50 blocks away from it, it is no longer rendered by the player's client and will disappear when the player comes back. In this case, it would make sense to temporarily despawn the NPC when the player is too far away while being able to respawn it at any time (which can be done using NPC activities, which is shown later in this article).

So despawning is when you want to make the NPC invisible for the client while keeping the data of it in the Kelp NPC repository, so the data is still stored. Removing instead means that the NPC is not only despawned, but also removed from the repository, and all data (including listeners, etc.) is cleared. So be careful to choose the right option for your case.

// only despawns the npc
npc.deSpawn();
    
// -> ... so it can later be spawned again
npc.spawn();
    
// despawns the npc and removes it from the repository
npc.remove();

NPC activities

Activities are what makes Kelp NPCs extensible for plugin developers. NPCs have their own asynchronous tick system for making them walk for example. This tick system is slightly slower than the server's default one to save performance but still look fluently.

You can hook into this tick system by creating activities. There are some default activities in Kelp, which are demonstrated below, but you can also create your own activities, which is shown in another article.

Making the NPC look to a certain player

Okay, actually you can make the NPC look at any location with this activity, but most of you will use it for this case I think.

npc.addActivity(
  LookToActivity.create()
    .target(() -> player.getLocation()) // provide any location here
);

This will make the NPC always look at the player's location. Note that the location is passed via a Supplier<T>, so it is dynamic and will update with every NPC tick.

Making an NPC imitate sneaking behavior

Lets the NPC sneak/unsneak when the given player (un-)sneaks as well.

npc.addActivity(
  SneakingActivity.create()
    // player to imitate the behavior of
    // this does not essentially have to be the owner of the NPC
    .imitatedPlayer(player) 
);

Avoiding that an NPC despawns when it is out of range

As explained in the chapter about despawning/removing, when NPCs are out of range of their clients, they will be despawned automatically, which cannot be tracked by the server. When the player comes back to the location, the NPC won't respawn by default. But there is an activity preventing this behavior:

npc.addActivity(
  AutoSpawnActivity.create()
    .distanceThreshold(40) // optional (default: 40)
);

When the NPC is more than 40 blocks away from the player, it will be despawned, and as soon as this distance falls below the given value, it is respawned automatically again. It is recommended to apply this activity to all of your NPCs if you are not sure if the player might walk too far away.

NPC movement

NPC movement is still experimental and might cause unexpected behavior. NPCs might not respect the laws of physics and run into the ground or into the air.

npc.addActivity(WalkToTargetActivity.create()
  .target(targetLocation)
);
      
npc.addActivity(WalkToDirectionActivity.create()
  .direction(player.getLocation().getDirection())
  .distance(10) // in blocks
);

The NPC goes to the given target location or the given direction in a linear path.

Playing animations

You can play some animations on an NPC to indicate certain behavior such as hitting a player.

npc.playAnimation(NpcAnimation.MAIN_HAND_SWING);
npc.playAnimation(NpcAnimation.TAKE_DAMAGE);

A detailed overview of all animations can befound in the JavaDocs of the NpcAnimation class.

Clone this wiki locally