2 // DamageCalculator.swift
5 // Created by Hori,Masaki on 2017/07/23.
6 // Copyright © 2017年 Hori,Masaki. All rights reserved.
18 final class DamageCalculator {
20 private let store = TemporaryDataStore.oneTimeEditor()
22 private let json: JSON
23 private let battleType: BattleType
25 init(_ json: JSON, _ battleType: BattleType = .normal) {
27 self.battleType = battleType
32 // MARK: - Battle type
33 extension DamageCalculator {
35 func calculateBattle() {
46 func calcCombinedBattleAir() {
57 func calcEachBattleAir() {
68 func calcEachNightToDay() {
81 func calcEnemyCombinedBattle() {
83 // same phase as combined air
84 calcCombinedBattleAir()
89 calculateMidnightBattle()
93 // MARK: - Battle phase
94 extension DamageCalculator {
96 private func calcKouku() {
98 calculateFDam(baseKeyPath: "api_data.api_kouku.api_stage3")
99 calculateFDam(baseKeyPath: "api_data.api_kouku2.api_stage3")
101 let bf: () -> BattleFleet = {
103 switch self.battleType {
110 default: return .normal
113 calculateFDam(baseKeyPath: "api_data.api_kouku.api_stage3_combined", bf)
114 calculateFDam(baseKeyPath: "api_data.api_kouku2.api_stage3_combined", bf)
118 private func calcOpeningAttack() {
120 calculateFDam(baseKeyPath: "api_data.api_opening_atack")
123 private func calcOpeningTaisen() {
125 calculateHogeki(baseKeyPath: "api_data.api_opening_taisen")
128 private func calcHougeki1() {
130 calculateHogeki(baseKeyPath: "api_data.api_hougeki1")
133 private func calcHougeki2() {
135 calculateHogeki(baseKeyPath: "api_data.api_hougeki2")
138 private func calcHougeki3() {
140 calculateHogeki(baseKeyPath: "api_data.api_hougeki3")
143 private func calcNightHogeki1() {
145 calculateHogeki(baseKeyPath: "api_data.api_n_hougeki1")
148 private func calcNightHogeki2() {
150 calculateHogeki(baseKeyPath: "api_data.api_n_hougeki2")
153 private func calcRaigeki() {
155 calculateFDam(baseKeyPath: "api_data.api_raigeki")
158 private func calculateMidnightBattle() {
160 calculateHogeki(baseKeyPath: "api_data.api_hougeki")
164 // MARK: - Properties
165 extension DamageCalculator {
167 private var damages: [Damage] {
169 let array = store.sortedDamagesById()
175 return store.sortedDamagesById()
181 private var isCombinedBattle: Bool {
184 case .combinedAir, .combinedWater, .eachCombinedAir, .eachCombinedWater:
192 private func makeDamage(num: Int) -> [Damage] {
194 guard let battle = store.battle() else { return Logger.shared.log("Battle is invalid.", value: []) }
196 return (0..<num).flatMap {
198 guard let damage = store.createDamage() else { return Logger.shared.log("Can not create Damage", value: nil) }
200 damage.battle = battle
207 private func buildDamages(first: [Ship], second: [Ship]?) {
209 guard let battle = store.battle() else { return Logger.shared.log("Battle is invalid.") }
211 let damages = makeDamage(num: 12)
213 func setShip(_ ship: Ship, into damage: Damage) {
215 damage.shipID = ship.id
216 damage.hp = ship.nowhp
218 Debug.excute(level: .debug) {
220 print("add Damage entity of \(ship.name) at \(damage.id)")
224 zip(first, damages).forEach(setShip)
226 if let second = second {
228 let secondsDamage = damages[6...]
229 zip(second, secondsDamage).forEach(setShip)
234 private func buildDamagedEntity() {
236 guard let battle = store.battle() else { return Logger.shared.log("Battle is invalid.") }
239 let firstFleetShips = ServerDataStore.default.ships(byDeckId: battle.deckId)
242 if isCombinedBattle {
244 let secondFleetShips = ServerDataStore.default.ships(byDeckId: 2)
245 buildDamages(first: firstFleetShips, second: secondFleetShips)
248 buildDamages(first: firstFleetShips, second: nil)
253 // MARK: - Primitive Calclator
254 extension DamageCalculator {
256 private func hogekiTargets(_ list: JSON) -> [[Int]]? {
258 guard let targetArraysArray = list
260 .flatMap({ $0.array?.flatMap { $0.int } }) else {
265 guard list.count == targetArraysArray.count else {
267 return Logger.shared.log("api_df_list is wrong", value: nil)
270 return targetArraysArray
273 private func hogekiDamages(_ list: JSON) -> [[Int]]? {
275 return list.array?.flatMap { $0.array?.flatMap { $0.int } }
278 private func enemyFlags(_ list: JSON) -> [Int]? {
280 return list.array?.flatMap { $0.int }
283 private func validTargetPos(_ targetPos: Int, in battleFleet: BattleFleet) -> Bool {
285 return 0..<damages.count ~= targetPos
288 private func position(_ pos: Int, in fleet: BattleFleet) -> Int? {
290 let damagePos = (fleet == .secondOnly ? pos + 6 : pos)
292 guard case 0..<damages.count = damagePos else { return nil }
297 private func calcHP(damage: Damage, receive: Int) {
299 Debug.excute(level: .debug) {
301 if receive != 0, let ship = ServerDataStore.default.ship(by: damage.shipID) {
303 print("\(ship.name) recieve Damage \(receive)")
309 if damage.hp > 0 { return }
311 guard let ship = ServerDataStore.default.ship(by: damage.shipID) else { return }
313 damage.hp = damageControlIfPossible(ship: ship)
314 damage.useDamageControl = (damage.hp != 0)
318 private func calculateHogeki(baseKeyPath: String, _ bf: () -> BattleFleet) {
320 calculateHogeki(baseKeyPath: baseKeyPath, battleFleet: bf())
323 private func omitEnemyDamage(targetPosLists: [[Int]], damageLists: [[Int]], eFlags: [Int]?) -> [([Int], [Int])] {
325 guard let eFlags = eFlags else { return zip(targetPosLists, damageLists).map { $0 } }
327 return zip(zip(targetPosLists, damageLists), eFlags).filter { $0.1 == 1 }.map { $0.0 }
330 private func calculateHogeki(baseKeyPath: String, battleFleet: BattleFleet = .normal) {
332 let baseValue = json[baseKeyPath.components(separatedBy: ".")]
334 guard let targetPosLists = hogekiTargets(baseValue["api_df_list"]),
335 let damageLists = hogekiDamages(baseValue["api_damage"]) else {
337 Debug.print("Cound not find api_df_list or api_damage for \(baseKeyPath)", level: .full)
341 guard targetPosLists.count == damageLists.count else { return Logger.shared.log("api_damage is wrong.") }
343 Debug.print("Start Hougeki \(baseKeyPath)", level: .debug)
345 omitEnemyDamage(targetPosLists: targetPosLists, damageLists: damageLists, eFlags: enemyFlags(baseValue["api_at_eflag"]))
346 .map { (targetPosList, damageList) -> (Int, Int) in
348 guard let pos = targetPosList.first else { return (0, 0) }
350 return (pos, damageList.filter { $0 > 0 }.reduce(0, +))
352 .forEach { (targetPos, damage) in
354 guard validTargetPos(targetPos, in: battleFleet) else { return Logger.shared.log("invalid position \(targetPos)") }
356 guard let damagePos = position(targetPos, in: battleFleet) else {
358 return Logger.shared.log("damage pos is larger than damage count")
361 calcHP(damage: damages[damagePos], receive: damage)
363 Debug.print("Hougeki \(targetPos) -> \(damage)", level: .debug)
367 private func calculateFDam(baseKeyPath: String, _ bf: () -> BattleFleet) {
369 calculateFDam(baseKeyPath: baseKeyPath, battleFleet: bf())
372 private func calculateFDam(baseKeyPath: String, battleFleet: BattleFleet = .normal) {
374 let baseValue = json[baseKeyPath.components(separatedBy: ".")]
376 guard let fdamArray = baseValue["api_fdam"].arrayObject else {
378 Debug.print("Could not find api_fdam of \(baseKeyPath)", level: .full)
383 guard let IntFdamArray = fdamArray as? [IntConvertable] else {
385 Debug.print("api_fdam value of \(baseKeyPath) is not [Int].", level: .debug)
386 Debug.print(baseValue, level: .debug)
390 let frendDamages = IntFdamArray.map { $0.toInt() }
392 Debug.print("Start FDam \(baseKeyPath)", level: .debug)
394 frendDamages.enumerated().forEach { (idx, damage) in
396 guard let damagePos = position(idx, in: battleFleet) else { return }
398 calcHP(damage: damages[damagePos], receive: damage)
400 Debug.print("FDam \(idx) -> \(damage)", level: .debug)
405 // MARK: - Damage control
406 extension DamageCalculator {
408 private func damageControlIfPossible(ship: Ship) -> Int {
410 let store = ServerDataStore.default
412 let damageControl = ship
415 .flatMap { $0 as? SlotItem }
416 .map { store.masterSlotItemID(by: $0.id) }
417 .flatMap { DamageControlID(rawValue: $0) }
420 if let validDamageControl = damageControl {
422 switch validDamageControl {
424 Debug.print("Damage Control", level: .debug)
425 return Int(Double(ship.maxhp) * 0.2)
428 Debug.print("Goddes", level: .debug)
434 let exItemId = store.masterSlotItemID(by: ship.slot_ex)
436 guard let exType = DamageControlID(rawValue: exItemId) else { return 0 }
440 Debug.print("Damage Control", level: .debug)
441 return Int(Double(ship.maxhp) * 0.2)
444 Debug.print("Goddes", level: .debug)