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