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

Difference between revisions of "Module:Sandbox/Combat"

From Kancolle Wiki
Jump to navigation Jump to search
m
m
Line 183: Line 183:
 
-- * Ship object with extra fields related to combat.
 
-- * Ship object with extra fields related to combat.
  
local Ship = {}
+
function Combat.ship(ship, equip, night_attack, spotting)
  
function Ship:new(ship, equip, night_attack, spotting)
+
local ship = {
 +
_firepower_max = ship._firepower_max or 0,
 +
_torpedo_max = ship._torpedo_max or 0,
 +
_luck = ship._luck or 0,
 +
equipment = equip or {},
 +
equipment_bonus = { firepower = 0, torpedo = 0 },
 +
health = ship.health or Combat.modifier.health.normal,
 +
anti_ground = ship.anti_ground and Combat.modifier.anti_ground or 1,
 +
anti_ground_bonus = ship.wg and Combat.modifier.wg_bonus or 0,
 +
anti_sub = ship.anti_sub or 1,
 +
night_attack = night_attack or 1,
 +
night_attack_x2 = night_attack == Combat.modifier.night_attack.cut_in.torpedo or
 +
  night_attack == Combat.modifier.night_attack.cut_in.mixed,
 +
spotting = spotting or 1,
 +
critical = ship.critical or 1,
 +
ap = ship.ap or 1,
 +
ammo = ship.ammo or 1,
 +
}
  
self._firepower_max = ship._firepower_max or 0
+
for _, eq in pairs(ship.equipment) do
self._torpedo_max = ship._torpedo_max or 0
 
self._luck = ship._luck or 0
 
 
 
self.equipment = equip or {}
 
self.equipment_bonus = { firepower = 0, torpedo = 0 }
 
for _, eq in pairs(self.equipment) do
 
 
if eq.firepower then
 
if eq.firepower then
self.equipment_bonus.firepower = self.equipment_bonus.firepower + eq.firepower + (eq.k and eq.rank and eq.k * math.sqrt(eq.rank) or 0)
+
ship.equipment_bonus.firepower = ship.equipment_bonus.firepower + eq.firepower + (eq.k and eq.rank and eq.k * math.sqrt(eq.rank) or 0)
 
end
 
end
 
if eq.torpedo then
 
if eq.torpedo then
self.equipment_bonus.torpedo = self.equipment_bonus.torpedo + eq.torpedo + (eq.k and eq.rank and eq.k * math.sqrt(eq.rank) or 0)
+
ship.equipment_bonus.torpedo = ship.equipment_bonus.torpedo + eq.torpedo + (eq.k and eq.rank and eq.k * math.sqrt(eq.rank) or 0)
 
end
 
end
 
end
 
end
self.equipment_bonus.firepower = math.floor(self.equipment_bonus.firepower)
+
ship.equipment_bonus.firepower = math.floor(ship.equipment_bonus.firepower)
self.equipment_bonus.torpedo = math.floor(self.equipment_bonus.torpedo)
+
ship.equipment_bonus.torpedo = math.floor(ship.equipment_bonus.torpedo)
  
self.health = ship.health or Combat.modifier.health.normal
+
return ship
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
Line 317: Line 317:
 
function format_damage(ship_, type_code, damage_type_fn, equip_setup, night_attack, spotting)
 
function format_damage(ship_, type_code, damage_type_fn, equip_setup, night_attack, spotting)
 
local combat = Combat:new(night_attack)
 
local combat = Combat:new(night_attack)
local normal_damage = combat:damage(damage_type_fn(combat, Ship:new(ship_, {}, night_attack)))
+
local normal_damage = combat:damage(damage_type_fn(combat,Combat.ship(ship_, {}, night_attack)))
 
local normal_damage_string =
 
local normal_damage_string =
 
normal_damage >= combat.cap
 
normal_damage >= combat.cap
Line 324: Line 324:
 
local equip = equipment[type_code][equip_setup]
 
local equip = equipment[type_code][equip_setup]
 
spotting = spotting or 1
 
