Bay 12 Games Forum

Please login or register.

Login with username, password and session length
Advanced search  

Author Topic: Methods for races that undergo metamorphosis.  (Read 1750 times)

Central Speaker Dan

  • Bay Watcher
    • View Profile
Methods for races that undergo metamorphosis.
« on: November 18, 2020, 08:18:12 am »

So, I've had the vague idea of creating an insectoid playable race for a long time, using different castes to create the structure of a Eusocial colony, like ants, and have finally started to research it.

During my research I seem to have discovered a hypothetical means of doing it successfully:

  • Create the race, making sure that all the physical attributes are defined in the castes.
  • Make all castes immortal. This will ensure that world-generated instances of these casts will always survive long enough for the transformation to occur correctly.
  • Create a syndrome that is infectous on contact and that triggers a transformation after a fixed time period.
  • Create a contaminant material that applies the syndrome.
  • Give all the castes an internal organ that continously secretes the contaminant material. This material and syndrome may need to be different for each life-stage.
  • Make sure that the syndrome immediately removes the secrete tag from the infected creature. This will prevent the contaminant material from being produced every tick after the creature is already infected, preserving performance. It would functinally be a 'secrete continuously unitl infected' condition.
  • Make the syndrome on the final life-stage castes trigger death by old age, possibly by adding a very low maximum age, mimicking a lifespan based not on how old they are, but on how long they've been in that life-stage. This time will need to be unique per final life-stage caste.
  • Test, test, test...

This process, if correct, is dependant on a few unknows, so here are my questions:

  • Are secretions computed during worldgen?
  • Are contact syndromes computed during worldgen?
  • Is there a better way to prevent the randomly-aged and randomly-caste initial-spawns of the civilization from dying out, due to transformation times, than the immortality trick I described above?

I understand that this is a very complex issue that doesn't seem to have been resolved yet, and that, as a first time modder, its a big undertaking. Thank you all for any help or advice you can give.
I will be trying to *Science* the secretion and syndrome questions over the next few days and report back my findings, if I can get anything to work as it should.

P.S.
If the secretion method doesn't work, I have an idea for a method based almost entirely on DFHack scripts that I will persue instead (assuming I haven't dropped my efforts by then).
Logged

MC

  • Bay Watcher
    • View Profile
Re: Methods for races that undergo metamorphosis.
« Reply #1 on: November 18, 2020, 08:45:20 am »

Secretions ARE NOT computed during worldgen. Spreading syndromes during worldgen is also a pain and while I can't say it's the ONLY method, secrets and attacks are the only really reliable way of doing it that I've found.
Logged
This is a terrible mod. All crashmanship is of the highest quality. This object is adorned with hanging rings of notification spam. This object menaces with spikes of llama wool. On the item is an image of a large oval dwarf flesh cabochan in elf bone. The artwork relates to the attack of an unknown creature on an unknown creature in a time before time. It was inevitable.

Central Speaker Dan

  • Bay Watcher
    • View Profile
Re: Methods for races that undergo metamorphosis.
« Reply #2 on: November 18, 2020, 08:55:42 am »

Thank you for the information. I'll have to tackle it through DFHack scripts instead.
Logged

MC

  • Bay Watcher
    • View Profile
Re: Methods for races that undergo metamorphosis.
« Reply #3 on: November 18, 2020, 09:14:33 am »

Please post your findings! I'm really interested in this.
Logged
This is a terrible mod. All crashmanship is of the highest quality. This object is adorned with hanging rings of notification spam. This object menaces with spikes of llama wool. On the item is an image of a large oval dwarf flesh cabochan in elf bone. The artwork relates to the attack of an unknown creature on an unknown creature in a time before time. It was inevitable.

Central Speaker Dan

  • Bay Watcher
    • View Profile
Re: Methods for races that undergo metamorphosis.
« Reply #4 on: November 18, 2020, 10:08:01 am »

