IMIsle Modkit
Pages in this area

species

Server

modkit.species manages custom playable species. A custom species is a host species from the game with a new look, new values and its own attacks. The player controls a real host dinosaur, so movement, swimming, hunger, growth, saving and the game's own rules stay as they are. The client mod hides the host model and shows the custom model instead. The server applies the values and runs the attacks. The custom species guide walks through the whole flow.

Definitions come from two places. The Server Tool writes species\<name>\species.json under the server root, one folder per species, and builds the model into client-mods\species_<name>.pak. Scripts define a species with modkit.species.define. A script definition replaces a file definition of the same name while the server runs. The plugin looks at the files every 5 seconds and loads them again when they changed. Values and attacks apply right away. New models reach a player on the next join, because the launcher downloads the pak files before the game starts.

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

Definition fields

Every field is optional except host. A missing field has its default. Distances are Unreal units (100 per metre), times are seconds, shares go from 0 to 1, angles are degrees.

Field Type Default Description
label string the name display name
host string required key of the game species the player really plays, from modkit.ai.species()
enabled boolean true a disabled species can not be assigned and is not sent to the clients
disableHostAttacks boolean false damage from the host's own attacks is dropped. Use it when the species has custom attacks only.
source table data of the asset pipeline. Kept as it is, not sent to the clients.

visual

Field Default Range Description
scale 1 0.05 to 20 size of the custom model relative to the host at full growth
offset {0, 0, 0} -5000 to 5000 each shift of the model on the host
yaw 0 -360 to 360 turns the model when it faces the wrong way
hideHost true hides the host model
attachTo "capsule" "capsule" or "mesh"
castShadow true

animations holds one entry per role. A role is a short id of an animation (idle = "idle"), a table { anim = "idle", rate = 1, loop = true }, or a blend space { blend = { { anim = "swim", x = 0, y = 0 }, ... }, rate = 1 } with up to 32 samples. x runs from -1 (turning left) to 1 (turning right), y from -1 (diving) to 1 (climbing). rate goes from 0.05 to 10.

A blend role plays the sample closest to the current turn and pitch. The client switches to another sample only when it is clearly closer and the current one has played for at least 0.3 seconds, and the new sample continues at the same point of its cycle, so a stroke does not restart. When the built blend space asset carries blend data, the client blends the samples smoothly instead.

Role When
idle standing on land
walk, trot, run moving on land by speed. A missing role falls back to the next slower one.
swimIdle in water without speed, falls back to idle
swim in water at normal speed, falls back to swimIdle
swimFast in water above the swimFast threshold, falls back to swim
swimBack swimming backwards, falls back to swim
hit got hit, plays once
death died, plays once, then the last frame holds or deathPose loops
deathPose loop after death
rest, eat, drink, call optional, otherwise idle

thresholds sets the speeds in units per second at which the roles switch: { trot = 350, run = 700, swimFast = 900 }, each from 1 to 10000. A missing threshold comes from the host's movement values.

stats: a number replaces the host value, { factor = 1.5 } multiplies it.

Field As number As factor Description
health 1 to 1000000 0.01 to 100 maximum health
damageTaken 0 to 100 share of incoming damage. A plain number is read as a factor too.
walkSpeed, runSpeed 1 to 20000 0.05 to 10 land speeds. runSpeed scales the trot speed along with it. A plain number applies at every growth stage.
swimSpeed, swimFastSpeed 1 to 20000 0.05 to 10 water speeds
stamina 1 to 1000000 0.01 to 100 maximum stamina
growthSeconds 60 to 2592000 0.01 to 100 time from hatchling to adult. Applied through the growth control, so player:setGrowthMultiplier overrides it.
hungerRate, thirstRate 0 to 100 decay of hunger and thirst. A plain number is read as a factor too.

habitat

Field Default Range Description
water true may be in water
land true may be on land
outOfHabitatDamage 0 0 to 1 damage per second outside the habitat as a share of maximum health. 0 turns it off.
outOfHabitatGrace 10 0 to 3600 seconds before the damage starts

attacks holds up to 16 entries.

