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

Module:Sandbox/Combat

From Kancolle Wiki
Revision as of 21:56, 27 October 2015 by がか (talk | contribs)
Jump to navigation Jump to search

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).
-- Evasion rate formula: 検証、仮説スレ 10, 488.
-- Accuracy rate formula: 検証、仮説スレ17, 314, http://kancolle.wikia.com/wiki/Sandbox/Accuracy_Evasion_Tables
-- 

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_modifier)
		local max = math.floor((attack_power - min_armor) * ship.ammo_modifier)
		return { min, max }
	else
		return math.floor(attack_power * ship.ammo_modifier)
	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

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

-- 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 math.floor(math.sqrt(modifier.k * modifier.luck_cap))
	end
end

function Combat.evasion_rate(ship, high_morale)
    local e = ship:evasion_leveled()
    if high_morale then
        return math.floor(100 * 2 * e / (2 * e + 40))
    else
        return math.floor(100 * e / (e + 40))
    end
end

function Combat.accuracy_rate(ship)
    return math.floor(100 * ((math.sqrt(ship._level) - 1) / 45 + ship._luck / 1000))
end

function Combat.air_power(ship)
    local slot1 = math.floor(25 + 10 * math.sqrt(ship._equipment[1].size))
    local slot2 = math.floor(25 + 10 * math.sqrt(ship._equipment[2].size))
    local slot3 = math.floor(25 + 10 * math.sqrt(ship._equipment[3].size))
    local slot4 = ship._equipment[4] and math.floor(25 + 10 * math.sqrt(ship._equipment[4].size)) or 0
    return {
        slot_all = slot1 + slot2 + slot3 + slot4,
        slot1 = slot1,
        slot2 = slot2,
        slot3 = slot3,
        slot4 = slot4,
    }
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_modifier = ship.ammo_modifier 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

-- Using Module:ShipCapabilities.

local BaseData = require("Module:BaseData")
local ShipCapabilities = require("Module:ShipCapabilities")

local Combat2 = BaseData{

	fleet = {
		single = {
			line_ahead   = { shelling = 1.0, salvo = 1.0, asw = 0.6, firepower = 0, torpedo = 0, },
			double_line  = { shelling = 0.8, salvo = 0.8, asw = 0.8, firepower = 0, torpedo = 0, },
			diamond      = { shelling = 0.7, salvo = 0.7, asw = 1.2, firepower = 0, torpedo = 0, },
			echelon      = { shelling = 0.6, salvo = 0.6, asw = 1.0, firepower = 0, torpedo = 0, },
			line_abreast = { shelling = 0.6, salvo = 0.6, asw = 1.3, firepower = 0, torpedo = 0, },
		},
		ctf_main = {
			line_abreast = { shelling = 0.8, salvo = nil, asw = 1.0, firepower = 0, torpedo = nil, },
			double_line  = { shelling = 1.0, salvo = nil, asw = 0.8, firepower = 0, torpedo = nil, },
			diamond      = { shelling = 0.7, salvo = nil, asw = 0.75, firepower = 0, torpedo = nil, },
			line_ahead   = { shelling = 1.1, salvo = nil, asw = 0.5, firepower = 0, torpedo = nil, },
		},
		ctf_escort = {
			line_abreast = { shelling = 0.8, salvo = 0.7, asw = 1.0, firepower = 10, torpedo = -5, },
			double_line  = { shelling = 1.0, salvo = 0.9, asw = 0.8, firepower = 10, torpedo = -5, },
			diamond      = { shelling = 0.7, salvo = 0.6, asw = 0.75, firepower = 10, torpedo = -5, },
			line_ahead   = { shelling = 1.1, salvo = 1.0, asw = 0.5, firepower = 10, torpedo = -5, },
		},
		stf_main = {
			line_abreast = { shelling = 0.8, salvo = nil, asw = 1.0, firepower = 10, torpedo = nil, },
			double_line  = { shelling = 1.0, salvo = nil, asw = 0.8, firepower = 10, torpedo = nil, },
			diamond      = { shelling = 0.7, salvo = nil, asw = 0.75, firepower = 10, torpedo = nil, },
			line_ahead   = { shelling = 1.1, salvo = nil, asw = 0.5, firepower = 10, torpedo = nil, },
		},
		stf_escort = {
			line_abreast = { shelling = 0.8, salvo = 0.7, asw = 1.0, firepower = -5, torpedo = -5, },
			double_line  = { shelling = 1.0, salvo = 0.9, asw = 0.8, firepower = -5, torpedo = -5, },
			diamond      = { shelling = 0.7, salvo = 0.6, asw = 0.75, firepower = -5, torpedo = -5, },
			line_ahead   = { shelling = 1.1, salvo = 1.0, asw = 0.5, firepower = -5, torpedo = -5, },
		},
	},

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

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

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

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

	contact = {
		[0] = 1.12,
		[1] = 1.12,
		[2] = 1.17,
		[3] = 1.20,
	},

        day_battle = 1,
        opening_torpedo = 2,
        closing_torpedo = 3,
        night_battle = 4,
        asw = 5,
        opening_airstrike = 6,
        day_battle = 7,

}

