I should explain how to add fields to the aircraft entries in the combatParameters table. It has some pretty robust data validation, but I need to explain how to use it.
Near the top of the file, I define the 'combatParameter' data type. This is done using the function
Code:
-- gen.createDataType(dataName,specificKeyTable,generalKeyTable,defaultValueTable,fixedKeyTable) -->
-- newItemFunction(table) --> dataType,
-- creates (and validates) a new instance of the data type
-- isDataTypeFunction(value) --> boolean,
-- creates a function that checks if a value is the dataType
-- dataTypeMetatable
-- the metatable for the data type
A data type created in this way will only have keys and values that we specify ahead of time. These keys and allowable values are specified by these tables: the specificKeyTable, generalKeyTable,defaultValueTable,fixedKeyTable.
The fixedKeyTable is a table that specifies keys in the data type which can't be changed after they are created. {anImmutableValue=true,anotherImmutableValue=true} would mean that the keys anImmutableValue and anotherImmutableValue can't be changed after the data type is created. The combatParameter data type doesn't have any fixed keys at the moment.
The defaultValueTable specifies default values for keys, if values are not provided when the combatParameter is created. At the moment, there are two default values:
Code:
local defaultValueTable = {
interceptionRange=0,
attackMoveCost = 255,
}
If interceptionRange is not specified, it is 0, and if the attackMoveCost isn't specified, it is 255.
The keys of the specificKeyTable are allowable keys for a combatParameter. The values of the specificKeyTable are 'valueSpecification' tables (explained below), and they describe the allowable values for their corresponding key. At the moment, here is the specificKeyTable
Code:
local numberSpec = {["number"] = true}
local specificKeyTable = {
pursuitSpeed = numberSpec,
escapeSpeed = numberSpec,
pursuitSpeedLow = numberSpec,
pursuitSpeedHigh = numberSpec,
pursuitSpeedNight = numberSpec,
escapeSpeedLow = numberSpec,
escapeSpeedHigh = numberSpec,
escapeSpeedNight = numberSpec,
interceptionRange = {["number"]={minVal=0, maxVal = maxInterceptionRange, integer=true}},
attackMoveCost = {["number"]={minVal=0,maxVal=255,integer=true}},
}
All of these keys must have number values. The interceptionRange must be at least 0, and not more than the local variable maxInterceptionRange, and must also be an integer. Similarly, the attackMoveCost must be an integer between 0 and 255.
Since none of these specifications allow for 'nil' values, they must all exist in the combatParameter. pursuitSpeedLow/High/Night and escapeSpeedLow/High/Night are set to pursuitSpeed/escapeSpeed during pre-processing of makeCombatParameter, so they don't actually have to be specified. (Keys with default values also don't have to be specified.)
The generalKeyTable works similarly to the specificKeyTable, but has function(candidateKey)-->boolean as keys instead. If a candidateKey makes a key return true in the generalKeyTable, that key can be in the combatParameter, if it satisfies the corresponding valueSpecification. (I don't think this will be used for combatParameters.)
The key to this is the valueSpecification, which is a
A valueSpecification is a table with the following keys and values:
["nil"] = true or nil
If this key is true, the specified value can be nil.
["boolean"] = true, "true", "false", or nil
If this key is true (boolean value), the specified value can be a boolean.
If this key is "true" (string), then the specified value can be true, but not false.
If this key is "false" (string), then the specified value can be false, but not true.
If this key is nil, the specified value can't be a boolean.
["function"] = true, string, or nil
if this key is true or string, the specified value can be a function.
If string, the string describes the function, e.g. function(unit)-->number. Value specification checks do not check if the function actually matches the description, only that it is a function.
If this key is nil, the specified value can't be a function.
["number"] = true or nil or {minVal=number/nil, maxVal=number/nil, integer=true/nil}
If true, the specified value can be any number. If nil, the specified value can't be a number.
If table, any number must also be larger than the minVal (if specified) and smaller than the maxVal (if specified). If the integer key is true, the value must also be an integer.
["string"] = true or {[validString] = truthy} or nil
If this key is true, any string is allowed.
If it is a table, any string value must be a key in that table, with a truthy (anything other than false/nil) associated value.
If nil, the value can't be a string.
["table"]=string, true, nil, or {[1]=function(table)->true or string, [2]=string}
If the key is a string, any table is accepted, and the string describes the kind of table needed.
If true, any table is accepted, and a generated description will be 'table'.
If the key is a table, the table's value for 1 is a function, which returns true if specified value is an acceptable table, and a string describing the problem if it is not. The value for 2 is a string describing the required table, for generated descriptions/errors.
If nil, the specified value can't be a table.
["userdata"] = {[dataTypeName]=isDataTypeFn} or nil
The keys to this table are strings that describe acceptable userdata, and the values are functions which return true if the specified value matches the type, and false otherwise.
E.g.
{["unitTypeObject"] = civ.isUnitType, ["cityObject"] = civ.isCity}
Allows unitTypeObjects and cityObjects, but not other kinds of userdata.
The combatParameters table is made into a "dataTable" with the following command
Code:
gen.makeDataTable(combatParameters)
--[[A dataTable acts as an ordinary table, but, if desired, you can forbid values from being changed, forbid new key-value pairs from being stored, and forbid trying to access keys with a `nil` value. These features can make debugging easier by causing an error to happen on the line the mistake is made.
The following functions can be used to control the data table's features:
Code:
gen.forbidReplacement(dataTable) --> void
gen.allowReplacement(dataTable) --> void
gen.forbidNewKeys(dataTable) --> void
gen.allowNewKeys(dataTable) --> void
gen.forbidNilValueAccess(dataTable) --> void
gen.allowNilValueAccess(dataTable) --> void
gen.restrictValues(dataTable,isValidValueFn,makeValidValueFn) --> void
The relevant function for us is the last one. (Note: I just wrote this function, so it isn't part of the template yet, but it will be part of my next update. If anyone is impatient to use it, let me know.)
After this line,
Code:
gen.restrictValues(combatParameters,isCombatParameter,makeCombatParameter)
any value added to the combatParameters table will first be checked using the 'isCombatParameter' function. If it is one, then it is accepted into the table. If not, then 'makeCombatParameter(value)' is run on the proposed value.
Since makeCombatParameter will generate errors if there is missing or invalid data (or if a key is misspelled), this should be a fairly robust way of making sure that all the data in the combatParameters table is good data.