AI and saved data
Lesson 7.9
This lesson moves mobs along paths, gives them goals written in C, and saves data on entities, chunks and the world.
Paths
cl_path_opts o = CL_PATH_OPTS_INIT;
o.doors = 1; /* walk through (and open) wooden doors */
o.climb = true; /* ladders, vines, scaffolding */
cl_vec3 home = {x + 0.5, y, z + 0.5};
if (cl_entity_path_to(mob, home, 1.0, &o) == CL_E_NOT_FOUND) { /* no path at all */ }
if (cl_entity_path_state(mob) == CL_PATH_DONE) { /* arrived */ }
Planning without moving gives an owned path:
o.entity = mob; /* the mob's size and navigation */
cl_path p = cl_level_find_path(level, from, home, &o);
if (p.h) {
cl_path_info info;
cl_path_get_info(p, &info); /* reaches_target, nodes, length, ... */
cl_path_release(p);
}
A path cost changes where paths go (negative: never step there):
static float avoid_water(cl_level level, cl_block_pos pos, void* user) {
cl_block_state s = 0;
cl_level_get_block(level, pos, &s);
return s == water_state ? -1.0f : 0.0f;
}
cl_path_cost_desc c = CL_PATH_COST_DESC_INIT;
c.id = CL_STR("my_mod:no_water");
c.cost = avoid_water;
cl_ai_path_cost_register(&c);
o.cost = CL_STR("my_mod:no_water");
Goals
A goal type is a set of callbacks; register it once, then add it to mobs:
static bool guard_can_use(cl_entity e, cl_goal g, void* user) {
cl_vec3 pos;
cl_entity_position(e, &pos);
cl_entity near;
return cl_level_nearest_player(cl_entity_level(e), pos, 8.0, &near) == CL_OK;
}
static void guard_tick(cl_entity e, cl_goal g, void* user) {
cl_vec3 pos;
cl_entity near;
cl_entity_position(e, &pos);
if (cl_level_nearest_player(cl_entity_level(e), pos, 8.0, &near) == CL_OK) cl_entity_look_at_entity(e, near, 0, 0);
}
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 = guard_can_use;
d.tick = guard_tick;
cl_ai_goal_type_register(&d, NULL);
cl_goal g;
cl_ai_add_goal(mob, 2, CL_STR("my_mod:guard"), &g); /* one mob */
cl_ai_type_goal(CL_STR("minecraft:iron_golem"), 2, CL_STR("my_mod:guard")); /* every golem, also after loading */
Goals are not saved; cl_ai_type_goal adds them again when mobs load. cl_ai_set_target(mob, player) chooses who a hostile mob attacks.
Saved data
Values are SNBT text (what /data get shows) or binary NBT in a cl_buf:
cl_entity_data_set(player, CL_STR("my_mod:home"), CL_STR("{x:12,y:64,z:-3}"), 0);
char text[256];
size_t len = 0;
if (cl_entity_data_get(player, CL_STR("my_mod:home"), text, sizeof text, &len) == CL_OK) { /* parse text */ }
cl_level_chunk_data_set(level, x >> 4, z >> 4, CL_STR("my_mod:claim"), CL_STR("{owner:\"Steve\"}"));
cl_level_saved_data_set(level, CL_STR("my_mod:stats"), CL_STR("{visits:1}"));
CL_DATA_SYNC in cl_entity_data_set also sends the value to Celestial clients with CLight. Everything is saved with the world, and vanilla ignores it.
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
Count how many times each chunk was entered by a player and keep the total for the whole world.
Show a solution
static void count_chunk(cl_level level, cl_vec3 pos) {
const int cx = (int)floor(pos.x) >> 4, cz = (int)floor(pos.z) >> 4;
char text[64];
size_t len = 0;
int n = 0;
if (cl_level_chunk_data_get(level, cx, cz, CL_STR("my_mod:visits"), text, sizeof text, &len) == CL_OK) n = atoi(text);
snprintf(text, sizeof text, "%d", n + 1);
cl_level_chunk_data_set(level, cx, cz, CL_STR("my_mod:visits"), (cl_str){text, strlen(text)});
int total = 0;
if (cl_level_saved_data_get(level, CL_STR("my_mod:visits"), text, sizeof text, &len) == CL_OK) total = atoi(text);
snprintf(text, sizeof text, "%d", total + 1);
cl_level_saved_data_set(level, CL_STR("my_mod:visits"), (cl_str){text, strlen(text)});
}
Call it from a tick.server_post handler when a player’s chunk changed.
Recap: cl_entity_path_to and cl_level_find_path move and plan, goal types run your C code inside the mob’s goal list, and the *_data_* functions keep values on entities, chunks and levels. Guides: AI and Pathfinding, Saved Data.