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
m (→‎Code Samples: Condensed into {{Scriptdata}})
(Putnam's examples)
Line 4: Line 4:
 
''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 upcoming feature. 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."
  
 
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.
  
 
==Code Samples==
 
==Code Samples==
Line 38: Line 40:
 
     end
 
     end
 
     return tbl
 
     return tbl
 +
end}}
 +
 +
===New divine metal===
 +
You can add new metal descriptions for divine metal pretty easily, for example:
 +
 +
{{Scriptdata
 +
|title=Laughing metal
 +
|script=
 +
metal_by_sphere.CHILDREN={
 +
    name="laughing metal",
 +
    col="7:0:1",
 +
    color="WHITE"
 +
}
 +
}}
 +
 +
===New forgotten beast===
 +
Add a new kind of forgotten beast.
 +
 +
{{Scriptdata
 +
|title=Unbidden spirit
 +
|script=
 +
creatures.fb.unbidden=function(layer_type,tok)
 +
    if layer_type==0 then return nil end -- land only
 +
    local tbl={}
 +
    local options={
 +
        strong_attack_tweak=true,
 +
        always_make_uniform=true,
 +
        always_insubstantial=true,
 +
        intangible_flier=true,
 +
        spheres={CAVERNS=true},
 +
        is_evil=true,
 +
        sickness_name="beast sickness",
 +
        token=tok
 +
    }
 +
    tbl=split_to_lines(tbl,[[
 +
    [FEATURE_BEAST]
 +
    [ATTACK_TRIGGER:0:0:2]
 +
    [NAME:unbidden spirit:unbidden spirit:unbidden spirit]
 +
    [CASTE_NAME:unbidden spirit:unbidden spirit:unbidden spirit]
 +
    [NO_GENDER]
 +
    [CARNIVORE]
 +
    [DIFFICULTY:10]
 +
 +
    [NATURAL_SKILL:WRESTLING:6]
 +
    [NATURAL_SKILL:BITE:6]
 +
    [NATURAL_SKILL:GRASP_STRIKE:6]
 +
    [NATURAL_SKILL:STANCE_STRIKE:6]
 +
    [NATURAL_SKILL:MELEE_COMBAT:6]
 +
    [NATURAL_SKILL:DODGING:6]
 +
    [NATURAL_SKILL:SITUATIONAL_AWARENESS:6]
 +
    [LARGE_PREDATOR]
 +
    ]])
 +
    add_regular_tokens(tbl,options)
 +
    tbl[#tbl+1]=layer_type==0 and "[BIOME:SUBTERRANEAN_WATER]" or "[BIOME:SUBTERRANEAN_CHASM]"
 +
    if layer_type==0 then options.spheres.WATER=true end
 +
    options.spheres[pick_random(evil_spheres)]=true
 +
    options.do_water=layer_type==0
 +
    populate_sphere_info(tbl,options)
 +
    local rcp=get_random_creature_profile(options)
 +
    add_body_size(tbl,math.max(10000000,rcp.min_size),options)
 +
    tbl[#tbl+1]="[CREATURE_TILE:"..tile_string(rcp.tile).."]"
 +
    build_procgen_creature(rcp,tbl,options)
 +
    -- Weight is a float; all vanilla objects have weight 1
 +
    return {creature=tbl,weight=0.5}
 +
end
 +
}}
 +
 +
===Remove default forgotten beast===
 +
 +
<syntaxhighlight lang="lua" >
 +
creatures.fb.default=nil
 +
</syntaxhighlight>
 +
 +
===Adamantine alloys===
 +
 +
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.
 +
 +
{{Scriptdata
 +
|title=Adamantine alloys
 +
|script=
 +
preprocess.adamantine_alloys=function()
 +
    if not random_object_parameters.main_world_randoms then return end
 +
    local l=get_debug_logger(2)
 +
    local lines={}
 +
    local reaction_lines={}
 +
    local reaction_names={}
 +
    local adamantine=world.inorganic.inorganic.ADAMANTINE
 +
    if not adamantine then return end
 +
    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
 +
    for k,v in ipairs(world.inorganic.inorganic) do
 +
        if not v.flags.SPECIAL and v.material.flags.IS_METAL then
 +
            l(v.token)
 +
            local token="GEN_ADAMANTINE_"..v.token
 +
            lines[#lines+1]="[INORGANIC:"..token.."]"
 +
            add_generated_info(lines)
 +
            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}}
 
end}}
 
[[Category:Modding]]
 
[[Category:Modding]]
 
[[Category:Lua]]
 
[[Category:Lua]]

Revision as of 00:24, 27 February 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.


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, as well as from any included files.

Code Samples

Divine language

This is the divine language, which generates a bunch of random-sounding words from a set of syllables.

languages.GEN_DIVINE=function()

    local letters={}
    letters.vowel={}
    letters.cons={}
    letters.vowel.COMMON_NUM=5
    letters.vowel.NUM=35
    letters.cons.COMMON_NUM=12
    letters.cons.NUM=22
    letters.vowel.lookup={
        "a","e","i","o","u",
        "ae","ai","ao","au","ea","ei","eo","eu","ia","ie","io","iu","oa","oe","oi","ou","ua","ue","ui","uo","ah","eh","ih","oh","uh","ay","ey","iy","oy","uy"
    }
    letters.cons.lookup={
        "b","p","g","k","c","z","s","d","t","m","n","ng",
        "v","f","w","h","j","l","r","q","x","y"
    }

    for k,v in pairs(letters) do
        v.common={}
        v.rare={}
        for i=1,5 do
            if trandom(5)~=0 then v.common[i]=v.lookup[trandom(v.COMMON_NUM)+1] else v.common[i]=v.lookup[trandom(v.NUM)+1] end
        end
        for i=1,15 do 
            v.rare[i]=v.lookup[trandom(v.NUM)+1]
        end
    end

    local function letter(t)
        if trandom(5)~=0 then
            return pick_random(t.common)
        else
            return pick_random(t.rare)
        end
    end
    local gen_divine={}
    for k,v in ipairs(world.language.word) do
        local str=""
        if trandom(2)~=0 then
            str=str..letter(letters.cons)
            str=str..letter(letters.vowel)
        else
            str=str..letter(letters.vowel)
        end
        local num_letters=trandom(3)
        str=str..letter(letters.cons)
        if num_letters>0 then str=str..letter(letters.vowel) end
        if num_letters>1 then str=str..letter(letters.cons) end
        gen_divine[v.token]=str
    end

    return gen_divine
end


Identity language

This makes a language called GEN_IDENTITY which is, like. "Abbey abbeyabbeys the abbey of abbeys" - i.e. it's the "English" language you might see occasionally.

New divine metal

You can add new metal descriptions for divine metal pretty easily, for example:

New forgotten beast

Add a new kind of forgotten beast.

Remove default forgotten beast

creatures.fb.default=nil

Adamantine alloys

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.

CancelHideAbout

Rating Lua scripting