Skip to content

AI and saved data

Lesson 7.9

The C++ wrappers own paths for you and return Expected<> values. This lesson moves mobs, gives them goals and saves data; the C lesson has the same calls in plain C.

Paths

clight::Level lv(level);
clight::Entity mob(raw_mob);
cl_path_opts o = CL_PATH_OPTS_INIT;
o.doors = 1;
o.climb = true;
auto walked = mob.path_to({x + 0.5, double(y), z + 0.5}, 1.0, &o);    // Expected<void>; CL_E_NOT_FOUND: no path at all

o.entity = raw_mob;
clight::Path p = lv.find_path(mob.position().value(), {x + 0.5, double(y), z + 0.5}, &o);   // released by the last copy
if (p) {
    auto info = p.get_info();                    // reaches_target, nodes, length ...
    auto nodes = p.nodes();                      // Expected<std::vector<cl_block_pos>>
    mob.follow_path(p.raw(), 1.2);
}
if (mob.path_state() == CL_PATH_STUCK) mob.path_stop();

Goals

Goal callbacks are plain functions (or captureless lambdas); register the type once:

cl_goal_type_desc d = CL_GOAL_TYPE_DESC_INIT;
d.id = CL_STR("my_mod:guard");
d.flags = CL_GOAL_LOOK;
d.can_use = [](cl_entity e, cl_goal, void*) -> bool { return nearest_player(e).has_value(); };
d.tick = [](cl_entity e, cl_goal, void*) {
    if (auto p = nearest_player(e)) clight::Entity(e).look_at_entity(*p, 0, 0);
};
cl_ai_goal_type_register(&d, nullptr);

clight::Goal g = clight::add_goal(mob, 2, "my_mod:guard").value();
g.running();                                     // later
g.remove_goal();
cl_ai_type_goal(CL_STR("minecraft:iron_golem"), 2, CL_STR("my_mod:guard"));   // every golem, also after loading

nearest_player stands for your own helper (for example Level::nearest_player).

Saved data

mob.data_set("my_mod:owner", "\"Steve\"", 0);                  // SNBT text
auto owner = mob.data_get("my_mod:owner");                      // Expected<std::string>
clight::Buf raw = mob.data_get_nbt("my_mod:owner");             // binary NBT (type byte + payload)
lv.chunk_data_set(x >> 4, z >> 4, "my_mod:claim", "{owner:\"Steve\"}");
lv.saved_data_set("my_mod:stats", "{visits:1}");

Data with a default

A typed attachment gives a key a default and decides whether a player keeps it through death (API 1.20):

cl_attachment_desc d = CL_ATTACHMENT_DESC_INIT;
d.default_snbt = CL_STR("{value:20}");
d.flags = CL_ATTACH_SYNC;                  // without CL_ATTACH_COPY_ON_DEATH: back to the default after a death
cl_attachment_register(CL_STR("mana"), &d);

cl_entity_data_get then returns {value:20} for a player who has none yet. More in the guide Data Attachments.

Exercise

Write a goal that makes a mob walk back to a saved home position (my_mod:home, {x:..,y:..,z:..} on the mob) when it is more than 10 blocks away.

Show a solution
static std::optional<cl_vec3> home_of(cl_entity e) {
    auto s = clight::Entity(e).data_get("my_mod:home");
    if (!s) return std::nullopt;
    double x = 0, y = 0, z = 0;
    if (std::sscanf(s->c_str(), "{x:%lfd,y:%lfd,z:%lfd}", &x, &y, &z) != 3) return std::nullopt;
    return cl_vec3{x, y, z};
}
static bool far_from_home(cl_entity e, cl_goal, void*) {
    auto h = home_of(e);
    auto p = clight::Entity(e).position();
    if (!h || !p) return false;
    const double dx = h->x - p->x, dz = h->z - p->z;
    return dx * dx + dz * dz > 100.0;
}
static void go_home(cl_entity e, cl_goal, void*) {
    if (auto h = home_of(e)) clight::Entity(e).path_to(*h, 1.0, nullptr);
}

void register_home_goal() {
    cl_goal_type_desc d = CL_GOAL_TYPE_DESC_INIT;
    d.id = CL_STR("my_mod:go_home");
    d.flags = CL_GOAL_MOVE;
    d.can_use = far_from_home;
    d.start = go_home;
    cl_ai_goal_type_register(&d, nullptr);
}

Store the home with mob.data_set("my_mod:home", "{x:1.5d,y:64.0d,z:-3.5d}", 0).

Recap: Entity::path_to, Level::find_path and the RAII Path plan and walk, goal types run your code inside the mob’s goal list, and data_set / chunk_data_set / saved_data_set keep values. Guides: AI and Pathfinding, Saved Data.