• Welcome to the Kancolle Wiki!
  • If you have any questions regarding site content, account registration, etc., please visit the KanColle Wiki Discord

Module:Calc

From Kancolle Wiki
Revision as of 17:04, 9 November 2017 by がか (talk | contribs)
Jump to navigation Jump to search

Documentation for this module may be created at Module:Calc/doc

local Utils = require("Module:Utils")
local format = require("Module:StringOperations").format
local Formatting = require("Module:Formatting")
local Equipment = require("Module:Equipment")
local Ship = require("Module:Ship")
local ShipIterator = require("Module:ShipIterator")
local ShipCapabilities = require("Module:ShipCapabilities")
local ShipBattleCardKai = require("Module:ShipBattleCardKai")
local Combat2 = require("Module:Combat2")

local args = nil
local ship = nil
local target = nil
local shipCapabilities = {}
local frame = nil

local debug = {}

function debugLog(s)
    table.insert(debug, s)
end

function debugString()
    return #debug > 0 and args.debug and ([[<pre>]] .. table.concat(debug, "\n") .. [[</pre>]]) or ""
end

local enumerating_functions = {

    args = function()
        return mw.text.split(args.args, "%s*,%s*")
    end,

    base_names = function()
        return ShipIterator.baseForms
    end,

    all_names = function()
        return ShipIterator.allForms
    end,
    
    enemy = function()
        return ShipIterator.enemyForms
    end,

}

-- TODO: have a syntax for calling Module:Formatting functions?
local formatting_functions = {

    night_battle_power = function(ship)
        return (ship:firepower_max() or 0) + (ship:torpedo_max() or 0)
    end,
    
    code = function(ship)
        return Formatting:format_ship_code(ship:type())
    end,

    -- overrides ShipData:type
    type = function(ship)
        return Formatting:format_ship_type(ship:type())
    end,

    -- overrides ShipData:link
    link = function(ship)
        return Formatting:format_link(ship:link())
    end,

    -- overrides ShipData:implementation_date
    implementation_date = function(ship)
        local date = ship:implementation_date()
        return not date and "??" or format{
            "${year}/${month}/${day}",
            year = date[1],
            month = date[2] < 10 and "0" .. date[2] or date[2],
            day = date[3] < 10 and "0" .. date[3] or date[3],
        }
    end,

    -- overrides ShipData:card
    card = function(ship)
        return ShipBattleCardKai:Asset({ ship:name("/") })
    end,
    
    format_morale = function(ship)
        local morale = ship:morale() or 49
        return morale <= 19 and "Red" or morale <= 29 and "Orange" or morale <= 49 and "Normal" or "Sparkle"
    end,

    hit_rate = function(ship, target)
        local r = Combat2.hit_rate(ship, target)
        if r then
            ship.hit_rate = r
            return r .. "%"
        else
            return "nil"
        end
    end,

    critical_hit_rate = function(ship, target)
        local r = Combat2.critical_hit_rate(ship, target)
        if r then
            ship.critical_hit_rate = r
            return r .. "%"
        else
            return "nil"
        end
    end,

}

function addFormattingFunctions(name, table)
    for k, v in pairs(table) do
        formatting_functions[name .. "." .. k] = v
    end
end

local Plugins = require("Module:Calc/Plugins")

for _, name in ipairs(Plugins) do
    local data = require("Module:Calc/Plugins/" .. name)
    addFormattingFunctions(name, data)
end

function format_lua(lua)
    if type(lua) == "table" then
        return tostring(table.concat(lua, args.concat_value or ", "))
    else
        return tostring(lua)
    end
end

function format_value(key, ship, target)
    local formatting_function = formatting_functions[key]
    if formatting_function then
        return formatting_function(ship, target)
    else
        if shipCapabilities[key] then
            local a, b = shipCapabilities[key](shipCapabilities)
            return format_lua(b or a)
        elseif ship then
            local lua = ship[key]
            if type(lua) == "function" then
                return format_lua(lua(ship))
            else
                return format_lua(lua)
            end
        end
    end
end

local sequence = nil
local sequence_position = nil

function format_arg(arg)
    return args.format == "table" and ("|" .. arg .. "\n") or arg
end

