Skip to content

Entities

Lesson 7.5

Mobs, arrows, boats, dropped items: everything in the world that moves is an entity, a clight::Entity. This lesson shows how to spawn entities, move and turn them, change their size, react to every tick of one type, and register an entity type of your own.

Spawning

Level::spawn puts a new entity into the world and returns an Expected<Entity>:

cl_spawn_opts o = CL_SPAWN_OPTS_INIT;
o.yaw = 90.0F;                                   // face west
cl_vec3 at = player.pos();
at.x += 2;
if (auto pig = player.level().spawn("minecraft:pig", at, &o)) {
    clight::log::info("a pig, id {}", pig->id());
} else {
    clight::log::warn("no pig: {}", static_cast<int>(pig.error()));
}

cl_spawn_opts starts from CL_SPAWN_OPTS_INIT: yaw, pitch (degrees; yaw 0 looks south, 90 west), velocity (blocks per tick), nbt (a tag like /summon’s: CL_STR("{NoAI:1b}")) and finalize. Pass nullptr for the defaults. Spawning runs on the server tick, at a loaded position.

e.remove(CL_REMOVE_DISCARDED) makes an entity vanish, CL_REMOVE_KILLED kills it like /kill. Afterwards calls on it return CL_E_STALE_HANDLE.

Moving and turning

pig.set_velocity({0.0, 0.6, 0.0});         // a hop
pig.set_rotation(180.0F, 0.0F);            // face north
const cl_rotation r = pig.rotation().value_or(cl_rotation{});
const bool flying = !pig.on_ground();

Living entities turn their head too: head_rotation() / set_head_rotation(yaw, pitch). eye_position() gives the eyes and look_vector() a unit vector along the view.

Size and pose

zombie.set_dimensions(0.6F, 0.6F, 0.0F);   // width, height, eye height (0: 0.85 x height)
zombie.set_pose(CL_POSE_SWIMMING);         // the crawl animation
const cl_dimensions d = zombie.dimensions().value_or(cl_dimensions{});

The size drives collisions, hits and pathfinding and is saved with the entity; the pose is only the animation. reset_dimensions() goes back to normal. Players give CL_E_UNSUPPORTED.

Every tick of one type

clight::on<clight::ev::EntityTick>([](cl_ev_entity_tick& e) {
    if (e.client) return;                  // it runs on clients too
    clight::Entity cow(e.entity);
    if (cow.on_ground() && (clight::time_now_ns() & 63) == 0) cow.set_velocity({0.0, 0.5, 0.0});
}, {.filter = "minecraft:cow"}).detach();

Always set the filter: the game then skips every other type without calling your mod.

Your own entity type

void on_register() override {
    cl_entity_type_desc d = CL_ENTITY_TYPE_DESC_INIT;
    d.id = CL_STR("my_mod:slime_ball");
    d.kind = CL_ENTITY_KIND_MOB;                    // or CL_ENTITY_KIND_MISC / CL_ENTITY_KIND_PROJECTILE
    d.vanilla_fallback = CL_STR("minecraft:slime"); // what players without your mod see
    d.width = 0.5F;
    d.height = 0.5F;
    d.max_health = 6.0F;
    d.attributes = CL_STR("movement_speed=0.3");
    if (!clight::registry::entity_type_register(&d)) clight::log::error("slime_ball failed to register");
}

The server simulates your type with your size and health; other clients see the fallback, and Celestial clients with your mod can draw a model of your own (Models and Animation). A mob has no AI of its own: move it from EntityTick. More in the guide Custom Entities.

Try it

Spawn a zombie next to every player who joins, shrink it to 0.6 x 0.6 and make it crawl.

Show a solution
clight::on<clight::ev::PlayerJoin>([](cl_ev_player& e) {
    clight::Entity p(e.player);
    cl_vec3 at = p.pos();
    at.x += 3;
    if (auto z = p.level().spawn("minecraft:zombie", at, nullptr)) {
        z->set_dimensions(0.6F, 0.6F, 0.0F);
        z->set_pose(CL_POSE_SWIMMING);
    }
}).detach();

Recap: Level::spawn makes entities, set_velocity / set_rotation move and turn them, set_dimensions and set_pose change size and animation, ev::EntityTick with a filter runs every tick, and registry::entity_type_register adds your own type with a vanilla fallback.