Bay 12 Games Forum

Please login or register.

Login with username, password and session length
Advanced search  

Author Topic: Script: Migrants Replacement  (Read 3391 times)

Anon_404

  • Bay Watcher
    • View Profile
Script: Migrants Replacement
« on: March 21, 2018, 06:51:57 pm »

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
Code: [Select]
-- 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.
« Last Edit: March 26, 2018, 08:50:37 pm by Anon_404 »
Logged

Putnam

  • Bay Watcher
  • DAT WIZARD
    • View Profile
Re: Script: Migrants Replacement
« Reply #1 on: March 21, 2018, 09:49:01 pm »

create-unit might be making crashy dwarves, this script technically increases your population exponentially so actually makes population growth much faster past the early game

PatrikLundell

  • Bay Watcher
    • View Profile
Re: Script: Migrants Replacement
« Reply #2 on: March 22, 2018, 02:03:18 am »

You can change the pop cap after each migrant wave. Sure, it requires saving/exiting/change pop cap/start DF/load/continue, but not that many times. When I don't play dead civs I change the pop cap to current pop + 10. I don't know if you can change the pop can dynamically and have it take effect, but if it did, it would probably be a better script basis.
Logged

Putnam

  • Bay Watcher
  • DAT WIZARD
    • View Profile
Re: Script: Migrants Replacement
« Reply #3 on: March 22, 2018, 02:22:54 am »

You can change the pop cap after each migrant wave. Sure, it requires saving/exiting/change pop cap/start DF/load/continue, but not that many times. When I don't play dead civs I change the pop cap to current pop + 10. I don't know if you can change the pop can dynamically and have it take effect, but if it did, it would probably be a better script basis.

you can also adjust it at df.global.d_init.population_cap; in my experience init stuff, including such things as colors, can be edited at runtime and work as expected, but that's not a particularly good assumption and testing should be carried out

Anon_404

  • Bay Watcher
    • View Profile
Re: Script: Migrants Replacement
« Reply #4 on: March 22, 2018, 05:32:59 pm »

You can change the pop cap after each migrant wave. Sure, it requires saving/exiting/change pop cap/start DF/load/continue, but not that many times. When I don't play dead civs I change the pop cap to current pop + 10. I don't know if you can change the pop can dynamically and have it take effect, but if it did, it would probably be a better script basis.

you can also adjust it at df.global.d_init.population_cap; in my experience init stuff, including such things as colors, can be edited at runtime and work as expected, but that's not a particularly good assumption and testing should be carried out

Tried changing df.global.d_init.population_cap while the game was running and found that it had no effect on migrant waves. Additionally, using the force-migrants command is not a good indicator as it can either display "Fortress attracted no migrants" or generate a migrant wave of 7. (Believe this only happens if called multiple times in the same season)

Quote
create-unit might be making crashy dwarves, this script technically increases your population exponentially so actually makes population growth much faster past the early game

What part of the code makes the population expand exponentially? The number of new dwarves has a max value of twice your population, a minimum value of 4, and outside of the random number generator for 0-3 the only growth aspect is with relation to how much value you have created. (ie: 100k total wealth and exports allows for a whopping 10 total supported dwarves; this is including your current population, and the base 4 migrant wave).

Any tips on particular log files that might help identify if it is crashing because of the create-unit call?
Logged

Roses

  • Bay Watcher
    • View Profile
Re: Script: Migrants Replacement
« Reply #5 on: March 22, 2018, 08:11:16 pm »

Quote
create-unit might be making crashy dwarves, this script technically increases your population exponentially so actually makes population growth much faster past the early game

What part of the code makes the population expand exponentially? The number of new dwarves has a max value of twice your population, a minimum value of 4, and outside of the random number generator for 0-3 the only growth aspect is with relation to how much value you have created. (ie: 100k total wealth and exports allows for a whopping 10 total supported dwarves; this is including your current population, and the base 4 migrant wave).

Any tips on particular log files that might help identify if it is crashing because of the create-unit call?

Twice your population means you could get waves of 100's later in game
Logged

Anon_404

  • Bay Watcher
    • View Profile
Re: Script: Migrants Replacement
« Reply #6 on: March 22, 2018, 09:09:01 pm »

Twice your population means you could get waves of 100's later in game

Allow me to rephrase, it is capped at twice. The wave number cannot go higher than that. In order to get a 100 migrant wave, you would have to have a total wealth and exported wealth of over 2 million (1 million would satisfy the current population of 100, 1 million would allow for the extra 100).

As an aside, anyone know if the Migrants timed_event still gets called after the population cap gets reached? Because my code does not take that into account :(
Logged

Anon_404

  • Bay Watcher
    • View Profile
Re: Script: Migrants Replacement
« Reply #7 on: March 26, 2018, 09:03:27 pm »

Updated the code to fix the issue with it still running after the cap limit has been reached. It should default back to the standard Migrant event triggers (namely, no more migrants).

Also, as it has been indirectly pointed out, the code makes the incorrect assumption that fortress wealth is a good indicator of population level. This is not true as the hypothetical situation of a large loss of population (via siege, etc) does not necessarily mean a loss of wealth. With that in mind, I changed the code for calculating the wealth ratio to the below:
Code: [Select]
local wRatio = 10000 * (math.floor(ui.fortress_age/40320) + ui_stats.total_deaths + 1)
This means that as a fort gets older (in years) the wealth required to generate additional migrants beyond the base wave of 2 gets higher. Furthermore, dwarven combat deaths (presumably only, as insanities and executions are recorded in a separate variable) will increase this ratio much faster. Thus a 100 dwarf massacre will force the ratio up 100 times. This makes the minimum replacement wealth 200 million at least (100 million for 100 dwarves remaining, 100 million to generate 100 dwarves). This should solve or limit any late-game issues regarding massive replacement migrant waves.
Logged