OSDN Git Service

洋上補給の補強増設用のショートネームをつけた
[kcd/KCD.git] / KCD / DamageCalculator.swift
1 //
2 //  DamageCalculator.swift
3 //  KCD
4 //
5 //  Created by Hori,Masaki on 2017/07/23.
6 //  Copyright © 2017年 Hori,Masaki. All rights reserved.
7 //
8
9 import Cocoa
10 import SwiftyJSON
11
12 enum BattleFleet {
13     
14     case normal
15     case secondOnly
16 }
17
18 final class DamageCalculator {
19     
20     private let store = TemporaryDataStore.oneTimeEditor()
21     
22     private let json: JSON
23     private let battleType: BattleType
24     
25     init(_ json: JSON, _ battleType: BattleType = .normal) {
26         
27         self.battleType = battleType
28         self.json = json
29     }
30 }
31
32 // MARK: - Battle type
33 extension DamageCalculator {
34     
35     func calculateBattle() {
36         
37         calcKouku()
38         calcOpeningTaisen()
39         calcOpeningAttack()
40         calcHougeki1()
41         calcHougeki2()
42         calcHougeki3()
43         calcRaigeki()
44     }
45     
46     func calcCombinedBattleAir() {
47         
48         calcKouku()
49         calcOpeningTaisen()
50         calcOpeningAttack()
51         calcHougeki1()
52         calcRaigeki()
53         calcHougeki2()
54         calcHougeki3()
55     }
56     
57     func calcEachBattleAir() {
58         
59         calcKouku()
60         calcOpeningTaisen()
61         calcOpeningAttack()
62         calcHougeki1()
63         calcHougeki2()
64         calcRaigeki()
65         calcHougeki3()
66     }
67     
68     func calcEachNightToDay() {
69         
70         calcNightHogeki1()
71         calcNightHogeki2()
72         calcKouku()
73         calcOpeningTaisen()
74         calcOpeningAttack()
75         calcHougeki1()
76         calcHougeki2()
77         calcRaigeki()
78         calcHougeki3()
79     }
80     
81     func calcEnemyCombinedBattle() {
82         
83         // same phase as combined air
84         calcCombinedBattleAir()
85     }
86     
87     func calcMidnight() {
88         
89         calculateMidnightBattle()
90     }
91 }
92
93 // MARK: - Battle phase
94 extension DamageCalculator {
95     
96     private func calcKouku() {
97         
98         calculateFDam(baseKeyPath: "api_data.api_kouku.api_stage3")
99         calculateFDam(baseKeyPath: "api_data.api_kouku2.api_stage3")
100         
101         let bf: () -> BattleFleet = {
102             
103             switch self.battleType {
104             case .combinedWater,
105                  .combinedAir,
106                  .eachCombinedWater,
107                  .eachCombinedAir:
108                 return .secondOnly
109                 
110             default: return .normal
111             }
112         }
113         calculateFDam(baseKeyPath: "api_data.api_kouku.api_stage3_combined", bf)
114         calculateFDam(baseKeyPath: "api_data.api_kouku2.api_stage3_combined", bf)
115         
116     }
117     
118     private func calcOpeningAttack() {
119         
120         calculateFDam(baseKeyPath: "api_data.api_opening_atack")
121     }
122     
123     private func calcOpeningTaisen() {
124         
125         calculateHogeki(baseKeyPath: "api_data.api_opening_taisen")
126     }
127     
128     private func calcHougeki1() {
129         
130         calculateHogeki(baseKeyPath: "api_data.api_hougeki1")
131     }
132     
133     private func calcHougeki2() {
134         
135         calculateHogeki(baseKeyPath: "api_data.api_hougeki2")
136     }
137     
138     private func calcHougeki3() {
139         
140         calculateHogeki(baseKeyPath: "api_data.api_hougeki3")
141     }
142     
143     private func calcNightHogeki1() {
144         
145         calculateHogeki(baseKeyPath: "api_data.api_n_hougeki1")
146     }
147     
148     private func calcNightHogeki2() {
149         
150         calculateHogeki(baseKeyPath: "api_data.api_n_hougeki2")
151     }
152     
153     private func calcRaigeki() {
154         
155         calculateFDam(baseKeyPath: "api_data.api_raigeki")
156     }
157     
158     private func calculateMidnightBattle() {
159         
160         calculateHogeki(baseKeyPath: "api_data.api_hougeki")
161     }
162 }
163
164 // MARK: - Properties
165 extension DamageCalculator {
166     
167     private var damages: [Damage] {
168         
169         let array = store.sortedDamagesById()
170         
171         if array.isEmpty {
172             
173             buildDamagedEntity()
174             
175             return store.sortedDamagesById()
176         }
177         
178         return array
179     }
180     
181     private var isCombinedBattle: Bool {
182         
183         switch battleType {
184         case .combinedAir, .combinedWater, .eachCombinedAir, .eachCombinedWater:
185             return true
186             
187         default:
188             return false
189         }
190     }
191     
192     private func makeDamage(num: Int) -> [Damage] {
193         
194         guard let battle = store.battle() else { return Logger.shared.log("Battle is invalid.", value: []) }
195         
196         return (0..<num).flatMap {
197             
198             guard let damage = store.createDamage() else { return Logger.shared.log("Can not create Damage", value: nil) }
199             
200             damage.battle = battle
201             damage.id = $0
202             
203             return damage
204         }
205     }
206     
207     private func buildDamages(first: [Ship], second: [Ship]?) {
208         
209         guard let battle = store.battle() else { return Logger.shared.log("Battle is invalid.") }
210         
211         let damages = makeDamage(num: 12)
212         
213         func setShip(_ ship: Ship, into damage: Damage) {
214             
215             damage.shipID = ship.id
216             damage.hp = ship.nowhp
217             
218             Debug.excute(level: .debug) {
219                 
220                 print("add Damage entity of \(ship.name) at \(damage.id)")
221             }
222         }
223         
224         zip(first, damages).forEach(setShip)
225
226         if let second = second {
227
228             let secondsDamage = damages[6...]
229             zip(second, secondsDamage).forEach(setShip)
230         }
231         
232     }
233     
234     private func buildDamagedEntity() {
235         
236         guard let battle = store.battle() else { return Logger.shared.log("Battle is invalid.") }
237         
238         // 第一艦隊
239         let firstFleetShips = ServerDataStore.default.ships(byDeckId: battle.deckId)
240         
241         // 第二艦隊
242         if isCombinedBattle {
243             
244             let secondFleetShips = ServerDataStore.default.ships(byDeckId: 2)
245             buildDamages(first: firstFleetShips, second: secondFleetShips)
246         } else {
247             
248             buildDamages(first: firstFleetShips, second: nil)
249         }
250     }
251 }
252
253 // MARK: - Primitive Calclator
254 extension DamageCalculator {
255     
256     private func hogekiTargets(_ list: JSON) -> [[Int]]? {
257         
258         guard let targetArraysArray = list
259             .array?
260             .flatMap({ $0.array?.flatMap { $0.int } }) else {
261                 
262                 return nil
263         }
264         
265         guard list.count == targetArraysArray.count else {
266             
267             return Logger.shared.log("api_df_list is wrong", value: nil)
268         }
269         
270         return targetArraysArray
271     }
272     
273     private func hogekiDamages(_ list: JSON) -> [[Int]]? {
274         
275         return list.array?.flatMap { $0.array?.flatMap { $0.int } }
276     }
277     
278     private func enemyFlags(_ list: JSON) -> [Int]? {
279         
280         return list.array?.flatMap { $0.int }
281     }
282     
283     private func validTargetPos(_ targetPos: Int, in battleFleet: BattleFleet) -> Bool {
284         
285         return 0..<damages.count ~= targetPos
286     }
287     
288     private func position(_ pos: Int, in fleet: BattleFleet) -> Int? {
289         
290         let damagePos = (fleet == .secondOnly ? pos + 6 : pos)
291         
292         guard case 0..<damages.count = damagePos else { return nil }
293         
294         return damagePos
295     }
296     
297     private func calcHP(damage: Damage, receive: Int) {
298         
299         Debug.excute(level: .debug) {
300             
301             if receive != 0, let ship = ServerDataStore.default.ship(by: damage.shipID) {
302                 
303                 print("\(ship.name) recieve Damage \(receive)")
304             }
305         }
306         
307         damage.hp -= receive
308         
309         if damage.hp > 0 { return }
310         
311         guard let ship = ServerDataStore.default.ship(by: damage.shipID) else { return }
312         
313         damage.hp = damageControlIfPossible(ship: ship)
314         damage.useDamageControl = (damage.hp != 0)
315         
316     }
317     
318     private func calculateHogeki(baseKeyPath: String, _ bf: () -> BattleFleet) {
319         
320         calculateHogeki(baseKeyPath: baseKeyPath, battleFleet: bf())
321     }
322     
323     private func omitEnemyDamage(targetPosLists: [[Int]], damageLists: [[Int]], eFlags: [Int]?) -> [([Int], [Int])] {
324         
325         guard let eFlags = eFlags else { return zip(targetPosLists, damageLists).map { $0 } }
326         
327         return zip(zip(targetPosLists, damageLists), eFlags).filter { $0.1 == 1 }.map { $0.0 }
328     }
329     
330     private func calculateHogeki(baseKeyPath: String, battleFleet: BattleFleet = .normal) {
331         
332         let baseValue = json[baseKeyPath.components(separatedBy: ".")]
333         
334         guard let targetPosLists = hogekiTargets(baseValue["api_df_list"]),
335             let damageLists = hogekiDamages(baseValue["api_damage"]) else {
336                 
337                 Debug.print("Cound not find api_df_list or api_damage for \(baseKeyPath)", level: .full)
338                 return
339         }
340         
341         guard targetPosLists.count == damageLists.count else { return Logger.shared.log("api_damage is wrong.") }
342         
343         Debug.print("Start Hougeki \(baseKeyPath)", level: .debug)
344         
345         omitEnemyDamage(targetPosLists: targetPosLists, damageLists: damageLists, eFlags: enemyFlags(baseValue["api_at_eflag"]))
346             .map { (targetPosList, damageList) -> (Int, Int) in
347                 
348                 guard let pos = targetPosList.first else { return (0, 0) }
349                 
350                 return (pos, damageList.filter { $0 > 0 }.reduce(0, +))
351             }
352             .forEach { (targetPos, damage) in
353                 
354                 guard validTargetPos(targetPos, in: battleFleet) else { return Logger.shared.log("invalid position \(targetPos)") }
355                 
356                 guard let damagePos = position(targetPos, in: battleFleet) else {
357                     
358                     return Logger.shared.log("damage pos is larger than damage count")
359                 }
360                 
361                 calcHP(damage: damages[damagePos], receive: damage)
362                 
363                 Debug.print("Hougeki \(targetPos) -> \(damage)", level: .debug)
364         }
365     }
366     
367     private func calculateFDam(baseKeyPath: String, _ bf: () -> BattleFleet) {
368         
369         calculateFDam(baseKeyPath: baseKeyPath, battleFleet: bf())
370     }
371     
372     private func calculateFDam(baseKeyPath: String, battleFleet: BattleFleet = .normal) {
373         
374         let baseValue = json[baseKeyPath.components(separatedBy: ".")]
375         
376         guard let fdamArray = baseValue["api_fdam"].arrayObject else {
377
378             Debug.print("Could not find api_fdam of \(baseKeyPath)", level: .full)
379             
380             return
381         }
382         
383         guard let IntFdamArray = fdamArray as? [IntConvertable] else {
384             
385             Debug.print("api_fdam value of \(baseKeyPath) is not [Int].", level: .debug)
386             Debug.print(baseValue, level: .debug)
387             
388             return
389         }
390         let frendDamages = IntFdamArray.map { $0.toInt() }
391         
392         Debug.print("Start FDam \(baseKeyPath)", level: .debug)
393         
394         frendDamages.enumerated().forEach { (idx, damage) in
395             
396             guard let damagePos = position(idx, in: battleFleet) else { return }
397             
398             calcHP(damage: damages[damagePos], receive: damage)
399             
400             Debug.print("FDam \(idx) -> \(damage)", level: .debug)
401         }
402     }
403 }
404
405 // MARK: - Damage control
406 extension DamageCalculator {
407     
408     private func damageControlIfPossible(ship: Ship) -> Int {
409         
410         let store = ServerDataStore.default
411         
412         let damageControl = ship
413             .equippedItem
414             .lazy
415             .flatMap { $0 as? SlotItem }
416             .map { store.masterSlotItemID(by: $0.id) }
417             .flatMap { DamageControlID(rawValue: $0) }
418             .first
419         
420         if let validDamageControl = damageControl {
421             
422             switch validDamageControl {
423             case .damageControl:
424                 Debug.print("Damage Control", level: .debug)
425                 return Int(Double(ship.maxhp) * 0.2)
426                 
427             case .goddes:
428                 Debug.print("Goddes", level: .debug)
429                 return ship.maxhp
430             }
431         }
432         
433         // check extra slot
434         let exItemId = store.masterSlotItemID(by: ship.slot_ex)
435         
436         guard let exType = DamageControlID(rawValue: exItemId) else { return 0 }
437         
438         switch exType {
439         case .damageControl:
440             Debug.print("Damage Control", level: .debug)
441             return Int(Double(ship.maxhp) * 0.2)
442             
443         case .goddes:
444             Debug.print("Goddes", level: .debug)
445             return ship.maxhp
446         }
447     }
448 }