Module:Mech
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Mech/doc
-- Module for handling information related to a BattleMech -- p for "package", the interface of this module. Returned at the end local p = {} -- imports local cargo = mw.ext.cargo local getArgs = require('Module:Arguments').getArgs p.cargo = {} -- p.cargo.chassis represents the schema of the information contained in a -- chassisdef file. p.cargo.chassis = { -- table is the name of the cargo table table = 'Chassis', -- fields is the schema of all of the fields of the Mech fields = { -- id is the primary key for this table. We expect it to be unique. It -- will be used as the foreign key in other tables to refer to this -- particular mech variant. id = { field = 'Id', type = 'String', }, cost = { -- field defines the name of the field in the cargo table. for the -- field name, we use the name of the property in the ChassisDef -- json. field = 'Cost', -- type defines the type of the field type = 'Integer', }, rarity = { field = 'Rarity', type = 'Integer', }, purchasable = { field = 'Purchasable', type = 'Boolean', }, manufacturer = { field = 'Manufacturer', type = 'String', }, model = { field = 'Model', type = 'String', }, uiName = { field = 'UIName', type = 'String', }, name = { field = 'Name', type = 'String', }, details = { field = 'Details', -- Details use the Text type, which is unindexed and intended for -- longer-form data. type = 'Text', }, icon = { field = 'Icon', type = 'String', }, tonnage = { field = 'Tonnage', type = 'Integer', }, weightClass = { -- TODO(rust dev): this is probably an enum type. I bet we can -- do something with that. field = 'weightClass', type = 'String' }, variantName = { field = 'VariantName', type = 'String', }, stockRole = { field = 'StockRole', type = 'Text', }, yangsThoughts = { field = 'YangsThoughts', type = 'Text', }, leftArmActuatorLimit = { -- TODO(rust dev): another enum field = 'LeftArmActuatorLimit', type = 'String', }, rightArmActuatorLimit = { field = 'RightArmActuatorLimit', }, } } -- p.cargo.chassis_locations describes the information about a location on a mech -- chassis p.cargo.chassis_locations = { table = 'ChassisLocations', fields = { -- chassisID is the id of the chassis this location belongs to chassisID = { field = 'ChassisID', type = 'String', }, location = { -- TODO(rust dev): this is probably an enum, and we can do something with -- that. field = 'Location', type = 'String' }, tonnage = { field = 'Tonnage', type = 'Integer', }, inventorySlots = { field = 'InventorySlots', type = 'Integer', }, maxArmor = { field = 'MaxArmor', type = 'Integer', }, maxRearArmor = { field = 'MaxRearArmor', type = 'Integer', }, internalStructure = { field = 'InternalStructure', type = 'Integer', }, hardpoints = { -- Hardpoints are done as a list of string. The value of the string is the -- type of hardpoint so, it might have the form in the template like -- -- |Hardpoints = Energy,Energy,Missile -- field = 'Hardpoints', type = 'List (,) of String', }, omniHardpoints = { -- OmniHardpoints are the same, but for hardpoints where Omni == true field = 'OmniHardpoints', type = 'List (,) of String', } } } -- p.cargo.mech represents the information contained in a mechdef file. p.cargo.mech = { table = 'Mech', fields = { -- id is the id of this mechdef. id = { field = 'ID', type = 'String', }, -- chassisID is the foreign key pointed to the ChassisDef that corresponds -- to this mech. chassisID = { field = 'ChassisID', type = 'String', }, -- mechTags is a list of strings, the tags for the mech. corresponds to -- MechTags.items in the json file. mechTags = { field = 'MechTags', type = 'List (,) of String', }, cost = { field = 'Cost', type = 'Integer', }, rarity = { field = 'Rarity', type = 'Integer', }, purchasable = { field = 'Purchasable', type = 'Boolean', }, manufacturer = { field = 'Manufacturer', type = 'String', }, model = { field = 'Model', type = 'String', }, uiName = { field = 'UIName', type = 'String', }, name = { field = 'Name', type = 'String', }, details = { field = 'Details', -- Details use the Text type, which is unindexed and intended for -- longer-form data. type = 'Text', }, icon = { field = 'Icon', type = 'String', }, simGameMechPartCost = { field = 'simGameMechPartCost', type = 'Integer', }, version = { field = 'Version', type = 'Integer', }, }, } p.cargo.mech_locations = { table = 'MechLocations', -- a lot of the data in a mechdef seems redundant and not interesting for -- our purposes. however, include it here in the interest of correctness. fields = { -- mechID is the ID of the mech that this location belongs to mechID = { field = 'MechID', type = 'String', }, -- location is the location being described here location = { field = 'Location', type = 'String', }, currentArmor = { field = 'CurrentArmor', type = 'Integer', }, currentRearArmor = { field = 'CurrentRearArmor', type = 'Integer', }, currentInternalStructure = { field = 'CurrentInternalStructure', type = 'Integer', }, assignedArmor = { field = 'AssignedArmor', type = 'Integer', }, assignedRearArmor = { field = 'AssignedRearArmor', type = 'Integer', }, }, } -- p.cargo.mech_inventory describes an item in the inventory of a mech -- -- excludes some fields which don't appear to be relevant: -- * SimGameUID -- * GUID -- * DamageLevel -- * prefabName -- * hasPrefabName p.cargo.mech_inventory = { table = 'MechInventory', fields = { -- mechID is the ID of the mechdef that this inventory item is from mechID = { field = 'MechID', type = 'String', }, mountedLocation = { field = 'MountedLocation', type = 'String', }, -- componentDefID is the ID of the component that that is mounted in this -- location componentDefID = { field = 'ComponentDefID', type = 'String', }, componentDefType = { field = 'ComponentDefType', -- probably yet another enum. type = 'String' }, hardpointSlot = { field = 'HardpointSlot', type = 'Integer', }, -- fixedEquipment is a bit different. a mech can have equipment defined in -- the chassisdef as well as the mechdef. Equipment defined in the -- chassisdef is "FixedEquipment". Instead of having a separate table for -- this, we will instead set this flag true, and the mechID will instead -- refer to a chassis ID. fixedEquipment = { field = 'FixedEquipment', type = 'Boolean', }, }, } -- cargo_declare is a function which returns another function. The return value -- is evaluated to declare the cargo table. -- -- this is taken in large part from the Path of Exile wiki's Item2 module. -- -- TODO(rust dev): define this in a separate module for reuse function cargo_declare (data) return function (frame) if frame == nil then frame = mw.getCurrentFrame() end -- dcl_args are the arguments we will be passing to #cargo_declare local dcl_args = {} -- set the table name dcl_args._table = data.table -- for every field, we create an argument mapping the field name to its -- type. for k, field_data in pairs(data.fields) do if field_data then dcl_args[field_data.field] = field_data.type end end -- call #cargo_declare to declare the table before ending the function. frame:callParserFunction('#cargo_declare:', dcl_args) end end -- invoke these functions to declare the tables. p.table_chassis = cargo_declare(p.cargo.chassis) p.table_chassis_locations = cargo_declare(p.cargo.chassis_locations) p.table_mech = cargo_declare(p.cargo.mech) p.table_mech_locations = cargo_declare(p.cargo.mech_locations) p.table_inventory = cargo_declare(p.cargo.mech_inventory) -- stores the data in cargo. We don't just pass in all the template arguments, -- as the template may have parameters that are not part of the chassis -- definition. here instead, we iterate through the known fields of the table -- and look for matching argument names. If we find one, we add it to the data -- we use in the cargo store call. function cargo_store(frame, schema, tpl_args) local cargo_data = {_table = schema.table} for _, field in pairs(schema.fields) do local arg = tpl_args[field.field] if arg ~= nil then -- consistently cast boolean values to the same type as 1 or 0. if type(arg) == 'boolean' then if arg == true then arg = '1' else arg ='0' end end cargo_data[field.field] = arg end end return frame:callParserFunction('#cargo_store:', cargo_data) end -- -- Templates -- -- rawTable generates a raw table consisting of the data function rawTable(schema, tpl_args) local rawTable = mw.html.create('table') rawTable:addClass('wikitable') for _, field in pairs(schema.fields) do rawTable:tag('tr') :tag('th'):wikitext(field.field):done() :tag('td'):wikitext(tpl_args[field.field]) end return rawTable end -- p.chassis is a template which defines a Mech chassis. function p.chassis (frame) tpl_args = getArgs(frame, { -- parentFirst tells us to prefer args from the parent page (the template -- that calls #invoke). parentFirst = true }) -- p.mechChassis is a template for raw data, and so we display the raw data -- instead of anything fancy. To really display a mech, we need way more than -- the chassis, anyway. local chassisTable = rawTable(p.cargo.chassis, tpl_args) cargo_store(frame, p.cargo.chassis, tpl_args) return tostring(chassisTable) end -- p.chassisLocation is a template which defines a Mech chassis location function p.chassisLocation (frame) tpl_args = getArgs(frame, { parentFirst = true, }) local locationTable = rawTable(p.cargo.chassis_locations, tpl_args) cargo_store(frame, p.cargo.chassis_locations, tpl_args) return tostring(locationTable) end function p.mech (frame) tpl_args = getArgs(frame, { parentFirst = true }) cargo_store(frame, p.cargo.mech, tpl_args) return tostring(rawTable(p.cargo.mech, tpl_args)) end function p.mechLocation (frame) tpl_args = getArgs(frame, { parentFirst = true }) cargo_store(frame, p.cargo.mech_locations, tpl_args) return tostring(rawTable(p.cargo.mech_locations, tpl_args)) end function p.mechInventory (frame) tpl_args = getArgs(frame, { parentFirst = true }) cargo_store(frame, p.cargo.mech_inventory, tpl_args) return tostring(rawTable(p.cargo.mech_inventory, tpl_args)) end -- mechInfoBox creates a mech infobox. function p.mechInfoBox (frame) tpl_args = getArgs(frame, { parentFirst = true }) -- this function takes one argument, the VariantName. -- we only need a subset of the chassis fields. local chassisFields = 'VariantName,weightClass,Tonnage,StockRole,Id' local chassisArgs = { -- this uses string formatting to produce a string something like: -- 'Chassis.VariantName="MAD-5A"' -- the where tells the database to only take entries with this VariantName. where = string.format( '%s.%s="%s"', p.cargo.chassis.table, p.cargo.chassis.fields.variantName.field, tpl_args[p.cargo.chassis.fields.variantName.field] ) } local chassisData = cargo.query(p.cargo.chassis.table, chassisFields, chassisArgs) -- we should only ever get 1 entry for each VariantName, but cargo.query -- always returns a list. Lists in Lua are 1-indexed (as opposed to the -- typical 0-indexing in most languages), so this just mean's we're taking -- the first entry. chassisData = chassisData[1] local chassisID = chassisData['Id'] -- setting up another database query, this time for the mech locations. We -- will use this data to get the sum of the internal structure, and to count -- up the number of available hardpoints. local locationFields = 'InternalStructure,Hardpoints,OmniHardpoints' local locationArgs = { where = string.format( '%s.%s="%s"', p.cargo.chassis_locations.table, p.cargo.chassis_locations.fields.chassisID.field, chassisID ) } local chassisLocations = cargo.query( p.cargo.chassis_locations.table, locationFields, locationArgs ) local structure = 0 -- table keys in lua are also strings. these table keys are the same strings -- we expect the hardpoints to be. this is a shortcut to make counting -- hardpoints easier. local hardpoint_counts = { Energy = 0, Ballistic = 0, Missile = 0, AntiPersonnel = 0, } -- mechtype is 'Standard BattleMech', unless we find a non-zero number of -- Omni hardpoints. then, we'll change it to be 'OmniMech'. local mechtype = 'Standard BattleMech' for _, location in ipairs(chassisLocations) do -- add the value of each location's InternalStructure together. all of the -- fields returned from cargo are strings, so we have to first convert them -- to numbers. I think. I might be wrong about this, actually. structure = structure + tonumber(location['InternalStructure'], 10) -- hardpoints is a list of strings. we split that list along the commas to -- get an actual list we can iterate. for _, hardpoint in ipairs(mw.text.split(location['Hardpoints'], ",")) do -- then, we check to see if the hardpoint is one of the keys of the -- hardpoint_counts table if hardpoint_counts[hardpoint] ~= nil then -- if it is, then we increment that entry's counter. hardpoint_counts[hardpoint] = hardpoint_counts[hardpoint] + 1 end end if #location['OmniHardpoints'] > 0 then mechtype = 'OmniMech' end end -- in this iteration, the only thing we need is the mechdef's ID. currently, -- we only have the chassisdef's ID. local mechdefFields = 'ID' local mechdefArgs = { -- this is where we select only the row with the same chassis ID. where = string.format( '%s.%s="%s"', p.cargo.mech.table, p.cargo.mech.fields.chassisID.field, chassisID ) } local mechdef = cargo.query( p.cargo.mech.table, mechdefFields, mechdefArgs ) -- remember, Lua is 1-indexed. mechID = mechdef[1]['ID'] -- now, we need to get the total armor. This is part of the MechLocations, -- not the ChassisLocations, so we do another query. local mechLocationFields = 'AssignedArmor,AssignedRearArmor' local mechLocationArgs = { where = string.format( '%s.%s="%s"', p.cargo.mech_locations.table, p.cargo.mech_locations.fields.mechID.field, mechID ) } local mechLocations = cargo.query( p.cargo.mech_locations.table, mechLocationFields, mechLocationArgs ) local armor = 0 for _, location in ipairs(mechLocations) do -- same thing as with structure. armor = armor + tonumber(location['AssignedArmor'], 10) -- for locations, like arms, legs, and the head, that do not have rear -- armor, the rear armor value is '-1'. We should only add the rear armor -- value to the total if it is not -1, because otherwise we'd come up 5 -- points short from adding in the -1s if location['AssignedRearArmor'] ~= '-1' then armor = armor + tonumber(location['AssignedRearArmor'], 10) end end -- this isn't actually used yet, because we need to fully populate the item -- database. However, when the item database is filled, we can get the full -- details of all of the items on the 'Mech, and use that to calculate things -- like alpha strike damage. local inventoryFields = 'ComponentDefID,ComponentDefType' local inventoryArgs = { where = string.format( '%s.%s="%s" OR %s.%s="%s"', p.cargo.mech_inventory.table, p.cargo.mech_inventory.fields.mechID.field, mechID, p.cargo.mech_inventory.table, p.cargo.mech_inventory.fields.mechID.field, chassisID ) } -- finally, to avoid re-inventing the wheel for this demo, we call the -- existing InfoboxVariant template with the data we've computed from the -- mech's database entries. local template_args = { variantname = chassisData['VariantName'], mechtype = mechtype, class = chassisData['weightClass'], ballistic = hardpoint_counts.Ballistic, energy = hardpoint_counts.Energy, missile = hardpoint_counts.Missile, support = hardpoint_counts.AntiPersonnel, armor = armor, structure = structure } template_args['stock role'] = chassisData['StockRole'] return frame:expandTemplate{ title = 'InfoboxVariant', args = template_args } end return p