Building blocks
Say what you mean: a spell, a sword, a potion, a status, a creature. Kiln picks the game data, the ids and the programs.
Works since 0.2.0-dev.14: on test servers, and much of it seen in the game (spells, abilities, items, creature rules, VFX). A few things are open in the game; see What works today. What 0.2.0-dev.16 added (marked New below) is tested on a lab server or in Kiln’s VFX tests, and not yet seen in the game.
A block is one thing your mod adds, said in its own terms: a spell with a mana cost and a projectile, a sword with a level
and an on-hit effect, a status that ticks. Kiln chooses which game entry to start from, derives the ids from your mod id and
the block’s name, writes the Impact programs, fills in the tooltip and works out what players’ games
need. There’s no base to name and no GUID to write.
The older ways still work and stay there for what blocks don’t cover yet: new items by clone, game data and string paths.
New in 0.2.0-dev.20: standalone loot tables/recipes, quest and dialogue blocks, world-block fields, and UI blocks. These require the dev.20 runtime and MDK or newer; see the release and test boundary.
Setup
In the mod project:
<KilnServices>Blocks</KilnServices>
Or start from a template: kiln new spell MySpells. The samples FrostboltBlock, ItemBlocks, AbilityBlocks,
CreatureBlocks and StationBlocks in the Kiln repository show every block below.
C# or JSON
Blocks.Define(name, block) in OnLoad, or the same object in blocks/*.json. Both make the same thing with the same ids.
var chilled = Blocks.Define("chilled", new StatusBlock
{
Name = "Chilled", Seconds = 8, TickEvery = 2, Look = StatusLook.Frost,
OnTick = [new EffectBlock { Damage = Damage.Ice(3), Flinch = Flinch.None }],
Modifies = { [Stat.MoveSpeed] = -0.5 },
});
var burst = Blocks.Define("frost-burst", new EffectBlock { Damage = Damage.Ice(40), Knockback = Knockback.Small, Applies = chilled });
Blocks.Define("frostbolt", new SpellBlock
{
Name = "Frostbolt",
Cast = new CastBlock { ManaCost = 30 },
Delivery = new ProjectileBlock { Speed = 30, OnImpact = [new EffectBlock { Area = new AreaBlock { Radius = 3, Effects = [burst] } }] },
Recipe = new RecipeBlock { Makes = 10, Needs = { [GameItems.Material_T1_Twigs] = 2 } },
});
{ "format": 1,
"statuses": { "chilled": { "name": "Chilled", "seconds": 8, "tickEvery": 2, "look": "frost",
"onTick": [ { "damage": { "ice": 3 }, "flinch": "none" } ], "modifies": { "move-speed": -0.5 } } },
"effects": { "frost-burst": { "damage": { "ice": 40 }, "knockback": "small", "applies": "chilled" } },
"spells": { "frostbolt": { "name": "Frostbolt", "cast": { "manaCost": 30 },
"delivery": { "type": "projectile", "speed": 30, "onImpact": [ { "area": { "radius": 3, "effects": [ "frost-burst" ] } } ] },
"recipe": { "makes": 10, "needs": { "Material_T1_Twigs": 2 } } } } }
The JSON schema gives VS Code completion and errors. A field that takes a block takes it inline, as what Define
returned, or by name: a block of this mod, or "other-mod:name".
For comparison, the same spell without blocks was a cloned ammo item, a cloned buff, two cloned templates, a recipe, five hand-made GUIDs and a dozen game field paths.
The blocks
| Block | What it makes | More |
|---|---|---|
EffectBlock |
What happens: damage, heal, restore or drain mana and stamina, apply or remove a status, knockback, an area, a spawn, a sound, a VFX, a chance, more effects after | below |
StatusBlock |
A buff or debuff: time, look, effects on apply, every tick and on expiry, stats it changes while it runs | below |
SpellBlock |
A staff spell: an ammo item with a cast, a delivery and a recipe | Abilities |
WandBlock |
A wand whose primary, heavy and special attacks are spells, with no ammo | |
| Deliveries | How it gets there: projectile (with homing and piercing), self, around, cone, beam, zone, chain, summon | |
AbilityBlock |
Cast on a key, a chat command, an item, or from code; costs and cooldown | Abilities |
PerkBlock |
An enhancement on an item: stat changes, on-hit, on-kill, on-hit-taken | |
TalentBlock, TalentTreeBlock |
A node in the game’s skill tree, or a tree of your own | Talents |
TriggerBlock |
Effects when a player enters a state or does something: sits, glides, eats; can stop the action | |
ItemBlock |
Material, potion, food, arrow, any of the game’s tools, watering can or water pouch | New items |
WeaponBlock |
A weapon of one of 11 classes, with exact tooltip numbers | |
ArmorBlock, CosmeticBlock |
Armour by slot and set; a vanity piece | 3D models |
RecipeBlock, LootBlock; dev.20+: LootTableBlock |
Where it’s made and from what; where it drops; reusable loot tables | |
StationBlock, props, furniture |
Things players place: a station with its own recipes, a rack, a chair | World blocks |
dev.20+: QuestBlock, DialogueBlock |
Quest chains and NPC conversations | Quests |
dev.20+: WindowBlock, HudLineBlock, KeyBindingBlock |
UI from data, bound to config and safe game values | UI blocks |
| Building materials, shapes, terrain | New building blocks and shapes for the hammer | |
CreatureBlock |
A creature: body, animation, AI, behaviour, stats, spawns | Creature AI |
NpcBlock |
A friendly NPC: body, talk, wares, roster place | Friendly NPCs |
VfxBlock |
Particles and a light, from nothing | below |
MaterialBlock |
A surface for a model, from PNGs and numbers | below |
SoundBlock |
A sound from your audio files | Sounds |
An effect’s Sound plays a sound block by its name, or a cue from Sounds and voice.
Updating C# from dev.19 to dev.20+: an item’s Loot.Tables entries use DropPlace now, for example
new DropPlace { DropsLike = GameItems.Material_T1_Twigs }. The old LootTableBlock name now describes a standalone
table with Entries; old placement initializers fail to build. JSON loot.tables keeps its existing shape.
Effects
An effect is what happens. It has no game entry of its own: it becomes part of the program of whatever triggers it, so the same effect works on a spell, a weapon, a status or a talent.
| Part | What it does |
|---|---|
Chance |
0 to 1: the whole effect happens this often |
On |
EffectOn.Target (default) or EffectOn.Caster: who cast or who wields |
Area |
everyone in Radius metres gets Effects; enemies, or friends with Affects |
Spawns |
a game template there, at the spawner’s level and faction |
Vfx, Sound, Screen |
a VFX, a sound, a full-screen effect for a player |
Damage, Knockback, Flinch |
a hit: Damage.Ice(40) + Damage.Fire(10) |
Heal, Restores |
Restore.Mana(20) + Restore.Stamina(10); below 0 drains |
Removes, Applies |
statuses taken off or put on: your blocks or the game’s (GameBuffs) |
Then |
more effects after this one, at the same target or place |
Casts |
an ability the target casts, with its costs and cooldown |
Where an effect runs and what its target is:
| Trigger | Target |
|---|---|
a projectile’s OnImpact |
none, a place: use an Area |
a projectile’s OnHit |
what it struck; with Pierce, each creature it flies through |
a spell’s OnCast |
the caster |
a status’s OnApply, OnTick, OnExpire |
who has it |
a weapon’s OnHit, OnKill; armour’s OnHitTaken |
what was hit or killed; who hit |
a perk’s or talent’s OnHit, OnKill, OnHitTaken |
as on a weapon |
an ability’s OnCast, an item’s OnUse or OnEat, a trigger’s Do |
the caster, the user, the player |
A hit an effect deals doesn’t trigger these again, so an effect can’t loop on itself.
New: ProjectileBlock.Pierce = 3 flies through three creatures, running OnHit on each once, and stops at the
fourth or at anything that isn’t a creature, where OnImpact runs.
Statuses
Seconds, Kind (buff or debuff), Look (StatusLook.Frost, Poison, Fire: the icon and body effect), Refreshes,
TickEvery, the three triggers, Description, and:
Modifieschanges stats while it runs and puts them back after:Modifies = { [Stat.MoveSpeed] = -0.5, [Stat.Strength] = 5, [Stat.ResistIce] = 0.25 }. Whole points for attributes, armour and max health; shares for speed, damage and resistances (0.1 is 10%).While = PlayerStateId.Glidingkeeps it on a player for as long as they’re in that state, with no timer.Vfxshows on whoever has it.- New:
Icon = "icons/chill.png"gives it a PNG icon of its own: square, 256x256 like the game’s, at most 1024x1024, and the mod then needsclients: mod. Until a player’s game has the picture ready, the buff bar shows the look’s icon, never a missing one. A game status’s icon (GameBuffs) works too. A perk takesIconthe same way.
Blocks.Define("weakened", new StatusBlock
{
Name = "Weakened", Seconds = 10,
Modifies = { [Stat.MoveSpeed] = -0.3, [Stat.MeleeDamage] = -0.2 },
OnExpire = [new EffectBlock { Restores = Restore.Stamina(20) }],
});
Items and weapons
The block solves the game’s formulas, so the tooltip shows what you wrote: Level = 5, Power = 30 is a level 5 weapon with
30 damage. The kind of item follows from what you fill in: Heals or OnUse makes a potion, Feeds food, Tool a tool,
Ammo an arrow.
var shard = Blocks.Define("ember-shard", new ItemBlock { Name = "Ember Shard", Stack = 100, Fuel = 120 });
Blocks.Define("healing-draught", new ItemBlock
{
Name = "Healing Draught", Heals = 150, Stack = 10,
Recipe = new RecipeBlock { Makes = 2, Needs = { [shard] = 1 } },
});
Blocks.Define("ember-sword", new WeaponBlock
{
Name = "Ember Sword",
Class = WeaponClass.Sword,
Stats = new WeaponStats { Level = 5, Power = 30, Rarity = Rarity.Rare },
OnHit = [new EffectBlock { Chance = 0.2, Damage = Damage.Fire(15) }], // in hand, no unlock needed
Recipe = new RecipeBlock { Needs = { [shard] = 4 } },
});
{ "format": 1,
"items": { "ember-shard": { "name": "Ember Shard", "stack": 100, "fuel": 120 } },
"weapons": { "ember-sword": { "name": "Ember Sword", "class": "sword",
"stats": { "level": 5, "power": 30, "rarity": "rare" }, "recipe": { "needs": { "ember-shard": 4 } } } } }
- Weapon classes: sword, axe, mace, greatsword, greataxe, hammer, daggers, bow, staff, shield, wand.
- Tools (new: every kind):
Tool = new ToolBlock { Harvests = ToolClass.Rake, Power = 750, Range = 5 }. Axe, pickaxe, rake, mining sieve, glider, grappling hook, fishing rod and torch, each with the numbers it has: a rake’sPowerandRange, a hook’sRange, a rod’sFishing { Strength, Endurance }, a torch’sDurability(seconds it burns), andBreaksInto(what it becomes when worn out, like the game’s burnt torch). A field a tool has no number for is an error that says why. - A VFX on it (new):
Visual = new VisualBlock { Vfx = ... }shows a VFX on the item in the hand, at the grip;Offsetmoves it up the blade. - Armour:
Slot,Set,Physical,Magical,Bonus; its look is a game item’s or a model of your own. - Water containers:
Kind = ItemKind.WateringCanorWaterPouch, withHolds(charges, up to 1,023) andFlow(how fast it fills and pours). - Perks:
Perks = [myPerk]on a weapon or armour. The game’s rule applies: a perk runs once its enhancement is unlocked at an upgrade. A weapon’s ownOnHitneeds no unlock. - Loot:
Likean existing item’s drops, or into drop tables.
Abilities
Blocks.Define("flame-step", new AbilityBlock
{
Name = "Flame Step", Key = "G", Command = "step",
Cast = new CastBlock { ManaCost = 20, StaminaCost = 10 }, Cooldown = 8,
Delivery = new ConeBlock { Range = 8, Angle = 60, Effects = [new EffectBlock { Damage = Damage.Fire(30) }] },
});
A key needs every player to have the mod; a chat command works for anyone. Abilities.Cast(caster, "mymod:flame-step")
casts it from code. A TalentBlock can Unlock it, so only players with that talent can cast it.
What players do
Blocks.Define("rest", new TriggerBlock { On = PlayerStateId.Sitting, Do = [new EffectBlock { Restores = Restore.Mana(5) }], Cooldown = 10 });
On takes any of the game’s player states and actions: entering a state, starting an action, eating. When, Unless,
Cooldown, OnEnd when a state is left, and Stop to stop the action.
Creatures and NPCs
A creature is four parts: a body, an animation, an AI and a behaviour. Each has premades taken from the game’s
creatures (Bodies.Vukah, AIs.Vukah, Behaviours.VukahMelee) that you can change with with { ... }.
Blocks.Define("vengeful-vukah", new CreatureBlock
{
Body = Bodies.Vukah, Animation = Animations.Vukah,
AI = AIs.Vukah with { Sight = 36, Aggro = 16 },
Behaviour = Behaviours.VukahMelee with
{
Rules = [new RuleBlock { Every = 1, When = [Is.HealthBelow(0.5)], Do = [Act.SetPhase("enraged"), Act.Effect(burst)] }],
},
Stats = new CreatureStats { Level = 20 },
Spawns = [new SpawnBlock { Replaces = "Enemy_Vukah_Melee", Chance = 0.15 }],
});
A game body with a new mind needs nothing on the players’ side. New: who it fights (Relations, a SenseFilter, the
world’s faction table), more AI fields (night sight, hearing, roaming, fleeing, calling for help) and rules that summon,
cast, heal and apply statuses: see Creature AI and behaviour.
A body and clips of your own are cooked from glTF and need every player to have the mod; they run on test servers but
aren’t yet confirmed in the game. kiln new body MyGolem starts one, and clips from another rig can be
retargeted. Is.Custom and
Act.Custom take your C#, and run on the tick thread. An NpcBlock uses a game NPC’s body and adds talk, a vendor and a
roster place: see Friendly NPCs and Creatures.
World blocks
Things players place: a StationBlock that opens a game workshop’s menu or has recipes of its own and works from its own
inventory over time, like the drying rack or the forge; props; furniture (containers with a lock, beds that set a
spawn point, seats, things to use with an effect); changes to how hard a game material is to dig; new building materials,
shapes and terrain materials. A world block in blocks/*.json is there from the first start. One defined in
C# is there from the next start, because the game reads these tables once.
A station that works on its own and lists Recipes has a workshop of its own: it runs only its recipes, and its menu
lists only them, titled with its name. Its menu’s title and list are still
open in the game.
New: a prop can have a model of your own, Visual = new VisualBlock { Model = "models/lantern.glb" }. In
blocks/*.json, kiln cook cooks the glTF by itself; from C#, add a model step to assets/cook.json. Its collision is
the box around the model, or Collider = new BoxCollider { Size = ... }. OnInteract works as for furniture. A
Visual.Vfx on a placed thing stays on it while it stands, like a lamp’s light.
VFX
VfxBlock makes particles and a light from nothing: no game data, no compiler.
var sparks = Blocks.Define("frost_sparks", new VfxBlock
{
Particles = [new ParticleBlock { Burst = 24, Lifetime = (0.4f, 0.9f), Speed = (2f, 5f), Spread = Shape.Cone(35),
Color = Gradient.Of("#ffffff", "#7fd4ff"), Texture = "vfx/shard.png", Blend = Blend.Additive }],
Light = new LightBlock { Color = "#7fd4ff", Radius = 6f, Brightness = 4f, Lifetime = 0.6f },
});
Attach it with a Vfx field on a cast, a projectile, an effect, an area or a status, or name one of the game’s with
"game:<name>". A VFX in a data file is there from the first start; one from code from the next. Kiln-built bursts
and the game’s VFX by name have been seen in the game; frost VFX on hits haven’t yet.
New, not yet seen in the game:
- Spawn shapes. Particles can start anywhere on a shape:
"shape": {"ring": 1.5}, orsphere(withinnerfor a hollow one),shell,hemisphere,box,disc,line."spread": "outward"sends them away from the shape’s centre: a shockwave ring, a burst out of a sphere. The shape follows the effect’s scale and rotation. - Streaks.
"align": "velocity"points each particle the way it moves, and"stretch": 0.05lengthens it with its speed: sparks, rain, trails. - One effect, many looks. Every Kiln VFX takes five parameters when it plays:
color,size,speed,brightnessandcount. They multiply what the block says, so one ember burst can be blue on a frost spell and twice as big on a boss. On a cast, a status or an effect:"vfx": { "name": "warm_glow", "parameters": { "color": "#80c0ff" } }. From code:Vfx.PlayAt(burst, at, parameters: new VfxParams().Set("color", new Color(64, 160, 255))). A projectile’s or a prop’s VFX can’t take them yet. - Your own particle code, for the advanced: Particle shaders.
Not yet: an effect’s own sound, decals, fog, custom meshes, and particles that draw a model.
Materials
MaterialBlock makes a surface for a model: its colour, how metal and rough it is, what glows, from PNGs and numbers.
It draws with the game’s own shaders. A model step in assets/cook.json names it like a game material, and a glTF’s own
materials can become blocks by themselves. Use blocks/*.json for materials, since the game lists them once at start.
Players need the mod (clients: mod). With "replaces", a material takes the place of one of the game’s: see
Material reskins.
What players’ games need
Kiln works it out from your blocks and writes clients for you:
| Your blocks contain | Players need |
|---|---|
| effects and triggers only | nothing |
| new items, spells, statuses, perks, abilities | Kiln (they get the definitions from the server) |
| your own sounds, icons, models or VFX, a key, talents | the mod |
Below a block
Tweak sets any game field the block doesn’t cover, on what the block made:
Blocks.Define("frostbolt", new SpellBlock { /* ... */ Tweak = { { GameFields.Items.MaxStackSize, 5 } } });
Names, ids and mistakes
- Ids come from your mod id and the block’s name. Keep names: saves hold the ids. To rename, keep the old name in
Was = "frostbolt". - A mistake names the block and field:
block spell bad, cast.manaCost: -1 is less than 0. A broken block makes nothing; the file’s other blocks still load. Analyzers andkiln lintcatch most of these before a server starts. kiln blocks listandkiln blocks show frostbolt --programsshow what a block became. In a test,Blocks.Lowered(name).