project

Under the Hood of a Modern Minecraft NPC Engine

Non-Player Characters (NPCs) are the lifeblood of any dynamic game world, but creating them in Minecraft can be a challenge. You're often stuck with re-skinned villagers or clunky armor stands. I wanted to build something better: a powerful, flexible, and performant NPC system from the ground up.

Cristopher Cousiño
Author
Tuesday, May 20, 2025
Published
6 min read
Read time

Non-Player Characters (NPCs) are the lifeblood of any dynamic game world, but creating them in Minecraft can be a challenge. You're often stuck with re-skinned villagers or clunky armor stands. I wanted to build something better: a powerful, flexible, and performant NPC system from the ground up for the modern PaperMC platform. The result is NpcModule, and today I want to give you a deep dive into its architecture and features.

The Architectural Foundation: A Clean API

The first and most important design decision was to split the project into two distinct modules: api and dist.

The api Module

This is the public face of the project, written in Java for maximum compatibility. It contains only interfaces and data classes (like LivingNPC, ModelNPC, and NPCTrait). Developers who want to build plugins that interact with NpcModule only need to use this module. It's the stable, documented contract.

The dist Module

This is the core implementation, written in Kotlin. It contains all the "real" logic: how NPCs are spawned, how their packets are sent, and how they interact with the world.

This separation is critical. It means I can completely refactor the internal logic, fix bugs, or even switch out a core dependency in the dist module, and as long as the api contract is honored, other plugins won't break.

Feature Spotlight 1: More Than Just a Player Skin

NpcModule supports two primary types of NPCs:

Skin NPCs

The classic NPC that looks like a Minecraft player. The system handles everything from fetching skins from a URL or Mojang's servers to caching them to avoid repeated, slow web requests.

Model NPCs

This is where things get exciting. By integrating with the powerful BetterModel library, NpcModule can spawn NPCs that are actual 3D models. The ModelNPC interface and its implementation, ModelNPCImpl, manage everything from spawning the host entity (typically an invisible armor stand) to controlling animations.

Feature Spotlight 2: Pluggable Behaviors with the "Trait" System

How does an NPC do things? Instead of hard-coding behaviors, I implemented a "Trait" system, which is a practical application of the Strategy design pattern. A NPCTrait is a small, reusable behavior that can be attached to any NPC via configuration.

For example, if you want an NPC to send a message on interaction, you just add a message trait. The TraitFactory reads the type, finds the corresponding MessageTraitImpl class, and instantiates it with the given configuration. This makes the system incredibly extensible.

Under the Hood: Performance is Not an Option

A poorly written NPC plugin can bring a server to its knees. I focused on two key areas to ensure NpcModule was fast and efficient:

Asynchronous Everything

Any task that could take time—reading files from disk, fetching skins from the internet—is handled asynchronously using CompletableFuture. The ConfigurationLoaderImpl loads all NPC files on a separate thread, and the SkinCacheImpl handles all web requests without ever blocking the main server thread.

Low-Level Packet Control

Instead of relying solely on the Bukkit API for spawning and visibility, NpcModule uses the PacketEvents library. This allows it to send spawn and destroy packets directly to specific players. The NPCPoolImpl class runs a timer that checks player proximity to NPCs. When a player comes into range, it sends the packets to make the NPC appear.

Final Thoughts

Building NpcModule was an incredible learning experience. It was a deep dive into modern plugin development, from managing a multi-project Gradle build to designing a clean API and tackling complex performance challenges. The final result is a system that is not only powerful and feature-rich but also stable and efficient.

RELATED

Continue Reading

Enjoyed this article?

Check out more of my thoughts and projects, or get in touch to discuss your next idea.