Obviously, since I'm totally new to all this, what I have at the moment is just a plan, but I'll explain it here anyway. I was also just speaking with Fleeting Frames over on the discord, who wrote some of the officially included DFHack scripts, so that has given me some useful information.

  • Create the race and all the castes, defining all physical characteristics in the caste definitions, ensuring that all non-final-life-stage caste's lifespans are the same as that of the oldest possible final-life-stage caste or final transformation age plus a bit.
    • Create the Core Transformation Script that handles the transformations.
    • This script would transform a specified creature or historical figure of the race to the cast appropriate to their age, by ID.
    • In order to prevent crashes associated with injuries being on bodyparts that no longer exist, all injuries would need to be pulled from the creature and stored separately. It would then be fully healed before transformation and the injuries would be re-applied after the transfomration.
    • This proccess must include conversions for injuries that cannot exist on the new creature. For example, a caterpillar has more legs than a butterfly, so injuries to those legs cannot be mapped 1-1 onto the adult. You may instead tell it to ignore those injuries, functionally healing them, or assign new locations for them to go, such as left-legs 1 and 2 on the caterpillar mapping to left-leg 1 on the buttterfly, 3 and 4 to 2 and so on.
    • During the transformation, certain conditions, specifically pregnancy, may cause crashes. This can be resolved by having instant births, but having those under X years of age be transformed immediately into an immobile and vulnerable egg-caste.
    • Create a script that runs every time a member of the species is born or spawned, icnluding new historical figures being generated in worldgen.
    • This script would immediately pass the creature or historical figure to the Core Transformation Script.
    • This script would also grab their current age and date of birth. It would then assign the creature or historical fuigure's ID to a given year as a key-value pairing of year against a list of creature or historical figure IDs.
  • Create a script that runs every time one of these creatures dies, removing their ID from the assigned year. This could be skipped by having their status checked by the Transform script instead.
    • Create a script that triggers the transformations.
    • This script would run once per year. It would iterate over all creature or historical figure IDs associated with that year's number, running the Core Transformation Script on each of them.
    • This step could be made much more precise, potentially down to a day-by-day process, however, it may lead to issues to run it too often. Ultimately, greater precision is better, but once every spring, say, is good enough. Also, I don't know how small the timesteps during worldgen actually are, so it may be limited by that interval.

And viola. You should have a reasonably performant, highly detailed structure that can handle any number of creatures and historical figures, accross any number of races, provided you either create seperate Core Transformation Scripts or have conditionals for each race.

I make it sound so simple here, but I'm also kind of dreading it already. Never done much with Lua either.

EDIT:
Looks like this may be easier as a C++ plugin, rather than a collection of scripts. Also, my brain is liquifying from the amount of information I've been processing in the last 16 hours, so I'm taking a much needed break... and playing more Dwarf Frotress :)
« Last Edit: November 18, 2020, 10:27:02 am by Central Speaker Dan »
Logged

Central Speaker Dan

  • Bay Watcher
    • View Profile
Re: Methods for races that undergo metamorphosis.
« Reply #5 on: November 19, 2020, 03:39:56 pm »

Here's the very first protottype of my metamorphosis handler script, purely for feedback purposes.
All it does at the moment is schedule the unit or units to be transformed. It doesn't yet work on historical figures, transform the entity, handle multiple transformations in a lifetime, or handle wounds. Don't get your hope up just yet...

Code: [Select]
-- Handle sequential transformations over a race's lifespan, simulating life-stages.
-- by ilikegoodfood
local help = [====[
Metamorphosis Handler
==============
Manages life-stages, transformations and injuries for races that undergo metamorphosis throughout their lives. Suitable real-world examples include most insect.
]====]

local utils = require('utils')
local validArgs = utils.invert({
'race',
'unit',
'anual',
})
local args = utils.processArgs({...}, validArgs)

-- --------------------------------------------------
-- Declare all local variables
-- --------------------------------------------------
-- Once able to link to other scripts and race specific files, the shedules will be stored within here, keyed by raceID.
-- local schedules = {}
schedule = {}

-- --------------------------------------------------
-- Declare all functions
-- --------------------------------------------------

-- --------------------------------------------------
-- This function schedules the next transformation for the unit based upon the units age. If the next transformation should be done at a time that has already passed, it will make a transformation call.
function ScheduleTransformation(unit)
--Replace later with method to get transform age from file.
local transformAge = 10

local dateOfBirth = unit.birth_year
local currentYear = df.global.cur_year
local age = currentYear - dateOfBirth
local transformYear = dateOfBirth + transformAge

if transformYear <= currentYear then
Transform(unit)
end

if schedule[transformYear]==nil then
schedule[transformYear] = {}
end

