v50 Steam/Premium information for editors
  • v50 information can now be added to pages in the main namespace. v0.47 information can still be found in the DF2014 namespace. See here for more details on the new versioning policy.
  • Use this page to report any issues related to the migration.
This notice may be cached—the current version can be found here.

Difference between revisions of "Lua scripting"

From Dwarf Fortress Wiki
Jump to navigation Jump to search
(Putnam's examples)
(Some formatting, function name consistency)
(15 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
{{Template:Under_construction}}
 
{{Template:Under_construction}}
 +
[[File:Lua-Logo.svg|100px|right]]
 
{{Modding}}
 
{{Modding}}
  
 
''This article is about procedural raw generation. Information on [[Utility:DFHack]] scripting can be found at https://docs.dfhack.org/en/stable/.''
 
''This article is about procedural raw generation. Information on [[Utility:DFHack]] scripting can be found at https://docs.dfhack.org/en/stable/.''
 +
  
 
[https://www.lua.org/ Lua] scripting is an experimental feature{{version|51.06}}. It is used to create custom procedurally-generated objects that were previously created by hardcoded methods. It was announced in a [https://www.youtube.com/watch?v=Z9rzhFwgfUk video], with the stated goal of "supporting future [[Magic|magical]] endeavors."
 
[https://www.lua.org/ Lua] scripting is an experimental feature{{version|51.06}}. It is used to create custom procedurally-generated objects that were previously created by hardcoded methods. It was announced in a [https://www.youtube.com/watch?v=Z9rzhFwgfUk video], with the stated goal of "supporting future [[Magic|magical]] endeavors."
Line 8: Line 10:
 
Inorganic [[Material definition token|materials]], [[Language token|languages]], [[Creature token|creatures]], [[Interaction token|interactions]],  [[Item token|items]] (currently excluding instruments), [[reaction]]s, [[Entity token|entities]], and [[Plant token|plants]] are open to this system.
 
Inorganic [[Material definition token|materials]], [[Language token|languages]], [[Creature token|creatures]], [[Interaction token|interactions]],  [[Item token|items]] (currently excluding instruments), [[reaction]]s, [[Entity token|entities]], and [[Plant token|plants]] are open to this system.
  
Scripts are loaded from a mod's ``scripts/init.lua`` file, as well as from any included files.
+
Scripts are loaded from a mod's ``scripts/init.lua`` file, and can ``require()`` other files.
  
==Code Samples==
+
==Structure==
  
===Divine language===
+
As of right now, Lua scripting is confined to generation of procedural objects. This is done by running the ``generate`` function, a global function loaded in ``'''data/init/generators.lua'''``. It runs unit tests, preprocess, do_once, materials, items, languages, creatures, interactions, entities and postprocessing, in that order.
This is the [[divine language]], which generates a bunch of random-sounding words from a set of syllables.
 
  
<!--Needs support for {{raw}} in Scriptdata to condense -->
+
When random objects are first generated, the game populates two global tables, ``world`` and ``random_object_parameters``. ``world`` contains info about the world currently being generated (or, in the future, played in), while ``random_object_parameters`` contains what the game expects to be generated. The most important thing between these is ``random_object_parameters.main_world_randoms``, which is ``true`` for exactly one generation at the start of worldgen; it's what you want to check for if you're generating your own objects outside of ``do_once`` and the standard generation contexts.
{{Main:Divine language/script}}
 
  
===Identity language===
+
===Debugging===
This makes a [[Language token|language]] called ``GEN_IDENTITY`` which is, like. "Abbey abbeyabbeys the abbey of abbeys" - i.e. it's the "English" language you might see occasionally.
+
You can set the global ``debug_level`` variable to print some debug info. It's a number, but what numbers are there are completely arbitrary. If it's >0, it'll run unit tests; if it's >=0.5, it'll display what step of generation it's at, at every step. You can use ``get_debug_logger(x)`` to return a function that logs to ``lualog.txt`` if the debug level is at least ``x``.
 +
 
 +
Unit tests are functions that return a table, containing ``good``, which, if truthy, is considered passed, and ``info``, which is a string that contains information on said pass or fail. These unit tests should have no side effects, i.e. they shouldn't muck with global state any. Here's an example unit test, one that was used during development (but had no reason to be removed):
  
 
{{Scriptdata
 
{{Scriptdata
|title=GEN_IDENTITY
+
|title=get_random_creature
 
|script=
 
|script=
languages.GEN_IDENTITY=function()
+
     get_random_creature=function()
    -- just to demonstrate the absolute most basic method of generating one of these
+
         local cr=world.creature.get_random_creature()
     -- also so that you can just mod stuff to use GEN_IDENTITY
+
         local res={}
    local tbl={}
+
         res.good=type(cr)=='table'
    local unempty = function(str1, str2)  
+
         res.info=res.good and ("Got a random creature: "..cr.token) or "No random creature could be gotten, even at most permissive!"
         return str1=='' and str2 or str1
+
         return res
    end
+
    end}}
    for k,v in ipairs(world.language.word) do
+
 
         local str=''
+
===Object generation===
         str=unempty(str,v.NOUN_SING)
+
The first steps after debugging are ``preprocess`` and ``do_once``. These are tables of functions, running each one at a time. This is where you want your side effects.
         str=unempty(str,v.ADJ)
+
 
         str=unempty(str,v.VERB_FIRST_PRES)
+
If you're registering an entirely new procedural object type, you can use these steps. ``do_once`` only runs in the "main world randoms" generation call and is the safest option for adding new objects. The [[Lua script examples#Adamantine alloys|"adamantine alloys" example]] is done through these steps. You can also add functions to ``postprocess``, which is like preprocessing but run after all the other generation steps.
        str=unempty(str,string.lower(v.token))
+
 
        tbl[v.token]=str
+
You can also mess around with ``random_object_parameters`` in preprocessing. Vanilla demon types are assigned here, and you can change the proportions as an end user if you want.
    end
+
 
    return tbl
+
====Generation from list====
end}}
+
The game then generates all of the individual objects; the general procedure for this is that the game calls the ``generate_from_list()`` function on a table of functions, which calls every function and picks one of the resulting values at random depending on their weights.
  
===New divine metal===
+
For example, the ``interactions.secrets`` table contains one entry, that for necromancers; it returns a table containing three entries: ``{interaction=tbl,weight=1,spheres=spheres}``.
You can add new metal descriptions for divine metal pretty easily, for example:
+
<!--Pre-release information: arbitrary tbl keys (ie: "interaction") to be made equivalent with "raws"-->
 +
*``interaction`` is the full raw text of the interaction
 +
*``weight`` is the random weight for the interaction, i.e. if you add another function which returns a table containing ``weight=2``, that will be twice as likely as necromancers.
 +
*``spheres`` is some extra data the generator might be able to use. It actually doesn't, at this point, but one could override ``generate_random_interactions()`` with their own version that takes into account ``spheres`` and, say, tries to evenly distribute generated secrets over available spheres. (This didn't end up in vanilla primarily out of concerns of bug-like behavior cropping up).
  
{{Scriptdata
+
====Languages====
|title=Laughing metal
+
Languages are special, though; as can be seen in the [[Divine language/script]] or [[Lua script examples#Identity language|identity language]]. The ``languages`` table just expects to return table containing translations, e.g. ``tbl["ABBEY"]="abbey"``. If you want to procedurally add words or symbols (and yes, these are both doable), you can do so with ``raws.register_languages()`` in another function table.
|script=
 
metal_by_sphere.CHILDREN={
 
    name="laughing metal",
 
    col="7:0:1",
 
    color="WHITE"
 
}
 
}}
 
  
===New forgotten beast===
+
==Creatures==
Add a new kind of forgotten beast.
+
Creatures have a lot more to them than other procedural objects. Forgotten beasts are, in a sense, the simplest of them:
  
 
{{Scriptdata
 
{{Scriptdata
|title=Unbidden spirit
+
|title=creatures.fb.default
 
|script=
 
|script=
creatures.fb.unbidden=function(layer_type,tok)
+
creatures.fb.default=function(layer_type,tok)
    if layer_type==0 then return nil end -- land only
 
 
     local tbl={}
 
     local tbl={}
 
     local options={
 
     local options={
 
         strong_attack_tweak=true,
 
         strong_attack_tweak=true,
        always_make_uniform=true,
 
        always_insubstantial=true,
 
        intangible_flier=true,
 
 
         spheres={CAVERNS=true},
 
         spheres={CAVERNS=true},
 
         is_evil=true,
 
         is_evil=true,
Line 77: Line 71:
 
     [FEATURE_BEAST]
 
     [FEATURE_BEAST]
 
     [ATTACK_TRIGGER:0:0:2]
 
     [ATTACK_TRIGGER:0:0:2]
     [NAME:unbidden spirit:unbidden spirit:unbidden spirit]
+
     [NAME:forgotten beast:forgotten beasts:forgotten beast]
     [CASTE_NAME:unbidden spirit:unbidden spirit:unbidden spirit]
+
     [CASTE_NAME:forgotten beast:forgotten beasts:forgotten beast]
 
     [NO_GENDER]
 
     [NO_GENDER]
 
     [CARNIVORE]
 
     [CARNIVORE]
Line 102: Line 96:
 
     tbl[#tbl+1]="[CREATURE_TILE:"..tile_string(rcp.tile).."]"
 
     tbl[#tbl+1]="[CREATURE_TILE:"..tile_string(rcp.tile).."]"
 
     build_procgen_creature(rcp,tbl,options)
 
     build_procgen_creature(rcp,tbl,options)
    -- Weight is a float; all vanilla objects have weight 1
+
     return {creature=tbl,weight=1}
     return {creature=tbl,weight=0.5}
+
end}}
end
 
}}
 
  
===Remove default forgotten beast===
+
This is a lot of info! First, you build an ``options`` table; it's possible to make a full [[Lua functions#Options|list of options used in vanilla]], but other mods can also use arbitrary options. It then adds all the usual special-to-forgotten-beast tokens, in a big string, followed by calling ``add_regular_tokens(tbl,options)``, which adds some stuff common to all (vanilla) procedural creatures, based on the options given.
  
<syntaxhighlight lang="lua" >
+
It sets ``do_water`` and the WATER [[sphere]] if the FB is in a water [[cavern]], an option which whitelists certain random creature profiles, as well as adding a random evil sphere.
creatures.fb.default=nil
 
</syntaxhighlight>
 
  
===Adamantine alloys===
+
``populate_sphere_info()`` is similar to ``add_regular_tokens()``; it adds all of the spheres in ``options.spheres`` to the creature, using the {{token|SPHERE}} token, then, if certain options are set, does more.
  
You can add your own arbitrary generated objects, though as of right now there's no way to make settings for them. This allows for some ''truly'' wild stuff; here's a fun example: adamantine-metal alloys for every single non-special metal, giving you an average of the properties of them.
+
Then, it gets a random creature profile using ``get_random_creature_profile()`` and the options, uses ``add_body_size()`` to set the BODY_SIZE tokens and attendant things that come with it, sets the creature tile, and finally runs the Big Function, ``build_procgen_creature()``, which creates the description, body, tissues, et cetera.
 +
 
 +
===Random Creature Profiles===
 +
A random creature profile is a type of "thing" a generated creature can be. For example:
  
 
{{Scriptdata
 
{{Scriptdata
|title=Adamantine alloys
+
|title=random_creature_types.GENERAL_QUADRUPED
 
|script=
 
|script=
preprocess.adamantine_alloys=function()
+
GENERAL_QUADRUPED={
    if not random_object_parameters.main_world_randoms then return end
+
name_string="quadruped",
    local l=get_debug_logger(2)
+
tile='Q',
    local lines={}
+
body_base="QUADRUPED",
    local reaction_lines={}
+
c_class="UNIFORM",
    local reaction_names={}
+
cannot_have_get_more_legs=true,
    local adamantine=world.inorganic.inorganic.ADAMANTINE
+
min_size=70000,
    if not adamantine then return end
+
weight=1000
    local adamantine_color=world.descriptor.color[world.descriptor.color_pattern[adamantine.material.color_pattern.SOLID].color[1]]
+
},
    local adamantine_modulus = 2500000  --mildly arbitrary, just below the theoretical limit
+
}}
    l("Starting")
+
 
    local done_category=false
+
Of these, only ``cannot_have_get_more_legs`` is optional. ``build_procgen_creature()`` has direct access to the RCP, as the first argument, and thus extra table entries can be used however you like.
    for k,v in ipairs(world.inorganic.inorganic) do
+
 
        if not v.flags.SPECIAL and v.material.flags.IS_METAL then
+
===Logic insertion===
            l(v.token)
+
While ``SELECT_CREATURE`` cannot target generated objects, ``btc1_tweaks`` is a global table of functions. ``build_procgen_creature()`` calls every function in it before determining the first tweak. The arguments supplied are ``lines,options,add_to_body,add_to_body_unique,add_tweak_candidate``, where ``lines`` and ``options`` are the creature's raw lines and options, and the latter three pass local functions that add the chosen [[Body token|body part]] to the creature's definition or allow a certain tweak to be chosen.
            local token="GEN_ADAMANTINE_"..v.token
+
 
            lines[#lines+1]="[INORGANIC:"..token.."]"
+
===Other stuff===
            add_generated_info(lines)
+
TODO: Tweaks, random creature materials, random creature classes, color pickers, function that ``build_procgen_creature()`` calls in the process of building that can be used to inject your own logic into creature building (e.g. ``btc1_tweaks``), etc.
            lines[#lines+1]="[USE_MATERIAL_TEMPLATE:METAL_TEMPLATE]"
+
 
            for kk,vv in pairs(v.material.adj) do
+
 
                lines[#lines+1]="[STATE_ADJ:"..kk..":adamantine "..vv.."]" --"adamantine molten steel"? it's fine
 
            end
 
            for kk,vv in pairs(v.material.name) do
 
                lines[#lines+1]="[STATE_NAME:"..kk..":adamantine "..vv.."]"
 
            end
 
            l(2)
 
            local mat_values={}
 
            -- Find the ratio for which you get closest to (but not below) 2000000 in the material's worst property
 
            local worst=math.min(v.material.yield.IMPACT,v.material.fracture.SHEAR)
 
            local wafers=1
 
            local bars=1
 
            if worst < 2000000 then
 
                local ratio = (2000000-3*worst)/1000000
 
                local best_diff=1
 
                for i=1,10 do
 
                    local wafer_amt=i*ratio
 
                    if wafer_amt>1 and wafer_amt<20 and math.ceil(wafer_amt)-wafer_amt<best_diff then
 
                        best_diff=math.ceil(wafer_amt)-wafer_amt
 
                        wafers=math.ceil(wafer_amt)
 
                        bars=i
 
                    end
 
                end
 
            end
 
            local avg_denom=1/(bars*3+wafers) -- Multiplication just a bit faster than division, we're rounding at the end anyway
 
            local solid_cl=nil
 
            for kk,vv in pairs(v.material.color_pattern) do
 
                -- time to get silly
 
                local this_color=world.descriptor.color[world.descriptor.color_pattern[vv].color[1]]
 
                local wanted_color={
 
                    r=(this_color.r*bars*3+adamantine_color.r*wafers)*avg_denom,
 
                    g=(this_color.g*bars*3+adamantine_color.g*wafers)*avg_denom,
 
                    b=(this_color.b*bars*3+adamantine_color.b*wafers)*avg_denom,
 
                }
 
                local best_total_diff=1000000000
 
                local best_clp=nil
 
                for _,clp in ipairs(world.descriptor.color_pattern) do
 
                    if clp.pattern=="MONOTONE" then
 
                        local cl=world.descriptor.color[clp.color[1]]
 
                        local diff=math.abs(wanted_color.r-cl.r)+math.abs(wanted_color.b-cl.b)+math.abs(wanted_color.g-cl.g)
 
                        if diff<best_total_diff then
 
                            best_clp=clp
 
                            best_total_diff=diff
 
                        end
 
                    end
 
                end
 
                lines[#lines+1]="[STATE_COLOR:"..kk..":"..best_clp.token.."]"
 
                if kk=="SOLID" then solid_cl=world.descriptor.color[best_clp.color[1]] end
 
            end
 
            local color_str=solid_cl.col_f..":0:"..solid_cl.col_br
 
            l(color_str)
 
            lines[#lines+1]="[DISPLAY_COLOR:"..color_str.."]"
 
            lines[#lines+1]="[BUILD_COLOR:"..color_str.."]"
 
            lines[#lines+1]="[ITEMS_METAL][ITEMS_HARD][ITEMS_SCALED][ITEMS_BARRED]"
 
            lines[#lines+1]="[SPECIAL]"
 
            if v.material.flags.ITEMS_DIGGER then
 
                lines[#lines+1]="[ITEMS_DIGGER]"
 
            end
 
            local function new_value(str)
 
                mat_values[str]=mat_values[str] or math.floor((adamantine.material[str]*wafers+v.material[str]*bars*3)*avg_denom+0.5)
 
                l(str,mat_values[str])
 
                return mat_values[str]
 
            end
 
            local function new_value_nested(str1,str2)
 
                mat_values[str1..str2]=mat_values[str1..str2] or math.floor((adamantine.material[str1][str2]*wafers+v.material[str1][str2]*bars*3)/(bars*3+wafers)+0.5)
 
                l(str1..str2,mat_values[str1..str2])
 
                return mat_values[str1..str2]
 
            end
 
            if new_value_nested("fracture","SHEAR")>170000 or new_value_nested("yield","IMPACT")>245000 then
 
                lines[#lines+1]="[ITEMS_WEAPON][ITEMS_AMMO]"
 
                if new_value("solid_density")<10000 then
 
                    lines[#lines+1]="[ITEMS_WEAPON_RANGED][ITEMS_ARMOR]"
 
                end
 
            end
 
            lines[#lines+1]="[MATERIAL_VALUE:"..new_value("base_value").."]"
 
            lines[#lines+1]="[SPEC_HEAT:"..new_value("temp_spec_heat").."]"
 
            lines[#lines+1]="[MELTING_POINT:"..new_value("temp_melting_point").."]"
 
            lines[#lines+1]="[BOILING_POINT:"..new_value("temp_boiling_point").."]"
 
            lines[#lines+1]="[SOLID_DENSITY:"..new_value("solid_density").."]"
 
            lines[#lines+1]="[LIQUID_DENSITY:"..new_value("liquid_density").."]"
 
            lines[#lines+1]="[MOLAR_MASS:"..new_value("molar_mass").."]" -- i don't think this is actually correct
 
            for _,thing in ipairs({"yield","fracture"}) do
 
                for force,_ in pairs(v.material[thing]) do
 
                    lines[#lines+1]="["..string.upper(force).."_"..string.upper(thing)..":"..new_value_nested(thing,force).."]"
 
                end
 
            end
 
            for _,force in ipairs("IMPACT","COMPRESSIVE","TENSILE","TORSION","SHEAR","BENDING") do
 
                local modulus = v.yield[force] / v.elasticity[force]
 
                local average_modulus = (adamantine_modulus*wafers + modulus*bars*3)*avg_denom
 
                local strain_at_yield = math.floor(new_value_nested("yield",force) / average_modulus + 0.5) -- usually zero, but can be 1 or 2 sometimes
 
                lines[#lines+1]="["..string.upper(force).."_YIELD:"..new_value_nested("yield",force).."]"
 
                lines[#lines+1]="["..string.upper(force).."_FRACTURE:"..new_value_nested("fracture",force).."]"
 
                lines[#lines+1]="["..string.upper(force).."_STRAIN_AT_YIELD:"..strain_at_yield.."]"
 
            end
 
            lines[#lines+1]="[MAX_EDGE:"..new_value("max_edge").."]"
 
            local reaction_token=token.."_MAKING"
 
            reaction_lines[#reaction_lines+1]="[REACTION:"..reaction_token.."]"
 
            add_generated_info(reaction_lines)
 
            reaction_lines[#reaction_lines+1]="[NAME:make adamantine "..v.material.name.SOLID.." (use bars)]"
 
            reaction_lines[#reaction_lines+1]="[BUILDING:SMELTER:NONE]"
 
            reaction_lines[#reaction_lines+1]="[REAGENT:A:"..tostring(150*wafers)..":BAR:NO_SUBTYPE:METAL:ADAMANTINE]"
 
            reaction_lines[#reaction_lines+1]="[REAGENT:B:"..tostring(150*bars)..":BAR:NO_SUBTYPE:METAL:"..v.token.."]"
 
            reaction_lines[#reaction_lines+1]="[PRODUCT:100:"..tostring(bars+wafers)..":BAR:NO_SUBTYPE:METAL:"..token.."][PRODUCT_DIMENSION:150]"
 
            reaction_lines[#reaction_lines+1]="[FORTRESS_MODE_ENABLED]"
 
            reaction_lines[#reaction_lines+1]="[CATEGORY:ADAMANTINE_ALLOYS]"
 
            if not done_category then
 
                done_category=true
 
                reaction_lines[#reaction_lines+1]="[CATEGORY_NAME:Adamantine alloys]"
 
                reaction_lines[#reaction_lines+1]="[CATEGORY_DESCRIPTION:Debase adamantine with other metals to get extremely strong alloys.]"
 
                reaction_lines[#reaction_lines+1]="[CATEGORY_KEY:CUSTOM_SHIFT_A]"
 
            end
 
            reaction_lines[#reaction_lines+1]="[FUEL]"
 
            reaction_lines[#reaction_lines+1]="[SKILL:SMELT]"
 
        end
 
    end
 
    local entity_lines={}
 
    raws.register_inorganics(lines)
 
    -- not used in vanilla right now, due to lack of instruments, but you CAN do this
 
    raws.register_reactions(reaction_lines)
 
end}}
 
 
[[Category:Modding]]
 
[[Category:Modding]]
[[Category:Lua]]
+
[[Category:Lua|S]]

Revision as of 23:17, 11 June 2025

Dwarven science stretched.png Research Pending!
This article or section is incomplete/under construction (likely due to recent changes) and may still be outdated or missing details. Feel free to do some testing and expand it.
Lua-Logo.svg


This article is about procedural raw generation. Information on Utility:DFHack scripting can be found at https://docs.dfhack.org/en/stable/.


Lua scripting is an experimental featurev51.06. It is used to create custom procedurally-generated objects that were previously created by hardcoded methods. It was announced in a video, with the stated goal of "supporting future magical endeavors."

Inorganic materials, languages, creatures, interactions, items (currently excluding instruments), reactions, entities, and plants are open to this system.

Scripts are loaded from a mod's scripts/init.lua file, and can require() other files.

Structure

As of right now, Lua scripting is confined to generation of procedural objects. This is done by running the generate function, a global function loaded in data/init/generators.lua. It runs unit tests, preprocess, do_once, materials, items, languages, creatures, interactions, entities and postprocessing, in that order.

When random objects are first generated, the game populates two global tables, world and random_object_parameters. world contains info about the world currently being generated (or, in the future, played in), while random_object_parameters contains what the game expects to be generated. The most important thing between these is random_object_parameters.main_world_randoms, which is true for exactly one generation at the start of worldgen; it's what you want to check for if you're generating your own objects outside of do_once and the standard generation contexts.

Debugging

You can set the global debug_level variable to print some debug info. It's a number, but what numbers are there are completely arbitrary. If it's >0, it'll run unit tests; if it's >=0.5, it'll display what step of generation it's at, at every step. You can use get_debug_logger(x) to return a function that logs to lualog.txt if the debug level is at least x.

Unit tests are functions that return a table, containing good, which, if truthy, is considered passed, and info, which is a string that contains information on said pass or fail. These unit tests should have no side effects, i.e. they shouldn't muck with global state any. Here's an example unit test, one that was used during development (but had no reason to be removed):

Object generation

The first steps after debugging are preprocess and do_once. These are tables of functions, running each one at a time. This is where you want your side effects.

If you're registering an entirely new procedural object type, you can use these steps. do_once only runs in the "main world randoms" generation call and is the safest option for adding new objects. The "adamantine alloys" example is done through these steps. You can also add functions to postprocess, which is like preprocessing but run after all the other generation steps.

You can also mess around with random_object_parameters in preprocessing. Vanilla demon types are assigned here, and you can change the proportions as an end user if you want.

Generation from list

The game then generates all of the individual objects; the general procedure for this is that the game calls the generate_from_list() function on a table of functions, which calls every function and picks one of the resulting values at random depending on their weights.

For example, the interactions.secrets table contains one entry, that for necromancers; it returns a table containing three entries: {interaction=tbl,weight=1,spheres=spheres}.

  • interaction is the full raw text of the interaction
  • weight is the random weight for the interaction, i.e. if you add another function which returns a table containing weight=2, that will be twice as likely as necromancers.
  • spheres is some extra data the generator might be able to use. It actually doesn't, at this point, but one could override generate_random_interactions() with their own version that takes into account spheres and, say, tries to evenly distribute generated secrets over available spheres. (This didn't end up in vanilla primarily out of concerns of bug-like behavior cropping up).

Languages

Languages are special, though; as can be seen in the Divine language/script or identity language. The languages table just expects to return table containing translations, e.g. tbl["ABBEY"]="abbey". If you want to procedurally add words or symbols (and yes, these are both doable), you can do so with raws.register_languages() in another function table.

Creatures

Creatures have a lot more to them than other procedural objects. Forgotten beasts are, in a sense, the simplest of them:

This is a lot of info! First, you build an options table; it's possible to make a full list of options used in vanilla, but other mods can also use arbitrary options. It then adds all the usual special-to-forgotten-beast tokens, in a big string, followed by calling add_regular_tokens(tbl,options), which adds some stuff common to all (vanilla) procedural creatures, based on the options given.

It sets do_water and the WATER sphere if the FB is in a water cavern, an option which whitelists certain random creature profiles, as well as adding a random evil sphere.

populate_sphere_info() is similar to add_regular_tokens(); it adds all of the spheres in options.spheres to the creature, using the [SPHERE] token, then, if certain options are set, does more.

Then, it gets a random creature profile using get_random_creature_profile() and the options, uses add_body_size() to set the BODY_SIZE tokens and attendant things that come with it, sets the creature tile, and finally runs the Big Function, build_procgen_creature(), which creates the description, body, tissues, et cetera.

Random Creature Profiles

A random creature profile is a type of "thing" a generated creature can be. For example:

Of these, only cannot_have_get_more_legs is optional. build_procgen_creature() has direct access to the RCP, as the first argument, and thus extra table entries can be used however you like.

Logic insertion

While SELECT_CREATURE cannot target generated objects, btc1_tweaks is a global table of functions. build_procgen_creature() calls every function in it before determining the first tweak. The arguments supplied are lines,options,add_to_body,add_to_body_unique,add_tweak_candidate, where lines and options are the creature's raw lines and options, and the latter three pass local functions that add the chosen body part to the creature's definition or allow a certain tweak to be chosen.

Other stuff

TODO: Tweaks, random creature materials, random creature classes, color pickers, function that build_procgen_creature() calls in the process of building that can be used to inject your own logic into creature building (e.g. btc1_tweaks), etc.

CancelHideAbout

Rating Lua scripting