function Combat2:create(stage)
	local this = {}
	this.stage = stage or {
		fleet = Combat2.fleet.single.line_ahead,
		engagement = Combat2.engagement.parallel,
	}
	setmetatable(this, this)
	this.__index = self
	return this
end

function Combat2:_get_ammo_modifier(ammo_bars)
	return ammo_bars >= 5 and 1 or ammo_bars / 5
end

function Combat2:damage(type, ship, enemy, critical)

	local vs_installation = enemy and enemy:is_installation()

	local fleet = self.stage.fleet
	local formation = fleet.shelling
	local engagement = self.stage.engagement

	local health = ship._health or Combat2.health.normal

	local cap = 150
	local spotting = 1
	local ap = 1
	local contact = 1
	local expert = 1
	local attack_power = 0

	if type == Combat2.day_battle then
		spotting = ship._spotting or 1
		ap = ship._ap or 1
		expert = 1 + ((ship._expert_n or 0) + (ship._expert_first and 1 or 0)) / 10
		_, attack_power = ship:day_battle(vs_installation, fleet.firepower)
	elseif type == Combat2.closing_torpedo or type == Combat2.opening_torpedo then
		if not fleet.torpedo then
			return false
		end
		formation = fleet.salvo
		attack_power = Combat2.closing_torpedo and ship:closing_torpedo(fleet.torpedo) or Combat2.opening_torpedo and ship:opening_torpedo(fleet.torpedo)
	elseif type == Combat2.asw then
		formation = fleet.asw
		cap = 100
		attack_power = ship:asw_attack()
	elseif type == Combat2.night_battle then
		if health == Combat2.health.taiha then
			return false
		end
		formation = 1
		engagement = 1
		cap = 300
		_, attack_power = ship:night_battle(vs_installation, self.stage.night_contact)	   
	elseif type == Combat2.opening_airstrike then
		expert = 1 + ((ship._expert_n or 0) + (ship._expert_first and 1 or 0)) / 10
		contact = self.stage.contact and Combat2.contact[self.stage.contact] or 1
		attack_power = ship:opening_airstrike()
	end

	if not attack_power then
		return false
	end

	attack_power = engagement * formation * health * attack_power

	if attack_power > cap then
		attack_power = cap + math.sqrt(attack_power - cap)
	end
	attack_power = math.floor(attack_power)

	expert = critical and expert or 1
	local critical = critical and 1.5 or 1

	attack_power = spotting * contact * math.floor(expert * critical * math.floor(ap * attack_power))

	local ammo_modifier = ship._ammo_bars and self:_get_ammo_modifier(ship._ammo_bars) or 1

	if enemy and enemy._armor then
		local armor = enemy._armor
		local min_armor = armor * 0.7
		local max_armor = armor * 0.7 + math.max(armor - 1, 0) * 0.6
		local min = math.floor((attack_power - max_armor) * ammo_modifier)
		local max = math.floor((attack_power - min_armor) * ammo_modifier)
		return { min = math.max(0, min), max = math.max(0, max) }
	else
		return math.floor(attack_power * ammo_modifier)
	end

end

Combat.Combat2 = Combat2

return Combat