Difference between revisions of "Module:Gear"

From BTAWiki
Jump to navigation Jump to search
Line 456: Line 456:
 
     { where = string.format('%s="%s"', 'Gear.Id', engine.cooling) }
 
     { where = string.format('%s="%s"', 'Gear.Id', engine.cooling) }
 
   )
 
   )
   local dissipation = tonumber(heatsinks[1]['Heatsinks.DissipationCapacity'], 10)
+
   local dissipation = tonumber(heatsinks[1]['Heatsinks.Dissipation'], 10)
  
 
   -- engines have internal heat sinks, which don't take up any space. smaller
 
   -- engines have internal heat sinks, which don't take up any space. smaller

Revision as of 06:54, 18 January 2021

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
-- 
core.schema = {}

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',
    },{
      field = 'AllowedLocations',
      type = 'List (,) of String',
    },{
      field = 'DisallowedLocations',
      type = 'List (,) of String',
    },{
      field = 'BattleValue',
      type = 'Integer',
    },{
      field = 'Bonuses',
      type = 'List (,) of String',
    },
  },
}

core.schema.heatsink = {
  table = 'Heatsinks',
  parent_table = core.schema.gear,
  fields = {
    {
      field = 'DissipationCapacity',
      type = 'Integer',
    },{
      field = 'EngineSlot',
      type = 'Boolean',
    },{
      field = 'EnginePart',
      type = 'Boolean',
    },
  },
}

core.schema.cooling = {
  table = 'Cooling',
  parent_table = core.schema.heatsink,
  fields = {
    {
      field = 'HeatsinkDefID',
      type = 'String',
    },
  },
}

core.schema.engineheatblock = {
  table = 'EngineHeatBlocks',
  parent_table = core.schema.heatsink,
  fields = {
    {
      field = 'HeatsinkCount',
      type = 'Integer',
    },
  },
}

core.schema.engineshield = {
  table = 'EngineShields',
  parent_table = core.schema.heatsink,
  fields = {
    {
      field = 'ReservedSlots',
      type = 'Integer',
    },{
      field = 'EngineFactor',
      type = 'Float',
    },
  },
}

core.schema.enginecore = {
  parent_table = core.schema.heatsink,
  table = 'EngineCore',
  fields = {
    {
      field = 'Rating',
      type = 'Integer',
    },
  },
}

core.schema.upgrade = {
  table = 'UpgradeDef',
  parent_table = core.schema.gear,
  fields = {},
}

core.schema.jumpjet = {
  table = 'JumpJets',
  parent_table = core.schema.gear,
  fields = {
    {
      field = 'MinTonnage',
      type = 'Integer',
    },{
      field = 'MaxTonnage',
      type = 'Integer',
    },{
      field = 'JumpCapcity',
      type = 'Float',
    },
  },
}

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',
    },
  },
}

core.schema.ammunition = {
  table = 'Ammunition',
  parent_table = core.schema.gear,
  fields = {
    {
      field = 'Capacity',
      type = 'Integer',
    },{
      field = 'Category',
      type = 'String',
    },
  }
}

--
-- 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)
  frame:expandTemplate{title=string.format('Template:Gear/cargo/attach/%s', schema.table), args={}}

  local data = {}
  data._table = schema.table

  for _, field in ipairs(schema.fields) do
    local arg = tpl_args[field.field]
    if arg ~= nil then
      data[field.field] = arg
    end
  end

  if schema.parent_table ~= nil then
    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.
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

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

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

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
-- 
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

function core.get_gear(chassisID, mechID, schema)
  local all_fields = {}
  for _, field in ipairs(core.schema.gear.fields) do
    table.insert(all_fields, core.schema.gear.table .. '.' .. field.field)
  end

  local args = {
    join = 'Gear.Id=MechInventory.ComponentDefID',
    where = string.format(
      'MechInventory.MechID="%s" OR MechInventory.MechID="%s"',
      chassisID, mechID
    )
  }

  local gear = core.cargo.query(core.schema.gear, all_fields, args)
end

-- core.get_engine returns an object representing details about the given
-- mech.
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', 
    { where = where, join = join, extratables = extratables }
  )
  engine.shield = {
    name = shieldRow[1]['Gear.Name'],
    id = shieldRow[1]['Gear.Id'],
  }

  local coreRow = core.cargo.query(
    core.schema.enginecore, 'EngineCore.Rating,Gear.Tonnage',
    { where = where, join = join, extratables = extratables }
  )
  engine.core = tonumber(coreRow[1]['EngineCore.Rating'], 10)
  engine.tonnage = tonumber(coreRow[1]['Gear.Tonnage'], 10)

  local ecoolingRow = core.cargo.query(
    core.schema.engineheatblock, 'EngineHeatBlocks.HeatsinkCount',
    { where = where, join = join, extratables = extratables }
  )

  engine.ecooling = tonumber(ecoolingRow[1]['EngineHeatBlocks.HeatsinkCount'], 10)

  local coolingRow = core.cargo.query(
    core.schema.cooling, 'Cooling.HeatsinkDefID',
    { where = where, join = join, extratables = extratables }
  )

  engine.cooling = coolingRow[1]['HeatsinkDefID']

  local heatsinks = core.cargo.query(
    core.schema.heatsink, 'Heatsinks.DissipationCapacity',
    { 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

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

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',
  }

  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),
      heatDamage = tonumber(weaponRow['Weapons.HeatDamage'], 10),
      instability = tonumber(weaponRow['Weapons.Instability'], 10),
    }
    
    table.insert(weapons, weapon)
  end

  return weapons
end

--
-- Templates
--
-- these functions define templates.
--

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)

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