Difference between revisions of "Module:Gear"
Jump to navigation
Jump to search
(49 intermediate revisions by 2 users not shown) | |||
Line 6: | Line 6: | ||
-- core is lua-only methods, not for use in the public intertace | -- core is lua-only methods, not for use in the public intertace | ||
local core = {} | local core = {} | ||
+ | |||
+ | p.core = core | ||
local id_field = { | local id_field = { | ||
Line 16: | Line 18: | ||
-- | -- | ||
local getArgs = require('Module:Arguments').getArgs | local getArgs = require('Module:Arguments').getArgs | ||
+ | local cargo = mw.ext.cargo | ||
-- | -- | ||
-- schema | -- schema | ||
-- | -- | ||
+ | -- schema defines the various database tables and their database schema. | ||
+ | -- | ||
+ | -- tables defined in schema have 2 or 3 fields: | ||
+ | -- * table - the name of the table that this schema defines | ||
+ | -- * fields - an array of fields belonging to this table. each field contains: | ||
+ | -- * field - the name of the field | ||
+ | -- * type - the database type of the field. | ||
+ | -- * parent_table - Optional. A reference to the parent table. Used for | ||
+ | -- sub-types like Weapon and EngineCore. | ||
core.schema = {} | core.schema = {} | ||
+ | -- gear is the schema for the master Gear table. gear is the parent type for | ||
+ | -- every piece of gear that might be mounted on Mech. | ||
core.schema.gear = { | core.schema.gear = { | ||
table = 'Gear', | table = 'Gear', | ||
Line 69: | Line 83: | ||
type = 'Float', | type = 'Float', | ||
},{ | },{ | ||
+ | -- TODO(rust dev): this might actually just be a string | ||
field = 'AllowedLocations', | field = 'AllowedLocations', | ||
type = 'List (,) of String', | type = 'List (,) of String', | ||
},{ | },{ | ||
+ | -- TODO(rust dev): this also might actually just be a string. | ||
field = 'DisallowedLocations', | field = 'DisallowedLocations', | ||
− | type = 'List(,) of String', | + | type = 'List (,) of String', |
},{ | },{ | ||
field = 'BattleValue', | field = 'BattleValue', | ||
type = 'Integer', | type = 'Integer', | ||
+ | },{ | ||
+ | field = 'Bonuses', | ||
+ | type = 'List (,) of String', | ||
+ | },{ | ||
+ | field = 'CustomCategories', | ||
+ | type = 'List (,) of String', | ||
}, | }, | ||
}, | }, | ||
} | } | ||
+ | -- heatsinks defines the Heatsinks table. For weird game reasons, engine parts | ||
+ | -- count as heat sinks. This relationship is reflected in the database schema, | ||
+ | -- but does not strictly need to be. However, that ship has already sailed. | ||
core.schema.heatsink = { | core.schema.heatsink = { | ||
table = 'Heatsinks', | table = 'Heatsinks', | ||
+ | -- parent_table points to the gear schema, because heatsink is a subtype of | ||
+ | -- gear. | ||
parent_table = core.schema.gear, | parent_table = core.schema.gear, | ||
fields = { | fields = { | ||
{ | { | ||
− | field = ' | + | field = 'Dissipation', |
type = 'Integer', | type = 'Integer', | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
}, | }, | ||
}, | }, | ||
} | } | ||
+ | -- cooling defines the Cooling table. Cooling parts are those that represent | ||
+ | -- the Mech's cooling kit, most commonly either single or double heatsinks, but | ||
+ | -- other heatsink types exist. | ||
core.schema.cooling = { | core.schema.cooling = { | ||
table = 'Cooling', | table = 'Cooling', | ||
+ | -- Cooling is a subtype of Heatsink, and so its parent is the Heatsink table. | ||
parent_table = core.schema.heatsink, | parent_table = core.schema.heatsink, | ||
fields = { | fields = { | ||
{ | { | ||
+ | -- HeatsinkDefID is the ID of the Heatsink that this Cooling kit uses. | ||
field = 'HeatsinkDefID', | field = 'HeatsinkDefID', | ||
type = 'String', | type = 'String', | ||
Line 109: | Line 135: | ||
} | } | ||
+ | -- EngineHeatBlocks represent the E-Cooling on an engine. | ||
core.schema.engineheatblock = { | core.schema.engineheatblock = { | ||
table = 'EngineHeatBlocks', | table = 'EngineHeatBlocks', | ||
Line 120: | Line 147: | ||
} | } | ||
+ | -- EngineShield is the kind of engine; Standard, XL, XXL, etc. | ||
core.schema.engineshield = { | core.schema.engineshield = { | ||
table = 'EngineShields', | table = 'EngineShields', | ||
Line 134: | Line 162: | ||
} | } | ||
+ | -- EngineCore is the actual engine rating. | ||
core.schema.enginecore = { | core.schema.enginecore = { | ||
− | + | parent_table = core.schema.heatsink, | |
table = 'EngineCore', | table = 'EngineCore', | ||
fields = { | fields = { | ||
Line 145: | Line 174: | ||
} | } | ||
+ | -- UpgradeDef encompasses all miscellaneous mech parts. This includes gyros, | ||
+ | -- actuators, quirks, EW modules, and others. It currently has no fields that | ||
+ | -- Gear does not, but its existence allows us to filter only Upgrade parts, | ||
+ | -- and future-proofs us for changes later. | ||
core.schema.upgrade = { | core.schema.upgrade = { | ||
table = 'UpgradeDef', | table = 'UpgradeDef', | ||
Line 151: | Line 184: | ||
} | } | ||
+ | -- JumpJets are obviously all JumpJets, but things like Partial Wings also | ||
+ | -- count as JumpJets | ||
core.schema.jumpjet = { | core.schema.jumpjet = { | ||
table = 'JumpJets', | table = 'JumpJets', | ||
Line 162: | Line 197: | ||
type = 'Integer', | type = 'Integer', | ||
},{ | },{ | ||
− | field = ' | + | field = 'JumpCapacity', |
type = 'Float', | type = 'Float', | ||
}, | }, | ||
Line 168: | Line 203: | ||
} | } | ||
+ | -- Weapon defines a weapon. Like everything else, it is a subclass of gear. | ||
core.schema.weapon = { | core.schema.weapon = { | ||
table = 'Weapons', | table = 'Weapons', | ||
Line 247: | Line 283: | ||
field = 'WeaponEffectID', | field = 'WeaponEffectID', | ||
type = 'String', | type = 'String', | ||
+ | },{ | ||
+ | field = 'NoMelee', | ||
+ | type = 'Boolean', | ||
}, | }, | ||
}, | }, | ||
} | } | ||
+ | -- Ammunition encompasses AmmunitionBoxes. There are acutally two kinds of defs | ||
+ | -- in BattleTech for Ammunition, but for our purposes, we only need the | ||
+ | -- AmmunitionBox, which is the gear item. However, when parsing ammo types, we | ||
+ | -- also look at the Ammunition object, which defines details about the actual | ||
+ | -- shots, because this is where the Category is stored. That, however, is not | ||
+ | -- necessary for the wiki side of things. | ||
+ | core.schema.ammunition = { | ||
+ | table = 'Ammunition', | ||
+ | parent_table = core.schema.gear, | ||
+ | fields = { | ||
+ | { | ||
+ | field = 'Capacity', | ||
+ | type = 'Integer', | ||
+ | },{ | ||
+ | -- Category is what kind of gun shoots this ammo. | ||
+ | field = 'Category', | ||
+ | type = 'String', | ||
+ | },{ | ||
+ | field = 'PerUnitCost', | ||
+ | type = 'Integer', | ||
+ | }, | ||
+ | } | ||
+ | } | ||
-- | -- | ||
Line 261: | Line 323: | ||
-- then recursively stores the data of the parent tables' fields. | -- then recursively stores the data of the parent tables' fields. | ||
function core.cargo.store(frame, schema, tpl_args) | function core.cargo.store(frame, schema, tpl_args) | ||
+ | -- When we store a bit of gear to the database, we have to store it to two or | ||
+ | -- more different tables. However, it is a limitation of Cargo that we can | ||
+ | -- only attach or declare one table per template. To get around this | ||
+ | -- limitation, we have an 'attach' template for every table type, and we | ||
+ | -- include every table needed for a given schema when we store the data. | ||
frame:expandTemplate{title=string.format('Template:Gear/cargo/attach/%s', schema.table), args={}} | frame:expandTemplate{title=string.format('Template:Gear/cargo/attach/%s', schema.table), args={}} | ||
Line 266: | Line 333: | ||
data._table = schema.table | data._table = schema.table | ||
+ | -- there may be extraneous arguments in the tpl_args, so we can't pass them | ||
+ | -- verbatim to cargo. Instead, we iterate through the fields of the table, | ||
+ | -- and we pull out the value of the parameter that matches the field.field. | ||
for _, field in ipairs(schema.fields) do | for _, field in ipairs(schema.fields) do | ||
local arg = tpl_args[field.field] | local arg = tpl_args[field.field] | ||
Line 273: | Line 343: | ||
end | end | ||
+ | -- this is where the magic happens. if the schema we're using is a sub-type | ||
+ | -- of another schema, we call cargo_store recursively on the parent schema, | ||
+ | -- to store all of the parent data as well. | ||
if schema.parent_table ~= nil then | if schema.parent_table ~= nil then | ||
− | core.cargo. | + | -- additionally, subtypes don't have the 'Id' field defined, so we add it |
+ | -- manually here. | ||
+ | data[id_field.field] = tpl_args[id_field.field] | ||
+ | core.cargo.store(frame, schema.parent_table, tpl_args) | ||
end | end | ||
Line 281: | Line 357: | ||
-- core.cargo.cargo_declare declares a given table. Additionally, if the table is | -- core.cargo.cargo_declare declares a given table. Additionally, if the table is | ||
− | -- has a parent table, we add an Id field. | + | -- has a parent table, we add an Id field to the declaration. |
function core.cargo.declare(schema) | function core.cargo.declare(schema) | ||
return function(frame) | return function(frame) | ||
Line 291: | Line 367: | ||
dcl_args[field.field] = field.type | dcl_args[field.field] = field.type | ||
end | end | ||
− | if | + | if schema.parent_table ~= nil then |
dcl_args[id_field.field] = id_field.type | dcl_args[id_field.field] = id_field.type | ||
end | end | ||
frame:callParserFunction('#cargo_declare:', dcl_args) | frame:callParserFunction('#cargo_declare:', dcl_args) | ||
end | end | ||
+ | end | ||
+ | |||
+ | -- tables is a recursive method, used by core.cargo.query, that adds the | ||
+ | -- schema's table and all of its parent tables to the list of tables we query. | ||
+ | function core.cargo.tables(schema) | ||
+ | local table = schema.table | ||
+ | if schema.parent_table ~= nil then | ||
+ | return table .. "," .. core.cargo.tables(schema.parent_table) | ||
+ | else | ||
+ | return table | ||
+ | end | ||
+ | end | ||
+ | |||
+ | -- join_parents takes a schema and creates the join conditions for joining that | ||
+ | -- schema to its parent schema. it is a recursive method; after joining the | ||
+ | -- schema and it's parent, it calls join_parents again to add the join | ||
+ | -- conditions for the parent's parent as well, and so on. | ||
+ | function core.cargo.join_parents(schema) | ||
+ | -- if the schema has no parent, then we have nothing to join to | ||
+ | if schema.parent_table == nil then | ||
+ | return nil | ||
+ | end | ||
+ | |||
+ | -- otherwise, if the schema does have a parent, then join to the parent | ||
+ | local join = string.format( | ||
+ | "%s.%s=%s.%s", | ||
+ | schema.table, id_field.field, | ||
+ | schema.parent_table.table, id_field.field | ||
+ | ) | ||
+ | |||
+ | -- now, call join_parents for the parent table. if the result is not nil, | ||
+ | -- then we need to add the parent table's join to its parent. | ||
+ | local parent_join = core.cargo.join_parents(schema.parent_table) | ||
+ | if parent_join ~= nil then | ||
+ | return join .. "," .. parent_join | ||
+ | end | ||
+ | |||
+ | return join | ||
+ | end | ||
+ | |||
+ | -- core.cargo.query is probably the most useful function in this library for | ||
+ | -- any outside user. It handles the details of adding all of the tables and | ||
+ | -- joining all of the parents of a schema. It takes 3 arguments: | ||
+ | -- | ||
+ | -- * schema - the schema that we want to query, which should be one of | ||
+ | -- core.schema | ||
+ | -- * fields - the fields we want to query. this is one of the places that the | ||
+ | -- abstraction of core.cargo.query leaks through, as fields must be | ||
+ | -- specified in the form tablename.fieldname, so you must know the | ||
+ | -- table that the field you want belongs to. | ||
+ | -- * args - the arguments to pass to the cargo query. If this contains a | ||
+ | -- join, it will be added to the end of the join_parents output. | ||
+ | -- Additionally, args takes a special argument, "extratables", a | ||
+ | -- string that specifies any extra tables that should be queried. | ||
+ | function core.cargo.query(schema, fields, args) | ||
+ | if args == nil then | ||
+ | args = {} | ||
+ | end | ||
+ | |||
+ | local tables = core.cargo.tables(schema) | ||
+ | |||
+ | local join = core.cargo.join_parents(schema) | ||
+ | if join ~= nil then | ||
+ | if args.join ~= nil then | ||
+ | args.join = join .. ',' .. args.join | ||
+ | else | ||
+ | args.join = join | ||
+ | end | ||
+ | end | ||
+ | |||
+ | if args.extratables ~= nil and args.extratables ~= '' then | ||
+ | tables = tables .. ',' .. args.extratables | ||
+ | end | ||
+ | |||
+ | return cargo.query(tables, fields, args) | ||
end | end | ||
Line 301: | Line 452: | ||
-- Helpers | -- Helpers | ||
-- | -- | ||
+ | -- these functions are miscellaneous helper functions | ||
function core.format_table(title, tpl_args) | function core.format_table(title, tpl_args) | ||
local rawTable = mw.html.create('table') | local rawTable = mw.html.create('table') | ||
rawTable:addClass('wikitable') | rawTable:addClass('wikitable') | ||
− | rawTable:tag('tr'):attr('colspan', '2'):wikitext(title) | + | rawTable:tag('tr'):tag('th'):attr('colspan', '2'):wikitext(title) |
− | for | + | for param, arg in pairs(tpl_args) do |
rawTable:tag('tr') | rawTable:tag('tr') | ||
− | :tag('th'):wikitext( | + | :tag('th'):wikitext(param):done() |
− | :tag('td'):wikitext( | + | :tag('td'):wikitext(arg) |
end | end | ||
return rawTable | return rawTable | ||
+ | end | ||
+ | |||
+ | -- | ||
+ | -- core.get_gear retrieves all of the gear, fixed and otherwise, for the given | ||
+ | -- mech. It only returns data from the actual gear table, so any data from | ||
+ | -- subtypes needs to be queried separately. | ||
+ | -- | ||
+ | -- returns an array of gear objects, each of which has these fields: | ||
+ | -- * location - the MountedLocation of the gear | ||
+ | -- * name - the Name of the gear | ||
+ | -- * uiname - the UIName of the gear, which is usually a shorter name. | ||
+ | -- * id - the Id of the gear | ||
+ | -- * fixed - whether or not this is FixedEquipment | ||
+ | -- * categories - an array of strings of categories of the gear | ||
+ | function core.get_gear(chassisID, mechID) | ||
+ | local args = { | ||
+ | join = 'Gear.Id=MechInventory.ComponentDefID', | ||
+ | where = string.format( | ||
+ | 'MechInventory.MechID="%s" OR MechInventory.MechID="%s"', | ||
+ | chassisID, mechID | ||
+ | ), | ||
+ | extratables = 'MechInventory', | ||
+ | } | ||
+ | |||
+ | |||
+ | local gearData = core.cargo.query(core.schema.gear, 'Gear.Name,Gear.UIName,MechInventory.MountedLocation,MechInventory.FixedEquipment,Gear.Id,Gear.CustomCategories', args) | ||
+ | local gear = {} | ||
+ | for _, item in ipairs(gearData) do | ||
+ | local fixedField = item['MechInventory.FixedEquipment'] | ||
+ | table.insert(gear, { | ||
+ | location = item['MechInventory.MountedLocation'], | ||
+ | name = item['Gear.Name'], | ||
+ | uiname = item['Gear.UIName'], | ||
+ | id = item['Gear.Id'], | ||
+ | fixed = (fixedField == '1'), | ||
+ | categories = mw.text.split(item['Gear.CustomCategories'], ','), | ||
+ | }) | ||
+ | end | ||
+ | |||
+ | return gear | ||
+ | end | ||
+ | |||
+ | -- core.get_engine returns an object representing details about the engine in | ||
+ | -- the given mech | ||
+ | -- | ||
+ | -- returns an engine object, which has these fields: | ||
+ | -- * shield - an object for the engine shield, having these fields: | ||
+ | -- * name - the Name of the shield | ||
+ | -- * id - the Id of the gear representing the shield | ||
+ | -- * fixed - whether the engine shield is fixed. | ||
+ | -- * core - an Integer representing the core rating of the enginecore | ||
+ | -- * tonnage - the total tonnage of the engine core, sans shield modifiers | ||
+ | -- and E-Cooling. not all that useful yet, needs to be updated | ||
+ | -- * ecooling - the number of E-Cooling heatsinks.l | ||
+ | -- * cooling - the Id of the Heatsink type that this engine uses. | ||
+ | -- * heatsinkkit - the Name of the Heatsink kit that this engine uses. | ||
+ | -- * dissipation - the total dissipation capacity of this engine, including | ||
+ | -- free heat sinks and E-Cooling heat sinks, but _not_ | ||
+ | -- including mandatory external heat sinks that are required on | ||
+ | -- smaller engines. | ||
+ | -- * fixed - whether the engine core is fixed or replaceable. | ||
+ | -- | ||
+ | -- These fields were added after the fact because of a mistake in API design: | ||
+ | -- * cooling_fixed - whether the cooling system (heat sink kit) is fixed | ||
+ | -- * ecooling_fixed - whether the ecooling is fixed | ||
+ | function core.get_engine(chassisID, mechID) | ||
+ | local engine = {} | ||
+ | |||
+ | local where = string.format( | ||
+ | 'MechInventory.MechID="%s" OR MechInventory.MechID="%s"', | ||
+ | chassisID, mechID | ||
+ | ) | ||
+ | local join = 'Gear.Id=MechInventory.ComponentDefID' | ||
+ | local extratables = 'MechInventory' | ||
+ | |||
+ | local shieldRow = core.cargo.query(core.schema.engineshield, 'Gear.Name,Gear.Id,MechInventory.FixedEquipment', | ||
+ | { where = where, join = join, extratables = extratables } | ||
+ | ) | ||
+ | if shieldRow[1] == nil then | ||
+ | return nil | ||
+ | end | ||
+ | engine.shield = { | ||
+ | name = shieldRow[1]['Gear.Name'], | ||
+ | id = shieldRow[1]['Gear.Id'], | ||
+ | fixed = shieldRow[1]['MechInventory.FixedEquipment'] == '1' | ||
+ | } | ||
+ | |||
+ | local coreRow = core.cargo.query( | ||
+ | core.schema.enginecore, 'EngineCore.Rating,Gear.Tonnage,MechInventory.FixedEquipment', | ||
+ | { where = where, join = join, extratables = extratables } | ||
+ | ) | ||
+ | engine.core = tonumber(coreRow[1]['EngineCore.Rating'], 10) | ||
+ | engine.tonnage = tonumber(coreRow[1]['Gear.Tonnage'], 10) | ||
+ | engine.fixed = (coreRow[1]['MechInventory.FixedEquipment'] == '1' or coreRow[1]['MechInventory.FixedEquipment'] == 'yes') | ||
+ | |||
+ | local ecoolingRow = core.cargo.query( | ||
+ | core.schema.engineheatblock, 'EngineHeatBlocks.HeatsinkCount,MechInventory.FixedEquipment', | ||
+ | { where = where, join = join, extratables = extratables } | ||
+ | ) | ||
+ | if ecoolingRow[1] == nil then | ||
+ | engine.ecooling = 0 | ||
+ | engine.ecooling_fixed = false | ||
+ | else | ||
+ | engine.ecooling = tonumber(ecoolingRow[1]['EngineHeatBlocks.HeatsinkCount'], 10) | ||
+ | engine.ecooling_fixed = ecoolingRow[1]['MechInventory.FixedEquipment'] == '1' | ||
+ | end | ||
+ | |||
+ | local coolingRow = core.cargo.query( | ||
+ | core.schema.cooling, 'Cooling.HeatsinkDefID,Gear.Name,MechInventory.FixedEquipment', | ||
+ | { where = where, join = join, extratables = extratables } | ||
+ | ) | ||
+ | |||
+ | engine.cooling = coolingRow[1]['Cooling.HeatsinkDefID'] | ||
+ | engine.heatsinkkit = coolingRow[1]['Gear.Name'] | ||
+ | engine.cooling_fixed = coolingRow[1]['MechInventory.FixedEquipment'] == '1' | ||
+ | |||
+ | local heatsinks = core.cargo.query( | ||
+ | core.schema.heatsink, 'Heatsinks.Dissipation', | ||
+ | { where = string.format('%s="%s"', 'Gear.Id', engine.cooling) } | ||
+ | ) | ||
+ | local dissipation = tonumber(heatsinks[1]['Heatsinks.Dissipation'], 10) | ||
+ | |||
+ | -- engines have internal heat sinks, which don't take up any space. smaller | ||
+ | -- engines can mount fewer heat sinks, while larger engines can mount more. | ||
+ | -- to compute the total effective heat sinks of the engine heat sinks, we | ||
+ | -- divide the engine rating by 25 and take the floor value. | ||
+ | -- so, for example, a 240-rated engine has 240/25 = 9.6, 9 heat sinks. | ||
+ | -- | ||
+ | -- additionally, engines over 250 can mount additional heat sinks on the | ||
+ | -- engine which add weight but take up no space. these are e-cooling. | ||
+ | -- 300 / 25 = 12 | ||
+ | -- | ||
+ | -- to calculate engine heat sinks, we divide the engine rating by 25 and take | ||
+ | -- the floor of that. then, we take the lesser of that value and 10, as 10 | ||
+ | -- is the maximum number of heat sinks that an engine can hold outside of | ||
+ | -- ecooling | ||
+ | -- | ||
+ | -- finally, we multiply the number of engine heatsinks by the dissipation | ||
+ | -- per sink | ||
+ | engine.heat_sinking = dissipation * (math.min(math.floor(engine.core / 25), 10) + engine.ecooling) | ||
+ | |||
+ | return engine | ||
+ | end | ||
+ | |||
+ | -- core.get_heatsinks returns information about the external, non-engine | ||
+ | -- heatsinks of a mech. | ||
+ | -- | ||
+ | -- returns an object with the follwoign fields: | ||
+ | -- * name - the name of the heatsink | ||
+ | -- * dissipation - the dissipation capacity of the heatsink | ||
+ | function core.get_heatsinks(chassisID, mechID) | ||
+ | local where = string.format( | ||
+ | 'MechInventory.MechID="%s" OR MechInventory.MechID="%s"', | ||
+ | chassisID, mechID | ||
+ | ) | ||
+ | local join = 'Gear.Id=MechInventory.ComponentDefID' | ||
+ | local fields = { | ||
+ | 'Gear.Name', | ||
+ | 'Heatsinks.Dissipation', | ||
+ | } | ||
+ | local extratables = 'MechInventory' | ||
+ | |||
+ | local heatsinkRows = core.cargo.query( | ||
+ | core.schema.heatsink, | ||
+ | table.concat(fields, ','), | ||
+ | { join = join, where = where, extratables = extratables } | ||
+ | ) | ||
+ | |||
+ | local heatsinks = {} | ||
+ | for _, heatsink in ipairs(heatsinkRows) do | ||
+ | local dissipation = tonumber(heatsink['Heatsinks.Dissipation'], 10) | ||
+ | if dissipation > 0 then | ||
+ | table.insert(heatsinks, { | ||
+ | name = heatsink['Gear.Name'], | ||
+ | dissipation = dissipation, | ||
+ | }) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | return heatsinks | ||
+ | end | ||
+ | |||
+ | -- core.get_weapons returns information about all of the weapons on the given | ||
+ | -- mech. | ||
+ | -- | ||
+ | -- returns an object with the following fields: | ||
+ | -- * name - the name of the weapon | ||
+ | -- * category - the category of the weapon | ||
+ | -- * type - the type of the weapon | ||
+ | -- * ammo - the AmmoCategory of the weapon. | ||
+ | -- * minRange - the weapon's minimum range | ||
+ | -- * maxRange - the weapon's maximum range | ||
+ | -- * heat - the heat generated when firing the weapon | ||
+ | -- * damage - the damage caused to the target by this weapon, with | ||
+ | -- standard ammo if applicable. | ||
+ | -- * heatDamage - the heat damage caused to the target by this weapon, with | ||
+ | -- standard ammo if applicable. | ||
+ | -- * instability - the stability damage caused to the attacker by this weapon, | ||
+ | -- with standard ammo if applicable. | ||
+ | function core.get_weapons(chassisID, mechID) | ||
+ | local weapons = {} | ||
+ | local where = string.format( | ||
+ | 'MechInventory.MechID="%s" OR MechInventory.MechID="%s"', | ||
+ | chassisID, mechID | ||
+ | ) | ||
+ | local join = 'Gear.Id=MechInventory.ComponentDefID' | ||
+ | |||
+ | local fields = { | ||
+ | 'Gear.Name', | ||
+ | 'Weapons.Category', | ||
+ | 'Weapons.Type', | ||
+ | 'Weapons.AmmoCategory', | ||
+ | 'Weapons.MinRange', | ||
+ | 'Weapons.MaxRange', | ||
+ | 'Weapons.HeatGenerated', | ||
+ | 'Weapons.Damage', | ||
+ | 'Weapons.HeatDamage', | ||
+ | 'Weapons.Instability', | ||
+ | 'Weapons.ShotsWhenFired', | ||
+ | } | ||
+ | |||
+ | local extratables = 'MechInventory' | ||
+ | |||
+ | local weaponsResult = core.cargo.query( | ||
+ | core.schema.weapon, table.concat(fields, ','), | ||
+ | {where = where, join = join, extratables = extratables} | ||
+ | ) | ||
+ | |||
+ | for _, weaponRow in ipairs(weaponsResult) do | ||
+ | local weapon = { | ||
+ | name = weaponRow['Gear.Name'], | ||
+ | category = weaponRow['Weapons.Category'], | ||
+ | type = weaponRow['Weapons.Type'], | ||
+ | ammo = weaponRow['Weapons.AmmoCategory'], | ||
+ | minRange = tonumber(weaponRow['Weapons.MinRange'], 10), | ||
+ | maxRange = tonumber(weaponRow['Weapons.MaxRange'], 10), | ||
+ | heat = tonumber(weaponRow['Weapons.HeatGenerated'], 10), | ||
+ | damage = tonumber(weaponRow['Weapons.Damage'], 10), | ||
+ | shots = tonumber(weaponRow['Weapons.ShotsWhenFired'], 10), | ||
+ | heatDamage = tonumber(weaponRow['Weapons.HeatDamage'], 10), | ||
+ | instability = tonumber(weaponRow['Weapons.Instability'], 10), | ||
+ | } | ||
+ | |||
+ | table.insert(weapons, weapon) | ||
+ | end | ||
+ | |||
+ | return weapons | ||
+ | end | ||
+ | |||
+ | -- core.get_jumpjets returns a list of jumpjets on the mech | ||
+ | function core.get_jumpjets(chassisID, mechID) | ||
+ | local jumpjets = {} | ||
+ | local where = string.format( | ||
+ | 'MechInventory.MechID="%s" OR MechInventory.MechID="%s"', | ||
+ | chassisID, mechID | ||
+ | ) | ||
+ | local join = 'Gear.Id=MechInventory.ComponentDefID' | ||
+ | |||
+ | local fields = { | ||
+ | 'Gear.Id', | ||
+ | 'JumpJets.JumpCapacity', | ||
+ | 'MechInventory.MountedLocation', | ||
+ | } | ||
+ | |||
+ | local extratables = 'MechInventory' | ||
+ | |||
+ | local jumpJetData = core.cargo.query( | ||
+ | core.schema.jumpjet, table.concat(fields, ','), | ||
+ | { where = where, join = join, extratables = extratables } | ||
+ | ) | ||
+ | |||
+ | for _, jumpJetRow in ipairs(jumpJetData) do | ||
+ | table.insert(jumpjets, { | ||
+ | id = jumpJetRow['Gear.Id'], | ||
+ | jump = tonumber(jumpJetRow['JumpJets.JumpCapacity']), | ||
+ | location = jumpJetRow['MechInventory.MountedLocation'] | ||
+ | }) | ||
+ | end | ||
+ | |||
+ | return jumpjets | ||
end | end | ||
Line 322: | Line 754: | ||
-- | -- | ||
+ | -- all of these functions are invoked to declare the cargo tables for the | ||
+ | -- defined schemas. | ||
p.table_gear = core.cargo.declare(core.schema.gear) | p.table_gear = core.cargo.declare(core.schema.gear) | ||
p.table_heatsink = core.cargo.declare(core.schema.heatsink) | p.table_heatsink = core.cargo.declare(core.schema.heatsink) | ||
Line 331: | Line 765: | ||
p.table_jumpjet = core.cargo.declare(core.schema.jumpjet) | p.table_jumpjet = core.cargo.declare(core.schema.jumpjet) | ||
p.table_weapon = core.cargo.declare(core.schema.weapon) | p.table_weapon = core.cargo.declare(core.schema.weapon) | ||
+ | p.table_ammunition = core.cargo.declare(core.schema.ammunition) | ||
+ | -- | ||
+ | -- All of the following define the template for defining a given piece of gear. | ||
function p.heatsink(frame) | function p.heatsink(frame) | ||
− | tpl_args = getArgs(frame, {parentFirst = true}) | + | local tpl_args = getArgs(frame, {parentFirst = true}) |
core.cargo.store(frame, core.schema.heatsink, tpl_args) | core.cargo.store(frame, core.schema.heatsink, tpl_args) | ||
− | return core.format_table('Heat Sink') | + | return core.format_table('Heat Sink', tpl_args) |
end | end | ||
function p.cooling(frame) | function p.cooling(frame) | ||
− | tpl_args = getArgs(frame, {parentFirst = true}) | + | local tpl_args = getArgs(frame, {parentFirst = true}) |
core.cargo.store(frame, core.schema.cooling, tpl_args) | core.cargo.store(frame, core.schema.cooling, tpl_args) | ||
Line 348: | Line 785: | ||
function p.engineheatblock(frame) | function p.engineheatblock(frame) | ||
− | tpl_args = getArgs(frame, {parentFirst = true}) | + | local tpl_args = getArgs(frame, {parentFirst = true}) |
core.cargo.store(frame, core.schema.engineheatblock, tpl_args) | core.cargo.store(frame, core.schema.engineheatblock, tpl_args) | ||
Line 355: | Line 792: | ||
function p.engineshield(frame) | function p.engineshield(frame) | ||
− | tpl_args = getArgs(frame, {parentFirst = true}) | + | local tpl_args = getArgs(frame, {parentFirst = true}) |
core.cargo.store(frame, core.schema.engineshield, tpl_args) | core.cargo.store(frame, core.schema.engineshield, tpl_args) | ||
Line 362: | Line 799: | ||
function p.enginecore(frame) | function p.enginecore(frame) | ||
− | tpl_args = getArgs(frame, {parentFirst = true}) | + | local tpl_args = getArgs(frame, {parentFirst = true}) |
core.cargo.store(frame, core.schema.enginecore, tpl_args) | core.cargo.store(frame, core.schema.enginecore, tpl_args) | ||
Line 369: | Line 806: | ||
function p.upgrade(frame) | function p.upgrade(frame) | ||
− | tpl_args = getArgs(frame, {parentFirst = true}) | + | local tpl_args = getArgs(frame, {parentFirst = true}) |
core.cargo.store(frame, core.schema.upgrade, tpl_args) | core.cargo.store(frame, core.schema.upgrade, tpl_args) | ||
Line 376: | Line 813: | ||
function p.jumpjet(frame) | function p.jumpjet(frame) | ||
− | tpl_args = getArgs(frame, {parentFirst = true}) | + | local tpl_args = getArgs(frame, {parentFirst = true}) |
core.cargo.store(frame, core.schema.jumpjet, tpl_args) | core.cargo.store(frame, core.schema.jumpjet, tpl_args) | ||
Line 383: | Line 820: | ||
function p.weapon(frame) | function p.weapon(frame) | ||
− | tpl_args = getArgs(frame, {parentFirst = true}) | + | local tpl_args = getArgs(frame, {parentFirst = true}) |
core.cargo.store(frame, core.schema.weapon, tpl_args) | core.cargo.store(frame, core.schema.weapon, tpl_args) | ||
return core.format_table('Weapon', tpl_args) | return core.format_table('Weapon', tpl_args) | ||
+ | end | ||
+ | |||
+ | function p.ammunition(frame) | ||
+ | local tpl_args = getArgs(frame, {parentFirst = true}) | ||
+ | |||
+ | core.cargo.store(frame, core.schema.ammunition, tpl_args) | ||
+ | return core.format_table('Ammunition', tpl_args) | ||
+ | end | ||
+ | |||
+ | function p.weapons_list(frame) | ||
+ | local tpl_args = getArgs(frame, {parentFirst = true}) | ||
+ | |||
+ | local simple_columns = { | ||
+ | 'UIName', | ||
+ | 'AmmoCategory', | ||
+ | 'Tonnage', | ||
+ | 'InventorySize', | ||
+ | 'Damage', | ||
+ | 'HeatDamage', | ||
+ | 'Instability', | ||
+ | 'ShotsWhenFired', | ||
+ | 'ProjectilesPerShot', | ||
+ | 'HeatGenerated', | ||
+ | 'AttackRecoil', | ||
+ | 'EvasivePipsIgnored', | ||
+ | 'CriticalChanceMultiplier', | ||
+ | } | ||
+ | |||
+ | local complex_columns = { | ||
+ | 'MinRange', | ||
+ | 'RangeSplit', | ||
+ | 'MaxRange', | ||
+ | } | ||
+ | |||
+ | local fields = table.concat(simple_columns, ',') .. ',' .. table.concat(complex_columns) | ||
+ | |||
+ | local args = {} | ||
+ | if tpl_args['Category'] ~= nil then | ||
+ | args.where = string.format('Category="%s"',tpl_args['Category']) | ||
+ | end | ||
+ | |||
+ | local items = core.cargo.query(core.schema.weapon, fields, args) | ||
+ | |||
+ | local t = mw.html.create('table') | ||
+ | t:addClass('wikitable') | ||
+ | |||
+ | local headrow = t:tag('tr') | ||
+ | headrow:tag('th'):attr('colspan', '2') | ||
+ | headrow:tag('th'):attr('colspan', '2'):wikitext('Size') | ||
+ | headrow:tag('th'):attr('colspan', '3'):wikitext('Damage') | ||
+ | headrow:tag('th'):attr('colspan', '4'):wikitext('Per Salvo') | ||
+ | headrow:tag('th'):attr('colspan', '3'):wikitext('Modifiers') | ||
+ | headrow:tag('th'):attr('colspan', '5'):wikitext('Range') | ||
+ | |||
+ | local subhead = t:tag('tr') | ||
+ | subhead:tag('th'):wikitext('Name') | ||
+ | subhead:tag('th'):wikitext('Ammo') | ||
+ | subhead:tag('th'):wikitext('Tonnage') | ||
+ | subhead:tag('th'):wikitext('Slots') | ||
+ | subhead:tag('th'):wikitext('Normal') | ||
+ | subhead:tag('th'):wikitext('Heat') | ||
+ | subhead:tag('th'):wikitext('Stab') | ||
+ | subhead:tag('th'):wikitext('Shots') | ||
+ | subhead:tag('th'):wikitext('Projectiles') | ||
+ | subhead:tag('th'):wikitext('Heat') | ||
+ | subhead:tag('th'):wikitext('Recoil') | ||
+ | subhead:tag('th'):wikitext('Accuracy') | ||
+ | subhead:tag('th'):wikitext('Evasion Ignored') | ||
+ | subhead:tag('th'):wikitext('Bonus Crit Chance') | ||
+ | subhead:tag('th'):wikitext('Min') | ||
+ | subhead:tag('th'):wikitext('Short') | ||
+ | subhead:tag('th'):wikitext('Medium') | ||
+ | subhead:tag('th'):wikitext('Long') | ||
+ | |||
+ | for _, item in ipairs(items) do | ||
+ | local row = t:tag('tr') | ||
+ | for _, column in ipairs(columns) do | ||
+ | row:tag('td'):wikitext(item[column]) | ||
+ | end | ||
+ | |||
+ | row:tag('td'):wikitext(item['MinRange']) | ||
+ | local range_brackets = mw.text.split(item['RangeSplit'], ',') | ||
+ | row:tag('td'):wikitext(range_brackets[1]) | ||
+ | row:tag('td'):wikitext(range_brackets[2]) | ||
+ | row:tag('td'):wikitext(range_brackets[3]) | ||
+ | row:tag('td'):wikitext(item['MaxRange']) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | -- get returns the fields for the given | ||
+ | -- first arg is the type, the table name | ||
+ | -- second arg is the id of the object | ||
+ | -- any subsequent args are the fields to return | ||
+ | function p.get(frame) | ||
+ | tpl_args = getArgs(frame, {parentFirst=true}) | ||
+ | -- the first named | ||
+ | local table = tpl_args[1] | ||
+ | local id = tpl_args[2] | ||
+ | local fields = table.concat(tpl_args, ',', 3) | ||
+ | |||
+ | local target_schema | ||
+ | |||
+ | for _, schema in pairs(core.schema) do | ||
+ | if schema.table == table then | ||
+ | target = schema | ||
+ | end | ||
+ | end | ||
+ | |||
+ | local row = core.cargo.query(schema, fields) | ||
end | end | ||
-- always return p | -- always return p | ||
return p | return p |
Latest revision as of 04:57, 1 June 2023
Documentation for this module may be created at Module:Gear/doc
-- Module:Gear handles the myriad of gear types. -- start with p, the package we expose local p = {} -- core is lua-only methods, not for use in the public intertace local core = {} p.core = core local id_field = { field = 'Id', type = 'String' } -- -- imports -- local getArgs = require('Module:Arguments').getArgs local cargo = mw.ext.cargo -- -- schema -- -- schema defines the various database tables and their database schema. -- -- tables defined in schema have 2 or 3 fields: -- * table - the name of the table that this schema defines -- * fields - an array of fields belonging to this table. each field contains: -- * field - the name of the field -- * type - the database type of the field. -- * parent_table - Optional. A reference to the parent table. Used for -- sub-types like Weapon and EngineCore. core.schema = {} -- gear is the schema for the master Gear table. gear is the parent type for -- every piece of gear that might be mounted on Mech. core.schema.gear = { table = 'Gear', fields = { id_field, { field = 'Cost', -- type defines the type of the field type = 'Integer', },{ field = 'Rarity', type = 'Integer', },{ field = 'Purchasable', type = 'Boolean', },{ field = 'Manufacturer', type = 'String', },{ field = 'Model', type = 'String', },{ field = 'UIName', type = 'String', },{ field = 'Name', type = 'String', },{ field = 'Details', -- Details use the Text type, which is unindexed and intended for -- longer-form data. type = 'Text', },{ field = 'Icon', type = 'String', },{ field = 'ComponentType', type = 'String', },{ field = 'ComponentSubType', type = 'String', },{ field = 'InventorySize', type = 'Integer', },{ field = 'Tonnage', type = 'Float', },{ -- TODO(rust dev): this might actually just be a string field = 'AllowedLocations', type = 'List (,) of String', },{ -- TODO(rust dev): this also might actually just be a string. field = 'DisallowedLocations', type = 'List (,) of String', },{ field = 'BattleValue', type = 'Integer', },{ field = 'Bonuses', type = 'List (,) of String', },{ field = 'CustomCategories', type = 'List (,) of String', }, }, } -- heatsinks defines the Heatsinks table. For weird game reasons, engine parts -- count as heat sinks. This relationship is reflected in the database schema, -- but does not strictly need to be. However, that ship has already sailed. core.schema.heatsink = { table = 'Heatsinks', -- parent_table points to the gear schema, because heatsink is a subtype of -- gear. parent_table = core.schema.gear, fields = { { field = 'Dissipation', type = 'Integer', }, }, } -- cooling defines the Cooling table. Cooling parts are those that represent -- the Mech's cooling kit, most commonly either single or double heatsinks, but -- other heatsink types exist. core.schema.cooling = { table = 'Cooling', -- Cooling is a subtype of Heatsink, and so its parent is the Heatsink table. parent_table = core.schema.heatsink, fields = { { -- HeatsinkDefID is the ID of the Heatsink that this Cooling kit uses. field = 'HeatsinkDefID', type = 'String', }, }, } -- EngineHeatBlocks represent the E-Cooling on an engine. core.schema.engineheatblock = { table = 'EngineHeatBlocks', parent_table = core.schema.heatsink, fields = { { field = 'HeatsinkCount', type = 'Integer', }, }, } -- EngineShield is the kind of engine; Standard, XL, XXL, etc. core.schema.engineshield = { table = 'EngineShields', parent_table = core.schema.heatsink, fields = { { field = 'ReservedSlots', type = 'Integer', },{ field = 'EngineFactor', type = 'Float', }, }, } -- EngineCore is the actual engine rating. core.schema.enginecore = { parent_table = core.schema.heatsink, table = 'EngineCore', fields = { { field = 'Rating', type = 'Integer', }, }, } -- UpgradeDef encompasses all miscellaneous mech parts. This includes gyros, -- actuators, quirks, EW modules, and others. It currently has no fields that -- Gear does not, but its existence allows us to filter only Upgrade parts, -- and future-proofs us for changes later. core.schema.upgrade = { table = 'UpgradeDef', parent_table = core.schema.gear, fields = {}, } -- JumpJets are obviously all JumpJets, but things like Partial Wings also -- count as JumpJets core.schema.jumpjet = { table = 'JumpJets', parent_table = core.schema.gear, fields = { { field = 'MinTonnage', type = 'Integer', },{ field = 'MaxTonnage', type = 'Integer', },{ field = 'JumpCapacity', type = 'Float', }, }, } -- Weapon defines a weapon. Like everything else, it is a subclass of gear. core.schema.weapon = { table = 'Weapons', parent_table = core.schema.gear, fields = { { field = 'Category', type = 'String', },{ field = 'Type', type = 'String', },{ field = 'WeaponSubType', type = 'String', },{ field = 'MinRange', type = 'Integer', },{ field = 'MaxRange', type = 'Integer', },{ field = 'RangeSplit', type = 'List (,) of Integer', },{ field = 'AmmoCategory', type = 'String', },{ field = 'StartingAmmoCapacity', type = 'Integer', },{ field = 'HeatGenerated', type = 'Integer', },{ field = 'Damage', type = 'Integer', },{ field = 'OverheatedDamageMultiplier', type = 'Float', },{ field = 'EvasiveDamageMultiplier', type = 'Float', },{ field = 'EvasivePipsIgnored', type = 'Integer', },{ field = 'DamageVariance', type = 'Float', },{ field = 'HeatDamage', type = 'Integer', },{ field = 'AccuracyModifier', type = 'Float', },{ field = 'CriticalChanceMultiplier', type = 'Float', },{ field = 'AOECapable', type = 'Boolean', },{ field = 'IndirectFireCapable', type = 'Boolean', },{ field = 'RefireModifier', type = 'Integer', },{ field = 'ShotsWhenFired', type = 'Integer', },{ field = 'ProjectilesPerShot', type = 'Integer', },{ field = 'AttackRecoil', type = 'Integer', },{ field = 'Instability', type = 'Integer', },{ field = 'WeaponEffectID', type = 'String', },{ field = 'NoMelee', type = 'Boolean', }, }, } -- Ammunition encompasses AmmunitionBoxes. There are acutally two kinds of defs -- in BattleTech for Ammunition, but for our purposes, we only need the -- AmmunitionBox, which is the gear item. However, when parsing ammo types, we -- also look at the Ammunition object, which defines details about the actual -- shots, because this is where the Category is stored. That, however, is not -- necessary for the wiki side of things. core.schema.ammunition = { table = 'Ammunition', parent_table = core.schema.gear, fields = { { field = 'Capacity', type = 'Integer', },{ -- Category is what kind of gun shoots this ammo. field = 'Category', type = 'String', },{ field = 'PerUnitCost', type = 'Integer', }, } } -- -- Cargo -- -- Handles the connection between this module and the Cargo database. core.cargo = {} -- core.cargo.cargo_store stores the data for the current schema's fields, and -- then recursively stores the data of the parent tables' fields. function core.cargo.store(frame, schema, tpl_args) -- When we store a bit of gear to the database, we have to store it to two or -- more different tables. However, it is a limitation of Cargo that we can -- only attach or declare one table per template. To get around this -- limitation, we have an 'attach' template for every table type, and we -- include every table needed for a given schema when we store the data. frame:expandTemplate{title=string.format('Template:Gear/cargo/attach/%s', schema.table), args={}} local data = {} data._table = schema.table -- there may be extraneous arguments in the tpl_args, so we can't pass them -- verbatim to cargo. Instead, we iterate through the fields of the table, -- and we pull out the value of the parameter that matches the field.field. for _, field in ipairs(schema.fields) do local arg = tpl_args[field.field] if arg ~= nil then data[field.field] = arg end end -- this is where the magic happens. if the schema we're using is a sub-type -- of another schema, we call cargo_store recursively on the parent schema, -- to store all of the parent data as well. if schema.parent_table ~= nil then -- additionally, subtypes don't have the 'Id' field defined, so we add it -- manually here. data[id_field.field] = tpl_args[id_field.field] core.cargo.store(frame, schema.parent_table, tpl_args) end frame:callParserFunction('#cargo_store:', data) end -- core.cargo.cargo_declare declares a given table. Additionally, if the table is -- has a parent table, we add an Id field to the declaration. function core.cargo.declare(schema) return function(frame) local dcl_args = {} dcl_args._table = schema.table for _, field in ipairs(schema.fields) do dcl_args[field.field] = field.type end if schema.parent_table ~= nil then dcl_args[id_field.field] = id_field.type end frame:callParserFunction('#cargo_declare:', dcl_args) end end -- tables is a recursive method, used by core.cargo.query, that adds the -- schema's table and all of its parent tables to the list of tables we query. function core.cargo.tables(schema) local table = schema.table if schema.parent_table ~= nil then return table .. "," .. core.cargo.tables(schema.parent_table) else return table end end -- join_parents takes a schema and creates the join conditions for joining that -- schema to its parent schema. it is a recursive method; after joining the -- schema and it's parent, it calls join_parents again to add the join -- conditions for the parent's parent as well, and so on. function core.cargo.join_parents(schema) -- if the schema has no parent, then we have nothing to join to if schema.parent_table == nil then return nil end -- otherwise, if the schema does have a parent, then join to the parent local join = string.format( "%s.%s=%s.%s", schema.table, id_field.field, schema.parent_table.table, id_field.field ) -- now, call join_parents for the parent table. if the result is not nil, -- then we need to add the parent table's join to its parent. local parent_join = core.cargo.join_parents(schema.parent_table) if parent_join ~= nil then return join .. "," .. parent_join end return join end -- core.cargo.query is probably the most useful function in this library for -- any outside user. It handles the details of adding all of the tables and -- joining all of the parents of a schema. It takes 3 arguments: -- -- * schema - the schema that we want to query, which should be one of -- core.schema -- * fields - the fields we want to query. this is one of the places that the -- abstraction of core.cargo.query leaks through, as fields must be -- specified in the form tablename.fieldname, so you must know the -- table that the field you want belongs to. -- * args - the arguments to pass to the cargo query. If this contains a -- join, it will be added to the end of the join_parents output. -- Additionally, args takes a special argument, "extratables", a -- string that specifies any extra tables that should be queried. function core.cargo.query(schema, fields, args) if args == nil then args = {} end local tables = core.cargo.tables(schema) local join = core.cargo.join_parents(schema) if join ~= nil then if args.join ~= nil then args.join = join .. ',' .. args.join else args.join = join end end if args.extratables ~= nil and args.extratables ~= '' then tables = tables .. ',' .. args.extratables end return cargo.query(tables, fields, args) end -- -- Helpers -- -- these functions are miscellaneous helper functions function core.format_table(title, tpl_args) local rawTable = mw.html.create('table') rawTable:addClass('wikitable') rawTable:tag('tr'):tag('th'):attr('colspan', '2'):wikitext(title) for param, arg in pairs(tpl_args) do rawTable:tag('tr') :tag('th'):wikitext(param):done() :tag('td'):wikitext(arg) end return rawTable end -- -- core.get_gear retrieves all of the gear, fixed and otherwise, for the given -- mech. It only returns data from the actual gear table, so any data from -- subtypes needs to be queried separately. -- -- returns an array of gear objects, each of which has these fields: -- * location - the MountedLocation of the gear -- * name - the Name of the gear -- * uiname - the UIName of the gear, which is usually a shorter name. -- * id - the Id of the gear -- * fixed - whether or not this is FixedEquipment -- * categories - an array of strings of categories of the gear function core.get_gear(chassisID, mechID) local args = { join = 'Gear.Id=MechInventory.ComponentDefID', where = string.format( 'MechInventory.MechID="%s" OR MechInventory.MechID="%s"', chassisID, mechID ), extratables = 'MechInventory', } local gearData = core.cargo.query(core.schema.gear, 'Gear.Name,Gear.UIName,MechInventory.MountedLocation,MechInventory.FixedEquipment,Gear.Id,Gear.CustomCategories', args) local gear = {} for _, item in ipairs(gearData) do local fixedField = item['MechInventory.FixedEquipment'] table.insert(gear, { location = item['MechInventory.MountedLocation'], name = item['Gear.Name'], uiname = item['Gear.UIName'], id = item['Gear.Id'], fixed = (fixedField == '1'), categories = mw.text.split(item['Gear.CustomCategories'], ','), }) end return gear end -- core.get_engine returns an object representing details about the engine in -- the given mech -- -- returns an engine object, which has these fields: -- * shield - an object for the engine shield, having these fields: -- * name - the Name of the shield -- * id - the Id of the gear representing the shield -- * fixed - whether the engine shield is fixed. -- * core - an Integer representing the core rating of the enginecore -- * tonnage - the total tonnage of the engine core, sans shield modifiers -- and E-Cooling. not all that useful yet, needs to be updated -- * ecooling - the number of E-Cooling heatsinks.l -- * cooling - the Id of the Heatsink type that this engine uses. -- * heatsinkkit - the Name of the Heatsink kit that this engine uses. -- * dissipation - the total dissipation capacity of this engine, including -- free heat sinks and E-Cooling heat sinks, but _not_ -- including mandatory external heat sinks that are required on -- smaller engines. -- * fixed - whether the engine core is fixed or replaceable. -- -- These fields were added after the fact because of a mistake in API design: -- * cooling_fixed - whether the cooling system (heat sink kit) is fixed -- * ecooling_fixed - whether the ecooling is fixed function core.get_engine(chassisID, mechID) local engine = {} local where = string.format( 'MechInventory.MechID="%s" OR MechInventory.MechID="%s"', chassisID, mechID ) local join = 'Gear.Id=MechInventory.ComponentDefID' local extratables = 'MechInventory' local shieldRow = core.cargo.query(core.schema.engineshield, 'Gear.Name,Gear.Id,MechInventory.FixedEquipment', { where = where, join = join, extratables = extratables } ) if shieldRow[1] == nil then return nil end engine.shield = { name = shieldRow[1]['Gear.Name'], id = shieldRow[1]['Gear.Id'], fixed = shieldRow[1]['MechInventory.FixedEquipment'] == '1' } local coreRow = core.cargo.query( core.schema.enginecore, 'EngineCore.Rating,Gear.Tonnage,MechInventory.FixedEquipment', { where = where, join = join, extratables = extratables } ) engine.core = tonumber(coreRow[1]['EngineCore.Rating'], 10) engine.tonnage = tonumber(coreRow[1]['Gear.Tonnage'], 10) engine.fixed = (coreRow[1]['MechInventory.FixedEquipment'] == '1' or coreRow[1]['MechInventory.FixedEquipment'] == 'yes') local ecoolingRow = core.cargo.query( core.schema.engineheatblock, 'EngineHeatBlocks.HeatsinkCount,MechInventory.FixedEquipment', { where = where, join = join, extratables = extratables } ) if ecoolingRow[1] == nil then engine.ecooling = 0 engine.ecooling_fixed = false else engine.ecooling = tonumber(ecoolingRow[1]['EngineHeatBlocks.HeatsinkCount'], 10) engine.ecooling_fixed = ecoolingRow[1]['MechInventory.FixedEquipment'] == '1' end local coolingRow = core.cargo.query( core.schema.cooling, 'Cooling.HeatsinkDefID,Gear.Name,MechInventory.FixedEquipment', { where = where, join = join, extratables = extratables } ) engine.cooling = coolingRow[1]['Cooling.HeatsinkDefID'] engine.heatsinkkit = coolingRow[1]['Gear.Name'] engine.cooling_fixed = coolingRow[1]['MechInventory.FixedEquipment'] == '1' local heatsinks = core.cargo.query( core.schema.heatsink, 'Heatsinks.Dissipation', { where = string.format('%s="%s"', 'Gear.Id', engine.cooling) } ) local dissipation = tonumber(heatsinks[1]['Heatsinks.Dissipation'], 10) -- engines have internal heat sinks, which don't take up any space. smaller -- engines can mount fewer heat sinks, while larger engines can mount more. -- to compute the total effective heat sinks of the engine heat sinks, we -- divide the engine rating by 25 and take the floor value. -- so, for example, a 240-rated engine has 240/25 = 9.6, 9 heat sinks. -- -- additionally, engines over 250 can mount additional heat sinks on the -- engine which add weight but take up no space. these are e-cooling. -- 300 / 25 = 12 -- -- to calculate engine heat sinks, we divide the engine rating by 25 and take -- the floor of that. then, we take the lesser of that value and 10, as 10 -- is the maximum number of heat sinks that an engine can hold outside of -- ecooling -- -- finally, we multiply the number of engine heatsinks by the dissipation -- per sink engine.heat_sinking = dissipation * (math.min(math.floor(engine.core / 25), 10) + engine.ecooling) return engine end -- core.get_heatsinks returns information about the external, non-engine -- heatsinks of a mech. -- -- returns an object with the follwoign fields: -- * name - the name of the heatsink -- * dissipation - the dissipation capacity of the heatsink function core.get_heatsinks(chassisID, mechID) local where = string.format( 'MechInventory.MechID="%s" OR MechInventory.MechID="%s"', chassisID, mechID ) local join = 'Gear.Id=MechInventory.ComponentDefID' local fields = { 'Gear.Name', 'Heatsinks.Dissipation', } local extratables = 'MechInventory' local heatsinkRows = core.cargo.query( core.schema.heatsink, table.concat(fields, ','), { join = join, where = where, extratables = extratables } ) local heatsinks = {} for _, heatsink in ipairs(heatsinkRows) do local dissipation = tonumber(heatsink['Heatsinks.Dissipation'], 10) if dissipation > 0 then table.insert(heatsinks, { name = heatsink['Gear.Name'], dissipation = dissipation, }) end end return heatsinks end -- core.get_weapons returns information about all of the weapons on the given -- mech. -- -- returns an object with the following fields: -- * name - the name of the weapon -- * category - the category of the weapon -- * type - the type of the weapon -- * ammo - the AmmoCategory of the weapon. -- * minRange - the weapon's minimum range -- * maxRange - the weapon's maximum range -- * heat - the heat generated when firing the weapon -- * damage - the damage caused to the target by this weapon, with -- standard ammo if applicable. -- * heatDamage - the heat damage caused to the target by this weapon, with -- standard ammo if applicable. -- * instability - the stability damage caused to the attacker by this weapon, -- with standard ammo if applicable. function core.get_weapons(chassisID, mechID) local weapons = {} local where = string.format( 'MechInventory.MechID="%s" OR MechInventory.MechID="%s"', chassisID, mechID ) local join = 'Gear.Id=MechInventory.ComponentDefID' local fields = { 'Gear.Name', 'Weapons.Category', 'Weapons.Type', 'Weapons.AmmoCategory', 'Weapons.MinRange', 'Weapons.MaxRange', 'Weapons.HeatGenerated', 'Weapons.Damage', 'Weapons.HeatDamage', 'Weapons.Instability', 'Weapons.ShotsWhenFired', } local extratables = 'MechInventory' local weaponsResult = core.cargo.query( core.schema.weapon, table.concat(fields, ','), {where = where, join = join, extratables = extratables} ) for _, weaponRow in ipairs(weaponsResult) do local weapon = { name = weaponRow['Gear.Name'], category = weaponRow['Weapons.Category'], type = weaponRow['Weapons.Type'], ammo = weaponRow['Weapons.AmmoCategory'], minRange = tonumber(weaponRow['Weapons.MinRange'], 10), maxRange = tonumber(weaponRow['Weapons.MaxRange'], 10), heat = tonumber(weaponRow['Weapons.HeatGenerated'], 10), damage = tonumber(weaponRow['Weapons.Damage'], 10), shots = tonumber(weaponRow['Weapons.ShotsWhenFired'], 10), heatDamage = tonumber(weaponRow['Weapons.HeatDamage'], 10), instability = tonumber(weaponRow['Weapons.Instability'], 10), } table.insert(weapons, weapon) end return weapons end -- core.get_jumpjets returns a list of jumpjets on the mech function core.get_jumpjets(chassisID, mechID) local jumpjets = {} local where = string.format( 'MechInventory.MechID="%s" OR MechInventory.MechID="%s"', chassisID, mechID ) local join = 'Gear.Id=MechInventory.ComponentDefID' local fields = { 'Gear.Id', 'JumpJets.JumpCapacity', 'MechInventory.MountedLocation', } local extratables = 'MechInventory' local jumpJetData = core.cargo.query( core.schema.jumpjet, table.concat(fields, ','), { where = where, join = join, extratables = extratables } ) for _, jumpJetRow in ipairs(jumpJetData) do table.insert(jumpjets, { id = jumpJetRow['Gear.Id'], jump = tonumber(jumpJetRow['JumpJets.JumpCapacity']), location = jumpJetRow['MechInventory.MountedLocation'] }) end return jumpjets end -- -- Templates -- -- these functions define templates. -- -- all of these functions are invoked to declare the cargo tables for the -- defined schemas. p.table_gear = core.cargo.declare(core.schema.gear) p.table_heatsink = core.cargo.declare(core.schema.heatsink) p.table_cooling = core.cargo.declare(core.schema.cooling) p.table_engineheatblock = core.cargo.declare(core.schema.engineheatblock) p.table_engineshield = core.cargo.declare(core.schema.engineshield) p.table_enginecore = core.cargo.declare(core.schema.enginecore) p.table_upgrade = core.cargo.declare(core.schema.upgrade) p.table_jumpjet = core.cargo.declare(core.schema.jumpjet) p.table_weapon = core.cargo.declare(core.schema.weapon) p.table_ammunition = core.cargo.declare(core.schema.ammunition) -- -- All of the following define the template for defining a given piece of gear. function p.heatsink(frame) local tpl_args = getArgs(frame, {parentFirst = true}) core.cargo.store(frame, core.schema.heatsink, tpl_args) return core.format_table('Heat Sink', tpl_args) end function p.cooling(frame) local tpl_args = getArgs(frame, {parentFirst = true}) core.cargo.store(frame, core.schema.cooling, tpl_args) return core.format_table('Cooling', tpl_args) end function p.engineheatblock(frame) local tpl_args = getArgs(frame, {parentFirst = true}) core.cargo.store(frame, core.schema.engineheatblock, tpl_args) return core.format_table('Engine Heat Block', tpl_args) end function p.engineshield(frame) local tpl_args = getArgs(frame, {parentFirst = true}) core.cargo.store(frame, core.schema.engineshield, tpl_args) return core.format_table('Engine Shield', tpl_args) end function p.enginecore(frame) local tpl_args = getArgs(frame, {parentFirst = true}) core.cargo.store(frame, core.schema.enginecore, tpl_args) return core.format_table('Engine Core', tpl_args) end function p.upgrade(frame) local tpl_args = getArgs(frame, {parentFirst = true}) core.cargo.store(frame, core.schema.upgrade, tpl_args) return core.format_table('Upgrade', tpl_args) end function p.jumpjet(frame) local tpl_args = getArgs(frame, {parentFirst = true}) core.cargo.store(frame, core.schema.jumpjet, tpl_args) return core.format_table('Jump Jet', tpl_args) end function p.weapon(frame) local tpl_args = getArgs(frame, {parentFirst = true}) core.cargo.store(frame, core.schema.weapon, tpl_args) return core.format_table('Weapon', tpl_args) end function p.ammunition(frame) local tpl_args = getArgs(frame, {parentFirst = true}) core.cargo.store(frame, core.schema.ammunition, tpl_args) return core.format_table('Ammunition', tpl_args) end function p.weapons_list(frame) local tpl_args = getArgs(frame, {parentFirst = true}) local simple_columns = { 'UIName', 'AmmoCategory', 'Tonnage', 'InventorySize', 'Damage', 'HeatDamage', 'Instability', 'ShotsWhenFired', 'ProjectilesPerShot', 'HeatGenerated', 'AttackRecoil', 'EvasivePipsIgnored', 'CriticalChanceMultiplier', } local complex_columns = { 'MinRange', 'RangeSplit', 'MaxRange', } local fields = table.concat(simple_columns, ',') .. ',' .. table.concat(complex_columns) local args = {} if tpl_args['Category'] ~= nil then args.where = string.format('Category="%s"',tpl_args['Category']) end local items = core.cargo.query(core.schema.weapon, fields, args) local t = mw.html.create('table') t:addClass('wikitable') local headrow = t:tag('tr') headrow:tag('th'):attr('colspan', '2') headrow:tag('th'):attr('colspan', '2'):wikitext('Size') headrow:tag('th'):attr('colspan', '3'):wikitext('Damage') headrow:tag('th'):attr('colspan', '4'):wikitext('Per Salvo') headrow:tag('th'):attr('colspan', '3'):wikitext('Modifiers') headrow:tag('th'):attr('colspan', '5'):wikitext('Range') local subhead = t:tag('tr') subhead:tag('th'):wikitext('Name') subhead:tag('th'):wikitext('Ammo') subhead:tag('th'):wikitext('Tonnage') subhead:tag('th'):wikitext('Slots') subhead:tag('th'):wikitext('Normal') subhead:tag('th'):wikitext('Heat') subhead:tag('th'):wikitext('Stab') subhead:tag('th'):wikitext('Shots') subhead:tag('th'):wikitext('Projectiles') subhead:tag('th'):wikitext('Heat') subhead:tag('th'):wikitext('Recoil') subhead:tag('th'):wikitext('Accuracy') subhead:tag('th'):wikitext('Evasion Ignored') subhead:tag('th'):wikitext('Bonus Crit Chance') subhead:tag('th'):wikitext('Min') subhead:tag('th'):wikitext('Short') subhead:tag('th'):wikitext('Medium') subhead:tag('th'):wikitext('Long') for _, item in ipairs(items) do local row = t:tag('tr') for _, column in ipairs(columns) do row:tag('td'):wikitext(item[column]) end row:tag('td'):wikitext(item['MinRange']) local range_brackets = mw.text.split(item['RangeSplit'], ',') row:tag('td'):wikitext(range_brackets[1]) row:tag('td'):wikitext(range_brackets[2]) row:tag('td'):wikitext(range_brackets[3]) row:tag('td'):wikitext(item['MaxRange']) end end -- get returns the fields for the given -- first arg is the type, the table name -- second arg is the id of the object -- any subsequent args are the fields to return function p.get(frame) tpl_args = getArgs(frame, {parentFirst=true}) -- the first named local table = tpl_args[1] local id = tpl_args[2] local fields = table.concat(tpl_args, ',', 3) local target_schema for _, schema in pairs(core.schema) do if schema.table == table then target = schema end end local row = core.cargo.query(schema, fields) end -- always return p return p