function interpret_setter(s)
    local kv = mw.text.split(s, "%s*~%s*")
    local k = kv[1]
    local v = kv[2]
    if k and v then
        if k == "_equipment" then
            local equipment = {}
            local accuracy = 0
            for eq in string.gmatch(v, '([^,]+)') do
                local eqObj = Equipment(eq)
                table.insert(equipment, { equipment = eqObj })
                accuracy = accuracy + (eqObj:shelling_accuracy() or 0)
            end
            ship._equipment = equipment
            ship._accuracy = accuracy
        else
            if v ~= "nil" then
                ship[k] = tonumber(v)
            end
        end
        return true
    else
        return false
    end
end

function interpret_arg(arg)
    local prefix = string.sub(arg, 1, 1)
    local prefix2 = string.sub(arg, 1, 2)
    if arg == "" then
        return
    elseif arg == "-" then
        return format_arg("")
    elseif prefix == "@" then
        local enumerator = string.sub(arg, 2)
        local enumerating_function = enumerating_functions[enumerator]
        if enumerating_function and not sequence then
            sequence = enumerating_function()
            sequence_position = 1
        end
    elseif arg == "!@" then
        local ship_key = sequence[sequence_position]
        sequence_position = sequence_position + 1
        ship = Ship(ship_key)
        shipCapabilities = ShipCapabilities{ ship = ship }
    elseif prefix2 == "!!" then
        local key = string.sub(arg, 3)
        target = Ship(key)
    elseif prefix == "!" then
        local key = string.sub(arg, 2)
        if not interpret_setter(key) then
            local parts = string.gmatch(key, '([^:]+)')
            local i = 1
            for part in parts do
                if i == 1 then
                    key = part
                    ship = Ship(key)
                    shipCapabilities = ShipCapabilities{ ship = ship }
                else
                    interpret_setter(part)
                end
                i = i + 1
            end
        end
    elseif prefix == "#" and args.format == "table" then
        return "|-\n"
    elseif prefix == "?" then
        local prefix2 = string.sub(arg, 1, 2)
        if prefix2 == "??" then
            return format_arg(format_value(string.sub(arg, 3), target, ship))
        elseif prefix2 == "?#" then
            return frame:preprocess(format{ '{{${template}|${ship}}}', template = string.sub(arg, 3), ship = ship:name('/') })
        else
            return format_arg(format_value(string.sub(arg, 2), ship, target))
        end
    else
        return format_arg(arg)
    end
end

local special_functions = {
    apply = function(args_)
        local frame_ = frame:getParent()
        local template = args_[2]
        local values = {}
        for i = 3, #args_ do
            table.insert(values, format{
                '{{${template}|${arg}}}',
                template = template,
                arg = args_[i]
            })
        end
        return frame_:preprocess(table.concat(values, args.concat or ""))
    end
}

function interpret_args(args_)
    args = args_
    local special_function = special_functions[args[1]]
    if special_function then
        return special_function(args)
    else
        local values = {}
        debugLog("#args = " .. #args)
        repeat
            for _, arg in ipairs(args_) do
                debugLog("arg = " .. (arg or ""))
                local value = interpret_arg(arg)
                if value then
                    debugLog("value = " .. (value or ""))
                    table.insert(values, value)
                end
            end
        until not sequence or sequence_position > #sequence
        return table.concat(values, args.concat or "") .. debugString()
    end
end

local Calc = {}

function Calc.format(frame_, args_)
    frame = frame_
    return interpret_args(args_ or Utils.getTemplateArgs(frame_).explicit)
end

function Calc.test()
    mw.log(
        Calc.format(nil, {
            format = "table",
            "!Nagato/Kai Ni",
            "!_equipment ~ 41cm Triple Gun Mount Kai, 41cm Triple Gun Mount Kai, Type 13 Air Radar Kai",
            "!_level ~ 99",
            "!_luck ~ nil",
            "!_morale ~ 25",
            "!!Destroyer Ro-Class",
            "!cl0 ~ 56",
            "!cl1 ~ 807",
            "!cl2 ~ 137",
            "?card",
            "?level",
            "?format_morale",
            "?accuracy",
            "?luck",
            "??card",
            "?cl0",
            "?cl1",
            "?cl2",
            "",
            "-",
            "?FitData.hit",
            "?FitData.error",
            "?FitData.crit",
            "?hit_rate",
            "?critical_hit_rate",
            "?FitData.difference",
            "?FitData.critical_difference",
            "?FitData.accuracy_value",
            "?FitData.fit",
        })
    )
end

return Calc