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.