2 // DamageCalculator.swift
5 // Created by Hori,Masaki on 2017/07/23.
6 // Copyright © 2017年 Hori,Masaki. All rights reserved.
19 final class DamageCalculator {
21 private let store = TemporaryDataStore.oneTimeEditor()
23 private let json: JSON
24 private let battleType: BattleType
26 init(_ json: JSON, _ battleType: BattleType = .normal) {
28 self.battleType = battleType
33 // MARK: - Battle type
34 extension DamageCalculator {
36 func calculateBattle() {
47 func calcCombinedBattleAir() {
58 func calcEachBattleAir() {
69 func calcEnemyCombinedBattle() {
71 // same phase as combined air
72 calcCombinedBattleAir()
77 calculateMidnightBattle()
81 // MARK: - Battle phase
82 extension DamageCalculator {
84 private func calcKouku() {
86 calculateFDam(baseKeyPath: "api_data.api_kouku.api_stage3")
87 calculateFDam(baseKeyPath: "api_data.api_kouku2.api_stage3")
92 // 連合vs連合(水上) 第2 全体 use kouku nor kouku2
93 // 連合vs連合(機動) 第1 全体 use kouku nor kouku2
94 let bf: () -> BattleFleet = {
96 switch self.battleType {
97 case .combinedWater, .combinedAir,
98 .eachCombinedWater, .eachCombinedAir:
101 default: return .first
104 calculateFDam(baseKeyPath: "api_data.api_kouku.api_stage3_combined", bf)
105 calculateFDam(baseKeyPath: "api_data.api_kouku2.api_stage3_combined", bf)
109 private func calcOpeningAttack() {
116 calculateFDam(baseKeyPath: "api_data.api_opening_atack") {
119 case .combinedWater, .combinedAir: return .second
121 case .eachCombinedWater, .eachCombinedAir: return .each
123 default: return .first
128 private func calcOpeningTaisen() {
130 calculateHogeki(baseKeyPath: "api_data.api_opening_taisen") {
132 isCombinedBattle ? .second : .first
136 private func calcHougeki1() {
143 calculateHogeki(baseKeyPath: "api_data.api_hougeki1") {
146 case .combinedAir: return .second
148 default: return .first
153 private func calcHougeki2() {
160 calculateHogeki(baseKeyPath: "api_data.api_hougeki2") {
163 case .eachCombinedWater: return .each
165 case .eachCombinedAir: return .each
167 default: return .first
172 private func calcHougeki3() {
179 calculateHogeki(baseKeyPath: "api_data.api_hougeki3") {
182 case .combinedWater: return .second
184 // case .eachCombinedWater: return .second // 1~12
185 case .eachCombinedAir: return .each
187 default: return .first
192 private func calcRaigeki() {
199 calculateFDam(baseKeyPath: "api_data.api_raigeki") {
202 case .combinedWater, .combinedAir: return .second
204 case .eachCombinedWater, .eachCombinedAir: return .each
206 default: return .first
211 private func calculateMidnightBattle() {
218 calculateHogeki(baseKeyPath: "api_data.api_hougeki") {
220 isCombinedBattle ? .second : .first
225 // MARK: - Properties
226 extension DamageCalculator {
228 private var damages: [Damage] {
230 let array = store.sortedDamagesById()
236 let newDamages = store.sortedDamagesById()
244 private var isCombinedBattle: Bool {
247 case .combinedAir, .combinedWater, .eachCombinedAir, .eachCombinedWater:
255 private func buildDamagesOfFleet(fleet: Int, ships: [Ship]) {
257 guard case 0...1 = fleet else { return Logger.shared.log("fleet must 0 or 1.") }
258 guard let battle = store.battle() else { return Logger.shared.log("Battle is invalid.") }
262 guard case 0..<ships.count = $0 else { return }
264 guard let damage = store.createDamage() else { return Logger.shared.log("Can not create Damage") }
266 damage.battle = battle
267 damage.id = $0 + fleet * 6
268 damage.hp = ships[$0].nowhp
269 damage.shipID = ships[$0].id
273 private func buildDamagedEntity() {
275 guard let battle = store.battle() else { return Logger.shared.log("Battle is invalid.") }
278 let firstFleetShips = ServerDataStore.default.ships(byDeckId: battle.deckId)
279 buildDamagesOfFleet(fleet: 0, ships: firstFleetShips)
282 let secondFleetShips = ServerDataStore.default.ships(byDeckId: 2)
283 buildDamagesOfFleet(fleet: 1, ships: secondFleetShips)
287 // MARK: - Primitive Calclator
288 extension DamageCalculator {
290 private func hogekiTargets(_ list: JSON) -> [[Int]]? {
292 guard let targetArraysArray = list
294 .flatMap({ $0.array?.flatMap { $0.int } }) else {
299 guard list.count == targetArraysArray.count else {
301 return Logger.shared.log("api_df_list is wrong", value: nil)
304 return targetArraysArray
307 private func hogekiDamages(_ list: JSON) -> [[Int]]? {
309 return list.array?.flatMap { $0.array?.flatMap { $0.int } }
312 private func enemyFlags(_ list: JSON) -> [Int]? {
314 return list.array?.flatMap { $0.int }
317 private func isTargetFriend(eFlags: [Int]?, index: Int) -> Bool {
319 if let eFlags = eFlags, 0..<eFlags.count ~= index {
321 return eFlags[index] == 1
327 private func validTargetPos(_ targetPos: Int, in battleFleet: BattleFleet) -> Bool {
329 let upper = (battleFleet == .each ? 12 : 6)
331 return 0..<upper ~= targetPos
334 private func position(_ pos: Int, in fleet: BattleFleet) -> Int? {
336 let shipOffset = (fleet == .second) ? 6 : 0
338 let damagePos = pos + shipOffset
340 guard case 0..<damages.count = damagePos else { return nil }
345 private func calcHP(damage: Damage, receive: Int) {
349 if damage.hp > 0 { return }
351 guard let ship = ServerDataStore.default.ship(by: damage.shipID) else { return }
353 damage.hp = damageControlIfPossible(ship: ship)
356 damage.useDamageControl = true
360 private func calculateHogeki(baseKeyPath: String, _ bf: () -> BattleFleet) {
362 calculateHogeki(baseKeyPath: baseKeyPath, battleFleet: bf())
365 private func calculateHogeki(baseKeyPath: String, battleFleet: BattleFleet = .first) {
367 let baseValue = json[baseKeyPath.components(separatedBy: ".")]
369 guard let targetPosLists = hogekiTargets(baseValue["api_df_list"]),
370 let damageLists = hogekiDamages(baseValue["api_damage"]) else {
372 Debug.print("Cound not find api_df_list or api_damage for \(baseKeyPath)", level: .full)
376 guard targetPosLists.count == damageLists.count else { return Logger.shared.log("api_damage is wrong.") }
378 let eFlags = enemyFlags(baseValue["api_at_eflag"])
380 Debug.print("Start Hougeki \(baseKeyPath)", level: .debug)
382 zip(targetPosLists, damageLists).enumerated().forEach { (i, list) in
384 if !isTargetFriend(eFlags: eFlags, index: i) {
386 Debug.excute(level: .debug) {
388 print("target is enemy")
394 zip(list.0, list.1).forEach { (targetPos, damage) in
396 guard validTargetPos(targetPos, in: battleFleet) else { return Logger.shared.log("invalid position \(targetPos)") }
398 guard let damagePos = position(targetPos, in: battleFleet) else {
400 return Logger.shared.log("damage pos is larger than damage count")
403 calcHP(damage: damages[damagePos], receive: damage)
405 Debug.excute(level: .debug) {
407 let shipOffset = (battleFleet == .second) ? 6 : 0
408 print("Hougeki \(targetPos + shipOffset) -> \(damage)")
414 private func calculateFDam(baseKeyPath: String, _ bf: () -> BattleFleet) {
416 calculateFDam(baseKeyPath: baseKeyPath, battleFleet: bf())
419 private func calculateFDam(baseKeyPath: String, battleFleet: BattleFleet = .first) {
421 let baseValue = json[baseKeyPath.components(separatedBy: ".")]
423 guard let fdamArray = baseValue["api_fdam"].arrayObject else {
425 Debug.print("Could not find api_fdam of \(baseKeyPath)", level: .full)
430 guard let IntFdamArray = fdamArray as? [IntConvertable] else {
432 Debug.print("api_fdam value of \(baseKeyPath) is not [Int].", level: .debug)
433 Debug.print(baseValue, level: .debug)
437 let frendDamages = IntFdamArray.map { $0.toInt() }
439 Debug.print("Start FDam \(baseKeyPath)", level: .debug)
441 frendDamages.enumerated().forEach { (idx, damage) in
443 // if idx == 0 { return }
445 guard let damagePos = position(idx, in: battleFleet) else { return }
447 calcHP(damage: damages[damagePos], receive: damage)
449 Debug.excute(level: .debug) {
451 let shipOffset = (battleFleet == .second) ? 6 : 0
452 print("FDam \(idx + shipOffset) -> \(damage)")
458 // MARK: - Damage control
459 extension DamageCalculator {
461 private func damageControlIfPossible(ship: Ship) -> Int {
463 let store = ServerDataStore.default
465 let damageControl = ship
468 .flatMap { $0 as? SlotItem }
469 .map { store.masterSlotItemID(by: $0.id) }
470 .flatMap { DamageControlID(rawValue: $0) }
473 if let validDamageControl = damageControl {
475 switch validDamageControl {
477 Debug.print("Damage Control", level: .debug)
478 return Int(Double(ship.maxhp) * 0.2)
481 Debug.print("Goddes", level: .debug)
487 let exItemId = store.masterSlotItemID(by: ship.slot_ex)
489 guard let exType = DamageControlID(rawValue: exItemId) else { return 0 }
493 Debug.print("Damage Control", level: .debug)
494 return Int(Double(ship.maxhp) * 0.2)
497 Debug.print("Goddes", level: .debug)