Just wanted to drop this script off for anyone interested.
Haven't played DF for long, but one thing which bothered me was the extremely high rate of migrants that seemed to come to forts (particularly if you weren't ready for them). Between the alternatives of either trying to roll with groups of 20+ migrants or modding the population cap such that you were reliant on natural growth, neither appeals to me. Playing without migrants gets tedious as you can/will expand so much faster than your population. Not to mention it starts to feel weird breathing heavily at your screen demanding that your dwarves "breed already!" Hence my short-term quest to build a script to replace the in-game migrant waves.
Please note that I am not experienced with Lua-scripting or coding in general. (Translation: the code looks like garbage, but I have tested it and it seems to work.) Also, it makes use of Milo's announce.lua script (see
http://www.bay12forums.com/smf/index.php?topic=137011.msg5076999#msg5076999)
See code below:
migrantsreplace.lua --Updated 2018-03-26
-- Last edited: 2018-03-24
-- Anon_404
-- Made for use with DFhack 0.44.05-r1
--[[This is a basic script that is meant to be used with the "enable"/"disable" commands.
What it does is test regularly for the Migrants timed_event. Once the event comes close to occuring, it erases the event and triggers a new
set of code to replace the incoming migrants. The new code is meant to provide easier control over the number of migrants coming (seeing as
previous tests to do so have failed; changing df.global.ui.tasks.wealth appears to have no effect)
Current issues/limitations:
- only produces peasants (completely unskilled)
- does not account for or include generating domesticated animals (you will have to buy them)
- on test map, some dwarfs would spawn underground in water :s (workaround: test for outside)[Fixed?]
- concern: how will the game react to 20 units spawning in one location?
- unsure if related but DF has appeared to crash while running this script
]]
local args = {...}
-- Code ripped from emigration.lua
if args[1] == "enable" then
enabled = true
elseif args[1] == "disable" then
enabled = false
end
----------------------------------
-- Code ripped from population-cap.lua
local ui = df.global.ui
local ui_stats = ui.tasks
local civ = df.historical_entity.find(ui.civ_id)
----------------------------------
local event = df.global.timed_events
local CAP = df.global.d_init.population_cap
----------------------------------
-- This is a basic function for finding and removing the programmed migrant wave just before it occurs.
function migrantsReplace()
local N = #event
if N > 0 and ui_stats.migrant_wave_idx < 2 and ui_stats.population <= CAP then --if there is an event to be had AND if the event is supposed to create migrants AND population is less than cap
-- Note on following line: unsure of how close season_tick + 200 will bring the event to the timeout period of 1 day
if event[0].type == df.timed_event_type.Migrants and event[0].season == df.global.cur_season and event[0].season_ticks <= df.global.cur_season_tick + 200 then
event:erase(0)
print("Migrants replaced")
new_migrants()
df.global.pause_state = true
dfhack.run_command('announce "Migrants have arrived."') --attempt to call 3rd party announce.lua
end
end
print("Monitoring")
end
-- This is the code to create new migrants using modtools/create-unit
function new_migrants()
-- This code is meant to determine how many migrants will appear. Attempting to have it linked to relative wealth of the fortress.
math.randomseed(os.clock())
local mbase = 2 --base wave
local wRatio = 10000 * (math.floor(ui.fortress_age/40320) + ui_stats.total_deaths + 1) --ratio of wealth to population desired; added fort age multiplier and total_deaths (combat deaths?)
-- Line below calculates the number of additional migrants based on the forts wealth (total + exported)
local wPop = math.floor((ui_stats.wealth.total + ui_stats.wealth.exported)/wRatio) - ui_stats.population - mbase
if wPop + ui_stats.population + mbase > CAP then -- should limit migrant wave to the population_cap value
wPop = CAP - ui_stats.population - mbase
elseif wPop > ui_stats.population then -- meant to help cap the overall population growth (basically allows for doubling the population, but nothing more)
wPop = ui_stats.population
end
if wPop < 0 then -- makes negative values invalid (don't want to bother dealing with negative migrants :p )
wPop = 0
end
-----------------------------------------------------------------------------------------------------
-- This code is for determining where the migrants will start (for simplicity, starting at a corner)
maxXYZ = {select(1, dfhack.maps.getTileSize())}
local xpos = 0
local ypos = 0
if math.random(0,2) <= 1 then
xpos = 0
else
xpos = maxXYZ[1] - 1 --map corner
end
if math.random(0,2) <= 1 then
ypos = 0
else
ypos = maxXYZ[2] - 1 --map corner
end
local Zcount = 0
::Zreset::
local zpos = maxXYZ[3] - 1
-- Line below compares the 'shape' of the tile to 'floor' (should result in ground level tile as air tiles do not have 'shape' 'floor' and below ground level at the map edge should be solid object)
while df.tiletype.attrs[dfhack.maps.getTileType(xpos,ypos,zpos)].shape ~= df.tiletype_shape.FLOOR do
zpos = zpos - 1
--what happens if the current x,y coordinates never meet condition? Need to start circling the map
--really unhappy about not being able to get the liquid type to work right. Instead having to rely on 'outside' field
if dfhack.maps.getTileType(xpos,ypos,zpos) == nil or zpos == 0 or dfhack.maps.getTileBlock(xpos,ypos,zpos).designation[xpos%16][ypos%16].outside == false then
Zcount = Zcount + 1 -- Zcount starts outside of looped goto and should trigger break in event of no proper positions
if Zcount > 500 then
return 1 -- error return
end
if xpos > 0 and ypos == maxXYZ[2] - 1 then
xpos = xpos - 1
goto Zreset
elseif xpos == 0 and ypos > 0 then
ypos = ypos - 1
goto Zreset
elseif xpos < maxXYZ[1] - 1 and ypos == 0 then
xpos = xpos + 1
goto Zreset
elseif xpos == maxXYZ[1] - 1 and ypos >= 0 then
ypos = ypos + 1
goto Zreset
end
end
end
-----------------------------------------------------------------------------------------------------
local N = mbase + math.floor(math.random(0,3)) + wPop
while N > 0 do
N = N - 1
local R = math.floor(math.random(1,2) + 0.5)
local gender = {"MALE","FEMALE"}
-- Was unable to determine how to get the dofile() or loadfile() commands to take specific arguments :s (this is a workaround)
dfhack.run_command('modtools/create-unit -race DWARF -caste '.. gender[R] ..' -setUnitToFort TRUE -name MOUNTAIN -location [ '.. xpos ..' '.. ypos ..' '.. zpos ..' ]')
end
return 0 -- succesful completion
end
-- Code initially ripped from emigration.lua
-- Changed the looping to occur close to when a timed_event is supposed to occur
local function event_loop()
if enabled then
migrantsReplace()
local N = #event
local T = 0
if N > 0 then
T = math.floor((event[0].season_ticks - df.global.cur_season_tick)/120) - 3
if T <= 1 then
dfhack.timeout(1, 'days', event_loop)
else
dfhack.timeout(T, 'days', event_loop)
end
else
dfhack.timeout(1, 'months', event_loop)
end
end
end
dfhack.onStateChange.loadmigrantsreplace = function(code)
if code==SC_MAP_LOADED then
if enabled then
print("migrantsreplace enabled.")
event_loop()
else
print("migrantsreplace disabled.")
end
end
end
if dfhack.isMapLoaded() then
dfhack.onStateChange.loadmigrantsreplace(SC_MAP_LOADED)
end
---------------------------------
Brief explanation: The script compares when the timed_event "Migrants" is supposed to occur and removes it shortly before the game calls it. It then runs a new set of code that creates named peasant dwarves using the modtools/create-unit command. It produces a minimum of 2 dwarves but will not produce more than 5 + your fort population (basically, a single migrant wave can only double your population).
As far as I've tested so far, the script works as intended. I have noticed that my game does tend to crash (program closes without prompting errors), but I am not sure if its code related or computer related as it is not repeatable. If you want to use it, mod it, etc, please feel free.
Furthermore, is there anyplace in particular I should post regarding what variables do not affect migrant wave size? Before I created this script I tried changing different reported values (wealth.exported, wealth.total, etc) and found that it had no effect on the number of migrants arriving. Might save someone else some time.
Enjoy.
Edit: Updated the code as of 2018-03-26.