User:Fleeting Frames/inject-raws

From Dwarf Fortress Wiki
Jump to navigation Jump to search

Usage: Make a backup of your save. Edit your raws to include new stuff you desire. Open the game and run the command with devel/inject-raws category RAWNAME, with last two being permitted to repeat for injecting multiple different things with 1 command. quicksave, then die, then reload.

-- Inject new raw definitions into the world
--[====[

devel/inject-raws
=================
WARNING: THIS SCRIPT CAN PERMANENLY DAMAGE YOUR SAVE.

This script attempts to inject new raw objects into your
world. If the injected references do not match the actual
edited raws, your save will refuse to load, or load but crash.

This script can handle reaction, item, colour and building definitions.

The savegame contains a list of the relevant definition tokens in
the right order, but all details are read from raws every time.
This allows just adding stub definitions, and simply saving and
reloading the game.

This is useful enough for modders and some users to justify the danger.

Usage example::

    devel/inject-raws trapcomp ITEM_TRAPCOMP_STEAM_PISTON workshop STEAM_ENGINE MAGMA_STEAM_ENGINE reaction STOKE_BOILER

]====]

local utils = require 'utils'

local raws = df.global.world.raws

print[[
WARNING: THIS SCRIPT CAN PERMANENLY DAMAGE YOUR SAVE.

This script attempts to inject new raw objects into your
world. If the injected references do not match the actual
edited raws, your save will refuse to load, or load but crash.
]]

if not utils.prompt_yes_no('Did you make a backup?') then
    qerror('Not backed up.')
end

df.global.pause_state = true

local changed = false

function inject_reaction(name)
    for _,v in ipairs(raws.reactions) do
        if v.code == name then
            print('Reaction '..name..' already exists.')
            return
        end
    end

    print('Injecting reaction '..name)
    changed = true

    raws.reactions:insert('#', {
        new = true,
        code = name,
        name = 'Dummy reaction '..name,
        index = #raws.reactions,
    })
end

local building_types = {
    workshop = { df.building_def_workshopst, raws.buildings.workshops },
    furnace = { df.building_def_furnacest, raws.buildings.furnaces },
}

function inject_building(btype, name)
    for _,v in ipairs(raws.buildings.all) do
        if v.code == name then
            print('Building '..name..' already exists.')
            return
        end
    end

    print('Injecting building '..name)
    changed = true

    local typeinfo = building_types[btype]

    local id = raws.buildings.next_id
    raws.buildings.next_id = id+1

    raws.buildings.all:insert('#', {
        new = typeinfo[1],
        code = name,
        name = 'Dummy '..btype..' '..name,
        id = id,
    })

    typeinfo[2]:insert('#', raws.buildings.all[#raws.buildings.all-1])
end

local itemdefs = raws.itemdefs
local item_types = {
    weapon = { df.itemdef_weaponst, itemdefs.weapons, 'weapon_type' },
    trainweapon = { df.itemdef_weaponst, itemdefs.weapons, 'training_weapon_type' },
    pick = { df.itemdef_weaponst, itemdefs.weapons, 'digger_type' },
    trapcomp = { df.itemdef_trapcompst, itemdefs.trapcomps, 'trapcomp_type' },
    toy = { df.itemdef_toyst, itemdefs.toys, 'toy_type' },
    tool = { df.itemdef_toolst, itemdefs.tools, 'tool_type' },
    instrument = { df.itemdef_instrumentst, itemdefs.instruments, 'instrument_type' },
    armor = { df.itemdef_armorst, itemdefs.armor, 'armor_type' },
    ammo = { df.itemdef_ammost, itemdefs.ammo, 'ammo_type' },
    siegeammo = { df.itemdef_siegeammost, itemdefs.siege_ammo, 'siegeammo_type' },
    gloves = { df.itemdef_glovest, itemdefs.gloves, 'gloves_type' },
    shoes = { df.itemdef_shoest, itemdefs.shoes, 'shoes_type' },
    shield = { df.itemdef_shieldst, itemdefs.shields, 'shield_type' },
    helm = { df.itemdef_helmst, itemdefs.helms, 'helm_type' },
    pants = { df.itemdef_pantsst, itemdefs.pants, 'pants_type' },
    food = { df.itemdef_foodst, itemdefs.food },
}

function add_to_civ(entity, bvec, id)
    for _,v in ipairs(entity.resources[bvec]) do
        if v == id then
            return
        end
    end

    entity.resources[bvec]:insert('#', id)
end

function add_to_dwarf_civs(btype, id)
    local typeinfo = item_types[btype]
    if not typeinfo[3] then
        print('Not adding to civs.')
    end

    for _,entity in ipairs(df.global.world.entities.all) do
        if entity.race == df.global.ui.race_id then
            add_to_civ(entity, typeinfo[3], id)
        end
    end
end

function inject_item(btype, name)
    for _,v in ipairs(itemdefs.all) do
        if v.id == name then
            print('Itemdef '..name..' already exists.')
            return
        end
    end

    print('Injecting item '..name)
    changed = true

    local typeinfo = item_types[btype]
    local vec = typeinfo[2]
    local id = #vec

    vec:insert('#', {
        new = typeinfo[1],
        id = name,
        subtype = id,
        name = name,
        name_plural = name,
    })

    itemdefs.all:insert('#', vec[id])

    add_to_dwarf_civs(btype, id)
end

function inject_color(colorID)
	for _,c in ipairs(raws.language.colors) do
        if c.id == colorID then
            print('Itemdef '..colorID..' already exists.')
            return
        end
    end
    
    print('Injecting color '..colorID)
    changed = true
    
    local newcolor = df.descriptor_color:new()
    newcolor.id = colorID
    newcolor.words = {[0]=2091} --dummying with Amber
    newcolor.name = colorID
    newcolor.color = 0
    newcolor.bold = 1
    newcolor.red = 0
    newcolor.green = 0
    newcolor.blue = 0
    
    df.global.world.raws.language.colors:insert('#', newcolor)
end


local args = {...}
local mode = nil
local ops = {}

for _,kv in ipairs(args) do
    if mode and string.match(kv, '^[%u_ ]+$') then --works with "uppercase words with spaces surrounded by quotation marks"
        table.insert(ops, curry(mode, kv))
    elseif kv == 'reaction' then
        mode = inject_reaction
    elseif building_types[kv] then
        mode = curry(inject_building, kv)
    elseif item_types[kv] then
        mode = curry(inject_item, kv)
    elseif kv == 'color' then
		mode = inject_color
    else
        qerror('Invalid option: '..kv)
    end
end

if #ops > 0 then
    print('')
    for _,v in ipairs(ops) do
        v()
    end
end

if changed then
    print('\nNow without unpausing save and reload the game to re-read raws.')
else
    print('\nNo changes made.')
end