IMIsle Modkit
Pages in this area

ai

Server

modkit.ai spawns AI creatures, counts them and gives them a brain of your own. Every spawn creates a pawn and a matching AI controller, lets the controller possess the pawn, fills its stats and registers it with the game's AI world spawner.

The first spawn of a class may load the blueprint from disk on the game thread. That can cause one short hitch. Later spawns of the same class are fast.

The first half of this page covers spawning and counting. The second half, from Brains on, covers custom AI. The custom AI guide walks through it step by step.

modkit.ai.species

modkit.ai.species()

Returns: array of the 39 keys that spawn accepts, all lower case. Dinosaurs such as "tyrannosaurus", "carnotaurus", "psittacosauruscoastal", animals such as "boar", "deer", "goat", "rabbit", "chicken", "crab", "bullfrog", "lizard", "seaturtle", and the fish schools "rainbowfish", "coalacanth", "muskelunge", "longear".

print(table.concat(modkit.ai.species(), ", "))

modkit.ai.spawn

modkit.ai.spawn(species, x, y, z [, options])

Parameter Type Description
species string key from modkit.ai.species(). Case and punctuation are ignored.
x, y, z number position. The pawn spawns 5 m above z and drops.
options.count integer 1 to 25, default 1. More than one are placed in rows of five, 3 m apart. Animals from one call form a pack.
options.growth number 0.01 to 1. Default 1, or stats.growth of the brain when options.brain is set.
options.yaw number facing in degrees
options.behavior string "default", "passive" (always flees) or "aggressive" (never flees, starts in the aggressive state). Has no effect on an animal with a brain.
options.brain string name of a brain. The animal runs on that brain from its first moment.
options.home table home point for the brain, { x, y, z } or { x = , y = , z = }. Default is the spawn position.

Returns: array of integer handles, or nil and a reason. Reasons are "unknown AI species", "unknown brain", "AI class could not be loaded", "too many script spawned AI alive (limit 500)", "spawn failed".

