Line 1: |
Line 1: |
| + | -- ** |
| + | -- Damage calculations are based on http://kancollecalc.web.fc2.com/damage_formula.html and WikiWiki combat page. |
| + | -- NB CI rate formula is from WikiWiki combat page (also, 検証、仮説スレ18). |
| + | -- |
| | | |
| local Combat = {} | | local Combat = {} |
| | | |
− | -- * Damage calculator. | + | -- * Experimental values. |
− | --
| |
− | -- TBD
| |
− | --
| |
− | -- http://kancollecalc.web.fc2.com/damage_formula.html
| |
− | -- http://kancollecalc.web.fc2.com/damage.js
| |
− | --
| |
− | -- http://wikiwiki.jp/kancolle/?%C0%EF%C6%AE%A4%CB%A4%C4%A4%A4%A4%C6
| |
− | -- http://wikiwiki.jp/kancolle/?%BB%D9%B1%E7%B4%CF%C2%E2
| |
− | --
| |
| | | |
− | local engagement_modifiers = {
| + | Combat.modifier = { |
− | green_t = 1.2,
| |
− | parallel = 1.0,
| |
− | head_on = 0.8,
| |
− | red_t = 0.6
| |
− | }
| |
| | | |
− | local spotting_modifiers = {
| + | comined_firepower = { |
− | cut_in = { | + | ctf_first = 0, |
− | main_second = 1.1, | + | ctf_second = 10, |
− | main_radar = 1.2, | + | stf_first = 10, |
− | main_ap = 1.3, | + | stf_second = -5, |
− | main_main = 1.5, | |
| }, | | }, |
− | double = 1.2
| |
− | }
| |
| | | |
− | local comined_fp = {
| + | comined_torpedo = -5, |
− | ctf_first = 0,
| |
− | ctf_second = 10, | |
− | stf_first = 10,
| |
− | stf_second = -5
| |
− | }
| |
| | | |
− | local comined_torp = -5
| + | night_contact = 5, |
| | | |
− | local formation_modifiers = {
| + | formation = { |
− | line_ahead = 1.0,
| + | line_ahead = 1.0, |
− | double_line = 0.8,
| + | double_line = 0.8, |
− | diamond = 0.7,
| |
− | echelon = 0.6,
| |
− | line_abreast = 0.6,
| |
− | combined = {
| |
− | line_ahead = 1.1,
| |
| diamond = 0.7, | | diamond = 0.7, |
− | double_line = 1.0, | + | echelon = 0.6, |
− | line_abreast = 0.8, | + | line_abreast = 0.6, |
− | } | + | combined = { |
− | } | + | line_ahead = 1.1, |
| + | diamond = 0.7, |
| + | double_line = 1.0, |
| + | line_abreast = 0.8, |
| + | }, |
| + | }, |
| + | |
| + | engagement = { |
| + | green_t = 1.2, |
| + | parallel = 1.0, |
| + | head_on = 0.8, |
| + | red_t = 0.6, |
| + | }, |
| + | |
| + | health = { |
| + | normal = 1.0, |
| + | chuuha = 0.7, |
| + | taiha = 0.4, |
| + | }, |
| | | |
− | local health_modifiers = {
| + | anti_ground = 2.5, |
− | normal = 1.0, | + | wg_bonus = 75, |
− | chuuha = 0.7, | |
− | taiha = 0.4
| |
− | }
| |
| | | |
− | function ammo_modifier(ammo)
| + | cap = { |
− | return ammo >= 5 and 1 or ammo / 5
| + | day = 150, |
− | end
| + | night = 300, |
| + | }, |
| | | |
− | local ap_modifiers = {
| + | spotting = { |
− | main_ap = 1.08,
| + | cut_in = { |
− | main_ap_radar = 1.1,
| + | main_main = 1.5, |
− | main_second_ap = 1.15,
| + | main_ap = 1.3, |
− | main_second_ap_radar = 1.15
| + | main_radar = 1.2, |
− | } | + | main_second = 1.1, |
| + | }, |
| + | double = 1.2, |
| + | }, |
| | | |
− | local critical_modifier = 1.5
| + | night_attack = { |
| + | cut_in = { |
| + | main = 2, |
| + | main_misc = 1.75, |
| + | torpedo = 1.5, |
| + | mixed = 1.3, |
| + | }, |
| + | double = 1.2, |
| + | normal = 1, |
| + | }, |
| | | |
− | local anti_ground_modifier = 2.5
| + | critical = 1.5, |
− | local anti_ground_wg_bonus = 75
| |
| | | |
− | local db_cap = 150
| + | ap = { |
− | local nb_cap = 300
| + | main_ap = 1.08, |
| + | main_ap_radar = 1.1, |
| + | main_second_ap = 1.15, |
| + | main_second_ap_radar = 1.15, |
| + | }, |
| | | |
− | local equip_coeffs = {
| |
− | small_gun = 1,
| |
− | medium_gun = 1,
| |
− | main_gun = 1.5,
| |
− | ap = 1,
| |
− | torpedo = 1.2,
| |
| } | | } |
| | | |
− | function default_modifiers(critical) | + | function Combat.ammo_modifier(ammo_bars) |
− | return { | + | return ammo_bars >= 5 and 1 or ammo_bars / 5 |
− | combined_fp = 0,
| |
− | combined_torp = 0,
| |
− | anti_ground_bonus = 0,
| |
− | engagement = engagement_modifiers.parallel,
| |
− | formation = formation_modifiers.line_ahead,
| |
− | health = health_modifiers.normal,
| |
− | anti_ground = 1,
| |
− | anti_sub = 1,
| |
− | night = 1,
| |
− | cap = db_cap,
| |
− | spotting = 1,
| |
− | contact = 1,
| |
− | expert = 1,
| |
− | critical = critical and critical_modifier or 1,
| |
− | ap = 1,
| |
− | ammo = 1,
| |
− | }
| |
| end | | end |
| | | |
− | function shelling_attack_power(fp, equip_bonus, modifiers)
| + | -- * Combat object with ship-independent values as fields. |
− | return 5 + fp + equip_bonus + modifiers.combined_fp + modifiers.anti_ground_bonus
| + | -- Ship and enemy values are passed via arguments. |
− | end
| |
| | | |
− | function torpedo_attack_power(torp, equip_bonus, modifiers) | + | function Combat:new(is_night_battle) |
− | return 5 + torp + equip_bonus + modifiers.combined_torp | + | local stage = { |
| + | combined_firepower = 0, |
| + | combined_torpedo = 0, |
| + | formation = self.modifier.formation.line_ahead, |
| + | engagement = self.modifier.engagement.parallel, |
| + | night_contact = 0, |
| + | is_night_battle = is_night_battle, |
| + | cap = is_night_battle and self.modifier.cap.night or self.modifier.cap.day, |
| + | contact = 1, -- TODO |
| + | expert = 1, -- TODO |
| + | } |
| + | setmetatable(stage, self) |
| + | self.__index = self |
| + | return stage |
| end | | end |
| | | |
− | function damage_pre_cap(attack_power, modifiers) | + | function Combat:damage_pre_cap(ship) |
− | return | + | return self.engagement * self.formation * ship.health * ship.anti_ground * ship.anti_sub * ship.night_attack * ship.attack_power |
− | modifiers.engagement * modifiers.formation * modifiers.health *
| |
− | modifiers.anti_ground * modifiers.anti_sub *
| |
− | modifiers.night *
| |
− | attack_power
| |
| end | | end |
| | | |
− | function damage_cap(attack_power, modifiers) | + | function Combat:damage_cap(attack_power) |
− | if attack_power > modifiers.cap then | + | if attack_power > self.cap then |
− | attack_power = modifiers.cap + math.sqrt(attack_power - modifiers.cap) | + | attack_power = self.cap + math.sqrt(attack_power - self.cap) |
| end | | end |
| return math.floor(attack_power) | | return math.floor(attack_power) |
| end | | end |
| | | |
− | function damage_post_cap(attack_power, modifiers, armor) | + | function Combat:damage_post_cap(attack_power, ship, enemy_armor) |
− | attack_power = | + | attack_power = ship.spotting * self.contact * math.floor(self.expert * ship.critical * math.floor(ship.ap * attack_power)) |
− | modifiers.spotting * modifiers.contact *
| + | -- Use a note on a caller side instead |
− | math.floor(modifiers.expert * modifiers.critical *
| + | --if ship.night_attack_x2 then |
− | math.floor(modifiers.ap * attack_power))
| + | -- attack_power = 2 * attack_power |
− | if armor then | + | --end |
− | local min_armor = armor * 0.7 | + | if enemy_armor then |
− | local max_armor = armor * 0.7 + math.max(armor - 1, 0) * 0.6 | + | local min_armor = enemy_armor * 0.7 |
− | local min = math.floor((attack_power - max_armor) * modifiers.ammo) | + | local max_armor = enemy_armor * 0.7 + math.max(enemy_armor - 1, 0) * 0.6 |
− | local max = math.floor((attack_power - min_armor) * modifiers.ammo) | + | local min = math.floor((attack_power - max_armor) * ship.ammo) |
| + | local max = math.floor((attack_power - min_armor) * ship.ammo) |
| return { min, max } | | return { min, max } |
| else | | else |
− | return math.floor(attack_power * modifiers.ammo) | + | return math.floor(attack_power * ship.ammo) |
| end | | end |
| end | | end |
| | | |
− | function damage(attack_power, modifiers, armor) | + | -- TODO: pass enemy table, do additional detection (e.g. torpedo = 0 vs. ground type enemies |
− | return damage_post_cap(damage_cap(damage_pre_cap(attack_power, modifiers), modifiers), modifiers, armor) | + | -- or attack_power = 0 when BB vs. enemy submarines, etc.) |
| + | function Combat:damage(ship, enemy_armor) |
| + | return self:damage_post_cap(self:damage_cap(self:damage_pre_cap(ship)), ship, enemy_armor) |
| end | | end |
| | | |
− | -- * CI rate formula. | + | -- TODO: carrier shelling, aerial, ASW, support expedition. |
| | | |
− | local cut_in_types = {
| + | Combat.nb_cut_in_types = { |
− | torpedo = { k = 70, cap = 60 }, | + | torpedo = { k = 70, luck_cap = 60 }, |
− | mixed = { k = 70, cap = 70 }, | + | mixed = { k = 70, luck_cap = 70 }, |
− | main = { k = 50, cap = 55 } | + | main = { k = 50, luck_cap = 55 }, |
| } | | } |
| | | |
− | function cut_in_rate(luck, t) | + | function Combat.nb_cut_in_rate(ship, modifier) |
− | if luck <= t.cap then | + | if ship._luck <= modifier.luck_cap then |
− | return math.floor(math.sqrt(t.k * luck)) | + | return math.floor(math.sqrt(modifier.k * ship._luck)) |
| else | | else |
− | return cut_in_rate(t.cap, t) | + | return Combat.nb_cut_in_rate(modifier.luck_cap, modifier) |
| end | | end |
| end | | end |
| | | |
− | -- * Tables.
| + | function Combat:shelling(ship) |
| + | ship.attack_power = 5 + ship._firepower_max + ship.equipment_bonus.firepower + self.combined_firepower + ship.anti_ground_bonus |
| + | return ship |
| + | end |
| | | |
− | local format = require('Module:StringInterpolation').format
| + | function Combat:torpedo(ship) |
− | local Ship = require('Module:Ship')
| + | ship.attack_power = 5 + ship._torpedo_max + ship.equipment_bonus.torpedo + self.combined_torpedo |
− | local Formatting = require('Module:Formatting')
| + | return ship |
− | local StatIcons = require('Module:StatIcons')
| + | end |
| | | |
− | local templates = {
| + | function Combat:night_battle(ship) |
− | | + | if ship.health == self.modifier.health.taiha then |
− | main = [[{| class="wikitable sortable typography-xl-optout" style="width:100%;"
| + | ship.attack_power = 0 |
− | ! colspan="2" |
| + | else |
− | ! colspan="4" |Day Battle
| + | ship.attack_power = self.night_contact + ship._firepower_max + ship._torpedo_max + ship.equipment_bonus.firepower + ship.equipment_bonus.torpedo |
− | ! colspan="5" |Night Battle
| + | end |
− | |-
| + | return ship |
− | !Rank
| |
− | !Name
| |
− | !${fp}
| |
− | !${torp}
| |
− | !<span title="Damage: normal attack without equipment, ${db_attack_note} with maxed equipment (all other modifiers = 1, see notes)">Hit</span>
| |
− | !<span title="Normal torpedo salvo damage, all modifiers = 1, see notes">Salvo</span>
| |
− | !${fp_plus_torp}
| |
− | !<span title="Maximal damage from double attack (combined)">DA</span>
| |
− | !<span title="Maximal damage from cut-in">CI</span>
| |
− | !${luck_minus_cap}
| |
− | !<span title="Torpedo/mixed cut-in rate, gun cut-in rate (only luck dependent part, no bonuses)">CI%</span>
| |
− | ${rows}|}]],
| |
− | | |
− | carrier = [[{| class="wikitable sortable typography-xl-optout" style="width:100%;"
| |
− | !Rank
| |
− | !Name
| |
− | !${fp}
| |
− | !<span title=""></span>
| |
− | !<span title=""></span>
| |
− | ${rows}|}]],
| |
− | | |
− | main_row = [[|- | |
− | |${rank}
| |
− | |${name}
| |
− | |${fp}
| |
− | |${torp}
| |
− | |${db_attack}
| |
− | |${db_torp}
| |
− | |${fp_plus_torp}
| |
− | |${nb_da}
| |
− | |${nb_ci}
| |
− | |${luck_minus_cap}
| |
− | |${nb_ci_rate}
| |
− | ]],
| |
− | | |
− | carrier_row = [[|-
| |
− | |${rank}
| |
− | |${name}
| |
− | |${fp}
| |
− | |
| |
− | |
| |
− | ]],
| |
− | | |
− | }
| |
− | | |
− | local map_types = {
| |
− | dd = "main", cl = "main", clt = "main", ca = "main", bb = "main",
| |
− | cvl = "carrier", cv = "carrier",
| |
− | }
| |
− | | |
− | function Combat.table(frame)
| |
− | local type = frame.args["type"]
| |
− | local db_attack_notes = {
| |
− | dd = "normal attack",
| |
− | cl = "double attack",
| |
− | clt = "normal attack",
| |
− | ca = "double attack",
| |
− | }
| |
− | return format{
| |
− | templates[map_types[type]],
| |
− | rows = frame.args[1] or "",
| |
− | db_attack_note = db_attack_notes[type] or "?",
| |
− | fp = Formatting:format_image{StatIcons.firepower, caption = Formatting:format_stat_name("firepower")}, | |
− | torp = Formatting:format_image{StatIcons.torpedo, caption = Formatting:format_stat_name("torpedo")},
| |
− | fp_plus_torp = | |
− | Formatting:format_image{StatIcons.firepower, caption = Formatting:format_stat_name("firepower")}
| |
− | .. "+" ..
| |
− | Formatting:format_image{StatIcons.torpedo, caption = Formatting:format_stat_name("torpedo")},
| |
− | luck_minus_cap =
| |
− | '<span title="Luck cap">60</span>−' ..
| |
− | Formatting:format_image{StatIcons.luck, caption = Formatting:format_stat_name("luck")}
| |
− | } | |
| end | | end |
| | | |
− | function get_db_attack_string(fp, t)
| + | -- * Ship object with extra fields related to combat. |
− | local modifiers = default_modifiers(false)
| |
− | local equip_fp = {
| |
− | dd = 2 * 3 + math.floor(2 * math.sqrt(10)),
| |
− | cl = 2 * 10 + math.floor(2 * math.sqrt(10)),
| |
− | clt = 2 * 8 + math.floor(2 * math.sqrt(10)),
| |
− | ca = 2 * 10 + math.floor(2 * math.sqrt(10)) + 3,
| |
− | }
| |
− | local normal = damage(shelling_attack_power(fp, 0, modifiers), modifiers)
| |
− | if t == "cl" or t == "ca" then
| |
− | modifiers.spotting = spotting_modifiers.double
| |
− | end
| |
− | local special = damage(shelling_attack_power(fp, equip_fp[t] or 0, modifiers), modifiers)
| |
− | return normal .. ", " .. special
| |
− | end
| |
| | | |
− | function Combat.table_row(frame)
| + | local Ship = {} |
| | | |
− | local rank = frame.args[1]
| + | function Ship:new(ship, equip, night_attack, spotting) |
− | local ship_key = frame.args[2]
| |
− | local typ = frame.args["type"]
| |
− | local note = frame.args["note"]
| |
| | | |
− | local name, suffix = Ship:process_ship_key(ship_key) | + | self._firepower_max = ship._firepower_max or 0 |
− | local ship_table = Ship:get_table(name, suffix) | + | self._torpedo_max = ship._torpedo_max or 0 |
| + | self._luck = ship._luck or 0 |
| | | |
− | if rank and typ and name and ship_table and ship_table._type then | + | self.equipment = equip or {} |
− | local fp = ship_table._firepower_max
| + | self.equipment_bonus = { firepower = 0, torpedo = 0 } |
− | local torp = ship_table._torpedo_max or 0
| + | for _, eq in pairs(self.equipment) do |
− | local luck = ship_table._luck
| + | if eq.firepower then |
− | local luck_diff = 60 - luck
| + | self.equipment_bonus.firepower = self.equipment_bonus.firepower + eq.firepower + (eq.k and eq.rank and eq.k * math.sqrt(eq.rank) or 0) |
− | return format{
| + | end |
− | templates[map_types[typ] .. "_row"],
| + | if eq.torpedo then |
− | rank = rank,
| + | self.equipment_bonus.torpedo = self.equipment_bonus.torpedo + eq.torpedo + (eq.k and eq.rank and eq.k * math.sqrt(eq.rank) or 0) |
− | name =
| + | end |
− | note and format{'[[${name}|<span title="${note}">${name}</span>]]<sup>?</sup>', name = name, note = note} or
| |
− | string.format("[[%s]]", name),
| |
− | fp = fp,
| |
− | torp = torp, | |
− | fp_plus_torp = fp + torp,
| |
− | db_attack = get_db_attack_string(fp, typ),
| |
− | db_torp = "",
| |
− | nb_da = "",
| |
− | nb_ci = "", | |
− | luck_minus_cap = luck_diff < 0 and "−" .. -luck_diff or luck_diff,
| |
− | nb_ci_rate =
| |
− | cut_in_rate(luck, cut_in_types.torpedo) .. "%, " ..
| |
− | cut_in_rate(luck, cut_in_types.main) .. "%",
| |
− | }
| |
− | else
| |
− | return "" | |
| end | | end |
| + | self.equipment_bonus.firepower = math.floor(self.equipment_bonus.firepower) |
| + | self.equipment_bonus.torpedo = math.floor(self.equipment_bonus.torpedo) |
| + | |
| + | self.health = ship.health or Combat.modifier.health.normal |
| + | self.anti_ground = ship.anti_ground and Combat.modifier.anti_ground or 1 |
| + | self.anti_ground_bonus = ship.wg and Combat.modifier.wg_bonus or 0 |
| + | self.anti_sub = ship.anti_sub or 1 |
| + | self.night_attack = night_attack or 1 |
| + | self.night_attack_x2 = night_attack == Combat.modifier.night_attack.cut_in.torpedo or |
| + | night_attack == Combat.modifier.night_attack.cut_in.mixed |
| + | self.spotting = spotting or 1 |
| + | self.critical = ship.critical or 1 |
| + | self.ap = ship.ap or 1 |
| + | self.ammo = ship.ammo or 1 |
| + | return self |
| | | |
| end | | end |
− |
| |
− | return Combat
| |