spotting = spotting or 1
local equip_damage_pre = combat:damage(damage_type_fn(combat, Ship:new(ship_, equip, night_attack)))
+
local equip_damage_pre = combat:damage(damage_type_fn(combat, Combat.ship(ship_, equip, night_attack)))
local equip_damage = combat:damage(damage_type_fn(combat, Ship:new(ship_, equip, night_attack, spotting)))
+
local equip_damage = combat:damage(damage_type_fn(combat, Combat.ship(ship_, equip, night_attack, spotting)))
 
local equip_damage_string =
 
local equip_damage_string =
 
equip_damage_pre >= combat.cap
 
equip_damage_pre >= combat.cap

Revision as of 21:22, 22 October 2015

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

-- **
-- 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 = {}

-- * Experimental values.

Combat.modifier = {

	comined_firepower = {
		ctf_first = 0,
		ctf_second = 10,
		stf_first = 10,
		stf_second = -5,
	},

	comined_torpedo = -5,

	night_contact = 5,

	formation = {
		line_ahead = 1.0,
		double_line = 0.8,
		diamond = 0.7,
		echelon = 0.6,
		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,
	},

	anti_ground = 2.5,
	wg_bonus = 75,

	cap = {
		day = 150,
		night = 300,
	},

	spotting = {
		cut_in = {
			main_main = 1.5,
			main_ap = 1.3,
			main_radar = 1.2,
			main_second = 1.1,
		},
		double = 1.2,
	},

	night_attack = {
		cut_in = {
			main = 2,
			main_misc = 1.75,
			torpedo = 1.5,
			mixed = 1.3,
		},
		double = 1.2,
		normal = 1,
	},

	critical = 1.5,

	ap = {
		main_ap = 1.08,
		main_ap_radar = 1.1,
		main_second_ap = 1.15,
		main_second_ap_radar = 1.15,
	},

}

function Combat.ammo_modifier(ammo_bars)
	return ammo_bars >= 5 and 1 or ammo_bars / 5
end

-- * Combat object with ship-independent values as fields.
--   Ship and enemy values are passed via arguments.

function Combat:new(is_night_battle)
	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

function Combat:damage_pre_cap(ship)
	return self.engagement * self.formation * ship.health * ship.anti_ground * ship.anti_sub * ship.night_attack * ship.attack_power
end

function Combat:damage_cap(attack_power)
	if attack_power > self.cap then
		attack_power = self.cap + math.sqrt(attack_power - self.cap)
	end
	return math.floor(attack_power)
end

function Combat:damage_post_cap(attack_power, ship, enemy_armor)
	attack_power = ship.spotting * self.contact * math.floor(self.expert * ship.critical * math.floor(ship.ap * attack_power))
	-- Use a note on a caller side instead
	--if ship.night_attack_x2 then
	--	attack_power = 2 * attack_power
	--end
	if enemy_armor then
		local min_armor = enemy_armor * 0.7
		local max_armor = enemy_armor * 0.7 + math.max(enemy_armor - 1, 0) * 0.6
		local min = math.floor((attack_power - max_armor) * ship.ammo)
		local max = math.floor((attack_power - min_armor) * ship.ammo)
		return { min, max }
	else
		return math.floor(attack_power * ship.ammo)
	end
end