schedule[transformYear][1+#schedule[transformYear]] = unit
end

-- --------------------------------------------------

function Transform(unit)
print(unit)
end

-- --------------------------------------------------

-- --------------------------------------------------
-- Script execution starts here
-- --------------------------------------------------
-- Taken from animal-control.lua
-- This section checks if it has been passed a raceID or raceName. If a name, it finds the matching ID.
if args.race and not tonumber(args.race) then
print ('Race provided is not numerical ID')
    args.race=string.upper(args.race)
print ('Race string is', args.race)
    local raceID
    for i,c in ipairs(df.global.world.raws.creatures.all) do
if c.creature_id == args.race then
raceID = i
break
end
end

if not raceID then
qerror('Invalid race: ' .. args.race)
end
args.race = raceID
print ('Numerical ID for race is', args.race)
end

-- This section branches the key behaviours dependant on the arguments given.
if args.race and tonumber(args.race) and args.anual then
print ('Annual transformations underway')
-- change to make a schedule for each raceID, and call only one schedule at a time.
if schedule[df.global.cur_year]~=nil then
for i,u in ipairs(schedule[df.global.cur_year]) do
Transform(u)
end
schedule[df.global.cur_year] = nil
end
else
if args.race and tonumber(args.race) then
if not args.unit then
print ('Iterating over members of race', args.race)
for _, unit in ipairs(df.global.world.units.all) do
if unit~=nil then
if unit.race==args.race then
ScheduleTransformation(unit)
end
end
end
-- Missing check for validity of unit state.
elseif args.unit and tonumber(args.unit) then
-- Finds unit of ID args.unit
print ('Aquiring unit of ID', args.unit)
args.unit = df.unit.find(args.unit)
-- If unit is of race args.race, schedules transformation. Otherwise, reports incorrect race.
if args.unit and args.unit.race == args.race then
ScheduleTransformation(args.unit)
else
print ('Unit is not of race', args.race)
end
end
else
print ('Race invalid')
end
end

EDIT: Updated with feedback. Everything that is currently in it, works as intended for the purposes of this prototype.
« Last Edit: November 19, 2020, 06:27:29 pm by Central Speaker Dan »
Logged

Central Speaker Dan

  • Bay Watcher
    • View Profile
Re: Methods for races that undergo metamorphosis.
« Reply #6 on: November 19, 2020, 08:42:24 pm »

Completely reworked from the trigger-dependant scheduling system I had planned, to the interval-iteration system that is possible. Currently none-funbctional due to anomolies around nested table construction on 69+.

EDIT: Heavily expanded, now with help function and robust flags. Still failing on the nested-table-constructor, now on line 107.

Code: [Select]
-- Handle sequential transformations over a race's lifespan, simulating life-stages.
-- by ilikegoodfood
local help = [====[
Metamorphosis Handler
==============
Manages life-stages, transformations and injuries for races that undergo metamorphosis throughout their lives. Suitable real-world examples include most insect.
It takes the following arguments:
-flag, -race, -unit, -sheduleTick, -script

-flag
-flag determines what the script will do and what arguments it needs. Valid flags are:
(not entering a flag at all) - runs the help command that you are seeing now.
help - runs the help command that you are seeing now.
addRace - initializes this script to include race -race and link to script command -script.
iterateUnits - iterates through all units and historical units to perform transformations.
iterateHistorical - iterates through all historical figures and performs.
schedule - takes the provided -unit and -scheduleTick and stores them. These values will then be used to call them later.
frequencyUnits - sets the frequency at which it iterates over units to the provided -scheduleTick. Default value of 1200 is recommended for fortress mode, 7200 for adventure mode.
frequencyHistorical - sets the frequency at which it iterates over historical figures to the provided -scheduleTick. Default value of 33600 is recommended for fortress mode, 1209600 for adventure mode.

-race
Valid inputs are a race\'s name, as written in the raw file (not case sensitive), or a raceID.

-unit
Valid inputs are a unitID or, for between-script communication, a unit reference.

-scheduleTick
Valid inputs are integers.

-script
NOT FOR MANUAL USE: Takes the string name of a lua script for storage alongside added races.
]====]

local utils = require('utils')
local validArgs = utils.invert({
'flag',
'race',
'unit',
'scheduleTick',
'script'
})
local args = utils.processArgs({...}, validArgs)

-- --------------------------------------------------
-- Declare all local variables
-- --------------------------------------------------
races = races or {}
frequencyUnit = 1200 -- how often the script iterates over units, in ticks. 1200 = once per day, which is aproximately 34 seconds at 30fps.
frequencyHistorical = 33600 -- how often the script iterates over historical figures. 33600 = once per month, which is aproximately 16 minutes 40 seconds at 30fps.

-- --------------------------------------------------
-- Initialize random number generator
-- --------------------------------------------------
math.randomseed(os.time()) -- random initialize
math.random(); math.random(); math.random() -- warming up

-- --------------------------------------------------
-- Declare all functions
-- --------------------------------------------------

-- --------------------------------------------------
-- This function will Metamorphosis the unit to the correct life stage, calling on the Metamorphosis method of that race in its own metamorphosis script.
function Metamorphosis(unit)
-- Will call race-scpeicifc metamorphosis command and pass in unit.
-- This allows for conditional testing, restrictions, branching paths and root castes to all be defined and processed per race, without editing this handler script.
print('unit ' .. unit.id)
end

-- --------------------------------------------------
-- Provided by Atkana on the unofficial Dwarf Fortress Discord.
-- This function returns the number of entries in a table.
function numberOfKeys(keyedTable)
  local keyNum = 0
  for key, entry in pairs(keyedTable) do
    keyNum = keyNum + 1
  end
  return keyNum
end

-- --------------------------------------------------

-- --------------------------------------------------
-- Script execution starts here
-- --------------------------------------------------
if not args.flag or args.flag == 'help' then
print (help)
elseif args.flag == 'addRace' then
if args.race and args.script then
-- Taken from animal-control.lua
-- This section checks if it has been passed a raceID or raceName. If a name, it finds the matching ID.
if not tonumber(args.race) then
print ('Race provided is not numerical ID')
args.race=string.upper(args.race)
print ('Race string is', args.race)
local raceID
for i,c in ipairs(df.global.world.raws.creatures.all) do
if c.creature_id == args.race then
raceID = i
break
end
end

if not raceID then
qerror('Invalid race: ' .. args.race)
end
args.race = raceID
print ('Numerical ID for race is', args.race)
else
args.race = tonumber(args.race);
end

-- Initializes table with race key and sub tables.
if races[args.race]==nil then
races[args.race] = { ["script"]=args.script, ["unitAge"]={}, ["targetAge"]={} }
-- Implement handshake protocall.
print ('New race ' .. args.race .. ' has been initialized for handling.')
else
print ('Race ' .. args.race .. ' has already been initialized.')
end
print (numberOfKeys(races) .. ' total races have been initialized by this handler.')
else
qerror ('addRace requires -race and -script args')
end
elseif args.flag == 'iterateUnits' then
-- If there are races listed, performs all operations on each unit of those races.
if numberOfKeys(races)==0 then
print('No races initialized')
else
print('Iterating units for ' .. numberOfKeys(races) .. ' races')
-- Calculate the current tick since the begining of time.
local cur_tick = ( df.global.cur_year * 58060800) + df.global.cur_year_tick
print ('current game-time in ticks is ' .. cur_tick)

for _, unit in ipairs(df.global.world.units.all) do
if races[unit.race]~=nil then
-- Set the current age of the unit in ticks.
races[unit.race]['unitAge'][unit] = cur_tick - unit.birth_time
print ('Unit ' .. unit.id .. ' is ' .. races[unit.race]['unitAge'][unit] .. ' ticks old.')

-- Set the tick upon which the next metamorphosis will take place if there is none, or metamorphosis if targetAge is less than unitAge.
if races[unit.race]['targetAge'][unit]~=nil then
-- If the unit is older than its next scehduled metamorphosis, it will metamorphosis. The Metamorphosis function will call the handler in the race-scpeicifc metamorphosis command.
if races[unit.race]['targetAge'][unit]~=-1 and races[unit.race]['unitAge'][unit] <= races[unit.race]['targetAge'][unit] then
Metamorphosis(unit)
end
else
-- If the unit has not been initialized, it will metamorphosis. The Metamorphosis function will call the handler in the race-scpeicifc metamorphosis command.
Metamorphosis(unit)
end
end
end
end
elseif args.flag == 'iterateUnits' then
print ('iterateUnits not yet implemented')
elseif args.flag == 'iterateHistorical' then
print ('iterateHistorical not yet implemented')
elseif args.flag == 'schedule' then
print ('schedule not yet implemented')
elseif args.flag == 'frequencyUnits' then
print ('frequencyUnits not yet implemented')
elseif args.flag == 'frequencyHistorical' then
print ('frequencyHistorical not yet implemented')
else
qerror ('Invalid flag ' .. args.flag .. '. Run script with no flag or with \'-flag help\' to get the flag and argument lists.')
end

EDIT 20/11/20: New code block being added to represent and account for the significant maturation of the script.
All the basic functionality has ben added to metamorphosis handler. The next step, script wise, is to begin developement on the race-specific metamorphosis script, so that I can create a working co-dependant system. From there, I should be able to create a template for the race-specific metamorphosis script that will allow any number of races, both my own and others', to hook into the metamorphosis-handler without significantly effecting performance.

EDIT 21/11/20: Minor update allowing for histories over 5326 years long and fixes to documentation.

Code: [Select]
-- Handle sequential transformations over a race's lifespan, simulating life-stages.
-- by ilikegoodfood
local help = [====[
Metamorphosis Handler
==============
Manages life-stages, transformations and injuries for races that undergo metamorphosis throughout their lives. Suitable real-world examples include most insect.
It takes the following arguments:
-flag, -race, -unit, -sheduleTick, -script

-flag
-flag determines what the script will do and what arguments it needs. Valid flags are:

(not entering a flag at all) - runs the help command that you are seeing now.
help - runs the help command that you are seeing now.
addRace - initializes this script to include race -race and link to script command -script.
iterateUnits - iterates through all units and historical units to perform transformations.
iterateHistorical - iterates through all historical figures and performs.
schedule - takes the provided -unit and -scheduleTick and stores them. These values will then be used to call them later.
debug - runs the debugHelp command for the debug options.
debugHelp - runs the debugHelp command for the debug options.

-race
Valid inputs are a race\'s name, as written in the raw file (not case sensitive), or a raceID.

-unit
Valid inputs are a unitID or, for between-script communication, a unit reference.

-scheduleYear
Valid inputs are integers.

-scheduleTick
Valid inputs are integers.

-script
NOT FOR MANUAL USE: Takes the string name of a lua script for storage alongside added races.
]====]

local debugHelp = [====[
Metamorphosis Handler Debug Flags
==============
Debug Flags:

debug - runs the debugHelp command that you are seeing now.
debugHelp - runs the debugHelp command that you are seeing now.

debugRemoveRace
Removes race -race from this handler.

debugReinitializeRace.
Removes race -race fromn this hanbdler, then re-initializes this script to include race -race and link to script command -script.

debugFrequencyUnits
Sets the frequency at which it iterates over units to the provided -scheduleTick. Default value of 1200 is recommended for fortress mode, 7200 for adventure mode.
Higher values are more performant, as they this script runs less often. This may lead to strange behaviours, such as children being born in the wrong caste remaining active in that caste for prolongued periods of time. This will also effect guests, invaders and merchants.
Lower values are more responsive, but are significantly less poerformant, as the script runs more often. This may lead to a loss in average frame-rate, manifesting strongly in specific ticks that corrispond to the interval of this script's operation.

debugFrequencyHistorical - sets the frequency at which it iterates over historical figures to the provided -scheduleTick. Default value of 33600 is recommended for fortress mode, 1209600 for adventure mode.
Higher values are more performant, as they this script runs less often. This may lead to strange behaviours, such as historical figures being born in the wrong caste remaining active in that caste for prolongued periods of time. This will also effect guests, invaders and merchants.
Lower values are more responsive, but are significantly less poerformant, as the script runs more often. This may lead to a loss in average frame-rate, manifesting strongly in specific ticks that corrispond to the interval of this script's operation.

]====]

local utils = require('utils')
local validArgs = utils.invert({
'flag',
'race',
'unit',
'scheduleYear',
'scheduleTick',
'script'
})
local args = utils.processArgs({...}, validArgs)

-- --------------------------------------------------
-- Declare all local variables
-- --------------------------------------------------
races = races or {}
frequencyUnits = 1200 -- how often the script iterates over units, in ticks. 1200 = once per day, which is aproximately 34 seconds at 30fps.
frequencyHistorical = 33600 -- how often the script iterates over historical figures. 33600 = once per month, which is aproximately 16 minutes 40 seconds at 30fps.

-- --------------------------------------------------
-- Declare all functions
-- --------------------------------------------------

-- --------------------------------------------------
-- This function will Metamorphosis the unit to the correct life stage, calling on the Metamorphosis method of that race in its own metamorphosis script.
function Metamorphosis(unit)
print('passing unit ' .. unit.id .. ' to ' .. tostring(races[unit.race]['script']))
dfhack.run_script(tostring(races[unit.race]['script']), '-flag', 'metamorphosis', '-unit', tostring(unit.id))
end

-- --------------------------------------------------
-- Taken from animal-control.lua and modified.
-- This function checks if args.race is a raceID or raceName. If a name, it finds the matching ID and assigns it to args.race.
function ValidateRace()
if args.race then
if not tonumber(args.race) then
print ('Race provided is not numerical ID')
args.race=string.upper(args.race)
print ('Race string is', args.race)
local raceID
for i,c in ipairs(df.global.world.raws.creatures.all) do
if c.creature_id == args.race then
raceID = i
break
end
end

if not raceID then
qerror('Invalid race: ' .. args.race)
end
args.race = raceID
print ('Numerical ID for race is ' .. args.race)
else
args.race = tonumber(args.race);
end
end
end

-- --------------------------------------------------
-- This function checks if args.unit is a unitID. If a unitID, it finds the matching unit reference and assigns it to args.unit.
function ValidateUnit()
if args.unit and tonumber(args.unit) then
args.unit = df.unit.find(args.unit)
end
end

-- --------------------------------------------------
-- This function validates -race and initializes it in this handler.
function AddRace()
if args.race and args.script then
ValidateRace()

local handshake = false
if args.script ~= 'test' then
handshake = dfhack.run_script(args.script, '-flag', 'handshake')
end

if handshake then
-- Initializes table with race key and sub tables.
if races[args.race]==nil then
races[args.race] = { ["script"]=args.script, ["unitAge"]={}, ["targetAge"]={} }
-- Implement handshake protocall.
print ('New race ' .. args.race .. ' has been initialized for handling.')
else
print ('Race ' .. args.race .. ' has already been initialized.')
end

local int = NumberOfKeys(races)
if int == 1 then
print ('A total of ' .. int .. ' race has been initialized by this handler.')
else
print ('A total of ' .. int .. ' races have been initialized by this handler.')
end
else
qerror ('Handshake failed to verify -script ' .. tostring(args.script) .. '. Please ensure that the script name provided exactly matches the script file name, excluding file extension, and that its \'handshake Branch\' returns true')
end
end
end

-- --------------------------------------------------
-- This function validates -race and removes it from this handler.
function RemoveRace()
if args.race then
ValidateRace()

-- Removes value for proivided race from table.
if races[args.race]==nil then
print ('Race ' .. args.race .. ' has not been initialized by this handler')
else
races[args.race] = nil
print ('Race ' .. args.race .. ' has been removed from this handler')
end

local int = NumberOfKeys(races)
if int == 1 then
print ('A total of ' .. int .. ' race has been initialized by this handler.')
else
print ('A total of ' .. int .. ' races have been initialized by this handler.')
end
else
qerror ('debugRemoveRace requires -race arg')
end
end

-- --------------------------------------------------
-- Provided by Atkana on the unofficial Dwarf Fortress Discord.
-- This function returns the number of entries in a table.
function NumberOfKeys(keyedTable)
  local keyNum = 0
  for key, entry in pairs(keyedTable) do
    keyNum = keyNum + 1
  end
  return keyNum
end

-- --------------------------------------------------
-- Script execution starts here
-- --------------------------------------------------
-- Prints help if no flag is entered, or if flag == help.
if not args.flag or args.flag == 'help' then
print (help)
-- --------------------------------------------------
-- addRace Branch: Script initializes to handle provided race.
elseif args.flag == 'addRace' then
if args.race and args.script then
AddRace()
else
qerror ('addRace requires -race and -script args')
end
-- --------------------------------------------------
-- iterateUnits Branch: Iterates over all units, documenting them and passing them on to species-specific metamorphosis script as required.
elseif args.flag == 'iterateUnits' then
-- If there are races listed, performs all operations on each unit of those races.
local int = NumberOfKeys(races)
if int==0 then
print('No races initialized')
else

if int == 1 then
print ('Iterating units for ' .. int .. ' race...')
else
print ('Iterating units for ' .. int .. ' races...')
end
print ('current game-time is ' .. df.global.cur_year .. ' years ' .. df.global.cur_year_tick .. ' ticks.')

for _, unit in ipairs(df.global.world.units.all) do
if races[unit.race] then
-- Set the current age of the unit in ticks.
if not races[unit.race]['unitAge'][unit] then
races[unit.race]['unitAge'][unit] = {}
end

races[unit.race]['unitAge'][unit]['years'] = df.global.cur_year - unit.birth_year
races[unit.race]['unitAge'][unit]['ticks'] = df.global.cur_year_tick - unit.birth_time

if races[unit.race]['unitAge'][unit]['ticks'] < 0 then
races[unit.race]['unitAge'][unit]['ticks'] = 403200 + races[unit.race]['unitAge'][unit]['ticks']
races[unit.race]['unitAge'][unit]['years'] = races[unit.race]['unitAge'][unit]['years'] - 1
end

print ('Unit ' .. unit.id .. ' is ' .. races[unit.race]['unitAge'][unit]['years'] .. ' years and ' .. races[unit.race]['unitAge'][unit]['ticks'] .. ' ticks old.')

-- Set the tick upon which the next metamorphosis will take place if there is none, or metamorphosis if targetAge is less than unitAge.
if races[unit.race]['targetAge'][unit] then
-- If the unit is older than its next scehduled metamorphosis, it will metamorphosis. The Metamorphosis function will call the handler in the race-scpeicifc metamorphosis command.
if races[unit.race]['targetAge'][unit]['years']~=-1 and races[unit.race]['unitAge'][unit]['years'] <= races[unit.race]['targetAge'][unit]['years'] and races[unit.race]['unitAge'][unit]['ticks'] <= races[unit.race]['targetAge'][unit]['ticks'] then
Metamorphosis(unit)
end
else
-- If the unit has not been initialized, it will metamorphosis. The Metamorphosis function will call the handler in the race-scpeicifc metamorphosis command.
Metamorphosis(unit)
end
end
end
end
-- --------------------------------------------------
-- iterateHistorical Branch: Iterates over all historical figures, documenting them and passing them on to species-specific metamorphosis script as required.
elseif args.flag == 'iterateHistorical' then
print ('iterateHistorical not yet implemented')
-- --------------------------------------------------
-- schedule Branch: stores targetAge for provided unit.
elseif args.flag == 'schedule' then
if args.unit and args.scheduleYear and tonumber (args.scheduleYear) and args.scheduleTick  and tonumber(args.scheduleTick) then
ValidateUnit()

if args.unit then
races[args.unit.race]['targetAge'][args.unit]['years'] = tonumber(args.scheduleYear)
races[args.unit.race]['targetAge'][args.unit]['ticks'] = tonumber(args.scheduleTick)
end
else
qerror('shedule requires -unit, -sceduleYear and -scheduleTick args')
end
-- --------------------------------------------------
-- debug Branch: Prints debugHelp if flag == debug.
elseif args.flag == 'debug' or args.flag == 'debugHelp' then
print (debugHelp)
-- --------------------------------------------------
-- debugFrequencyUnits Branch: Changes the frequency at which iterateUnits Branch is run.
elseif args.flag == 'debugFrequencyUnits' then
if args.scheduleTick and tonumber(args.scheduleTick) then
frequencyUnits = tonumber(args.scheduleTick)
print ('iterateUnits frequency changed to ' .. frequencyUnits .. ' ticks.')
end
-- --------------------------------------------------
-- debugFrequencyHistorical Branch: Changes the frequency at which iterateHistorical Branch is run.
elseif args.flag == 'debugFrequencyHistorical' then
if args.scheduleTick and tonumber(args.scheduleTick) then
frequencyHistorical = tonumber(args.scheduleTick)
print ('iterateHistorical frequency changed to ' .. frequencyHistorical .. ' ticks.')
end
-- --------------------------------------------------
-- debugRemoveRace Branch: Removes provided race from this handler.
elseif args.flag == 'debugRemoveRace' then
if args.race then
RemoveRace()
end
-- --------------------------------------------------
-- debugReinitializeRace Branch: Removes provided race from this handler, then initializes to handle provided race.
elseif args.flag == 'debugReinitializeRace' then
if args.race and args.script then
RemoveRace()
AddRace()
else
qerror ('debugRemoveRace requires -race and -script args')
end
-- --------------------------------------------------
-- Invalid Flag error message.
else
qerror ('Invalid flag ' .. args.flag .. '. Run script with no flag or with \'-flag help\' to get the flag and argument lists.')
end

And the first sample of a race-specific metamorphosis script:

Code: [Select]
-- Race-specific metamorphosis script for testing and developement purposes. Converts Dwarfs into Elfs at ~100 years of age.
-- template by ilikegoodfood, race-script by ilikegoodfood
local help = [====[
Metamorphosis Dwarf
==============
Handles Transformations for Dwarf race.
It takes the following arguments:
-flag, -unit

-flag
-flag determines what the script will do and what arguments it needs. Valid flags are:

(not entering a flag at all) - runs the help command that you are seeing now.
help - runs the help command that you are seeing now.
register - registers this race to the metamorphosis-handler.
handshake - confirms valid connection with the metamorphosis-handler.
metamorphosis - conducts all valid tests, scheduling and metamorphosis on unit -unit.

-unit
Valid inputs are a unitID or, for between-script communication, a unit reference.
]====]

local utils = require('utils')
local validArgs = utils.invert({
'flag',
'race',
'unit',
'scheduleYear',
'scheduleTick',
'script'
})
local args = utils.processArgs({...}, validArgs)

-- --------------------------------------------------
-- Declare all local variables
-- --------------------------------------------------
local race = 'dwarf'
local this = 'metamorphosis-dwarf'

-- --------------------------------------------------
-- Declare all functions
-- --------------------------------------------------

-- --------------------------------------------------
-- This function checks if args.unit is a unitID. If a unitID, it finds the matching unit reference and assigns it to args.unit.
function ValidateUnit()
if args.unit and tonumber(args.unit) then
args.unit = df.unit.find(args.unit)
else
args.unit = nil
end
end

function Process(unit)
print ('UnitID is ' .. unit.id .. '. Function not yet implemented.')
end

-- --------------------------------------------------
-- Script execution starts here
-- --------------------------------------------------
-- Prints help if no flag is entered, or if flag == help.
if not args.flag or args.flag == 'help' then
print (help)
-- --------------------------------------------------
-- redister Branch: Script calls metamorphosis-handler and adds this race and script to it.
elseif args.flag == 'register' then
dfhack.run_script('metamorphosis-handler', '-flag', 'addRace', '-race', tostring(race), '-script', tostring(this))
-- --------------------------------------------------
-- handshake Branch: returns true when called.
elseif args.flag == 'handshake' then
print ('Handshake successful')
return true
-- --------------------------------------------------
-- metamorphosis Branch: Performs all checks, validatiuons and transformations defined here in the race-specific metamorphosis script, under the process function.
elseif args.flag == 'metamorphosis' and args.unit then
ValidateUnit()

Process(args.unit);
-- --------------------------------------------------
-- Invalid Flag error message.
else
qerror ('Invalid flag ' .. args.flag .. '. Run script with no flag or with \'-flag help\' to get the flag and argument lists.')
end
« Last Edit: November 21, 2020, 05:42:40 pm by Central Speaker Dan »
Logged

VABritto

  • Bay Watcher
    • View Profile
Re: Methods for races that undergo metamorphosis.
« Reply #7 on: January 07, 2021, 10:25:12 am »

I thought it was possible to define different characteristic for a creature depending on its age, like average size for example. Isn't it possible to also change its bodily features dependent on age in the same manner so as to simulate metamorphosis?
Logged

Central Speaker Dan

  • Bay Watcher
    • View Profile
Re: Methods for races that undergo metamorphosis.
« Reply #8 on: January 07, 2021, 07:07:01 pm »

As far as I understand it, Dwarf Fortress only handles age as a scale factor between the initial creature-size and the final creature-size, alongside a single age that defines the transition from the 'child' job to an 'adult' job. No changes in appearence, descriptors or features are possible by default.

Due to performance concerns, I attempted to transition this from a script-script system, with a core script and a race-specific script created by the race's creator, to a plugin-script system. The latter would have been far more performant, easier for race-creators to use and significatnly more powerful/feature complete. Unfortunately DFHack's developement tools and functions are poorly documented and I have repeated failed to get them to do as they claim to. More specifically, they're not generating the required files from the clean DF instal to successfully compile the plugins. In addition to this, the modding channel of the Discord was unable to provide any meaningful aid or guidance with this issue.

As such, I have dropped this project for the time being.
If someone provides help in solving these issues, I'll happily return to it.
Logged

VABritto

  • Bay Watcher
    • View Profile
Re: Methods for races that undergo metamorphosis.
« Reply #9 on: January 07, 2021, 08:39:34 pm »

That's unfortunate, mate :/ Can you pass me the Discord channel? I am new to Modding myself
Logged

Central Speaker Dan

  • Bay Watcher
    • View Profile
Re: Methods for races that undergo metamorphosis.
« Reply #10 on: January 08, 2021, 09:44:39 am »

Here's the invite link for the DF Discord server: https://discord.gg/CvAEMWx
« Last Edit: January 08, 2021, 12:52:39 pm by Central Speaker Dan »
Logged

VABritto

  • Bay Watcher
    • View Profile
Re: Methods for races that undergo metamorphosis.
« Reply #11 on: January 08, 2021, 11:22:54 am »

Here's the invite link for the DF Siscord server: https://discord.gg/CvAEMWx

Thank you!
Logged