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
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 mindset | Katton mindset |
|---|---|
| Select entities, run a command on each one | Select entities, loop over objects |
| Store intermediate values in scoreboards | Store local values in Kotlin variables |
Use execute as ... at ... to change context | Pass the entity or level directly to a function |
| Repeat selector filters in many functions | Build helper functions that return selected entities |
Useful Pattern
Wrap selectors in named functions when you reuse them:
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:
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.
