1010-- sets baby/child profession and mood for creatures of the appropriate age where relevant
1111-- properly assigns civ_id to historical_figure to eliminate a number of hostility issues
1212-- removes the arena-generated string of numbers from the first name of units
13+ -- added 'quantity' arg for spawning multiple creatures simultaneously
14+ -- creature caste is now randomly selected if left unspecified
15+
1316--[[
1417 TODO
1518 confirm body size is computed appropriately for different ages / life stages
@@ -36,6 +39,7 @@ Creates a unit. Usage::
3639 HUMAN
3740 -caste casteName
3841 specify the caste of the unit to be created
42+ if not specified, the caste is randomly selected
3943 examples:
4044 MALE
4145 FEMALE
@@ -54,14 +58,20 @@ Creates a unit. Usage::
5458 Can be used instead of -civId \\LOCAL and -groupId \\LOCAL
5559 -name entityRawName
5660 set the unit's name to be a random name appropriate for the
57- given entity. examples:
61+ given entity
62+ examples:
5863 MOUNTAIN
64+ EVIL
5965 -nick nickname
6066 set the unit's nickname directly
6167 -location [ x y z ]
6268 create the unit at the specified coordinates
6369 -age howOld
6470 set the birth date of the unit by current age
71+ chosen randomly if not specified
72+ -quantity howMany
73+ replace "howMany" with the number of creatures you want to create
74+ defaults to 1 if not specified
6575 -flagSet [ flag1 flag2 ... ]
6676 set the specified unit flags in the new unit to true
6777 flags may be selected from df.unit_flags1, df.unit_flags2,
@@ -85,7 +95,7 @@ local utils=require 'utils'
8595function createUnit (...)
8696 local old_gametype = df .global .gametype
8797 local old_mode = df .global .ui .main .mode
88- local old_popups = {} -- as:df.popup_message[]
98+ local old_popups = {}
8999 for _ , popup in pairs (df .global .world .status .popups ) do
90100 table.insert (old_popups , popup )
91101 end
@@ -144,8 +154,8 @@ function createUnitInner(race_id, caste_id, location, entityRawName)
144154 local spawnScreen = dfhack .gui .getCurViewscreen ()
145155 if dfhack .world .isArena () then
146156 -- Just modify the current screen in arena mode (#994)
147- spawnScreen .race :insert (0 , race_id ) -- hint:df.viewscreen_layer_arena_creaturest
148- spawnScreen .caste :insert (0 , caste_id ) -- hint:df.viewscreen_layer_arena_creaturest
157+ spawnScreen .race :insert (0 , race_id )
158+ spawnScreen .caste :insert (0 , caste_id )
149159 end
150160 gui .simulateInput (spawnScreen , ' SELECT' )
151161
175185-- u.population_id = df.historical_entity.find(df.global.ui.civ_id).populations[0]
176186
177187-- Picking a caste or gender at random
178- -- luacheck: in=number
179- function getRandomCasteId (race_id )
180- local cr = df .creature_raw .find (race_id )
181- local caste_id , casteMax
182-
183- casteMax = # cr .caste - 1
188+ function getRandomCasteId (race )
189+ local casteMax = # race .caste - 1
184190
185191 if casteMax > 0 then
186192 return math.random (0 , casteMax )
@@ -303,9 +309,8 @@ function createNemesis(trgunit,civ_id,group_id)
303309
304310 nem .save_file_id =- 1
305311
306- local he = nil
307312 if civ_id ~= - 1 then
308- he = df .historical_entity .find (civ_id )
313+ local he = df .historical_entity .find (civ_id )
309314 he .nemesis_ids :insert (" #" ,id )
310315 he .nemesis :insert (" #" ,nem )
311316 allocateIds (nem ,he )
@@ -318,7 +323,7 @@ function createNemesis(trgunit,civ_id,group_id)
318323 he_group .nemesis_ids :insert (" #" ,id )
319324 he_group .nemesis :insert (" #" ,nem )
320325 end
321- nem .figure = createFigure (trgunit ,he ,he_group )
326+ nem .figure = trgunit . hist_figure_id ~= - 1 and df . historical_figure . find ( trgunit . hist_figure_id ) or createFigure (trgunit ,he ,he_group ) -- the histfig check is there just in case this function is called by another script to create nemesis data for a historical figure which somehow lacks it
322327 return nem
323328end
324329
@@ -352,7 +357,7 @@ function domesticate(uid, group_id)
352357
353358 -- And make them tame (from Dirst)
354359 u .flags1 .tame = true
355- u .training_level = df . animal_training_level . Domesticated
360+ u .training_level = 7
356361 end
357362end
358363
@@ -378,7 +383,6 @@ function wild(uid)
378383 end
379384end
380385
381- -- luacheck: in=number,string
382386function nameUnit (id , entityRawName )
383387 -- pick a random appropriate name
384388 -- choose three random words in the appropriate things
@@ -460,7 +464,7 @@ function setAgeProfession(unit)
460464 end
461465end
462466
463- local validArgs = utils .invert ({
467+ validArgs = utils .invert ({
464468 ' help' ,
465469 ' race' ,
466470 ' caste' ,
@@ -473,7 +477,8 @@ local validArgs = utils.invert({
473477 ' nick' ,
474478 ' location' ,
475479 ' age' ,
476- ' setUnitToFort' -- added by amostubal to get past an issue with \\LOCAL
480+ ' setUnitToFort' , -- added by amostubal to get past an issue with \\LOCAL
481+ ' quantity'
477482})
478483
479484if moduleMode then
@@ -490,8 +495,8 @@ local race
490495local raceIndex
491496local casteIndex
492497
493- if not args .race or not args . caste then
494- error ' Specfiy a race and caste for the new unit.'
498+ if not args .race then
499+ qerror ( ' Specify a race for the new unit.' )
495500end
496501
497502-- find race
@@ -504,25 +509,27 @@ for i,v in ipairs(df.global.world.raws.creatures.all) do
504509end
505510
506511if not race then
507- error ' Invalid race. '
512+ qerror ( ' Invalid race: ' .. args . race )
508513end
509514
510- for i ,v in ipairs (race .caste ) do
511- if v .caste_id == args .caste then
512- casteIndex = i
513- break
515+ if args .caste then -- if args.caste is omitted, casteIndex is randomly selected within the spawn loop below
516+ for i ,v in ipairs (race .caste ) do
517+ if v .caste_id == args .caste then
518+ casteIndex = i
519+ break
520+ end
514521 end
515- end
516522
517- if not casteIndex then
518- error ' Invalid caste.'
523+ if not casteIndex then
524+ qerror (' Invalid caste: ' .. args .caste )
525+ end
519526end
520527
521528local age
522529if args .age then
523530 age = tonumber (args .age )
524531 if not age and not age == 0 then
525- error (' Invalid age: ' .. args .age )
532+ qerror (' Invalid age: ' .. args .age )
526533 end
527534end
528535
@@ -546,87 +553,102 @@ if args.setUnitToFort then
546553 group_id = df .global .ui .group_id
547554end
548555
549- local unitId
550- if civ_id == - 1 then
551- unitId = createUnit (raceIndex , casteIndex , args .location , args .name )
552- else
553- unitId = createUnitInCiv (raceIndex , casteIndex , civ_id , group_id , args .location , args .name )
554- end
555-
556- if args .domesticate then
557- domesticate (unitId , group_id )
558- else
559- wild (unitId )
556+ local spawnNumber = 1
557+ if args .quantity then
558+ spawnNumber = tonumber (args .quantity )
559+ if not spawnNumber or spawnNumber < 1 then
560+ qerror (' Invalid spawn quantity: ' .. args .quantity )
561+ end
560562end
561563
562- local u = df .unit .find (unitId )
563- u .counters .soldier_mood_countdown = - 1
564- u .enemy .unk_450 = - 1
565- u .enemy .unk_454 = - 1
566- u .enemy .army_controller_id = - 1
564+ for n = 1 ,spawnNumber do
567565
568- -- these flags are an educated guess of how to get the game to compute sizes correctly: use -flagSet and -flagClear arguments to override or supplement
569- u .flags2 .calculated_nerves = false
570- u .flags2 .calculated_bodyparts = false
571- u .flags3 .body_part_relsize_computed = false
572- u .flags3 .size_modifier_computed = false
573- u .flags3 .compute_health = true
574- u .flags3 .weight_computed = false
566+ if not args .caste then -- randomly select caste each time
567+ casteIndex = getRandomCasteId (race )
568+ end
575569
576- if age or age == 0 then
577- if age == 0 then
578- u .birth_time = df .global .cur_year_tick
570+ local unitId
571+ if civ_id == - 1 then
572+ unitId = createUnit (raceIndex , casteIndex , args .location , args .name )
573+ else
574+ unitId = createUnitInCiv (raceIndex , casteIndex , civ_id , group_id , args .location , args .name )
579575 end
580- local u = df .unit .find (unitId )
581- local oldYearDelta = u .old_year - u .birth_year
582- u .birth_year = df .global .cur_year - age
583- if u .old_year ~= - 1 then
584- u .old_year = u .birth_year + oldYearDelta
585- end
586- if u .flags1 .important_historical_figure == true and u .flags2 .important_historical_figure == true then
587- local hf = df .historical_figure .find (u .hist_figure_id )
588- hf .born_year = u .birth_year
589- hf .born_seconds = u .birth_time
590- hf .old_year = u .old_year
591- hf .old_seconds = u .old_time
576+
577+ if args .domesticate then
578+ domesticate (unitId , group_id )
579+ else
580+ wild (unitId )
592581 end
593- end
594- setAgeProfession (u )
595582
596- if args .flagSet or args .flagClear then
597583 local u = df .unit .find (unitId )
598- local flagsToSet = {} -- as:bool[]
599- local flagsToClear = {} -- as:bool[]
600- for _ ,v in ipairs (args .flagSet or {}) do
601- flagsToSet [v ] = true
602- end
603- for _ ,v in ipairs (args .flagClear or {}) do
604- flagsToClear [v ] = true
605- end
606- for _ ,k in ipairs (df .unit_flags1 ) do
607- if flagsToSet [k ] then
608- u .flags1 [k ] = true ;
609- elseif flagsToClear [k ] then
610- u .flags1 [k ] = false ;
584+ u .counters .soldier_mood_countdown = - 1
585+ u .counters .death_cause = - 1
586+ u .enemy .unk_450 = - 1
587+ u .enemy .unk_454 = - 1
588+ u .enemy .army_controller_id = - 1
589+
590+ -- these flags are an educated guess of how to get the game to compute sizes correctly: use -flagSet and -flagClear arguments to override or supplement
591+ u .flags2 .calculated_nerves = false
592+ u .flags2 .calculated_bodyparts = false
593+ u .flags3 .body_part_relsize_computed = false
594+ u .flags3 .size_modifier_computed = false
595+ u .flags3 .compute_health = true
596+ u .flags3 .weight_computed = false
597+
598+ if age or age == 0 then
599+ if age == 0 then
600+ u .birth_time = df .global .cur_year_tick
611601 end
612- end
613- for _ ,k in ipairs (df .unit_flags2 ) do
614- if flagsToSet [k ] then
615- u .flags2 [k ] = true ;
616- elseif flagsToClear [k ] then
617- u .flags2 [k ] = false ;
602+ local u = df .unit .find (unitId )
603+ local oldYearDelta = u .old_year - u .birth_year
604+ u .birth_year = df .global .cur_year - age
605+ if u .old_year ~= - 1 then
606+ u .old_year = u .birth_year + oldYearDelta
607+ end
608+ if u .flags1 .important_historical_figure == true and u .flags2 .important_historical_figure == true then
609+ local hf = df .historical_figure .find (u .hist_figure_id )
610+ hf .born_year = u .birth_year
611+ hf .born_seconds = u .birth_time
612+ hf .old_year = u .old_year
613+ hf .old_seconds = u .old_time
618614 end
619615 end
620- for _ ,k in ipairs (df .unit_flags3 ) do
621- if flagsToSet [k ] then
622- u .flags3 [k ] = true ;
623- elseif flagsToClear [k ] then
624- u .flags3 [k ] = false ;
616+ setAgeProfession (u )
617+
618+ if args .flagSet or args .flagClear then
619+ local u = df .unit .find (unitId )
620+ local flagsToSet = {}
621+ local flagsToClear = {}
622+ for _ ,v in ipairs (args .flagSet or {}) do
623+ flagsToSet [v ] = true
624+ end
625+ for _ ,v in ipairs (args .flagClear or {}) do
626+ flagsToClear [v ] = true
627+ end
628+ for _ ,k in ipairs (df .unit_flags1 ) do
629+ if flagsToSet [k ] then
630+ u .flags1 [k ] = true ;
631+ elseif flagsToClear [k ] then
632+ u .flags1 [k ] = false ;
633+ end
634+ end
635+ for _ ,k in ipairs (df .unit_flags2 ) do
636+ if flagsToSet [k ] then
637+ u .flags2 [k ] = true ;
638+ elseif flagsToClear [k ] then
639+ u .flags2 [k ] = false ;
640+ end
641+ end
642+ for _ ,k in ipairs (df .unit_flags3 ) do
643+ if flagsToSet [k ] then
644+ u .flags3 [k ] = true ;
645+ elseif flagsToClear [k ] then
646+ u .flags3 [k ] = false ;
647+ end
625648 end
626649 end
627- end
628650
629- if args .nick and type (args .nick ) == ' string' then
630- dfhack .units .setNickname (df .unit .find (unitId ), args .nick )
651+ if args .nick and type (args .nick ) == ' string' then
652+ dfhack .units .setNickname (df .unit .find (unitId ), args .nick )
653+ end
631654end
632-
0 commit comments