-- TODO: pass enemy table, do additional detection (e.g. torpedo = 0 vs. ground type enemies
-- 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

-- TODO: carrier shelling, aerial, ASW, support expedition.

Combat.nb_cut_in_types = {
	torpedo = { k = 70, luck_cap = 60 },
	mixed = { k = 70, luck_cap = 70 },
	main = { k = 50, luck_cap = 55 },
}

function Combat.nb_cut_in_rate(ship, modifier)
	if ship._luck <= modifier.luck_cap then
		return math.floor(math.sqrt(modifier.k * ship._luck))
	else
		return Combat.nb_cut_in_rate(modifier.luck_cap, modifier)
	end
end

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

function Combat:torpedo(ship)
	ship.attack_power = 5 + ship._torpedo_max + ship.equipment_bonus.torpedo + self.combined_torpedo
	return ship
end

function Combat:night_battle(ship)
	if ship.health == self.modifier.health.taiha then
		ship.attack_power = 0
	else
		ship.attack_power = self.night_contact + ship._firepower_max + ship._torpedo_max + ship.equipment_bonus.firepower + ship.equipment_bonus.torpedo
	end
	return ship
end

-- * Ship object with extra fields related to combat.

function Combat.ship(ship, equip, night_attack, spotting)

	local ship = {
		_firepower_max = ship._firepower_max or 0,
		_torpedo_max = ship._torpedo_max or 0,
		_luck = ship._luck or 0,
		equipment = equip or {},
		equipment_bonus = { firepower = 0, torpedo = 0 },
		health = ship.health or Combat.modifier.health.normal,
		anti_ground = ship.anti_ground and Combat.modifier.anti_ground or 1,
		anti_ground_bonus = ship.wg and Combat.modifier.wg_bonus or 0,
		anti_sub = ship.anti_sub or 1,
		night_attack = night_attack or 1,
		night_attack_x2 = night_attack == Combat.modifier.night_attack.cut_in.torpedo or
						  night_attack == Combat.modifier.night_attack.cut_in.mixed,
		spotting = spotting or 1,
		critical = ship.critical or 1,
		ap = ship.ap or 1,
		ammo = ship.ammo or 1,
	}

	for _, eq in pairs(ship.equipment) do
		if eq.firepower then
			ship.equipment_bonus.firepower = ship.equipment_bonus.firepower + eq.firepower + (eq.k and eq.rank and eq.k * math.sqrt(eq.rank) or 0)
		end
		if eq.torpedo then
			ship.equipment_bonus.torpedo = ship.equipment_bonus.torpedo + eq.torpedo + (eq.k and eq.rank and eq.k * math.sqrt(eq.rank) or 0)
		end
	end
	ship.equipment_bonus.firepower = math.floor(ship.equipment_bonus.firepower)
	ship.equipment_bonus.torpedo = math.floor(ship.equipment_bonus.torpedo)

	return ship

end

-- * Table utils.

local format = require('Module:StringInterpolation').format
local Formatting = require('Module:Formatting')
local StatIcons = require('Module:StatIcons')
local Ship = require('Module:Ship')

local equipment = {
	DD = {
		main = {
			{ firepower = 3, rank = 10, k = 1 },
			{ firepower = 3, rank = 10, k = 1 },
		},
		torpedo = {
			-- Quint/53
			{ torpedo = 12, rank = 10, k = 1 },
			{ torpedo = 12, rank = 10, k = 1 },
			{ torpedo = 12, rank = 10, k = 1 },
		},
	},
	CL = {
		-- 20.3 #3
		main = {
			{ firepower = 10, rank = 10, k = 1 },
			{ firepower = 10, rank = 10, k = 1 },
		},
		torpedo = {
			{ torpedo = 12, rank = 10, k = 1 },
			{ torpedo = 12, rank = 10, k = 1 },
			{ torpedo = 12, rank = 10, k = 1 },
		},
	},
	CLT = {
		main = {
			-- OTO
			{ firepower = 8, rank = 10, k = 1 },
			{ firepower = 8, rank = 10, k = 1 },
			-- Hyouteki
			{ torpedo = 12 },
		},
		torpedo = {
			{ torpedo = 12, rank = 10, k = 1 },
			{ torpedo = 12, rank = 10, k = 1 },
			{ torpedo = 12 },
		},
	},
	CA = {
		main = {
			-- 20.3 #3
			{ firepower = 10, rank = 10, k = 1 },
			{ firepower = 10, rank = 10, k = 1 },
			-- FuMO
			{ firepower = 3 },
		},
		torpedo = {
			{ torpedo = 12, rank = 10, k = 1 },
			{ torpedo = 12, rank = 10, k = 1 },
			{ torpedo = 12, rank = 10, k = 1 },
			{ torpedo = 12, rank = 10, k = 1 },
		},
		mixed = {
		},
	},
}

local normalized_type_code = {
	DD = "DD",
	CL = "CL",
	CLT = "CLT",
	FBB = "BB",
	BB = "BB",
	CAV = "CA",
	BBV = "BB",
	CVL = "CV",
	CV = "CV",
	CVB = "CV",
}

local type_code_to_template = {
	DD = "main",
	CL = "main",
	CLT = "main",
	CA = "main",
	FBB = "main",
	BB = "main",
	CAV = "main_aviation",
	BBV = "main_aviation",
	CVL = "carrier",
	CV = "carrier",
	CVB = "carrier",
	-- SS = "submarine",
	-- SSV = "submarine_aviation",
	-- AV = "...",
	-- AO = "...",
}

function format_damage(ship_, type_code, damage_type_fn, equip_setup, night_attack, spotting)
	local combat = Combat:new(night_attack)
	local normal_damage = combat:damage(damage_type_fn(combat,Combat.ship(ship_, {}, night_attack)))
	local normal_damage_string =
		normal_damage >= combat.cap
		and string.format('<span style="color:red;">%s</span>', normal_damage)
		or normal_damage
	local equip = equipment[type_code][equip_setup]
	spotting = spotting or 1
	local equip_damage_pre = combat:damage(damage_type_fn(combat, Combat.ship(ship_, equip, night_attack)))
	local equip_damage = combat:damage(damage_type_fn(combat, Combat.ship(ship_, equip, night_attack, spotting)))
	local equip_damage_string =
		equip_damage_pre >= combat.cap
		and string.format('<span style="color:red;">%s</span>', equip_damage)
		or equip_damage
	return
		night_attack
		and string.format("%s", equip_damage_string)
		or string.format("%s, %s", normal_damage_string, equip_damage_string)
end

local templates = {

	main = [[{| class="wikitable sortable typography-xl-optout" style="width:100%;"
! colspan="2" |
! colspan="4" |Day Battle
! colspan="5" |Night Battle
|-
!style="width:60px;"|Rank
!style="width:80px;"|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%;"
!style="width:60px;"|Rank
!style="width:80px;"|Name
!${fp}
!<span title="Air Power with full >> Reppuu">Air Power</span>
!<span title="">Aerial Hit</span>
!<span title="">Shelling</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 attacks = {
	DD = {
		spotting = 1,
		note = "normal attack",
	},
	CL = {
		spotting = Combat.modifier.spotting.double,
		note = "double attack",
	},
	CLT = {
		spotting = 1,
		note = "normal attack",
	},
	CA = {
		spotting = Combat.modifier.spotting.double,
		note = "double attack",
	},
}

function Combat.header(frame)
	local type_code_ = frame.args["type"]
	local type_code = normalized_type_code[type_code_]
	local template = templates[type_code_to_template[type_code_]]
	return format{
		template,
		rows = frame.args[1] or "",
		db_attack_note = attacks[type_code].note 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

function Combat.row(frame)

	local rank = frame.args[1]
	local ship_key = frame.args[2]
	local note = frame.args["note"]

	local name, suffix = Ship:process_ship_key(ship_key)
	local ship = Ship:get_table(name, suffix)

	if rank and name and ship and ship._type then

		local type_code_ = Formatting:format_ship_code(ship._type)
		local type_code = normalized_type_code[type_code_]
		local template = templates[type_code_to_template[type_code_] .. "_row"]
		local luck_diff = 60 - ship._luck

		return format{
			template,
			rank = rank,
			name = note and format{'[[${name}|<span title="${note}">${name}</span>]]<sup>?</sup>', name = name, note = note} or
				   string.format("[[%s]]", name),
			fp = ship._firepower_max,
			torp = ship._torpedo_max,
			fp_plus_torp = ship._firepower_max + (ship._torpedo_max or 0),
			db_attack = format_damage(ship, type_code, Combat.shelling, "main", nil, attacks[type_code].spotting),
			db_torp = format_damage(ship, type_code, Combat.torpedo, "torpedo"),
			nb_da = format_damage(ship, type_code, Combat.night_battle, "main", Combat.modifier.night_attack.double),
			nb_ci = format_damage(ship, type_code, Combat.night_battle, "torpedo", Combat.modifier.night_attack.cut_in.torpedo),
			luck_minus_cap = luck_diff < 0 and "−" .. -luck_diff or luck_diff,
			nb_ci_rate = string.format(
				"%s%%, %s%%",
				Combat.nb_cut_in_rate(ship, Combat.nb_cut_in_types.torpedo),
				Combat.nb_cut_in_rate(ship, Combat.nb_cut_in_types.main)),
		}
	else
		return ""
	end

end

return Combat