Skip to content

AI and saved data

Lesson 12.10

Your mobs can move on their own now: plan a path, walk it, open doors, climb ladders, and run goals that decide what to do next. And everything your mod knows can be saved: on a mob, on a player, on a chunk, or for the whole world.

Walk somewhere

local ok = mob:path_to(cl.vec(x, y, z), 1.0)          -- 1.0 = walking speed; false: no path at all
mob:path_to(home, 1.2, { can_open_doors = true, can_climb = true })
if mob:path_done() then cl.log("arrived (or gave up)") end
mob:path_stop()

mob:path_state() tells you more: "none", "moving", "done" or "stuck".

To plan without moving, ask the level:

local path = world:find_path(mob.pos, target, { entity = mob })
if path and path:info().reaches_target then
  cl.log("%d steps", #path:nodes())
end

Keep mobs away from places with avoid: return false for positions they must not use, or a number to make them less attractive.

mob:path_to(target, 1.0, { avoid = function(pos)
  if world:light(pos).block >= 12 then return false end   -- stay out of bright light
end })

Goals

A goal is a small plan with can_use (should I start?), start, tick and stop. CLight puts it into the mob’s own goal list, next to the vanilla goals:

cl.ai.add_goal(mob, 1, {
  flags = { "look" },
  state = { player = nil },
  can_use = function(e, goal, s)
    s.player = e.world:nearest_player(e.pos, 8)
    return s.player ~= nil
  end,
  tick = function(e, goal, s) e:look_at(s.player) end,
})
  • A lower priority number wins; two goals with the same flag ("move", "look", "jump", "target") never run together.
  • state is your goal’s own table: every callback gets it as the third argument.
  • Goals are not saved. Give every mob of a type a goal with cl.ai.type_goal("minecraft:pig", 4, { ... }) in lifecycle.common_setup: it is added again whenever a pig loads.
  • mob:set_target(player) picks who a hostile mob attacks; mob:clear_goals() removes all goals (vanilla ones too) when you want full control.

Save data

Any Lua value goes: tables, numbers, strings, booleans.

player:set_data("quests", { active = "find_the_cave", done = {} })
local q = player:data("quests")              -- nil when there is nothing

mob:set_data("owner", player.name)           -- saved with the mob
world:chunk_at(player.pos):set_data("claim", { owner = player.name })

local stats = world:saved_data("stats")      -- a table saved with the world
stats.visits = (stats.visits or 0) + 1       -- written at every save, no call needed

player:set_data(key, value, { sync = true }) also sends the value to the player’s Celestial client, where player:data(key) reads it (for a HUD, for example). Keys without a namespace get your mod’s id, so "quests" is "my_mod:quests".

Data with a default

cl.attachment gives a key a default value, and decides whether a player keeps it when they die (API 1.20):

cl.attachment("mana", { default = { value = 20 }, sync = true })   -- full again after a death
cl.attachment("souls", { default = 0, copy_on_death = true })      -- kept

cl.on("player.join", function(e)
  print(e.player:data("mana").value)   --> 20, even the first time
end)

More in the guide Data Attachments.

Exercise

Make /home save the player’s position on the player (/home set), and make a tamed mob follow the player home: /home go walks every mob tagged pet within 16 blocks to the saved spot.

Show a solution
cl.on("server.command_register", function()
  cl.cmd.register(cl.cmd.literal("home")
    :then_(cl.cmd.literal("set"):executes(function(ctx)
      local p = ctx:player()
      if not p then return 0 end
      p:set_data("home", { x = p.pos.x, y = p.pos.y, z = p.pos.z })
      p:send_message("Home saved.")
      return 1
    end))
    :then_(cl.cmd.literal("go"):executes(function(ctx)
      local p = ctx:player()
      local home = p and p:data("home")
      if not home then return 0 end
      for _, pet in ipairs(p.world:entities_near(p.pos, 16, { tag = "pet" })) do
        pet:path_to(cl.vec(home.x, home.y, home.z), 1.2, { can_open_doors = true })
      end
      return 1
    end)))
end)

Recap: path_to and find_path move and plan, cl.ai.add_goal and cl.ai.type_goal add goals, data / set_data keep values on entities and chunks, and saved_data keeps them for the world. More in the guides AI and Pathfinding and Saved Data.