Field Default Range Description
id required 1 to 32 characters from a-z, 0-9 and _
label the id
source "custom" "custom" for a modkit attack, "host:<ability>" to give a host attack a new animation. <ability> is a piece of the ability's class name. For a host attack only animation, animationLeft, animationRight and rate count.
key "" Unreal key name, for example "LeftMouseButton", "RightMouseButton", "E", "SpaceBar". The client mod binds the key while the player has this species.
animation "" short id of the attack animation
animationLeft, animationRight "" played instead of animation while the player turns left or right
rate 1 0.05 to 10 play rate of the animation
damage 0 0 to 1000000
staminaCost 0 0 to 1000000 taken when the attack starts
cooldown 1 0 to 3600 seconds until the same attack may start again
hit.time 0.3 0 to 30 seconds after the start until the hit window opens
hit.duration 0.15 0.01 to 10 length of the hit window
hit.shape "cone" "cone" uses range, angle and height. "sphere" uses range as the distance of the centre in front of the attacker and radius.
hit.range 400 0 to 20000
hit.angle 60 1 to 360
hit.radius 200 0 to 20000
hit.height 300 0 to 20000 vertical tolerance of the cone
move "free" "free", "slow" (half speed until the hit window closes) or "locked"
allowIn { "water", "land" } where the attack may start
minGrowth 0 0 to 1
targets.players, targets.ai true, true AI targets are animals with a handle, see ai
knockback 0 0 to 100000 launch strength away from the attacker

access

Field Default Range Description
permission "" acl permission a player needs. Empty means everyone.
maxPlayers 0 0 to 1000 how many players may have the species at the same time. 0 means no limit.

How an attack runs

  1. The player presses the key. The client mod sends the request to the server and plays nothing yet.
  2. The server checks the species, the growth, the cooldown, the stamina, that the player is alive and not in another attack, and allowIn against the water.
  3. customAttack fires. A handler that returns false cancels the attack.
  4. The stamina is taken, the cooldown starts, and every player within 600 m receives the animation cue.
  5. From hit.time on, for hit.duration, the server tests the shape against the positions and facing of the players and the animals with a handle. Every victim is hit once per attack. Damage goes through the normal damage path, so god mode and damage free zones apply. A victim with a custom species takes damageTaken times the damage.
  6. customAttackHit fires for every hit.

A host attack with source = "host:<ability>" keeps the game's own hit detection and damage. The server only tells the clients which animation to play when the ability fires.

modkit.species.list

modkit.species.list()

Returns: the names of all species, sorted.

modkit.species.get

modkit.species.get(name)

Returns: the definition with every field filled in, defaults included, or nil.

local mosa = modkit.species.get("mosasaurus")
print(mosa.label, mosa.host, #mosa.attacks)

modkit.species.reload

modkit.species.reload()

Loads every species.json again right away. A file with broken JSON is skipped with a log line, and the definition from before stays. A bad value in a file is replaced by its default, and a log line names the field.

Returns: true.

modkit.species.define

modkit.species.define(name, definition)

Defines a species for as long as the server runs. Nothing is written to disk. The model and the animations must already be in a pak under client-mods, named after the species. An unknown field, a wrong type, a value outside its range, a missing host or a bad name raises a Lua error that names the field.

Returns: true.

modkit.species.define("night_deino", {
  label = "Night Deinosuchus",
  host = "deinosuchus",
  stats = { health = { factor = 1.5 }, swimSpeed = { factor = 1.2 } },
  habitat = { land = false, outOfHabitatDamage = 0.02, outOfHabitatGrace = 15 },
  access = { permission = "species.night" },
})

modkit.species.players

modkit.species.players(name)

Returns: array of player tables, the online players that have this species.

player:setCustomSpecies

player:setCustomSpecies(name [, force])

Gives the player the species, or removes it with nil. When the player plays another species than the host right now, the server first turns them into the host with player:setSpecies, which means a fresh dinosaur with full health and no skin. The values and attacks follow on the next spawn.

Without force the access rules apply: the permission and maxPlayers. force = true skips both.

The assignment is kept per Steam ID in data\species\players.json and applied again after every spawn as the host species. A spawn as another species removes the assignment. Parking in the garage stores the species with the dinosaur, and a restore brings it back.

Returns: true, or false and one of "unknown species", "species is disabled", "no permission", "species is full", "could not switch to the host species: ...".

modkit.commands.add("species", function(player, name)
  if not name then return player:message("Usage: /species <name> or /species none") end
  local ok, why = player:setCustomSpecies(name ~= "none" and name or nil)
  player:message(ok and "Done." or why)
end)

player:getCustomSpecies

player:getCustomSpecies()

Returns: the species name, or nil.

The player table also carries customSpecies with the same value.

Events

Event Arguments When
customSpeciesChanged player, from, to the species of a player was set or removed. from and to are names or nil.
customAttack player, attackId a custom attack is about to start. Return false to cancel it.
customAttackHit player, attackId, victim, damage a custom attack hit. victim is a player table or { kind = "ai", id = 12 }.
modkit.events.add("customAttack", function(player, attackId)
  for _, zone in ipairs(modkit.zones.at(player.x, player.y, player.z)) do
    if zone == "sanctuary" then return false end
  end
end)

modkit.events.add("customAttackHit", function(player, attackId, victim, damage)
  if victim.steamId then victim:message(player.name .. " hit you for " .. damage) end
end)