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() {
41 self.calcOpeningTaisen()
42 self.calcOpeningAttack()
50 func calcCombinedBattleAir() {
55 self.calcOpeningTaisen()
56 self.calcOpeningAttack()
64 func calcEachBattleAir() {
69 self.calcOpeningTaisen()
70 self.calcOpeningAttack()
78 func calcEachNightToDay() {
82 self.calcNightHogeki1()
83 self.calcNightHogeki2()
85 self.calcOpeningTaisen()
86 self.calcOpeningAttack()
94 func calcEnemyCombinedBattle() {
96 // same phase as combined air
97 calcCombinedBattleAir()
100 func calcMidnight() {
104 self.calculateMidnightBattle()
109 // MARK: - Battle phase
110 extension DamageCalculator {
112 private func calcKouku() {
114 calculateFDam(baseKeyPath: "api_data.api_kouku.api_stage3")
115 calculateFDam(baseKeyPath: "api_data.api_kouku2.api_stage3")
117 let bf: () -> BattleFleet = {
119 switch self.battleType {
134 calculateFDam(baseKeyPath: "api_data.api_kouku.api_stage3_combined", bf)
135 calculateFDam(baseKeyPath: "api_data.api_kouku2.api_stage3_combined", bf)
139 private func calcOpeningAttack() {
141 calculateFDam(baseKeyPath: "api_data.api_opening_atack")
144 private func calcOpeningTaisen() {
146 calculateHogeki(baseKeyPath: "api_data.api_opening_taisen")
149 private func calcHougeki1() {
151 calculateHogeki(baseKeyPath: "api_data.api_hougeki1")
154 private func calcHougeki2() {
156 calculateHogeki(baseKeyPath: "api_data.api_hougeki2")
159 private func calcHougeki3() {
161 calculateHogeki(baseKeyPath: "api_data.api_hougeki3")
164 private func calcNightHogeki1() {
166 calculateHogeki(baseKeyPath: "api_data.api_n_hougeki1")
169 private func calcNightHogeki2() {
171 calculateHogeki(baseKeyPath: "api_data.api_n_hougeki2")
174 private func calcRaigeki() {
176 calculateFDam(baseKeyPath: "api_data.api_raigeki")
179 private func calculateMidnightBattle() {
181 calculateHogeki(baseKeyPath: "api_data.api_hougeki")
185 // MARK: - Properties
186 extension DamageCalculator {
188 private var damages: [Damage] {
190 let array = store.sortedDamagesById()
196 return store.sortedDamagesById()
202 private var isCombinedBattle: Bool {
206 case .combinedAir, .combinedWater, .eachCombinedAir, .eachCombinedWater:
217 private func makeDamage(num: Int) -> [Damage] {
219 guard let battle = store.battle() else {
221 Logger.shared.log("Battle is invalid.")
226 return (0..<num).compactMap {
228 guard let damage = store.createDamage() else {
230 Logger.shared.log("Can not create Damage")
235 damage.battle = battle
242 private func buildDamages(first: [Ship], second: [Ship]?) {
244 guard let battle = store.battle() else {
246 Logger.shared.log("Battle is invalid.")
251 let damages = makeDamage(num: 12)
253 func setShip(_ ship: Ship, into damage: Damage) {
255 let sStore = ServerDataStore.default
257 damage.shipID = sStore.sync { ship.id }
258 damage.hp = sStore.sync { ship.nowhp }
260 Debug.excute(level: .debug) {
262 let name = sStore.sync { ship.name }
263 print("add Damage entity of \(name) at \(damage.id)")
267 zip(first, damages).forEach(setShip)
269 if let second = second {
271 let secondsDamage = damages[6...]
272 zip(second, secondsDamage).forEach(setShip)
277 private func buildDamagedEntity() {
279 guard let battle = store.battle() else {
281 Logger.shared.log("Battle is invalid.")
286 let sStore = ServerDataStore.default
288 let deckId = battle.deckId
289 let firstFleetShips = sStore.sync { sStore.ships(byDeckId: deckId) }
292 if isCombinedBattle {
294 let secondFleetShips = sStore.sync { sStore.ships(byDeckId: 2) }
295 buildDamages(first: firstFleetShips, second: secondFleetShips)
299 buildDamages(first: firstFleetShips, second: nil)
304 // MARK: - Primitive Calclator
305 extension DamageCalculator {
307 private func hogekiTargets(_ list: JSON) -> [[Int]]? {
309 guard let targetArraysArray = list
311 .compactMap({ $0.array?.compactMap { $0.int } }) else {
316 guard list.count == targetArraysArray.count else {
318 Logger.shared.log("api_df_list is wrong")
323 return targetArraysArray
326 private func hogekiDamages(_ list: JSON) -> [[Int]]? {
328 return list.array?.compactMap { $0.array?.compactMap { $0.int } }
331 private func enemyFlags(_ list: JSON) -> [Int]? {
333 return list.array?.compactMap { $0.int }
336 private func validTargetPos(_ targetPos: Int, in battleFleet: BattleFleet) -> Bool {
338 return 0..<damages.count ~= targetPos
341 private func position(_ pos: Int, in fleet: BattleFleet) -> Int? {
343 let damagePos = (fleet == .secondOnly ? pos + 6 : pos)
345 guard case 0..<damages.count = damagePos else {
353 private func calcHP(damage: Damage, receive: Int) {
355 Debug.excute(level: .debug) {
357 let store = ServerDataStore.default
358 if receive != 0, let shipName = store.sync(execute: { store.ship(by: damage.shipID)?.name }) {
360 print("\(shipName) recieve Damage \(receive)")
371 let sStore = ServerDataStore.default
372 guard let ship = sStore.sync(execute: { sStore.ship(by: damage.shipID) }) else {
377 damage.hp = damageControlIfPossible(ship: ship)
378 damage.useDamageControl = (damage.hp != 0)
382 private func calculateHogeki(baseKeyPath: String, _ bf: () -> BattleFleet) {
384 calculateHogeki(baseKeyPath: baseKeyPath, battleFleet: bf())
387 private func omitEnemyDamage(targetPosLists: [[Int]], damageLists: [[Int]], eFlags: [Int]?) -> [([Int], [Int])] {
389 guard let eFlags = eFlags else {
391 return zip(targetPosLists, damageLists).map { $0 }
394 return zip(zip(targetPosLists, damageLists), eFlags).filter { $0.1 == 1 }.map { $0.0 }
397 private func calculateHogeki(baseKeyPath: String, battleFleet: BattleFleet = .normal) {
399 let baseValue = json[baseKeyPath.components(separatedBy: ".")]
401 guard let targetPosLists = hogekiTargets(baseValue["api_df_list"]),
402 let damageLists = hogekiDamages(baseValue["api_damage"]) else {
404 Debug.print("Cound not find api_df_list or api_damage for \(baseKeyPath)", level: .full)
409 guard targetPosLists.count == damageLists.count else {
411 Logger.shared.log("api_damage is wrong.")
416 Debug.print("Start Hougeki \(baseKeyPath)", level: .debug)
418 omitEnemyDamage(targetPosLists: targetPosLists, damageLists: damageLists, eFlags: enemyFlags(baseValue["api_at_eflag"]))
419 .map { (targetPosList, damageList) -> (Int, Int) in
421 guard let pos = targetPosList.first else {
426 return (pos, damageList.filter { $0 > 0 }.reduce(0, +))
428 .forEach { (targetPos, damage) in
430 guard validTargetPos(targetPos, in: battleFleet) else {
432 Logger.shared.log("invalid position \(targetPos)")
437 guard let damagePos = position(targetPos, in: battleFleet) else {
439 Logger.shared.log("damage pos is larger than damage count")
444 calcHP(damage: damages[damagePos], receive: damage)
446 Debug.print("Hougeki \(targetPos) -> \(damage)", level: .debug)
450 private func calculateFDam(baseKeyPath: String, _ bf: () -> BattleFleet) {
452 calculateFDam(baseKeyPath: baseKeyPath, battleFleet: bf())
455 private func calculateFDam(baseKeyPath: String, battleFleet: BattleFleet = .normal) {
457 let baseValue = json[baseKeyPath.components(separatedBy: ".")]
459 guard let fdamArray = baseValue["api_fdam"].arrayObject else {
461 Debug.print("Could not find api_fdam of \(baseKeyPath)", level: .full)
466 guard let intFdamArray = fdamArray as? [IntConvertable] else {
468 Debug.print("api_fdam value of \(baseKeyPath) is not [Int].", level: .debug)
469 Debug.print(baseValue, level: .debug)
474 let frendDamages = intFdamArray.map { $0.toInt() }
476 Debug.print("Start FDam \(baseKeyPath)", level: .debug)
478 frendDamages.enumerated().forEach { (idx, damage) in
480 guard let damagePos = position(idx, in: battleFleet) else {
485 calcHP(damage: damages[damagePos], receive: damage)
487 Debug.print("FDam \(idx) -> \(damage)", level: .debug)
492 // MARK: - Damage control
493 extension DamageCalculator {
495 private func damageControlIfPossible(ship: Ship) -> Int {
497 let store = ServerDataStore.default
501 let damageControl = ship
505 .compactMap { $0 as? SlotItem }
506 .map { store.masterSlotItemID(by: $0.id) }
507 .compactMap { DamageControlID(rawValue: $0) }
510 if let validDamageControl = damageControl {
512 switch validDamageControl {
515 Debug.print("Damage Control", level: .debug)
517 return Int(Double(ship.maxhp) * 0.2)
520 Debug.print("Goddes", level: .debug)
528 let exItemId = store.masterSlotItemID(by: ship.slot_ex)
530 guard let exType = DamageControlID(rawValue: exItemId) else {
538 Debug.print("Damage Control", level: .debug)
540 return Int(Double(ship.maxhp) * 0.2)
543 Debug.print("Goddes", level: .debug)