Skip to content

Entity gameplay

Lesson 7.7

This lesson finds entities, changes their attributes and effects, sends synced data to clients, hurts them with damage types (your own too), lets your mobs spawn naturally and makes them ride each other. Everything works on vanilla mobs as well.

Find entities

A query descriptor filters a box:

cl_entity_query q = CL_ENTITY_QUERY_INIT;
q.type = CL_STR("#minecraft:undead");     /* a type id or a type tag */
q.flags = CL_QUERY_NEAREST;              /* nearest first; also CL_QUERY_LIVING, CL_QUERY_NO_PLAYERS, ... */
q.limit = 3;
cl_entity found[3];
size_t n = 0;
cl_vec3 lo = {p.x - 16, p.y - 8, p.z - 16}, hi = {p.x + 16, p.y + 8, p.z + 16};
if (cl_level_entities_in_box(level, lo, hi, &q, found, 3, &n) == CL_OK)
    for (size_t i = 0; i < n && i < 3; ++i) { /* found[i] */ }

cl_entity other;
if (cl_level_nearest_player(level, p, 32.0, &other) == CL_OK) { /* ... */ }

q.tag filters by entity tag (cl_entity_add_tag), q.category by MobCategory ("monster", "creature", …, "player"), q.exclude leaves one entity out. cl_entity_by_uuid(uuid, &e) finds an entity in any level.

Attributes and effects

double speed = 0;
cl_entity_attribute(zombie, CL_STR("movement_speed"), &speed);
cl_entity_add_attribute_modifier(zombie, CL_STR("movement_speed"), CL_STR("my_mod:haste"), 0.5, CL_ATTRIBUTE_ADD_MULTIPLIED_BASE);
cl_entity_remove_attribute_modifier(zombie, CL_STR("movement_speed"), CL_STR("my_mod:haste"));

cl_effect_opts eo = CL_EFFECT_OPTS_INIT;
eo.amplifier = 1;                                             /* Speed II */
cl_entity_add_effect(zombie, CL_STR("minecraft:speed"), 200, &eo);   /* 200 ticks; -1 = forever */

cl_entity_add_effect returns false when nothing changed (immune, or a stronger effect is on). Modifiers with different ids stack; the same id replaces.

Synced data

cl_entity_set_synced_number(boss, CL_STR("my_mod:anger"), 3.0);   /* server */
double anger = cl_entity_synced_number(boss, CL_STR("my_mod:anger"));   /* server or client */

Celestial clients with CLight get the value with the entity (and CL_EV_ENTITY_SYNCED when it changes); vanilla clients get nothing. At most 64 keys per entity.

Hurt, damage types, knockback

cl_hurt_opts h = CL_HURT_OPTS_INIT;
h.attacker = boss;            /* kill credit and the death message */
h.knockback = 0.0f;           /* no push from this hit */
bool hit = cl_entity_hurt(target, 4.0f, CL_STR("minecraft:magic"), &h);

Your own damage type, in CL_EV_LIFECYCLE_REGISTER:

cl_damage_type_desc d = CL_DAMAGE_TYPE_DESC_INIT;
d.id = CL_STR("my_mod:zap");
d.death_message = CL_STR("%1$s was zapped");
d.death_message_player = CL_STR("%1$s was zapped by %2$s");
d.tags = CL_STR("minecraft:bypasses_armor");
cl_damage_type_register(&d, NULL);

cl_entity_knockback(e, 0.4, dx, dz) pushes like a hit; CL_EV_ENTITY_KNOCKBACK lets you change (strength, dx, dz are mutable) or cancel every knockback.

Natural spawning

static bool high_enough(cl_level level, cl_str type, cl_block_pos pos, void* user) {
    (void)level, (void)type, (void)user;
    return pos.y > 60;
}
/* in CL_EV_LIFECYCLE_REGISTER */
cl_spawn_rule_desc r = CL_SPAWN_RULE_DESC_INIT;
r.type = CL_STR("my_mod:golem");
r.category = CL_STR("monster");
r.biomes = CL_STR("minecraft:dark_forest, #minecraft:is_taiga");
r.max_light = 7;
r.condition = high_enough;
cl_spawn_rule_register(&r);

The rule joins the biome’s spawn list like a data pack entry, so the monster cap and pack sizes work like vanilla.

Riding

cl_ride_opts ro = CL_RIDE_OPTS_INIT;          /* force = true */
cl_entity_set_passenger(horse, zombie, &ro);
cl_entity v;
if (cl_entity_vehicle(zombie, &v) == CL_OK) { /* v is the horse */ }
cl_entity_dismount(zombie);

ro.bone = CL_STR("back") seats the rider on a bone of the vehicle’s glTF model.

Equipment, new events and emitters

API 1.20 adds equipment, three entity events and particle emitters:

cl_entity_set_equipment(zombie, CL_EQUIP_HEAD, cl_stack_create_by_name(CL_STR("minecraft:diamond_helmet"), 1));
cl_entity_set_drop_chance(zombie, CL_EQUIP_HEAD, 1.0f);
cl_item_stack head = cl_entity_equipment(zombie, CL_EQUIP_HEAD);  /* owned: release it */
cl_stack_release(head);

static void no_phantoms(void* payload, void* user) {
    (void)user;
    cl_event_cancel(&((cl_ev_entity_spawn*)payload)->header);
}
/* cl_subscribe_opts o = CL_SUBSCRIBE_OPTS_INIT; o.filter = cl_ident_intern(CL_STR("minecraft:phantom"));
   cl_event_subscribe(CL_EV_ENTITY_SPAWN, &o, no_phantoms, NULL, NULL); */

cl_particle_spawn s = CL_PARTICLE_SPAWN_INIT;
s.pos = (cl_vec3){0, 2, 0};  /* an offset from the feet */
cl_emitter em;
cl_render_emitter_add(zombie, CL_STR("minecraft:flame"), CL_STR(""), &s, 2, &em);

player.interact_entity (right-click on an entity, cancellable) and entity.change_dimension (from, to) work the same way. Details: Entities, Particles.

Try it

Write a tick handler for my_mod:golem that gives every player within 6 blocks Slowness I for 2 seconds, once a second.

Show a solution
static void on_golem_tick(void* payload, void* user) {
    (void)user;
    const cl_ev_entity_tick* ev = (const cl_ev_entity_tick*)payload;
    if (ev->client || cl_level_game_time(ev->level) % 20 != 0) return;
    cl_vec3 p;
    if (cl_entity_position(ev->entity, &p) != CL_OK) return;
    cl_entity_query q = CL_ENTITY_QUERY_INIT;
    q.category = CL_STR("player");
    cl_entity found[16];
    size_t n = 0;
    cl_vec3 lo = {p.x - 6, p.y - 6, p.z - 6}, hi = {p.x + 6, p.y + 6, p.z + 6};
    if (cl_level_entities_in_box(ev->level, lo, hi, &q, found, 16, &n) != CL_OK) return;
    for (size_t i = 0; i < n && i < 16; ++i) cl_entity_add_effect(found[i], CL_STR("minecraft:slowness"), 40, NULL);
}

Subscribe it to CL_EV_ENTITY_TICK with the filter my_mod:golem.

Recap: cl_level_entities_in_box / cl_level_nearest_player find entities, attributes and effects change them, cl_entity_set_synced_* tells clients, cl_entity_hurt damages through the vanilla pipeline, cl_damage_type_register and cl_spawn_rule_register add damage types and natural spawning, cl_entity_set_passenger makes them ride. Guide: Entity Gameplay.