2 // DamageCalculator.swift
5 // Created by Hori,Masaki on 2017/07/23.
6 // Copyright © 2017年 Hori,Masaki. All rights reserved.
19 private struct PositionedDamage {
24 static let zero = PositionedDamage(position: 0, damage: 0)
27 extension PositionedDamage: Equatable {
29 static func == (lhs: PositionedDamage, rhs: PositionedDamage) -> Bool {
31 return lhs.position == rhs.position && lhs.damage == rhs.damage
35 private struct HogekiBattleData {
37 let targetPositionList: [Int]
42 private func friendDamage(_ data: HogekiBattleData) -> PositionedDamage {
44 guard !data.enemyFlags else { return .zero }
46 guard let pos = data.targetPositionList.first else { return .zero }
48 return PositionedDamage(position: pos,
49 damage: data.damageList.filter({ $0 > 0 }).reduce(0, +))
52 final class DamageCalculator {
54 private let store = TemporaryDataStore.oneTimeEditor()
56 private let json: JSON
57 private let battleType: BattleType
59 init(_ json: JSON, _ battleType: BattleType = .normal) {
61 self.battleType = battleType
66 // MARK: - Battle type
67 extension DamageCalculator {
69 func calculateBattle() {
74 self.calcOpeningTaisen()
75 self.calcOpeningAttack()
83 func calcCombinedBattleAir() {
88 self.calcOpeningTaisen()
89 self.calcOpeningAttack()
97 func calcEachBattleAir() {
102 self.calcOpeningTaisen()
103 self.calcOpeningAttack()
111 func calcEachNightToDay() {
115 self.calcNightHogeki1()
116 self.calcNightHogeki2()
118 self.calcOpeningTaisen()
119 self.calcOpeningAttack()
127 func calcEnemyCombinedBattle() {
129 // same phase as combined air
130 calcCombinedBattleAir()
133 func calcMidnight() {
137 self.calculateMidnightBattle()
142 // MARK: - Battle phase
143 extension DamageCalculator {
145 private func calcKouku() {
147 calculateFDam(baseKeyPath: "api_data.api_kouku.api_stage3")
148 calculateFDam(baseKeyPath: "api_data.api_kouku2.api_stage3")
150 let bf: () -> BattleFleet = {
152 switch self.battleType {
167 calculateFDam(baseKeyPath: "api_data.api_kouku.api_stage3_combined", bf)
168 calculateFDam(baseKeyPath: "api_data.api_kouku2.api_stage3_combined", bf)
172 private func calcOpeningAttack() {
174 calculateFDam(baseKeyPath: "api_data.api_opening_atack")
177 private func calcOpeningTaisen() {
179 calculateHogeki(baseKeyPath: "api_data.api_opening_taisen")
182 private func calcHougeki1() {
184 calculateHogeki(baseKeyPath: "api_data.api_hougeki1")
187 private func calcHougeki2() {
189 calculateHogeki(baseKeyPath: "api_data.api_hougeki2")
192 private func calcHougeki3() {
194 calculateHogeki(baseKeyPath: "api_data.api_hougeki3")
197 private func calcNightHogeki1() {
199 calculateHogeki(baseKeyPath: "api_data.api_n_hougeki1")
202 private func calcNightHogeki2() {
204 calculateHogeki(baseKeyPath: "api_data.api_n_hougeki2")
207 private func calcRaigeki() {
209 calculateFDam(baseKeyPath: "api_data.api_raigeki")
212 private func calculateMidnightBattle() {
214 calculateHogeki(baseKeyPath: "api_data.api_hougeki")
218 // MARK: - Properties
219 extension DamageCalculator {
221 private var damages: [Damage] {
223 let array = store.sortedDamagesById()
229 return store.sortedDamagesById()
235 private var isCombinedBattle: Bool {
239 case .combinedAir, .combinedWater, .eachCombinedAir, .eachCombinedWater:
250 private func makeDamage(num: Int) -> [Damage] {
252 guard let battle = store.battle() else {
254 Logger.shared.log("Battle is invalid.")
259 return (0..<num).compactMap {
261 guard let damage = store.createDamage() else {
263 Logger.shared.log("Can not create Damage")
268 damage.battle = battle
275 private func buildDamages(first: [Ship], second: [Ship]?) {
277 guard let battle = store.battle() else {
279 Logger.shared.log("Battle is invalid.")
284 let damages = makeDamage(num: 12)
286 func setShip(_ ship: Ship, into damage: Damage) {
288 let sStore = ServerDataStore.default
290 damage.shipID = sStore.sync { ship.id }
291 damage.hp = sStore.sync { ship.nowhp }
293 Debug.excute(level: .debug) {
295 let name = sStore.sync { ship.name }
296 print("add Damage entity of \(name) at \(damage.id)")
300 zip(first, damages).forEach(setShip)
302 if let second = second {
304 let secondsDamage = damages[6...]
305 zip(second, secondsDamage).forEach(setShip)
310 private func buildDamagedEntity() {
312 guard let battle = store.battle() else {
314 Logger.shared.log("Battle is invalid.")
319 let sStore = ServerDataStore.default
321 let deckId = battle.deckId
322 let firstFleetShips = sStore.sync { sStore.ships(byDeckId: deckId) }
325 if isCombinedBattle {
327 let secondFleetShips = sStore.sync { sStore.ships(byDeckId: 2) }
328 buildDamages(first: firstFleetShips, second: secondFleetShips)
332 buildDamages(first: firstFleetShips, second: nil)
337 // MARK: - Primitive Calclator
338 extension DamageCalculator {
340 private func hogekiTargets(_ list: JSON) -> [[Int]]? {
342 guard let targetArraysArray = list
344 .compactMap({ $0.array?.compactMap { $0.int } }) else {
349 guard list.count == targetArraysArray.count else {
351 Logger.shared.log("api_df_list is wrong")
356 return targetArraysArray
359 private func hogekiDamages(_ list: JSON) -> [[Int]]? {
361 return list.array?.compactMap { $0.array?.compactMap { $0.int } }
364 private func enemyFlags(_ list: JSON) -> [Int]? {
366 return list.array?.compactMap { $0.int }
369 private func validTargetPos(_ targetPos: Int, in battleFleet: BattleFleet) -> Bool {
371 return 0..<damages.count ~= targetPos
374 private func position(_ pos: Int, in fleet: BattleFleet) -> Int? {
376 let damagePos = (fleet == .secondOnly ? pos + 6 : pos)
378 guard case 0..<damages.count = damagePos else {
386 private func calcHP(damage: Damage, receive: Int) {
388 Debug.excute(level: .debug) {
390 let store = ServerDataStore.default
391 if receive != 0, let shipName = store.sync(execute: { store.ship(by: damage.shipID)?.name }) {
393 print("\(shipName) recieve Damage \(receive)")
404 let sStore = ServerDataStore.default
405 guard let ship = sStore.sync(execute: { sStore.ship(by: damage.shipID) }) else {
410 damage.hp = damageControlIfPossible(ship: ship)
411 damage.useDamageControl = (damage.hp != 0)
415 private func calculateHogeki(baseKeyPath: String, _ bf: () -> BattleFleet) {
417 calculateHogeki(baseKeyPath: baseKeyPath, battleFleet: bf())
420 private func buildBattleData(baseKeyPath: String) -> [HogekiBattleData] {
422 let baseValue = json[baseKeyPath.components(separatedBy: ".")]
424 guard let targetPosLists = hogekiTargets(baseValue["api_df_list"]),
425 let damageLists = hogekiDamages(baseValue["api_damage"]) else {
427 Debug.print("Cound not find api_df_list or api_damage for \(baseKeyPath)", level: .full)
432 guard targetPosLists.count == damageLists.count else {
434 Logger.shared.log("api_damage is wrong.")
439 guard let eFlags = enemyFlags(baseValue["api_at_eflag"]) else {
441 return zip(targetPosLists, damageLists).map { HogekiBattleData(targetPositionList: $0.0, damageList: $0.1, enemyFlags: false) }
444 return zip(zip(targetPosLists, damageLists), eFlags)
445 .map { arg -> HogekiBattleData in
447 let ((targetPosList, damageList), eflag) = arg
449 return HogekiBattleData(targetPositionList: targetPosList, damageList: damageList, enemyFlags: eflag != 1)
453 private func calculateHogeki(baseKeyPath: String, battleFleet: BattleFleet = .normal) {
455 Debug.print("Start Hougeki \(baseKeyPath)", level: .debug)
457 buildBattleData(baseKeyPath: baseKeyPath)
459 .filter { $0 != .zero }
460 .forEach { posDamage in
462 guard validTargetPos(posDamage.position, in: battleFleet) else {
464 Logger.shared.log("invalid position \(posDamage.position)")
469 guard let damagePos = position(posDamage.position, in: battleFleet) else {
471 Logger.shared.log("damage pos is larger than damage count")
476 calcHP(damage: damages[damagePos], receive: posDamage.damage)
478 Debug.print("Hougeki \(posDamage.position) -> \(posDamage.damage)", level: .debug)
482 private func calculateFDam(baseKeyPath: String, _ bf: () -> BattleFleet) {
484 calculateFDam(baseKeyPath: baseKeyPath, battleFleet: bf())
487 private func calculateFDam(baseKeyPath: String, battleFleet: BattleFleet = .normal) {
489 let baseValue = json[baseKeyPath.components(separatedBy: ".")]
491 guard let fdamArray = baseValue["api_fdam"].arrayObject else {
493 Debug.print("Could not find api_fdam of \(baseKeyPath)", level: .full)
498 guard let intFdamArray = fdamArray as? [IntConvertable] else {
500 Debug.print("api_fdam value of \(baseKeyPath) is not [Int].", level: .debug)
501 Debug.print(baseValue, level: .debug)
506 let frendDamages = intFdamArray.map { $0.toInt() }
508 Debug.print("Start FDam \(baseKeyPath)", level: .debug)
510 frendDamages.enumerated().forEach { (idx, damage) in
512 guard let damagePos = position(idx, in: battleFleet) else {
517 calcHP(damage: damages[damagePos], receive: damage)
519 Debug.print("FDam \(idx) -> \(damage)", level: .debug)
524 // MARK: - Damage control
525 extension DamageCalculator {
527 private func damageControlIfPossible(ship: Ship) -> Int {
529 let store = ServerDataStore.default
533 let damageControl = ship
537 .compactMap { $0 as? SlotItem }
538 .map { store.masterSlotItemID(by: $0.id) }
539 .compactMap { DamageControlID(rawValue: $0) }
542 if let validDamageControl = damageControl {
544 switch validDamageControl {
547 Debug.print("Damage Control", level: .debug)
549 return Int(Double(ship.maxhp) * 0.2)
552 Debug.print("Goddes", level: .debug)
560 let exItemId = store.masterSlotItemID(by: ship.slot_ex)
562 guard let exType = DamageControlID(rawValue: exItemId) else {
570 Debug.print("Damage Control", level: .debug)
572 return Int(Double(ship.maxhp) * 0.2)
575 Debug.print("Goddes", level: .debug)