OSDN Git Service

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