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()
232 if array.count != 12 {
236 let newDamages = store.sortedDamagesById()
238 guard newDamages.count == 12 else {
240 return Logger.shared.log("ERROR!!!! CAN NOT CREATE DAMAGE OBJECT", value: [])
249 private var isCombinedBattle: Bool {
252 case .combinedAir, .combinedWater, .eachCombinedAir, .eachCombinedWater:
260 private func buildDamagesOfFleet(fleet: Int, ships: [Ship]) {
262 guard case 0...1 = fleet else { return Logger.shared.log("fleet must 0 or 1.") }
263 guard let battle = store.battle() else { return Logger.shared.log("Battle is invalid.") }
267 guard let damage = store.createDamage() else { return Logger.shared.log("Can not create Damage") }
269 damage.battle = battle
270 damage.id = $0 + fleet * 6
272 guard case 0..<ships.count = $0 else { return }
274 damage.hp = ships[$0].nowhp
275 damage.shipID = ships[$0].id
279 private func buildDamagedEntity() {
281 guard let battle = store.battle() else { return Logger.shared.log("Battle is invalid.") }
284 let firstFleetShips = ServerDataStore.default.ships(byDeckId: battle.deckId)
285 buildDamagesOfFleet(fleet: 0, ships: firstFleetShips)
288 let secondFleetShips = ServerDataStore.default.ships(byDeckId: 2)
289 buildDamagesOfFleet(fleet: 1, ships: secondFleetShips)
293 // MARK: - Primitive Calclator
294 extension DamageCalculator {
296 private func hogekiTargets(_ list: JSON) -> [[Int]]? {
298 guard let targetArraysArray = list
300 .flatMap({ $0.array?.flatMap { $0.int } }) else {
305 guard list.count - 1 == targetArraysArray.count else {
307 return Logger.shared.log("api_df_list is wrong", value: nil)
310 return targetArraysArray
313 private func hogekiDamages(_ list: JSON) -> [[Int]]? {
315 return list.array?.flatMap { $0.array?.flatMap { $0.int } }
318 private func enemyFlags(_ list: JSON) -> [Int]? {
320 return list.array?.flatMap { $0.int }.filter { $0 != -1 }
323 private func isTargetFriend(eFlags: [Int]?, index: Int) -> Bool {
325 if let eFlags = eFlags, 0..<eFlags.count ~= index {
327 return eFlags[index] == 1
333 private func validTargetPos(_ targetPos: Int, in battleFleet: BattleFleet) -> Bool {
335 let upper = (battleFleet == .each ? 12 : 6)
337 return 1...upper ~= targetPos
340 private func position(_ pos: Int, in fleet: BattleFleet) -> Int? {
342 let shipOffset = (fleet == .second) ? 6 : 0
344 let damagePos = pos - 1 + shipOffset
346 guard case 0..<damages.count = damagePos else { return nil }
351 private func calcHP(damage: Damage, receive: Int) {
355 if damage.hp > 0 { return }
357 guard let ship = ServerDataStore.default.ship(by: damage.shipID) else { return }
359 damage.hp = damageControlIfPossible(ship: ship)
362 damage.useDamageControl = true
366 private func calculateHogeki(baseKeyPath: String, _ bf: () -> BattleFleet) {
368 calculateHogeki(baseKeyPath: baseKeyPath, battleFleet: bf())
371 private func calculateHogeki(baseKeyPath: String, battleFleet: BattleFleet = .first) {
373 let baseValue = json[baseKeyPath.components(separatedBy: ".")]
375 guard let targetPosLists = hogekiTargets(baseValue["api_df_list"]),
376 let damageLists = hogekiDamages(baseValue["api_damage"]) else {
378 Debug.print("Cound not find api_df_list or api_damage for \(baseKeyPath)", level: .full)
382 guard targetPosLists.count == damageLists.count else { return Logger.shared.log("api_damage is wrong.") }
384 let eFlags = enemyFlags(baseValue["api_at_eflag"])
386 Debug.print("Start Hougeki \(baseKeyPath)", level: .debug)
388 zip(targetPosLists, damageLists).enumerated().forEach { (i, list) in
390 if !isTargetFriend(eFlags: eFlags, index: i) {
392 Debug.excute(level: .debug) {
394 print("target is enemy")
400 zip(list.0, list.1).forEach { (targetPos, damage) in
402 guard validTargetPos(targetPos, in: battleFleet) else { return }
404 guard let damagePos = position(targetPos, in: battleFleet) else {
406 return Logger.shared.log("damage pos is larger than damage count")
409 calcHP(damage: damages[damagePos], receive: damage)
411 Debug.excute(level: .debug) {
413 let shipOffset = (battleFleet == .second) ? 6 : 0
414 print("Hougeki \(targetPos + shipOffset) -> \(damage)")
420 private func calculateFDam(baseKeyPath: String, _ bf: () -> BattleFleet) {
422 calculateFDam(baseKeyPath: baseKeyPath, battleFleet: bf())
425 private func calculateFDam(baseKeyPath: String, battleFleet: BattleFleet = .first) {
427 let baseValue = json[baseKeyPath.components(separatedBy: ".")]
429 guard let fdamArray = baseValue["api_fdam"].arrayObject else {
431 Debug.print("Could not find api_fdam of \(baseKeyPath)", level: .full)
436 guard let IntFdamArray = fdamArray as? [IntConvertable] else {
438 Debug.print("api_fdam value of \(baseKeyPath) is not [Int].", level: .debug)
439 Debug.print(baseValue, level: .debug)
443 let frendDamages = IntFdamArray.map { $0.toInt() }
445 Debug.print("Start FDam \(baseKeyPath)", level: .debug)
447 frendDamages.enumerated().forEach { (idx, damage) in
449 if idx == 0 { return }
451 guard let damagePos = position(idx, in: battleFleet) else { return }
453 calcHP(damage: damages[damagePos], receive: damage)
455 Debug.excute(level: .debug) {
457 let shipOffset = (battleFleet == .second) ? 6 : 0
458 print("FDam \(idx + shipOffset) -> \(damage)")
464 // MARK: - Damage control
465 extension DamageCalculator {
467 private func damageControlIfPossible(ship: Ship) -> Int {
469 let store = ServerDataStore.default
471 let damageControl = ship
474 .flatMap { $0 as? SlotItem }
475 .map { store.masterSlotItemID(by: $0.id) }
476 .flatMap { DamageControlID(rawValue: $0) }
479 if let validDamageControl = damageControl {
481 switch validDamageControl {
483 return Int(Double(ship.maxhp) * 0.2)
491 let exItemId = store.masterSlotItemID(by: ship.slot_ex)
493 guard let exType = DamageControlID(rawValue: exItemId) else { return 0 }
497 return Int(Double(ship.maxhp) * 0.2)