Difference between revisions of "Module:Mech"
Jump to navigation
Jump to search
Amidatelion (talk | contribs) |
|||
(77 intermediate revisions by 4 users not shown) | |||
Line 3: | Line 3: | ||
-- p for "package", the interface of this module. Returned at the end | -- p for "package", the interface of this module. Returned at the end | ||
local p = {} | local p = {} | ||
+ | |||
+ | local core = {} | ||
+ | p.core = core | ||
-- imports | -- imports | ||
local cargo = mw.ext.cargo | local cargo = mw.ext.cargo | ||
local getArgs = require('Module:Arguments').getArgs | local getArgs = require('Module:Arguments').getArgs | ||
+ | local gear = require('Module:Gear').core | ||
p.cargo = {} | p.cargo = {} | ||
Line 95: | Line 99: | ||
rightArmActuatorLimit = { | rightArmActuatorLimit = { | ||
field = 'RightArmActuatorLimit', | field = 'RightArmActuatorLimit', | ||
+ | type = 'String' | ||
+ | }, | ||
+ | startingTonnage = { | ||
+ | field = 'StartingTonnage', | ||
+ | type = 'Float', | ||
+ | }, | ||
+ | chassisTags = { | ||
+ | field = 'ChassisTags', | ||
+ | type = 'List (,) of String', | ||
}, | }, | ||
} | } | ||
Line 158: | Line 171: | ||
-- id is the id of this mechdef. | -- id is the id of this mechdef. | ||
id = { | id = { | ||
− | field = ' | + | field = 'Id', |
type = 'String', | type = 'String', | ||
}, | }, | ||
Line 235: | Line 248: | ||
location = { | location = { | ||
field = 'Location', | field = 'Location', | ||
− | |||
− | |||
− | |||
− | |||
− | |||
type = 'String', | type = 'String', | ||
}, | }, | ||
Line 308: | Line 316: | ||
field = 'FixedEquipment', | field = 'FixedEquipment', | ||
type = 'Boolean', | type = 'Boolean', | ||
+ | }, | ||
+ | -- count is a way to ensure that otherwise duplicate equipment in the same | ||
+ | -- spot gets a separate row in the database. | ||
+ | count = { | ||
+ | field = 'Count', | ||
+ | type = 'Integer', | ||
}, | }, | ||
}, | }, | ||
Line 362: | Line 376: | ||
if arg ~= nil then | if arg ~= nil then | ||
-- consistently cast boolean values to the same type as 1 or 0. | -- consistently cast boolean values to the same type as 1 or 0. | ||
− | if type | + | if field.type == 'Boolean' then |
− | if arg == true then | + | if arg == 'true' or arg == '1' or arg == 'yes' then |
arg = '1' | arg = '1' | ||
else | else | ||
Line 374: | Line 388: | ||
end | end | ||
return frame:callParserFunction('#cargo_store:', cargo_data) | return frame:callParserFunction('#cargo_store:', cargo_data) | ||
+ | end | ||
+ | |||
+ | -- | ||
+ | -- Classes | ||
+ | |||
+ | -- core.mech defines the lua-centric mech object | ||
+ | core.mech = {} | ||
+ | |||
+ | function core.mech.byVariant(variantName) | ||
+ | local where = string.format( | ||
+ | '%s.%s="%s"', p.cargo.chassis.table, 'VariantName', variantName | ||
+ | ) | ||
+ | |||
+ | return core.mech:new(where) | ||
+ | end | ||
+ | |||
+ | function core.mech.byChassisID(chassisID) | ||
+ | local where = string.format( | ||
+ | '%s.%s="%s"', p.cargo.chassis.table, 'Id', chassisID | ||
+ | ) | ||
+ | |||
+ | return core.mech:new(where) | ||
+ | end | ||
+ | |||
+ | function core.mech:new(where) | ||
+ | local mech = {} | ||
+ | |||
+ | setmetatable(mech, self) | ||
+ | self.__index = self | ||
+ | |||
+ | local chassisfields = { | ||
+ | 'Id', | ||
+ | 'Name', | ||
+ | 'UIName', | ||
+ | 'VariantName', | ||
+ | 'Details', | ||
+ | 'StockRole', | ||
+ | 'weightClass', | ||
+ | 'Tonnage', | ||
+ | 'StartingTonnage', | ||
+ | 'LeftArmActuatorLimit', | ||
+ | 'RightArmActuatorLimit', | ||
+ | } | ||
+ | |||
+ | local chassisRow = cargo.query( | ||
+ | p.cargo.chassis.table, table.concat(chassisfields, ','), { where = where } | ||
+ | ) | ||
+ | chassisRow = chassisRow[1] | ||
+ | if chassisRow == nil then | ||
+ | return nil | ||
+ | end | ||
+ | |||
+ | mech.chassisID = chassisRow['Id'] | ||
+ | mech.uiName = chassisRow['UIName'] | ||
+ | mech.name = chassisRow['Name'] | ||
+ | mech.variantName = chassisRow['VariantName'] | ||
+ | mech.details = chassisRow['Details'] | ||
+ | mech.weightClass = chassisRow['weightClass'] | ||
+ | mech.stockRole = chassisRow['StockRole'] | ||
+ | mech.leftArmActuatorLimit = chassisRow['LeftArmActuatorLimit'] | ||
+ | mech.rightArmActuatorLimit = chassisRow['RightArmActuatorLimit'] | ||
+ | mech.tonnage = tonumber(chassisRow['Tonnage'], 10) | ||
+ | mech.startingTonnage = tonumber(chassisRow['StartingTonnage'], 10) | ||
+ | |||
+ | local mechfields = { | ||
+ | 'MechTags', | ||
+ | 'Id', | ||
+ | } | ||
+ | local mechwhere = string.format( | ||
+ | '%s.%s="%s"', p.cargo.mech.table, 'ChassisID', mech.chassisID | ||
+ | ) | ||
+ | |||
+ | local mechRow = cargo.query( | ||
+ | p.cargo.mech.table, table.concat(mechfields, ','), { where = mechwhere } | ||
+ | ) | ||
+ | |||
+ | mechRow = mechRow[1] | ||
+ | mech.mechID = mechRow['Id'] | ||
+ | mech.tags = mw.text.split(mechRow['MechTags'], ',') | ||
+ | |||
+ | return mech | ||
+ | end | ||
+ | |||
+ | local standard_actuators = { | ||
+ | } | ||
+ | function core.mech:getLocations() | ||
+ | if self.locations ~= nil then | ||
+ | return self.locations | ||
+ | end | ||
+ | |||
+ | where = string.format( | ||
+ | '%s.%s="%s"', | ||
+ | p.cargo.chassis_locations.table, 'ChassisID', self.chassisID | ||
+ | ) | ||
+ | fields = { | ||
+ | 'Location', | ||
+ | 'InventorySlots', | ||
+ | 'MaxArmor', | ||
+ | 'MaxRearArmor', | ||
+ | 'InternalStructure', | ||
+ | 'Hardpoints', | ||
+ | 'OmniHardpoints', | ||
+ | } | ||
+ | |||
+ | local chassisLocations = cargo.query( | ||
+ | p.cargo.chassis_locations.table, | ||
+ | table.concat(fields, ','), | ||
+ | { where = where } | ||
+ | ) | ||
+ | |||
+ | self.locations = {} | ||
+ | for _, row in ipairs(chassisLocations) do | ||
+ | local location = { | ||
+ | tonnage = tonumber(row['Tonnage'], 10), | ||
+ | location = row['Location'], | ||
+ | maxArmor = tonumber(row['MaxArmor'], 10), | ||
+ | maxRearArmor = tonumber(row['MaxRearArmor'], 10), | ||
+ | internalStructure = tonumber(row['InternalStructure'], 10), | ||
+ | hardpoints = mw.text.split(row['Hardpoints'], ','), | ||
+ | gear = {}, | ||
+ | } | ||
+ | if row['OmniHardpoints'] == '' then | ||
+ | location.omniHardpoints = {} | ||
+ | else | ||
+ | location.omniHardpoints = mw.text.split(row['OmniHardpoints'], ',') | ||
+ | end | ||
+ | |||
+ | self.locations[row['Location']] = location | ||
+ | end | ||
+ | |||
+ | local mechwhere = string.format( | ||
+ | '%s.%s="%s"', | ||
+ | p.cargo.mech_locations.table, 'MechID', self.mechID | ||
+ | ) | ||
+ | local mechfields = { | ||
+ | 'Location', | ||
+ | 'AssignedArmor', | ||
+ | 'AssignedRearArmor', | ||
+ | } | ||
+ | |||
+ | |||
+ | local mechlocations = cargo.query( | ||
+ | p.cargo.mech_locations.table, | ||
+ | table.concat(mechfields, ','), | ||
+ | { where = mechwhere } | ||
+ | ) | ||
+ | |||
+ | for _, row in ipairs(mechlocations) do | ||
+ | self.locations[row['Location']].assignedArmor = tonumber(row['AssignedArmor'], 10) | ||
+ | self.locations[row['Location']].assignedRearArmor = tonumber(row['AssignedRearArmor'], 10) | ||
+ | end | ||
+ | |||
+ | local mechGear = gear.get_gear(self.chassisID, self.mechID) | ||
+ | for _, gear in ipairs(mechGear) do | ||
+ | -- do not include in the gear list fixed-location gear like structure, | ||
+ | -- gyro, or armor | ||
+ | local exempt = false | ||
+ | -- don't display standard actuators, which have IDs starting with emod | ||
+ | -- all other actuators, display | ||
+ | if string.match(gear.id, 'emod_arm_part') or string.match(gear.id, 'emod_leg') or string.match(gear.id, 'Gear_Cockpit_Generic_Standard') then | ||
+ | gear.nodisplay = true | ||
+ | elseif string.match(gear.id, 'emod_arm_part_lower') or string.match(gear.id, 'emod_arm_part_hand') then | ||
+ | gear.nodisplay = false | ||
+ | end | ||
+ | |||
+ | for _, category in ipairs(gear.categories) do | ||
+ | if category == 'EnginePart' then | ||
+ | gear.nodisplay = true | ||
+ | elseif category == 'Gyro' then | ||
+ | self.gyro = gear | ||
+ | exempt = true | ||
+ | elseif category == 'Armor' then | ||
+ | self.armor = gear | ||
+ | exempt = true | ||
+ | elseif category == 'Structure' then | ||
+ | self.structure = gear | ||
+ | exempt = true | ||
+ | end | ||
+ | |||
+ | if category == 'Quirk' then | ||
+ | self.quirk = gear | ||
+ | end | ||
+ | end | ||
+ | |||
+ | if not exempt then | ||
+ | table.insert(self.locations[gear.location].gear, gear) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | self.jumpjets = gear.get_jumpjets(self.chassisID, self.mechID) | ||
+ | |||
+ | return self.locations | ||
+ | end | ||
+ | |||
+ | function core.mech:getEngine() | ||
+ | if self.engine ~= nil then | ||
+ | return self.engine | ||
+ | end | ||
+ | |||
+ | self.engine = gear.get_engine(self.chassisID, self.mechID) | ||
+ | |||
+ | return self.engine | ||
+ | end | ||
+ | |||
+ | function core.mech:getWeapons() | ||
+ | if self.weapons ~= nil then | ||
+ | return self.weapons | ||
+ | end | ||
+ | |||
+ | self.weapons = gear.get_weapons(self.chassisID, self.mechID) | ||
+ | |||
+ | return self.weapons | ||
+ | end | ||
+ | |||
+ | function core.mech:getHeatsinks() | ||
+ | if self.heatsinks ~= nil then | ||
+ | return self.heatsinks | ||
+ | end | ||
+ | |||
+ | self.heatsinks = gear.get_heatsinks(self.chassisID, self.mechID) | ||
+ | |||
+ | return self.heatsinks | ||
end | end | ||
Line 380: | Line 616: | ||
-- | -- | ||
− | -- | + | -- raw_table generates a raw table consisting of the data |
− | function | + | function raw_table(schema, tpl_args) |
local rawTable = mw.html.create('table') | local rawTable = mw.html.create('table') | ||
rawTable:addClass('wikitable') | rawTable:addClass('wikitable') | ||
Line 404: | Line 640: | ||
-- instead of anything fancy. To really display a mech, we need way more than | -- instead of anything fancy. To really display a mech, we need way more than | ||
-- the chassis, anyway. | -- the chassis, anyway. | ||
− | local chassisTable = | + | local chassisTable = raw_table(p.cargo.chassis, tpl_args) |
cargo_store(frame, p.cargo.chassis, tpl_args) | cargo_store(frame, p.cargo.chassis, tpl_args) | ||
Line 417: | Line 653: | ||
}) | }) | ||
− | local locationTable = | + | local locationTable = raw_table(p.cargo.chassis_locations, tpl_args) |
cargo_store(frame, p.cargo.chassis_locations, tpl_args) | cargo_store(frame, p.cargo.chassis_locations, tpl_args) | ||
Line 431: | Line 667: | ||
cargo_store(frame, p.cargo.mech, tpl_args) | cargo_store(frame, p.cargo.mech, tpl_args) | ||
− | return tostring( | + | return tostring(raw_table(p.cargo.mech, tpl_args)) |
end | end | ||
Line 441: | Line 677: | ||
cargo_store(frame, p.cargo.mech_locations, tpl_args) | cargo_store(frame, p.cargo.mech_locations, tpl_args) | ||
− | return tostring( | + | return tostring(raw_table(p.cargo.mech_locations, tpl_args)) |
end | end | ||
Line 451: | Line 687: | ||
cargo_store(frame, p.cargo.mech_inventory, tpl_args) | cargo_store(frame, p.cargo.mech_inventory, tpl_args) | ||
− | return tostring( | + | return tostring(raw_table(p.cargo.mech_inventory, tpl_args)) |
end | end | ||
Line 460: | Line 696: | ||
}) | }) | ||
− | local | + | local mech |
− | + | if tpl_args['VariantName'] ~= nil and tpl_args['VariantName'] ~= '' then | |
− | + | mech = core.mech.byVariant(tpl_args['VariantName']) | |
− | ' | + | elseif tpl_args['ChassisID'] ~= nil and tpl_args['ChassisID'] ~= '' then |
− | + | mech = core.mech.byChassisID(tpl_args['ChassisID']) | |
− | + | end | |
− | + | ||
− | + | if mech == nil then | |
+ | local args = { | ||
+ | variantname = tpl_args['VariantName'], | ||
+ | mechtype = 'Unknown Mech', | ||
+ | weight = '???', | ||
+ | class = '???', | ||
+ | } | ||
+ | args['stock role'] = 'Mech not found' | ||
+ | return frame:expandTemplate{ | ||
+ | title = 'InfoboxVariant', | ||
+ | args = args | ||
+ | } | ||
+ | end | ||
+ | |||
+ | local structure = 0 | ||
+ | local armor = 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, | ||
+ | Artillery = 0, | ||
+ | AntiPersonnel = 0, | ||
+ | BombBay = 0, | ||
+ | MeleeWeapon = 0, | ||
} | } | ||
+ | local omniHardpoints = 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 = 'BattleMech' | ||
− | + | for _, location in pairs(mech:getLocations()) 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 + location.internalStructure | ||
+ | armor = armor + location.assignedArmor | ||
+ | if location.assignedRearArmor ~= -1 then | ||
+ | armor = armor + location.assignedRearArmor | ||
+ | end | ||
− | local | + | -- hardpoints is a list of strings. we split that list along the commas to |
− | local | + | -- get an actual list we can iterate. |
− | + | for _, hardpoint in ipairs(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 | ||
+ | omniHardpoints = omniHardpoints + #location.omniHardpoints | ||
+ | end | ||
+ | |||
+ | local damage = 0 | ||
+ | local stab = 0 | ||
+ | local heat_damage = 0 | ||
+ | local heat_gen = 0 | ||
+ | for _, weapon in ipairs(mech:getWeapons()) do | ||
+ | if weapon.damage ~= nil then | ||
+ | damage = damage + (weapon.damage * weapon.shots) | ||
+ | stab = stab + (weapon.instability * weapon.shots) | ||
+ | heat_damage = heat_damage + (weapon.heatDamage * weapon.shots) | ||
+ | heat_gen = heat_gen + weapon.heat | ||
+ | end | ||
+ | end | ||
+ | |||
+ | local engine = mech:getEngine() | ||
+ | local heatsinks = mech:getHeatsinks() | ||
+ | |||
+ | local dissipation = engine.heat_sinking | ||
+ | for _, heatsink in ipairs(heatsinks) do | ||
+ | dissipation = dissipation + heatsink.dissipation | ||
+ | end | ||
+ | |||
+ | -- 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 = mech.variantName, | ||
+ | image = mech.name .. '.png', | ||
+ | mechtype = mechtype, | ||
+ | weight = '' .. mech.tonnage .. 'T', | ||
+ | class = mech.weightClass, | ||
+ | armor = armor, | ||
+ | structure = structure, | ||
+ | maxdamage = damage, | ||
+ | maxstability = stab, | ||
+ | maxheat = heat_damage, | ||
+ | alphaheat = heat_gen, | ||
+ | heatsinking = dissipation, | ||
} | } | ||
+ | if engine ~= nil then | ||
+ | if engine.fixed then | ||
+ | template_args.coresize = engine.core .. ' (Fixed)' | ||
+ | else | ||
+ | template_args.coresize = engine.core | ||
+ | end | ||
+ | |||
+ | if engine.shield.fixed then | ||
+ | template_args.enginetype = engine.shield.name .. ' (Fixed)' | ||
+ | else | ||
+ | template_args.enginetype = engine.shield.name | ||
+ | end | ||
+ | |||
+ | if engine.ecooling > 0 then | ||
+ | if engine.ecooling_fixed then | ||
+ | template_args.ecooling = string.format('+%d (Fixed)', engine.ecooling) | ||
+ | else | ||
+ | template_args.ecooling = string.format('+%d', engine.ecooling) | ||
+ | end | ||
+ | else | ||
+ | template_args.ecooling = 'None' | ||
+ | end | ||
+ | |||
+ | if engine.cooling_fixed then | ||
+ | template_args.heatsinkkit = engine.heatsinkkit .. ' (Fixed)' | ||
+ | else | ||
+ | template_args.heatsinkkit = engine.heatsinkkit | ||
+ | end | ||
+ | end | ||
+ | |||
+ | if mech.gyro ~= nil then | ||
+ | if mech.gyro.fixed then | ||
+ | template_args.gyro = mech.gyro.name .. ' (Fixed)' | ||
+ | else | ||
+ | template_args.gyro = mech.gyro.name | ||
+ | end | ||
+ | if string.find(mech.gyro.name, "Omni") then | ||
+ | template_args.mechtype = 'OmniMech' | ||
+ | end | ||
+ | end | ||
+ | if mech.armor ~= nil then | ||
+ | if mech.armor.fixed then | ||
+ | template_args.armortype = mech.armor.name .. ' (Fixed)' | ||
+ | else | ||
+ | template_args.armortype = mech.armor.name | ||
+ | end | ||
+ | end | ||
+ | if mech.structure ~= nil then | ||
+ | template_args.structuretype = mech.structure.name .. ' (Fixed)' | ||
+ | end | ||
+ | |||
+ | for locationName, location in pairs(mech.locations) do | ||
+ | local locationstring = "" | ||
+ | if locationName == 'Head' then | ||
+ | locationstring = 'headequip' | ||
+ | elseif locationName == 'CenterTorso' then | ||
+ | locationstring = 'ctequip' | ||
+ | elseif locationName == 'LeftTorso' then | ||
+ | locationstring = 'ltequip' | ||
+ | elseif locationName == 'RightTorso' then | ||
+ | locationstring = 'rtequip' | ||
+ | elseif locationName == 'LeftArm' then | ||
+ | locationstring = 'laequip' | ||
+ | elseif locationName == 'RightArm' then | ||
+ | locationstring = 'raequip' | ||
+ | elseif locationName == 'LeftLeg' then | ||
+ | locationstring = 'llequip' | ||
+ | else | ||
+ | locationstring = 'rlequip' | ||
+ | end | ||
+ | |||
+ | local gearSlot = 1 | ||
+ | for i, gear in ipairs(location.gear) do | ||
+ | -- skip gear marked nodisplay, which includes stuff like actuators and | ||
+ | -- gyros. | ||
+ | if not gear.nodisplay then | ||
+ | if gear.fixed == '1' or gear.fixed then | ||
+ | template_args[locationstring .. gearSlot] = gear.uiname .. ' (Fixed)' | ||
+ | else | ||
+ | template_args[locationstring .. gearSlot] = gear.uiname | ||
+ | end | ||
+ | gearSlot = gearSlot + 1 | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | |||
+ | if hardpoint_counts.Ballistic > 0 then | ||
+ | template_args.ballistic = hardpoint_counts.Ballistic | ||
+ | end | ||
+ | if hardpoint_counts.Energy > 0 then | ||
+ | template_args.energy = hardpoint_counts.Energy | ||
+ | end | ||
+ | |||
+ | if hardpoint_counts.Missile > 0 then | ||
+ | template_args.missile = hardpoint_counts.Missile | ||
+ | end | ||
+ | |||
+ | if hardpoint_counts.Artillery > 0 then | ||
+ | template_args.artillery = hardpoint_counts.Artillery | ||
+ | end | ||
+ | |||
+ | if hardpoint_counts.AntiPersonnel > 0 then | ||
+ | template_args.support = hardpoint_counts.AntiPersonnel | ||
+ | end | ||
+ | |||
+ | if omniHardpoints > 0 then | ||
+ | template_args.omni = omniHardpoints | ||
+ | end | ||
+ | |||
+ | if hardpoint_counts.BombBay > 0 then | ||
+ | template_args.bomb = hardpoint_counts.BombBay | ||
+ | end | ||
+ | |||
+ | if hardpoint_counts.MeleeWeapon > 0 then | ||
+ | template_args.melee = hardpoint_counts.MeleeWeapon | ||
+ | end | ||
+ | |||
+ | -- walk MP, according to the mech bay, is some weird number. This math is | ||
+ | -- 110% made up out of nowhere, and only mostly correct, probably. For some | ||
+ | -- mechs, it'll need manual override. | ||
+ | -- | ||
+ | -- local walkMeters = (mech.engine.core / mech.tonnage) * 30 | ||
+ | -- local runMeters = walkMeters * 1.5 | ||
+ | |||
+ | -- local walkMP = math.floor(walkMeters / 28) | ||
+ | -- local runMP = math.floor(runMeters / 28) | ||
+ | |||
+ | local walkMP = math.ceil(mech.engine.core / mech.tonnage) | ||
+ | local runMP = math.ceil(walkMP * 1.5) | ||
+ | local jumpMP = 0 | ||
+ | for _, j in ipairs(mech.jumpjets) do | ||
+ | jumpMP = jumpMP + j.jump | ||
+ | end | ||
+ | |||
+ | if jumpMP >= 0 then | ||
+ | template_args.speed = string.format('%d/%d/%d', walkMP, runMP, jumpMP) | ||
+ | end | ||
+ | |||
+ | template_args['stock role'] = mech.stockRole | ||
+ | |||
+ | template_args['kickdamage'] = mech.tonnage | ||
+ | template_args['punchdamage'] = math.ceil(mech.tonnage / 2) | ||
+ | |||
+ | -- finally, lastly, the user can put in raw template data and override our | ||
+ | -- calculations. so, before we return anything, go through tpl_args and | ||
+ | -- replace any outgoing template args | ||
+ | for arg, val in pairs(tpl_args) do | ||
+ | -- skip the VariantName arg, though. That one belongs to us. | ||
+ | if arg ~= 'VariantName' then | ||
+ | template_args[arg] = val | ||
+ | end | ||
+ | end | ||
+ | |||
+ | return frame:expandTemplate{ | ||
+ | title = 'InfoboxVariant', | ||
+ | args = template_args | ||
+ | } | ||
+ | end | ||
+ | |||
+ | -- mechTableLine creates an entry in the Full List of Mechs. | ||
+ | function p.mechTableLine (frame) | ||
+ | tpl_args = getArgs(frame, { | ||
+ | parentFirst = true | ||
+ | }) | ||
− | local | + | local mech |
− | + | if tpl_args['VariantName'] ~= nil and tpl_args['VariantName'] ~= '' then | |
− | + | mech = core.mech.byVariant(tpl_args['VariantName']) | |
+ | elseif tpl_args['ChassisID'] ~= nil and tpl_args['ChassisID'] ~= '' then | ||
+ | mech = core.mech.byChassisID(tpl_args['ChassisID']) | ||
+ | end | ||
+ | |||
+ | if mech == nil then | ||
+ | local args = { | ||
+ | variantname = tpl_args['VariantName'], | ||
+ | mechtype = 'Unknown Mech', | ||
+ | weight = '???', | ||
+ | class = '???', | ||
+ | } | ||
+ | args['stock role'] = 'Mech not found' | ||
+ | return frame:expandTemplate{ | ||
+ | title = 'InfoboxVariant', | ||
+ | args = args | ||
+ | } | ||
+ | end | ||
local structure = 0 | local structure = 0 | ||
+ | local armor = 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 = { | local hardpoint_counts = { | ||
Energy = 0, | Energy = 0, | ||
Ballistic = 0, | Ballistic = 0, | ||
Missile = 0, | Missile = 0, | ||
+ | Artillery = 0, | ||
AntiPersonnel = 0, | AntiPersonnel = 0, | ||
+ | BombBay = 0, | ||
+ | MeleeWeapon = 0, | ||
} | } | ||
− | local | + | local omniHardpoints = 0 |
− | for _, location in | + | -- mechtype is 'Standard BattleMech', unless we find a non-zero number of |
− | structure = structure + location | + | -- Omni hardpoints. then, we'll change it to be 'OmniMech'. |
− | for _, hardpoint in ipairs( | + | local mechtype = 'BattleMech' |
+ | |||
+ | for _, location in pairs(mech:getLocations()) 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 + location.internalStructure | ||
+ | armor = armor + location.assignedArmor | ||
+ | if location.assignedRearArmor ~= -1 then | ||
+ | armor = armor + location.assignedRearArmor | ||
+ | end | ||
+ | |||
+ | -- 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(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 hardpoint_counts[hardpoint] ~= nil then | ||
+ | -- if it is, then we increment that entry's counter. | ||
hardpoint_counts[hardpoint] = hardpoint_counts[hardpoint] + 1 | hardpoint_counts[hardpoint] = hardpoint_counts[hardpoint] + 1 | ||
end | end | ||
end | end | ||
− | + | omniHardpoints = omniHardpoints + #location.omniHardpoints | |
− | + | end | |
+ | |||
+ | local damage = 0 | ||
+ | local stab = 0 | ||
+ | local heat_damage = 0 | ||
+ | local heat_gen = 0 | ||
+ | local melee_weapon = 'None' | ||
+ | for _, weapon in ipairs(mech:getWeapons()) do | ||
+ | if weapon.damage ~= nil then | ||
+ | damage = damage + (weapon.damage * weapon.shots) | ||
+ | stab = stab + (weapon.instability * weapon.shots) | ||
+ | heat_damage = heat_damage + (weapon.heatDamage * weapon.shots) | ||
+ | heat_gen = heat_gen + weapon.heat | ||
+ | end | ||
+ | -- this is so stupid - Amid | ||
+ | if weapon.minRange == 0 and weapon.type == 'MachineGun' and weapon.category ~= 'AntiPersonnel' then | ||
+ | melee_weapon = weapon.name | ||
end | end | ||
end | end | ||
− | local | + | local engine = mech:getEngine() |
− | local | + | local heatsinks = mech:getHeatsinks() |
− | + | ||
− | + | local dissipation = engine.heat_sinking | |
− | + | for _, heatsink in ipairs(heatsinks) do | |
− | + | dissipation = dissipation + heatsink.dissipation | |
− | + | end | |
− | + | ||
+ | -- 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 = mech.variantName, | ||
+ | image = mech.name .. '.png', | ||
+ | mechtype = mechtype, | ||
+ | weight = '' .. mech.tonnage .. 'T', | ||
+ | class = mech.weightClass, | ||
+ | armor = armor, | ||
+ | structure = structure, | ||
+ | maxdamage = damage, | ||
+ | maxstability = stab, | ||
+ | maxheat = heat_damage, | ||
+ | alphaheat = heat_gen, | ||
+ | heatsinking = dissipation, | ||
+ | melee_weapon = melee_weapon | ||
} | } | ||
+ | if engine ~= nil then | ||
+ | if engine.fixed then | ||
+ | template_args.coresize = engine.core .. ' (Fixed)' | ||
+ | else | ||
+ | template_args.coresize = engine.core | ||
+ | end | ||
+ | |||
+ | if engine.shield.fixed then | ||
+ | template_args.enginetype = engine.shield.name .. ' (Fixed)' | ||
+ | else | ||
+ | template_args.enginetype = engine.shield.name | ||
+ | end | ||
+ | |||
+ | if engine.ecooling > 0 then | ||
+ | if engine.ecooling_fixed then | ||
+ | template_args.ecooling = string.format('+%d (Fixed)', engine.ecooling) | ||
+ | else | ||
+ | template_args.ecooling = string.format('+%d', engine.ecooling) | ||
+ | end | ||
+ | else | ||
+ | template_args.ecooling = 'None' | ||
+ | end | ||
− | local | + | if engine.cooling_fixed then |
− | + | template_args.heatsinkkit = engine.heatsinkkit .. ' (Fixed)' | |
− | ) | + | else |
+ | template_args.heatsinkkit = engine.heatsinkkit | ||
+ | end | ||
+ | end | ||
+ | |||
+ | if mech.gyro ~= nil then | ||
+ | if mech.gyro.fixed then | ||
+ | template_args.gyro = mech.gyro.name .. ' (Fixed)' | ||
+ | else | ||
+ | template_args.gyro = mech.gyro.name | ||
+ | end | ||
+ | if string.find(mech.gyro.name, "Omni") then | ||
+ | template_args.mechtype = 'OmniMech' | ||
+ | end | ||
+ | end | ||
+ | if mech.armor ~= nil then | ||
+ | if mech.armor.fixed then | ||
+ | template_args.armortype = mech.armor.name | ||
+ | else | ||
+ | template_args.armortype = mech.armor.name | ||
+ | end | ||
+ | end | ||
+ | if mech.structure ~= nil then | ||
+ | template_args.structuretype = mech.structure.name | ||
+ | end | ||
+ | |||
+ | for locationName, location in pairs(mech.locations) do | ||
+ | local locationstring = "" | ||
+ | if locationName == 'Head' then | ||
+ | locationstring = 'headequip' | ||
+ | elseif locationName == 'CenterTorso' then | ||
+ | locationstring = 'ctequip' | ||
+ | elseif locationName == 'LeftTorso' then | ||
+ | locationstring = 'ltequip' | ||
+ | elseif locationName == 'RightTorso' then | ||
+ | locationstring = 'rtequip' | ||
+ | elseif locationName == 'LeftArm' then | ||
+ | locationstring = 'laequip' | ||
+ | elseif locationName == 'RightArm' then | ||
+ | locationstring = 'raequip' | ||
+ | elseif locationName == 'LeftLeg' then | ||
+ | locationstring = 'llequip' | ||
+ | else | ||
+ | locationstring = 'rlequip' | ||
+ | end | ||
+ | |||
+ | local gearSlot = 1 | ||
+ | for i, gear in ipairs(location.gear) do | ||
+ | -- skip gear marked nodisplay, which includes stuff like actuators and | ||
+ | -- gyros. | ||
+ | if not gear.nodisplay then | ||
+ | if gear.fixed == '1' or gear.fixed then | ||
+ | template_args[locationstring .. gearSlot] = gear.uiname .. ' (Fixed)' | ||
+ | else | ||
+ | template_args[locationstring .. gearSlot] = gear.uiname | ||
+ | end | ||
+ | gearSlot = gearSlot + 1 | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | |||
+ | template_args.ballistic = hardpoint_counts.Ballistic | ||
+ | template_args.energy = hardpoint_counts.Energy | ||
+ | template_args.missile = hardpoint_counts.Missile | ||
+ | template_args.artillery = hardpoint_counts.Artillery | ||
+ | template_args.support = hardpoint_counts.AntiPersonnel | ||
+ | template_args.omni = omniHardpoints | ||
+ | template_args.bomb = hardpoint_counts.BombBay | ||
+ | template_args.melee = hardpoint_counts.MeleeWeapon | ||
+ | |||
+ | -- walk MP, according to the mech bay, is some weird number. This math is | ||
+ | -- 110% made up out of nowhere, and only mostly correct, probably. For some | ||
+ | -- mechs, it'll need manual override. | ||
+ | -- | ||
+ | -- local walkMeters = (mech.engine.core / mech.tonnage) * 30 | ||
+ | -- local runMeters = walkMeters * 1.5 | ||
+ | |||
+ | -- local walkMP = math.floor(walkMeters / 28) | ||
+ | -- local runMP = math.floor(runMeters / 28) | ||
+ | |||
+ | local walkMP = math.ceil(mech.engine.core / mech.tonnage) | ||
+ | local runMP = math.ceil(walkMP * 1.5) | ||
+ | local jumpMP = 0 | ||
+ | for _, j in ipairs(mech.jumpjets) do | ||
+ | jumpMP = jumpMP + j.jump | ||
+ | end | ||
+ | |||
+ | if jumpMP >= 0 then | ||
+ | template_args.speed = string.format('%d/%d/%d', walkMP, runMP, jumpMP) | ||
+ | end | ||
− | + | template_args['stock role'] = mech.stockRole | |
− | + | template_args['kickdamage'] = mech.tonnage | |
− | + | template_args['punchdamage'] = math.ceil(mech.tonnage / 2) | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | -- finally, lastly, the user can put in raw template data and override our | |
− | + | -- calculations. so, before we return anything, go through tpl_args and | |
− | + | -- replace any outgoing template args | |
− | + | for arg, val in pairs(tpl_args) do | |
− | + | -- skip the VariantName arg, though. That one belongs to us. | |
− | + | if arg ~= 'VariantName' then | |
− | + | template_args[arg] = val | |
− | + | end | |
− | + | end | |
− | |||
return frame:expandTemplate{ | return frame:expandTemplate{ | ||
− | title = ' | + | title = 'TableLine', |
args = template_args | args = template_args | ||
} | } |
Latest revision as of 23:32, 3 October 2024
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 = {} local core = {} p.core = core -- imports local cargo = mw.ext.cargo local getArgs = require('Module:Arguments').getArgs local gear = require('Module:Gear').core 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', type = 'String' }, startingTonnage = { field = 'StartingTonnage', type = 'Float', }, chassisTags = { field = 'ChassisTags', type = 'List (,) of String', }, } } -- 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', }, -- count is a way to ensure that otherwise duplicate equipment in the same -- spot gets a separate row in the database. count = { field = 'Count', type = 'Integer', }, }, } -- 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 field.type == 'Boolean' then if arg == 'true' or arg == '1' or arg == 'yes' then arg = '1' else arg ='0' end end cargo_data[field.field] = arg end end return frame:callParserFunction('#cargo_store:', cargo_data) end -- -- Classes -- core.mech defines the lua-centric mech object core.mech = {} function core.mech.byVariant(variantName) local where = string.format( '%s.%s="%s"', p.cargo.chassis.table, 'VariantName', variantName ) return core.mech:new(where) end function core.mech.byChassisID(chassisID) local where = string.format( '%s.%s="%s"', p.cargo.chassis.table, 'Id', chassisID ) return core.mech:new(where) end function core.mech:new(where) local mech = {} setmetatable(mech, self) self.__index = self local chassisfields = { 'Id', 'Name', 'UIName', 'VariantName', 'Details', 'StockRole', 'weightClass', 'Tonnage', 'StartingTonnage', 'LeftArmActuatorLimit', 'RightArmActuatorLimit', } local chassisRow = cargo.query( p.cargo.chassis.table, table.concat(chassisfields, ','), { where = where } ) chassisRow = chassisRow[1] if chassisRow == nil then return nil end mech.chassisID = chassisRow['Id'] mech.uiName = chassisRow['UIName'] mech.name = chassisRow['Name'] mech.variantName = chassisRow['VariantName'] mech.details = chassisRow['Details'] mech.weightClass = chassisRow['weightClass'] mech.stockRole = chassisRow['StockRole'] mech.leftArmActuatorLimit = chassisRow['LeftArmActuatorLimit'] mech.rightArmActuatorLimit = chassisRow['RightArmActuatorLimit'] mech.tonnage = tonumber(chassisRow['Tonnage'], 10) mech.startingTonnage = tonumber(chassisRow['StartingTonnage'], 10) local mechfields = { 'MechTags', 'Id', } local mechwhere = string.format( '%s.%s="%s"', p.cargo.mech.table, 'ChassisID', mech.chassisID ) local mechRow = cargo.query( p.cargo.mech.table, table.concat(mechfields, ','), { where = mechwhere } ) mechRow = mechRow[1] mech.mechID = mechRow['Id'] mech.tags = mw.text.split(mechRow['MechTags'], ',') return mech end local standard_actuators = { } function core.mech:getLocations() if self.locations ~= nil then return self.locations end where = string.format( '%s.%s="%s"', p.cargo.chassis_locations.table, 'ChassisID', self.chassisID ) fields = { 'Location', 'InventorySlots', 'MaxArmor', 'MaxRearArmor', 'InternalStructure', 'Hardpoints', 'OmniHardpoints', } local chassisLocations = cargo.query( p.cargo.chassis_locations.table, table.concat(fields, ','), { where = where } ) self.locations = {} for _, row in ipairs(chassisLocations) do local location = { tonnage = tonumber(row['Tonnage'], 10), location = row['Location'], maxArmor = tonumber(row['MaxArmor'], 10), maxRearArmor = tonumber(row['MaxRearArmor'], 10), internalStructure = tonumber(row['InternalStructure'], 10), hardpoints = mw.text.split(row['Hardpoints'], ','), gear = {}, } if row['OmniHardpoints'] == '' then location.omniHardpoints = {} else location.omniHardpoints = mw.text.split(row['OmniHardpoints'], ',') end self.locations[row['Location']] = location end local mechwhere = string.format( '%s.%s="%s"', p.cargo.mech_locations.table, 'MechID', self.mechID ) local mechfields = { 'Location', 'AssignedArmor', 'AssignedRearArmor', } local mechlocations = cargo.query( p.cargo.mech_locations.table, table.concat(mechfields, ','), { where = mechwhere } ) for _, row in ipairs(mechlocations) do self.locations[row['Location']].assignedArmor = tonumber(row['AssignedArmor'], 10) self.locations[row['Location']].assignedRearArmor = tonumber(row['AssignedRearArmor'], 10) end local mechGear = gear.get_gear(self.chassisID, self.mechID) for _, gear in ipairs(mechGear) do -- do not include in the gear list fixed-location gear like structure, -- gyro, or armor local exempt = false -- don't display standard actuators, which have IDs starting with emod -- all other actuators, display if string.match(gear.id, 'emod_arm_part') or string.match(gear.id, 'emod_leg') or string.match(gear.id, 'Gear_Cockpit_Generic_Standard') then gear.nodisplay = true elseif string.match(gear.id, 'emod_arm_part_lower') or string.match(gear.id, 'emod_arm_part_hand') then gear.nodisplay = false end for _, category in ipairs(gear.categories) do if category == 'EnginePart' then gear.nodisplay = true elseif category == 'Gyro' then self.gyro = gear exempt = true elseif category == 'Armor' then self.armor = gear exempt = true elseif category == 'Structure' then self.structure = gear exempt = true end if category == 'Quirk' then self.quirk = gear end end if not exempt then table.insert(self.locations[gear.location].gear, gear) end end self.jumpjets = gear.get_jumpjets(self.chassisID, self.mechID) return self.locations end function core.mech:getEngine() if self.engine ~= nil then return self.engine end self.engine = gear.get_engine(self.chassisID, self.mechID) return self.engine end function core.mech:getWeapons() if self.weapons ~= nil then return self.weapons end self.weapons = gear.get_weapons(self.chassisID, self.mechID) return self.weapons end function core.mech:getHeatsinks() if self.heatsinks ~= nil then return self.heatsinks end self.heatsinks = gear.get_heatsinks(self.chassisID, self.mechID) return self.heatsinks end -- -- Templates -- -- raw_table generates a raw table consisting of the data function raw_table(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 = raw_table(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 = raw_table(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(raw_table(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(raw_table(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(raw_table(p.cargo.mech_inventory, tpl_args)) end -- mechInfoBox creates a mech infobox. function p.mechInfoBox (frame) tpl_args = getArgs(frame, { parentFirst = true }) local mech if tpl_args['VariantName'] ~= nil and tpl_args['VariantName'] ~= '' then mech = core.mech.byVariant(tpl_args['VariantName']) elseif tpl_args['ChassisID'] ~= nil and tpl_args['ChassisID'] ~= '' then mech = core.mech.byChassisID(tpl_args['ChassisID']) end if mech == nil then local args = { variantname = tpl_args['VariantName'], mechtype = 'Unknown Mech', weight = '???', class = '???', } args['stock role'] = 'Mech not found' return frame:expandTemplate{ title = 'InfoboxVariant', args = args } end local structure = 0 local armor = 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, Artillery = 0, AntiPersonnel = 0, BombBay = 0, MeleeWeapon = 0, } local omniHardpoints = 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 = 'BattleMech' for _, location in pairs(mech:getLocations()) 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 + location.internalStructure armor = armor + location.assignedArmor if location.assignedRearArmor ~= -1 then armor = armor + location.assignedRearArmor end -- 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(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 omniHardpoints = omniHardpoints + #location.omniHardpoints end local damage = 0 local stab = 0 local heat_damage = 0 local heat_gen = 0 for _, weapon in ipairs(mech:getWeapons()) do if weapon.damage ~= nil then damage = damage + (weapon.damage * weapon.shots) stab = stab + (weapon.instability * weapon.shots) heat_damage = heat_damage + (weapon.heatDamage * weapon.shots) heat_gen = heat_gen + weapon.heat end end local engine = mech:getEngine() local heatsinks = mech:getHeatsinks() local dissipation = engine.heat_sinking for _, heatsink in ipairs(heatsinks) do dissipation = dissipation + heatsink.dissipation end -- 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 = mech.variantName, image = mech.name .. '.png', mechtype = mechtype, weight = '' .. mech.tonnage .. 'T', class = mech.weightClass, armor = armor, structure = structure, maxdamage = damage, maxstability = stab, maxheat = heat_damage, alphaheat = heat_gen, heatsinking = dissipation, } if engine ~= nil then if engine.fixed then template_args.coresize = engine.core .. ' (Fixed)' else template_args.coresize = engine.core end if engine.shield.fixed then template_args.enginetype = engine.shield.name .. ' (Fixed)' else template_args.enginetype = engine.shield.name end if engine.ecooling > 0 then if engine.ecooling_fixed then template_args.ecooling = string.format('+%d (Fixed)', engine.ecooling) else template_args.ecooling = string.format('+%d', engine.ecooling) end else template_args.ecooling = 'None' end if engine.cooling_fixed then template_args.heatsinkkit = engine.heatsinkkit .. ' (Fixed)' else template_args.heatsinkkit = engine.heatsinkkit end end if mech.gyro ~= nil then if mech.gyro.fixed then template_args.gyro = mech.gyro.name .. ' (Fixed)' else template_args.gyro = mech.gyro.name end if string.find(mech.gyro.name, "Omni") then template_args.mechtype = 'OmniMech' end end if mech.armor ~= nil then if mech.armor.fixed then template_args.armortype = mech.armor.name .. ' (Fixed)' else template_args.armortype = mech.armor.name end end if mech.structure ~= nil then template_args.structuretype = mech.structure.name .. ' (Fixed)' end for locationName, location in pairs(mech.locations) do local locationstring = "" if locationName == 'Head' then locationstring = 'headequip' elseif locationName == 'CenterTorso' then locationstring = 'ctequip' elseif locationName == 'LeftTorso' then locationstring = 'ltequip' elseif locationName == 'RightTorso' then locationstring = 'rtequip' elseif locationName == 'LeftArm' then locationstring = 'laequip' elseif locationName == 'RightArm' then locationstring = 'raequip' elseif locationName == 'LeftLeg' then locationstring = 'llequip' else locationstring = 'rlequip' end local gearSlot = 1 for i, gear in ipairs(location.gear) do -- skip gear marked nodisplay, which includes stuff like actuators and -- gyros. if not gear.nodisplay then if gear.fixed == '1' or gear.fixed then template_args[locationstring .. gearSlot] = gear.uiname .. ' (Fixed)' else template_args[locationstring .. gearSlot] = gear.uiname end gearSlot = gearSlot + 1 end end end if hardpoint_counts.Ballistic > 0 then template_args.ballistic = hardpoint_counts.Ballistic end if hardpoint_counts.Energy > 0 then template_args.energy = hardpoint_counts.Energy end if hardpoint_counts.Missile > 0 then template_args.missile = hardpoint_counts.Missile end if hardpoint_counts.Artillery > 0 then template_args.artillery = hardpoint_counts.Artillery end if hardpoint_counts.AntiPersonnel > 0 then template_args.support = hardpoint_counts.AntiPersonnel end if omniHardpoints > 0 then template_args.omni = omniHardpoints end if hardpoint_counts.BombBay > 0 then template_args.bomb = hardpoint_counts.BombBay end if hardpoint_counts.MeleeWeapon > 0 then template_args.melee = hardpoint_counts.MeleeWeapon end -- walk MP, according to the mech bay, is some weird number. This math is -- 110% made up out of nowhere, and only mostly correct, probably. For some -- mechs, it'll need manual override. -- -- local walkMeters = (mech.engine.core / mech.tonnage) * 30 -- local runMeters = walkMeters * 1.5 -- local walkMP = math.floor(walkMeters / 28) -- local runMP = math.floor(runMeters / 28) local walkMP = math.ceil(mech.engine.core / mech.tonnage) local runMP = math.ceil(walkMP * 1.5) local jumpMP = 0 for _, j in ipairs(mech.jumpjets) do jumpMP = jumpMP + j.jump end if jumpMP >= 0 then template_args.speed = string.format('%d/%d/%d', walkMP, runMP, jumpMP) end template_args['stock role'] = mech.stockRole template_args['kickdamage'] = mech.tonnage template_args['punchdamage'] = math.ceil(mech.tonnage / 2) -- finally, lastly, the user can put in raw template data and override our -- calculations. so, before we return anything, go through tpl_args and -- replace any outgoing template args for arg, val in pairs(tpl_args) do -- skip the VariantName arg, though. That one belongs to us. if arg ~= 'VariantName' then template_args[arg] = val end end return frame:expandTemplate{ title = 'InfoboxVariant', args = template_args } end -- mechTableLine creates an entry in the Full List of Mechs. function p.mechTableLine (frame) tpl_args = getArgs(frame, { parentFirst = true }) local mech if tpl_args['VariantName'] ~= nil and tpl_args['VariantName'] ~= '' then mech = core.mech.byVariant(tpl_args['VariantName']) elseif tpl_args['ChassisID'] ~= nil and tpl_args['ChassisID'] ~= '' then mech = core.mech.byChassisID(tpl_args['ChassisID']) end if mech == nil then local args = { variantname = tpl_args['VariantName'], mechtype = 'Unknown Mech', weight = '???', class = '???', } args['stock role'] = 'Mech not found' return frame:expandTemplate{ title = 'InfoboxVariant', args = args } end local structure = 0 local armor = 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, Artillery = 0, AntiPersonnel = 0, BombBay = 0, MeleeWeapon = 0, } local omniHardpoints = 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 = 'BattleMech' for _, location in pairs(mech:getLocations()) 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 + location.internalStructure armor = armor + location.assignedArmor if location.assignedRearArmor ~= -1 then armor = armor + location.assignedRearArmor end -- 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(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 omniHardpoints = omniHardpoints + #location.omniHardpoints end local damage = 0 local stab = 0 local heat_damage = 0 local heat_gen = 0 local melee_weapon = 'None' for _, weapon in ipairs(mech:getWeapons()) do if weapon.damage ~= nil then damage = damage + (weapon.damage * weapon.shots) stab = stab + (weapon.instability * weapon.shots) heat_damage = heat_damage + (weapon.heatDamage * weapon.shots) heat_gen = heat_gen + weapon.heat end -- this is so stupid - Amid if weapon.minRange == 0 and weapon.type == 'MachineGun' and weapon.category ~= 'AntiPersonnel' then melee_weapon = weapon.name end end local engine = mech:getEngine() local heatsinks = mech:getHeatsinks() local dissipation = engine.heat_sinking for _, heatsink in ipairs(heatsinks) do dissipation = dissipation + heatsink.dissipation end -- 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 = mech.variantName, image = mech.name .. '.png', mechtype = mechtype, weight = '' .. mech.tonnage .. 'T', class = mech.weightClass, armor = armor, structure = structure, maxdamage = damage, maxstability = stab, maxheat = heat_damage, alphaheat = heat_gen, heatsinking = dissipation, melee_weapon = melee_weapon } if engine ~= nil then if engine.fixed then template_args.coresize = engine.core .. ' (Fixed)' else template_args.coresize = engine.core end if engine.shield.fixed then template_args.enginetype = engine.shield.name .. ' (Fixed)' else template_args.enginetype = engine.shield.name end if engine.ecooling > 0 then if engine.ecooling_fixed then template_args.ecooling = string.format('+%d (Fixed)', engine.ecooling) else template_args.ecooling = string.format('+%d', engine.ecooling) end else template_args.ecooling = 'None' end if engine.cooling_fixed then template_args.heatsinkkit = engine.heatsinkkit .. ' (Fixed)' else template_args.heatsinkkit = engine.heatsinkkit end end if mech.gyro ~= nil then if mech.gyro.fixed then template_args.gyro = mech.gyro.name .. ' (Fixed)' else template_args.gyro = mech.gyro.name end if string.find(mech.gyro.name, "Omni") then template_args.mechtype = 'OmniMech' end end if mech.armor ~= nil then if mech.armor.fixed then template_args.armortype = mech.armor.name else template_args.armortype = mech.armor.name end end if mech.structure ~= nil then template_args.structuretype = mech.structure.name end for locationName, location in pairs(mech.locations) do local locationstring = "" if locationName == 'Head' then locationstring = 'headequip' elseif locationName == 'CenterTorso' then locationstring = 'ctequip' elseif locationName == 'LeftTorso' then locationstring = 'ltequip' elseif locationName == 'RightTorso' then locationstring = 'rtequip' elseif locationName == 'LeftArm' then locationstring = 'laequip' elseif locationName == 'RightArm' then locationstring = 'raequip' elseif locationName == 'LeftLeg' then locationstring = 'llequip' else locationstring = 'rlequip' end local gearSlot = 1 for i, gear in ipairs(location.gear) do -- skip gear marked nodisplay, which includes stuff like actuators and -- gyros. if not gear.nodisplay then if gear.fixed == '1' or gear.fixed then template_args[locationstring .. gearSlot] = gear.uiname .. ' (Fixed)' else template_args[locationstring .. gearSlot] = gear.uiname end gearSlot = gearSlot + 1 end end end template_args.ballistic = hardpoint_counts.Ballistic template_args.energy = hardpoint_counts.Energy template_args.missile = hardpoint_counts.Missile template_args.artillery = hardpoint_counts.Artillery template_args.support = hardpoint_counts.AntiPersonnel template_args.omni = omniHardpoints template_args.bomb = hardpoint_counts.BombBay template_args.melee = hardpoint_counts.MeleeWeapon -- walk MP, according to the mech bay, is some weird number. This math is -- 110% made up out of nowhere, and only mostly correct, probably. For some -- mechs, it'll need manual override. -- -- local walkMeters = (mech.engine.core / mech.tonnage) * 30 -- local runMeters = walkMeters * 1.5 -- local walkMP = math.floor(walkMeters / 28) -- local runMP = math.floor(runMeters / 28) local walkMP = math.ceil(mech.engine.core / mech.tonnage) local runMP = math.ceil(walkMP * 1.5) local jumpMP = 0 for _, j in ipairs(mech.jumpjets) do jumpMP = jumpMP + j.jump end if jumpMP >= 0 then template_args.speed = string.format('%d/%d/%d', walkMP, runMP, jumpMP) end template_args['stock role'] = mech.stockRole template_args['kickdamage'] = mech.tonnage template_args['punchdamage'] = math.ceil(mech.tonnage / 2) -- finally, lastly, the user can put in raw template data and override our -- calculations. so, before we return anything, go through tpl_args and -- replace any outgoing template args for arg, val in pairs(tpl_args) do -- skip the VariantName arg, though. That one belongs to us. if arg ~= 'VariantName' then template_args[arg] = val end end return frame:expandTemplate{ title = 'TableLine', args = template_args } end return p