Added custom-raw-tokens utility (#2038)
* Added (chain) for [CHAIN_METAL_TEXT] armours in gui/materials.lua used by gui/create-item-- again (oops) * Added customRawData utility * Oops, whitespace * Revised rawStringsFieldNames * Dialed down on lua trickery and fixed wrongly formatted changelog entry * Fixed changelog in wrong place and made customRawData a proper module * Fixed not caching not-present tags, revised examples and fixed error * Fixed whitespace. Changing settings in editor! * customRawData docs * Added getCreatureTag for respecting caste tags, "fixed" bizarre caching error (quotes because I don't even know what was causing it) and updated docs * Added line limiting for docs, I guess * Added missing string convert argument * docs indent fix, code block fix, and revision * Major revision * gdi, docs error * Another? But... huh. * ... * Made requested changes * Whoops * Rearrange docs lines * Followed example, should fix linter issues * fix typo. linted offline this time...... * Make it so that last instance of tag is what is read from * Added requested change * eventful key change * i to lenArgs * change eventful key * add test for broken caste selection * Major redesign * tags --> tokens * Added plant growth behaviour and did some requested changes * More error handling * fix docs * Added basic error suppression * Docs clarification. * Docs registering example and fix error * Strip errors on frame after onWorldLoad, not on map load * Revert "Strip errors on frame after onWorldLoad, not on map load" This reverts commit e20a0ef8d3743f79d961077f46910b77b16f36b9. * Revert "Docs registering example and fix error" This reverts commit 9c848c54c3f84e0ecc1dc421137c8a8b4a52280d. * Revert "Docs clarification." This reverts commit 6b4b6a1aa40c50398504f37ecf1ff0f93d6459b1. * Revert "Added basic error suppression" This reverts commit d11cb1438cf1e56ff700469e944f0b9af64651d7. * Use more eventful key more consistent with other files * use onStateChange instead of eventful and remove redundant utils require * Code review stuff * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update docs/Lua API.rst committing a suggestion Co-authored-by: Alan <lethosor@users.noreply.github.com> * Prepend examples with DFHACK_ * Remove unused parameters * Use new ensure_key global * Named a couple of unnamed arguments (untested) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Alan <lethosor@users.noreply.github.com>develop
parent
f08a268e8a
commit
b9c36c1e63
@ -0,0 +1,361 @@
|
|||||||
|
--[[
|
||||||
|
custom-raw-tokens
|
||||||
|
Allows for reading custom tokens added to raws by mods
|
||||||
|
by Tachytaenius (wolfboyft)
|
||||||
|
|
||||||
|
Yes, non-vanilla raw tokens do quietly print errors into the error log but the error log gets filled with garbage anyway
|
||||||
|
|
||||||
|
NOTE: This treats plant growths similarly to creature castes but there is no way to deselect a growth, so don't put a token you want to apply to a whole plant after any growth definitions
|
||||||
|
]]
|
||||||
|
|
||||||
|
local _ENV = mkmodule("custom-raw-tokens")
|
||||||
|
|
||||||
|
local customRawTokensCache = {}
|
||||||
|
dfhack.onStateChange.customRawTokens = function(code)
|
||||||
|
if code == SC_WORLD_UNLOADED then
|
||||||
|
customRawTokensCache = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function doToken(cacheTable, token, iter)
|
||||||
|
local args, lenArgs = {}, 0
|
||||||
|
for arg in iter do
|
||||||
|
lenArgs = lenArgs + 1
|
||||||
|
args[lenArgs] = arg
|
||||||
|
end
|
||||||
|
if lenArgs == 0 then
|
||||||
|
cacheTable[token] = true
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
cacheTable[token] = args
|
||||||
|
return table.unpack(args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getSubtype(item)
|
||||||
|
if item:getSubtype() == -1 then return nil end -- number
|
||||||
|
return dfhack.items.getSubtypeDef(item:getType(), item:getSubtype()) -- struct
|
||||||
|
end
|
||||||
|
|
||||||
|
local rawStringsFieldNames = {
|
||||||
|
[df.inorganic_raw] = "str",
|
||||||
|
[df.plant_raw] = "raws",
|
||||||
|
[df.creature_raw] = "raws",
|
||||||
|
[df.itemdef_weaponst] = "raw_strings",
|
||||||
|
[df.itemdef_trapcompst] = "raw_strings",
|
||||||
|
[df.itemdef_toyst] = "raw_strings",
|
||||||
|
[df.itemdef_toolst] = "raw_strings",
|
||||||
|
[df.itemdef_instrumentst] = "raw_strings",
|
||||||
|
[df.itemdef_armorst] = "raw_strings",
|
||||||
|
[df.itemdef_ammost] = "raw_strings",
|
||||||
|
[df.itemdef_siegeammost] = "raw_strings",
|
||||||
|
[df.itemdef_glovesst] = "raw_strings",
|
||||||
|
[df.itemdef_shoesst] = "raw_strings",
|
||||||
|
[df.itemdef_shieldst] = "raw_strings",
|
||||||
|
[df.itemdef_helmst] = "raw_strings",
|
||||||
|
[df.itemdef_pantsst] = "raw_strings",
|
||||||
|
[df.itemdef_foodst] = "raw_strings",
|
||||||
|
[df.entity_raw] = "raws",
|
||||||
|
[df.language_word] = "str",
|
||||||
|
[df.language_symbol] = "str",
|
||||||
|
[df.language_translation] = "str",
|
||||||
|
[df.reaction] = "raw_strings",
|
||||||
|
[df.interaction] = "str"
|
||||||
|
}
|
||||||
|
|
||||||
|
local function getTokenCore(typeDefinition, token)
|
||||||
|
-- Have we got a table for this item subtype/reaction/whatever?
|
||||||
|
-- tostring is needed here because the same raceDefinition key won't give the same value every time
|
||||||
|
local thisTypeDefCache = ensure_key(customRawTokensCache, tostring(typeDefinition))
|
||||||
|
|
||||||
|
-- Have we already extracted and stored this custom raw token for this type definition?
|
||||||
|
local tokenData = thisTypeDefCache[token]
|
||||||
|
if tokenData ~= nil then
|
||||||
|
if type(tokenData) == "table" then
|
||||||
|
return table.unpack(tokenData)
|
||||||
|
else
|
||||||
|
return tokenData
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get data anew
|
||||||
|
local success, dftype = pcall(function() return typeDefinition._type end)
|
||||||
|
local rawStrings = typeDefinition[rawStringsFieldNames[dftype]]
|
||||||
|
if not success or not rawStrings then
|
||||||
|
error("Expected a raw type definition or instance in argument 1")
|
||||||
|
end
|
||||||
|
local currentTokenIterator
|
||||||
|
for _, rawString in ipairs(rawStrings) do -- e.g. "[CUSTOM_TOKEN:FOO:2]"
|
||||||
|
local noBrackets = rawString.value:sub(2, -2)
|
||||||
|
local iter = noBrackets:gmatch("[^:]*") -- iterate over all the text between colons between the brackets
|
||||||
|
if token == iter() then
|
||||||
|
currentTokenIterator = iter -- we return for last instance of token if multiple instances are present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if currentTokenIterator then
|
||||||
|
return doToken(thisTypeDefCache, token, currentTokenIterator)
|
||||||
|
end
|
||||||
|
-- Not present
|
||||||
|
thisTypeDefCache[token] = false
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getRaceCasteTokenCore(raceDefinition, casteNumber, token)
|
||||||
|
-- Have we got tables for this race/caste pair?
|
||||||
|
local thisRaceDefCache = ensure_key(customRawTokensCache, tostring(raceDefinition))
|
||||||
|
local thisRaceDefCacheCaste = ensure_key(thisRaceDefCache, casteNumber)
|
||||||
|
|
||||||
|
-- Have we already extracted and stored this custom raw token for this race/caste pair?
|
||||||
|
local tokenData = thisRaceDefCacheCaste[token]
|
||||||
|
if tokenData ~= nil then
|
||||||
|
if type(tokenData) == "table" then
|
||||||
|
return table.unpack(tokenData)
|
||||||
|
elseif tokenData == false and casteNumber ~= -1 then
|
||||||
|
return getRaceCasteTokenCore(raceDefinition, -1, token)
|
||||||
|
else
|
||||||
|
return tokenData
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get data anew. Here we have to track what caste is currently being written to
|
||||||
|
local casteId, thisCasteActive
|
||||||
|
if casteNumber ~= -1 then
|
||||||
|
casteId = raceDefinition.caste[casteNumber].caste_id
|
||||||
|
thisCasteActive = false
|
||||||
|
else
|
||||||
|
thisCasteActive = true
|
||||||
|
end
|
||||||
|
local currentTokenIterator
|
||||||
|
for _, rawString in ipairs(raceDefinition.raws) do
|
||||||
|
local noBrackets = rawString.value:sub(2, -2)
|
||||||
|
local iter = noBrackets:gmatch("[^:]*")
|
||||||
|
local rawStringToken = iter()
|
||||||
|
if rawStringToken == "CASTE" or rawStringToken == "SELECT_CASTE" or rawStringToken == "SELECT_ADDITIONAL_CASTE" or rawStringToken == "USE_CASTE" then
|
||||||
|
local newCaste = iter()
|
||||||
|
if newCaste then
|
||||||
|
thisCasteActive = newCaste == casteId or rawStringToken == "SELECT_CASTE" and newCaste == "ALL"
|
||||||
|
end
|
||||||
|
elseif thisCasteActive and token == rawStringToken then
|
||||||
|
currentTokenIterator = iter
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if currentTokenIterator then
|
||||||
|
return doToken(thisRaceDefCache, token, currentTokenIterator)
|
||||||
|
end
|
||||||
|
thisRaceDefCacheCaste[token] = false
|
||||||
|
if casteNumber == -1 then
|
||||||
|
return false -- Don't get into an infinite loop!
|
||||||
|
end
|
||||||
|
-- Not present, try with no caste
|
||||||
|
return getRaceCasteTokenCore(raceDefinition, -1, token)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getPlantGrowthTokenCore(plantDefinition, growthNumber, token)
|
||||||
|
-- Have we got tables for this plant/growth pair?
|
||||||
|
local thisPlantDefCache = ensure_key(customRawTokensCache, tostring(plantDefinition))
|
||||||
|
local thisPlantDefCacheGrowth = ensure_key(thisPlantDefCache, growthNumber)
|
||||||
|
|
||||||
|
-- Have we already extracted and stored this custom raw token for this plant/growth pair?
|
||||||
|
local tokenData = thisPlantDefCacheGrowth[token]
|
||||||
|
if tokenData ~= nil then
|
||||||
|
if type(tokenData) == "table" then
|
||||||
|
return table.unpack(tokenData)
|
||||||
|
elseif tokenData == false and growthNumber ~= -1 then
|
||||||
|
return getPlantGrowthTokenCore(plantDefinition, -1, token)
|
||||||
|
else
|
||||||
|
return tokenData
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get data anew. Here we have to track what growth is currently being written to
|
||||||
|
local growthId, thisGrowthActive
|
||||||
|
if growthNumber ~= -1 then
|
||||||
|
growthId = plantDefinition.growths[growthNumber].id
|
||||||
|
thisGrowthActive = false
|
||||||
|
else
|
||||||
|
thisGrowthActive = true
|
||||||
|
end
|
||||||
|
local currentTokenIterator
|
||||||
|
for _, rawString in ipairs(plantDefinition.raws) do
|
||||||
|
local noBrackets = rawString.value:sub(2, -2)
|
||||||
|
local iter = noBrackets:gmatch("[^:]*")
|
||||||
|
local rawStringToken = iter()
|
||||||
|
if rawStringToken == "GROWTH" then
|
||||||
|
local newGrowth = iter()
|
||||||
|
if newGrowth then
|
||||||
|
thisGrowthActive = newGrowth == growthId
|
||||||
|
end
|
||||||
|
elseif thisGrowthActive and token == rawStringToken then
|
||||||
|
currentTokenIterator = iter
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if currentTokenIterator then
|
||||||
|
return doToken(thisPlantDefCache, token, currentTokenIterator)
|
||||||
|
end
|
||||||
|
thisPlantDefCacheGrowth[token] = false
|
||||||
|
if growthNumber == -1 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return getPlantGrowthTokenCore(plantDefinition, -1, token)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Function signatures:
|
||||||
|
getToken(rawStruct, token)
|
||||||
|
getToken(rawStructInstance, token)
|
||||||
|
getToken(raceDefinition, casteNumber, token)
|
||||||
|
getToken(raceDefinition, casteString, token)
|
||||||
|
getToken(plantDefinition, growthNumber, token)
|
||||||
|
getToken(plantDefinition, growthString, token)
|
||||||
|
]]
|
||||||
|
|
||||||
|
local function getTokenArg1RaceDefinition(raceDefinition, b, c)
|
||||||
|
local casteNumber, token
|
||||||
|
if not c then
|
||||||
|
-- 2 arguments
|
||||||
|
casteNumber = -1
|
||||||
|
assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string")
|
||||||
|
token = b
|
||||||
|
elseif type(b) == "number" then
|
||||||
|
-- 3 arguments, casteNumber
|
||||||
|
assert(b == -1 or b < #raceDefinition.caste and math.floor(b) == b and b >= 0, "Invalid argument 2 to getToken, must be -1 or a caste name or number present in the creature raw")
|
||||||
|
casteNumber = b
|
||||||
|
assert(type(c) == "string", "Invalid argument 3 to getToken, must be a string")
|
||||||
|
token = c
|
||||||
|
else
|
||||||
|
-- 3 arguments, casteString
|
||||||
|
assert(type(b) == "string", "Invalid argument 2 to getToken, must be -1 or a caste name or number present in the creature raw")
|
||||||
|
local casteString = b
|
||||||
|
for i, v in ipairs(raceDefinition.caste) do
|
||||||
|
if v.caste_id == casteString then
|
||||||
|
casteNumber = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert(casteNumber, "Invalid argument 2 to getToken, caste name \"" .. casteString .. "\" not found")
|
||||||
|
assert(type(c) == "string", "Invalid argument 3 to getToken, must be a string")
|
||||||
|
token = c
|
||||||
|
end
|
||||||
|
return getRaceCasteTokenCore(raceDefinition, casteNumber, token)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getTokenArg1PlantDefinition(plantDefinition, b, c)
|
||||||
|
local growthNumber, token
|
||||||
|
if not c then
|
||||||
|
-- 2 arguments
|
||||||
|
growthNumber = -1
|
||||||
|
assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string")
|
||||||
|
token = b
|
||||||
|
elseif type(b) == "number" then
|
||||||
|
-- 3 arguments, growthNumber
|
||||||
|
assert(b == -1 or b < #plantDefinition.growths and math.floor(b) == b and b >= 0, "Invalid argument 2 to getToken, must be -1 or a growth name or number present in the plant raw")
|
||||||
|
growthNumber = b
|
||||||
|
assert(type(c) == "string", "Invalid argument 3 to getToken, must be a string")
|
||||||
|
token = c
|
||||||
|
else
|
||||||
|
-- 3 arguments, growthString
|
||||||
|
assert(type(b) == "string", "Invalid argument 2 to getToken, must be -1 or a growth name or number present in the plant raw")
|
||||||
|
local growthString = b
|
||||||
|
for i, v in ipairs(plantDefinition.growths) do
|
||||||
|
if v.id == growthString then
|
||||||
|
growthNumber = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert(growthNumber, "Invalid argument 2 to getToken, growth name \"" .. growthString .. "\" not found")
|
||||||
|
assert(type(c) == "string", "Invalid argument 3 to getToken, must be a string")
|
||||||
|
token = c
|
||||||
|
end
|
||||||
|
return getPlantGrowthTokenCore(plantDefinition, growthNumber, token)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getTokenArg1Else(userdata, token)
|
||||||
|
assert(type(token) == "string", "Invalid argument 2 to getToken, must be a string")
|
||||||
|
local rawStruct
|
||||||
|
if df.is_instance(df.historical_entity, userdata) then
|
||||||
|
rawStruct = userdata.entity_raw
|
||||||
|
elseif df.is_instance(df.item, userdata) then
|
||||||
|
rawStruct = getSubtype(userdata)
|
||||||
|
elseif df.is_instance(df.job, userdata) then
|
||||||
|
if job.job_type == df.job_type.CustomReaction then
|
||||||
|
for i, v in ipairs(df.global.world.raws.reactions.reactions) do
|
||||||
|
if job.reaction_name == v.code then
|
||||||
|
rawStruct = v
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif df.is_instance(df.proj_itemst, userdata) then
|
||||||
|
if not userdata.item then return false end
|
||||||
|
if df.is_instance(df.item_plantst, userdata.item) or df.is_instance(df.item_plant_growthst, userdata.item) then
|
||||||
|
-- use plant behaviour from getToken
|
||||||
|
return getToken(userdata.item, token)
|
||||||
|
end
|
||||||
|
rawStruct = userdata.item and userdata.item.subtype
|
||||||
|
elseif df.is_instance(df.proj_unitst, userdata) then
|
||||||
|
if not usertdata.unit then return false end
|
||||||
|
-- special return so do tag here
|
||||||
|
local unit = userdata.unit
|
||||||
|
return getRaceCasteTokenCore(df.global.world.raws.creatures.all[unit.race], unit.caste, token)
|
||||||
|
elseif df.is_instance(df.building_workshopst, userdata) or df.is_instance(df.building_furnacest, userdata) then
|
||||||
|
rawStruct = df.building_def.find(userdata.custom_type)
|
||||||
|
elseif df.is_instance(df.interaction_instance, userdata) then
|
||||||
|
rawStruct = df.global.world.raws.interactions[userdata.interaction_id]
|
||||||
|
else
|
||||||
|
-- Assume raw struct *is* argument 1
|
||||||
|
rawStruct = userdata
|
||||||
|
end
|
||||||
|
if not rawStruct then return false end
|
||||||
|
return getTokenCore(rawStruct, token)
|
||||||
|
end
|
||||||
|
|
||||||
|
function getToken(from, b, c)
|
||||||
|
-- Argument processing
|
||||||
|
assert(from and type(from) == "userdata", "Expected userdata for argument 1 to getToken")
|
||||||
|
if df.is_instance(df.creature_raw, from) then
|
||||||
|
-- Signatures from here:
|
||||||
|
-- getToken(raceDefinition, casteNumber, token)
|
||||||
|
-- getToken(raceDefinition, casteString, token)
|
||||||
|
return getTokenArg1RaceDefinition(from, b, c)
|
||||||
|
elseif df.is_instance(df.unit, from) then
|
||||||
|
-- Signatures from here:
|
||||||
|
-- getToken(rawStructInstance, token)
|
||||||
|
assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string")
|
||||||
|
local unit, token = from, b
|
||||||
|
return getRaceCasteTokenCore(df.global.world.raws.creatures.all[unit.race], unit.caste, token)
|
||||||
|
elseif df.is_instance(df.plant_raw, from) then
|
||||||
|
-- Signatures from here:
|
||||||
|
-- getToken(plantDefinition, growthNumber, token)
|
||||||
|
-- getToken(plantDefinition, growthString, token)
|
||||||
|
return getTokenArg1PlantDefinition(from, b, c)
|
||||||
|
elseif df.is_instance(df.plant, from) then
|
||||||
|
-- Signatures from here:
|
||||||
|
-- getToken(rawStructInstance, token)
|
||||||
|
assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string")
|
||||||
|
local plantDefinition, plantGrowthNumber, token = df.global.world.raws.plants.all[from.material], -1, b
|
||||||
|
return getPlantGrowthTokenCore(plantDefinition, plantGrowthNumber, token)
|
||||||
|
elseif df.is_instance(df.item_plantst, from) then
|
||||||
|
-- Signatures from here:
|
||||||
|
-- getToken(rawStructInstance, token)
|
||||||
|
local matInfo = dfhack.matinfo.decode(from)
|
||||||
|
if matInfo.mode ~= "plant" then return false end
|
||||||
|
assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string")
|
||||||
|
local plantDefinition, plantGrowthNumber, token = matInfo.plant, -1, b
|
||||||
|
return getPlantGrowthTokenCore(plantDefinition, plantGrowthNumber, token)
|
||||||
|
elseif df.is_instance(df.item_plant_growthst, from) then
|
||||||
|
-- Signatures from here:
|
||||||
|
-- getToken(rawStructInstance, token)
|
||||||
|
local matInfo = dfhack.matinfo.decode(from)
|
||||||
|
if matInfo.mode ~= "plant" then return false end
|
||||||
|
assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string")
|
||||||
|
local plantDefinition, plantGrowthNumber, token = matInfo.plant, from.growth_print, b
|
||||||
|
return getPlantGrowthTokenCore(plantDefinition, plantGrowthNumber, token)
|
||||||
|
else
|
||||||
|
-- Signatures from here:
|
||||||
|
-- getToken(rawStruct, token)
|
||||||
|
-- getToken(rawStructInstance, token)
|
||||||
|
return getTokenArg1Else(from, b)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return _ENV
|
Loading…
Reference in New Issue