modkit.commands.add("boars", function(player)
  local x, y, z = player:pos()
  local ids, why = modkit.ai.spawn("boar", x + 800, y, z, { count = 3, behavior = "passive" })
  player:message(ids and (#ids .. " boars spawned") or why)
end, true)

modkit.ai.despawn

modkit.ai.despawn(handle)

Returns: true when the creature was still there and got destroyed. Fires aiDespawned with the reason "script".

modkit.ai.list, count

modkit.ai.list() returns the creatures with a handle that still exist, as tables with id, species, alive, x, y, z. These are the creatures your scripts and spawners put into the world, plus every wild animal a brain took over. A dead creature stays in the list until the game removes its body. modkit.ai.count() returns the length of that list.

for _, a in ipairs(modkit.ai.list()) do print(a.id, a.species, a.alive) end

modkit.ai.census

modkit.ai.census()

Counts every AI controlled character in the world, including the game's own AI.

Returns: a table species -> count and the total as second value.

This walks all game objects. Call it on demand, not in a timer that runs every second.

local counts, total = modkit.ai.census()
print("AI in the world: " .. total)

modkit.ai.wipe

modkit.ai.wipe()

Destroys every AI controlled character in the world, the game's own AI included. The game spawns new AI afterwards as usual.

Returns: number of destroyed creatures. Walks all game objects.

Brains

A brain describes how an animal senses, moves, picks targets, fights, flees, behaves in a group and when it disappears. An animal with a brain keeps its body, its pathfinding and its attacks from the game. The game's own decision making for that animal is switched off and the brain decides instead. Taking the brain away gives the animal back to the game.

There are three ways to put a brain to work.

Way What it does
modkit.ai.spawn with options.brain, or modkit.ai.setBrain one animal, from a script
a spawner keeps a number of animals with a brain alive around a point and refills after deaths
wild AI gives every animal of one species that the game spawns a brain

Brains come from two places. The Server Tool writes data\ai\brains.json and data\ai\spawns.json under the server root. Scripts define brains with modkit.ai.defineBrain. A brain from a script replaces a file brain of the same name while the server runs. The file stays as it is. The plugin looks at both files every 5 seconds and loads them again when they changed.

Brain names and spawner ids have 1 to 32 characters from a-z, 0-9 and _.

Units

Distances are Unreal units, 100 units are 1 m. Times are seconds. Shares and chances go from 0 to 1. Angles are degrees. Species names are compared without regard to case and punctuation.

Brain fields

Every field is optional. A missing field has its default.

Field Type Default Description
label string the name display name in the Server Tool
preset string "custom" a marker for the Server Tool: "predator", "prey", "guard", "passive" or "custom". No effect on behaviour.

senses

Field Default Range Description
sightRange 6000 0 to 200000 how far the animal sees targets. An animal looks for targets while a player is within 150 m of it, so a player is spotted at 15000 at most.
sightAngle 360 1 to 360 view cone in degrees, 360 sees all around
hearingRange 9000 0 to 200000 range for calls of its own species and for the noise of a fight
memorySeconds 12 0 to 3600 how long a target out of sight is remembered
lineOfSight false check the line of sight to a player with a trace before spotting. Runs only while a player is within 60 m.

movement

Field Default Range Description
roamRadius 4000 100 to 500000 roaming area around the home point
roamPauseMin, roamPauseMax 2, 6 0 to 3600 pause between two walks
roamGait, chaseGait, fleeGait "walk", "sprint", "sprint" "walk", "trot" or "sprint"
leash 8000 0 to 1000000 how far beyond the roaming area a chase goes. 0 means no leash.
homeMode "spawn" "spawn" keeps the home point where the animal started. "wander" moves the home point along with the animal.

targets

Field Default Description
players true players are possible targets
ai false other animals with a brain are possible targets
onlySpecies {} when filled, only these species
ignoreSpecies {} never these species
ignoreSameSpecies true never its own species
minGrowth, maxGrowth 0, 1 growth of the target, 0 to 1
ignorePermission "" players with this acl permission are never a target

temperament

Field Default Range Description
aggression 0 0 to 1 chance to go after a target it sees. 0 never attacks first, 1 always does. The animal decides once per target and keeps the decision for memorySeconds.
retaliate true hits back when it is hit. With false a hit animal fights back with the chance from aggression and flees otherwise.
fightToDeath false never flees
fleeHealth 0.25 0 to 1 flees below this share of its health
fleeFromBigger true flees from a bigger player
fearSizeRatio 1.25 0.1 to 100 a player counts as bigger when the body is this many times as wide as the animal's
fleeFromEveryone false prey animal. Flees from every player it sees and from the noise of a fight nearby.
fleeSeconds 8 1 to 3600 shortest length of a flight
activeAt "any" "any", "day" (06:00 to 18:00) or "night". Outside that time the animal rests at its home point.

attack

Field Default Range Description
rangeFromBody true reach is the body radius times bodyRangeMultiplier plus biteLength
bodyRangeMultiplier 2.5 0.1 to 100
biteLength 250 0 to 10000
range 400 10 to 100000 fixed reach, used when rangeFromBody is false
cooldown 1.0 0.1 to 600 seconds between two attacks
autoAbilities true with an empty abilities list the brain picks the attacks of the species by itself
abilities {} up to 32 your own attacks, see below

An entry of attack.abilities is { name = "bite", weight = 1, minRange = 0, maxRange = 0, cooldown = 0 }.

Field Default Description
name required a piece of the ability's class name, case is ignored. modkit.ai.abilities(id) lists the full names.
weight 1 0 to 1000. Share in the random pick. 0 is never picked.
minRange, maxRange 0, 0 distance window to the target. maxRange 0 means the normal reach.
cooldown 0 seconds until this ability may fire again. 0 means attack.cooldown.

Before each attack the brain collects every ability whose distance window fits and whose cooldown is over, then picks one at random by weight.

pack

Field Default Range Description
size 1 1 to 25 animals per group when a spawner spawns
shareTarget true roaming pack mates take over the target of a member
answerCalls true answers calls of its own species
callRange 6000 0 to 200000 how far a call is answered. Also the distance within which animals of the same brain and species count as one pack when they were not spawned together.
callChance 0.5 0 to 1 chance to answer one call

idle is a list of up to 16 entries { ability = "roar", minSeconds = 60, maxSeconds = 240, onlyAlone = false, whenBusy = true }. The animal uses the ability at a random moment between minSeconds and maxSeconds, again and again. onlyAlone waits until no player is in sight range. whenBusy = false limits it to the roam state. Defaults are 60, 240, false and true.

stats

Field Default Description
infiniteStamina true stamina is topped up while a player is within 150 m
growth 1.0 0.01 to 1, growth of animals spawned with this brain

despawn

Field Default Range Description
afterSeconds 0 0 to 604800 lifetime. 0 means for ever.
noPlayerWithin 0 0 to 1000000 removes the animal when no player was inside this radius for noPlayerSeconds. 0 turns it off.
noPlayerSeconds 120 1 to 604800

States

An animal with a brain is in one of six states. The names are part of the API.

State Meaning
roam walks to random points in its roaming area, with pauses
chase runs at its target and attacks in reach
flee runs away from a threat
return the leash ended a chase, or the animal is outside its area. It trots home.
rest outside activeAt. It walks home and stands still.
scripted a command from a script is running

How often a brain thinks

The distance to the nearest player sets the pace.

Nearest player Think interval Notes
up to 60 m 0.2 s
up to 150 m 1 s
up to 400 m 5 s no spotting of targets, no think callback
further 20 s no new paths either

In chase the interval is 0.1 s. In flee and scripted it is 0.5 s at most. The distance is checked once per second, so an animal far away picks up speed within a second when a player comes close.

All brains together get 2 ms per server tick. What does not fit waits for the next tick. At most 500 animals can have a brain at the same time. Beyond that setBrain returns false, spawners with a brain stop filling up and further wild animals stay with the game's AI.

modkit.ai.defineBrain

modkit.ai.defineBrain(name, definition)

Parameter Type Description
name string 1 to 32 characters from a-z, 0-9 and _
definition table the brain fields, plus the callbacks

Returns: true. An unknown field, a wrong type, a value outside its range or a bad name raises a Lua error that names the field.

Defining a name again replaces the brain. Animals that already run on it use the new values from their next think step.

modkit.ai.defineBrain("night_stalker", {
  label = "Night stalker",
  preset = "predator",
  senses = { sightRange = 9000, sightAngle = 200 },
  movement = { roamRadius = 12000, leash = 20000 },
  targets = { minGrowth = 0.3 },
  temperament = { aggression = 0.8, fleeFromBigger = false, activeAt = "night" },
  attack = { abilities = { { name = "bite", weight = 3 }, { name = "claw", weight = 1 } } },
  pack = { size = 2 },
})

modkit.ai.getBrain, brains

modkit.ai.getBrain(name) returns a table with every brain field filled in, defaults included, plus name and source ("lua" or "file"). It returns nil for an unknown name. Callbacks are not part of the table.

modkit.ai.brains() returns the names of all brains, sorted.

for _, name in ipairs(modkit.ai.brains()) do
  local brain = modkit.ai.getBrain(name)
  print(name, brain.source, brain.temperament.aggression)
end

modkit.ai.removeBrain

modkit.ai.removeBrain(name)

Removes a brain that a script defined. When brains.json holds a brain of the same name, that one takes over and the animals keep running. When only the file has the name, the file brain is dropped until the file is loaded the next time.

Animals left without a brain go back to the game's AI. Wild animals among them get a new brain when their species still has one assigned.

Returns: true when a brain of that name existed.

modkit.ai.reload

modkit.ai.reload()

Loads brains.json and spawns.json again right away. A file with broken JSON is skipped with a log line and the brains and spawners from before stay. A brain in the file with an unknown field or a bad value is loaded with the default for that field, and one log line names the problem.

Spawners that are no longer in the file lose their animals, with aiDespawned and the reason "spawner_removed".

Returns: true.

modkit.ai.setBrain

modkit.ai.setBrain(id, name)

Parameter Type Description
id integer handle from spawn, list or an AI event
name string or nil brain name. nil takes the brain away and gives the animal back to the game's AI.

Returns: true on success. false for an unknown handle, an unknown brain, or when 500 animals already have a brain.

The home point is the place where the animal stands at that moment. Change it with setHome.

local ids = modkit.ai.spawn("deer", x, y, z, { count = 4 })
for _, id in ipairs(ids or {}) do modkit.ai.setBrain(id, "shy_prey") end

modkit.ai.info

modkit.ai.info(id)

Returns: a table, or nil when the handle is unknown.

Field Type Description
id integer the handle
species string
brain string brain name
state string one of the states
target table or nil { kind = "player", steamId = "..." } or { kind = "ai", id = 12 }
health, maxHealth number absolute values
x, y, z number position from the last think step
home table { x, y, z } as named fields
lod integer 0 to 3, the row of the think interval table
spawner string id of the spawner that made the animal. Missing for other animals.

An animal without a brain gives a short table with id, species, health, maxHealth, x, y, z. Test info.brain to tell the two apart.

Targets in commands

Wherever a function takes a target, three forms work: a player table, a Steam ID as string, or the handle of another animal as number.

modkit.ai.moveTo

modkit.ai.moveTo(id, x, y, z [, gait])

Sends the animal to a point. The state is scripted until it arrives or 120 seconds passed. After that the brain decides again. gait is "walk", "trot" or "sprint", default is the brain's roamGait.

Returns: true, or false when the animal has no brain.

modkit.ai.follow

modkit.ai.follow(id, target [, distance])

The animal follows the target and keeps distance (default 600). It walks when it is close, trots when it falls behind and sprints when it is far behind. The state stays scripted until release, another command, or until the target is dead or gone.

Returns: true, or false when the animal has no brain or the target does not exist.

modkit.ai.attack

modkit.ai.attack(id, target)

Forces chase on this target, whatever the brain's targets and aggression say. A forced chase ignores the leash, memorySeconds and fleeHealth. It ends when the target is dead or gone. hold, moveTo, follow and flee end it earlier.

Returns: true, or false when the animal has no brain or the target is dead or does not exist.

modkit.ai.flee

modkit.ai.flee(id, target [, seconds]) modkit.ai.flee(id, x, y, z [, seconds])

The animal runs away from a target or from a point. seconds defaults to the brain's fleeSeconds.

Returns: true, or false when the animal has no brain or the target does not exist.

modkit.ai.hold

modkit.ai.hold(id [, seconds])

The animal stops and stands still, in the state scripted. Without seconds it holds until release or another command.

Returns: true, or false when the animal has no brain.

modkit.ai.release

modkit.ai.release(id)

Ends scripted. The brain decides again.

Returns: true, or false when the animal has no brain or is not in scripted.

modkit.commands.add("heel", function(player)
  for _, id in ipairs(myPets[player.steamId] or {}) do modkit.ai.follow(id, player, 800) end
end)

modkit.commands.add("stay", function(player)
  for _, id in ipairs(myPets[player.steamId] or {}) do modkit.ai.hold(id) end
end)

modkit.commands.add("free", function(player)
  for _, id in ipairs(myPets[player.steamId] or {}) do modkit.ai.release(id) end
end)

modkit.ai.setHome

modkit.ai.setHome(id, x, y, z [, roamRadius])

Moves the home point of one animal. roamRadius replaces the brain's movement.roamRadius for this animal.

Returns: true, or false when the animal has no brain.

modkit.ai.abilities, useAbility

modkit.ai.abilities(id) returns the class names of the abilities the species has, for example "GA_Bite_C". The list is empty for an unknown handle.

modkit.ai.useAbility(id, name) lets the animal use an ability right now. name is a piece of the class name, case is ignored. It returns true when the ability was found and started. Both functions work for every animal with a handle, with or without a brain.

modkit.commands.add("abilities", function(player, id)
  player:message(table.concat(modkit.ai.abilities(tonumber(id) or 0), ", "))
end, true)

Brain callbacks

Four optional functions in the definition table let a script take part in the decisions. They exist only for brains from defineBrain.

Field Called Return value
think(agent, senses) at every think step while a player is within 150 m, before the built-in decision nil lets the built-in logic decide. A table decides, see below.
pickAttack(agent, target, distance, abilities) before every attack. abilities holds the names that fit the distance right now. an ability name, or nil for the weighted pick
onStateChange(agent, from, to) after every change of state none
onDamaged(agent, attacker) when the animal was hit, before it reacts. attacker has the form of info.target and can be nil. none

agent is the table from modkit.ai.info.

senses has these fields. Both lists are sorted by distance and hold 16 entries at most.

Field Description
players list of { steamId, species, growth, distance, visible } inside sightRange. visible is true when the player is inside the view cone.
ai list of { id, species, distance }, the animals with a brain inside sightRange
threat the current target, otherwise the last attacker, in the form of info.target, or nil
timeOfDay hour of the day, 0 to 24
health share of health, 0 to 1

Decisions that think can return.

Table Effect
{ state = "chase", target = t } chase t, like modkit.ai.attack
{ state = "flee", from = t } flee from a target or from a point { x, y, z }
{ state = "moveTo", x = , y = , z = , gait = } walk to a point, like modkit.ai.moveTo
{ state = "hold" } stand still. Lasts 3 seconds, return it again to keep holding.
{ state = "roam" } go back to roaming

A callback runs inside the server tick. Keep it short, and do not start HTTP requests or storage writes from think.

An error in a callback is logged with the brain name. After 5 errors that callback is switched off for this brain, and the animals keep running on the built-in logic. Defining the brain again turns it back on.

modkit.ai.defineBrain("gate_guard", {
  preset = "guard",
  movement = { roamRadius = 1500, leash = 3000 },
  temperament = { aggression = 0, fightToDeath = true },
  think = function(agent, senses)
    for _, p in ipairs(senses.players) do
      local player = modkit.players.get(p.steamId)
      if player and not modkit.acl.allowed(player, "gate.pass") and p.distance < 2500 then
        return { state = "chase", target = p.steamId }
      end
    end
  end,
  pickAttack = function(agent, target, distance, abilities)
    if agent.health < agent.maxHealth * 0.5 then return abilities[1] end
  end,
  onStateChange = function(agent, from, to)
    if to == "chase" then modkit.log.info("guard " .. agent.id .. " attacks") end
  end,
})

modkit.ai.addSpawner, removeSpawner, spawners

modkit.ai.addSpawner(definition) adds a spawner for as long as the server runs. It is not written to spawns.json. A spawner with the same id as one from the file replaces it while the server runs. An unknown field, a bad value or a species that is not in modkit.ai.species() raises a Lua error.

Field Default Range Description
id required 1 to 32 characters from a-z, 0-9 and _
label the id display name
species required key from modkit.ai.species()
brain "" brain name. Empty leaves the animals to the game's AI.
x, y, z required centre
radius 3000 0 to 500000 spawn points are spread inside this radius
count 1 0 to 100 how many animals the spawner keeps alive
respawnSeconds 300 1 to 604800 wait after a death or despawn before the refill
growth from the brain 0.01 to 1 growth of the animals. Replaces stats.growth of the brain.
activeAt "any" "any", "day" or "night". Outside that time the spawner does not spawn.
playersWithin 0 0 to 1000000 spawns only while a player is inside this radius. 0 spawns always.
enabled true

A spawner fills up in groups of pack.size of its brain, one group per second. Each group spawns at a random point inside radius. That point is the home of the group, and its animals are one pack.

modkit.ai.removeSpawner(id) removes a spawner that a script added and despawns its animals. When spawns.json has a spawner of the same id, that one takes over and the animals stay. It returns true when the spawner existed.

modkit.ai.spawners() returns all spawners as tables with the fields above, plus alive (animals alive right now) and source ("lua" or "file").

modkit.events.add("serverStarted", function()
  modkit.ai.addSpawner({
    id = "lake_boars", species = "boar", brain = "shy_prey",
    x = -180000, y = 52000, z = 1500, radius = 6000,
    count = 6, respawnSeconds = 600, playersWithin = 60000,
  })
end)

modkit.ai.setSpeciesBrain, speciesBrains

modkit.ai.setSpeciesBrain(species, name) gives every animal of that species that the game itself spawns the brain name. An assignment from a script wins over the wild entry of the same species in spawns.json. nil as name ends the assignment from a script. The entry from the file applies again when there is one. Without one, the animals of that species that were taken over go back to the game's AI.

Returns: true. false for an unknown brain, and for nil when no script had assigned that species.

The plugin looks for new wild animals every 10 seconds. A wild animal that got a brain has a handle from then on and shows up in modkit.ai.list.

modkit.ai.speciesBrains() returns a table species -> brain name with the assignments from scripts and from the file.

modkit.ai.setSpeciesBrain("deer", "shy_prey")

AI events

Event Arguments When
aiSpawned id a script or a spawner put an animal into the world
aiStateChanged id, from, to an animal with a brain changed its state
aiTargetChanged id, target an animal with a brain has a new target, or none (nil)
aiDamaged id, attacker an animal with a brain was hit. attacker can be nil.
aiDied id, killer an animal with a brain died. killer is the last attacker of the 15 seconds before, or nil.
aiDespawned id, reason an animal with a handle is gone

target, attacker and killer have the form of info.target.

Reasons of aiDespawned.

Reason Meaning
"script" modkit.ai.despawn
"timeout" despawn.afterSeconds of the brain
"no_players" despawn.noPlayerWithin of the brain
"spawner_removed" the spawner was removed
"gone" the game removed the animal
modkit.events.add("aiDied", function(id, killer)
  if killer and killer.kind == "player" then
    local player = modkit.players.get(killer.steamId)
    if player then player:notify("You brought down a guarded animal.") end
  end
end)

Clients can not send these names.