Skip to content
On this page

Selectors and Entity Access

Datapacks usually identify entities with selectors such as @a, @e[type=zombie,distance=..16], or @s. Katton supports that style, but once entities are selected, you can work with real entity objects instead of passing them through more command strings.

Build a Selector

kotlin
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.Identifier
import net.minecraft.world.effect.MobEffects
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.phys.Vec3
import top.katton.api.ServerPhase
import top.katton.api.ServerReadyContext
import top.katton.api.ServerScriptEntrypoint
import top.katton.api.dpcaller.addEffect
import top.katton.util.EntitySelectorBuilder

@ServerScriptEntrypoint(ServerPhase.READY)
fun targetSelectorExample(context: ServerReadyContext) {
    val creeperType = BuiltInRegistries.ENTITY_TYPE
        .getOptional(Identifier.parse("minecraft:creeper"))
        .orElseThrow()

    // Build a target selector
    val selector = EntitySelectorBuilder.allEntities()  //@e
        .type(creeperType)   //type = creeper
        .tag("qwq", false)  //tag = qwq
        .distanceBelow(16.0)    //distance = ..16
        .create()
    // So we get a target selector like this: @e[type=creeper,tag=qwq,distance=..16]

    // And then we need to build a command execution source, which is required by the selector to get the world and the position for distance calculation
    val source = context.server.createCommandSourceStack()
        .withLevel(context.server.overworld()) // Set the dimension for the command source
        .withPosition(Vec3(50.0, 70.0, 50.0)) // Set the position for distance calculation

    // Get entities selected by the selector
    val entities = selector.findEntities(source)

    // Once you get the references of the entities, you access those entities at anywhere in your code, and do whatever you want with them
    for (entity in entities) {
        if (entity is LivingEntity) {
            // add some effect to the entity
            addEffect(entity,
                MobEffects.GLOWING, // effect
                200,    // effect duration in **ticks**
                0,      // effect amplifier level
                false,  // show particles
                false   // ambient effect
            )
        }
    }
}

The selector still needs a command source because vanilla selectors depend on world, position, permission, and dimension context.

Selector Thinking vs Object Thinking

Datapack mindsetKatton mindset
Select entities, run a command on each oneSelect entities, loop over objects
Store intermediate values in scoreboardsStore local values in Kotlin variables
Use execute as ... at ... to change contextPass the entity or level directly to a function
Repeat selector filters in many functionsBuild helper functions that return selected entities

Useful Pattern

Wrap selectors in named functions when you reuse them:

kotlin
fun nearbyTaggedCreepers(): List<Entity> {
    val selector = EntitySelectorBuilder.allEntities()
        .type(EntityType.CREEPER)
        .tag("charged_target")
        .distanceBelow(16.0)
        .create()

    val source = requireServer().createCommandSourceStack()
    return selector.findEntities(source)
}

Then the rest of your script can talk about intent instead of selector syntax:

kotlin
for (creeper in nearbyTaggedCreepers()) {
    println("Found ${creeper.name.string}")
}

When Not to Select

If an event already gives you the player, block, entity, or world, use that event argument directly. Selecting again is usually slower and less precise than using the object Katton has already handed you.

See Events for event-driven alternatives.