OSDN Git Service

スクリーンショットの撮影にSierraまでは今までの実装を使用するようにした
[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.count != 12 {
233             
234             buildDamagedEntity()
235             
236             let newDamages = store.sortedDamagesById()
237             
238             guard newDamages.count == 12 else {
239                 
240                 return Logger.shared.log("ERROR!!!! CAN NOT CREATE DAMAGE OBJECT", value: [])
241             }
242             
243             return newDamages
244         }
245         
246         return array
247     }
248     
249     private var isCombinedBattle: Bool {
250         
251         switch battleType {
252         case .combinedAir, .combinedWater, .eachCombinedAir, .eachCombinedWater:
253             return true
254             
255         default:
256             return false
257         }
258     }
259     
260     private func buildDamagesOfFleet(fleet: Int, ships: [Ship]) {
261         
262         guard case 0...1 = fleet else { return Logger.shared.log("fleet must 0 or 1.") }
263         guard let battle = store.battle() else { return Logger.shared.log("Battle is invalid.") }
264         
265         (0..<6).forEach {
266             
267             guard let damage = store.createDamage() else { return Logger.shared.log("Can not create Damage") }
268             
269             damage.battle = battle
270             damage.id = $0 + fleet * 6
271             
272             guard case 0..<ships.count = $0 else { return }
273             
274             damage.hp = ships[$0].nowhp
275             damage.shipID = ships[$0].id
276         }
277     }
278     
279     private func buildDamagedEntity() {
280         
281         guard let battle = store.battle() else { return Logger.shared.log("Battle is invalid.") }
282         
283         // 第一艦隊
284         let firstFleetShips = ServerDataStore.default.ships(byDeckId: battle.deckId)
285         buildDamagesOfFleet(fleet: 0, ships: firstFleetShips)
286         
287         // 第二艦隊
288         let secondFleetShips = ServerDataStore.default.ships(byDeckId: 2)
289         buildDamagesOfFleet(fleet: 1, ships: secondFleetShips)
290     }
291 }
292
293 // MARK: - Primitive Calclator
294 extension DamageCalculator {
295     
296     private func hogekiTargets(_ list: JSON) -> [[Int]]? {
297         
298         guard let targetArraysArray = list
299             .array?
300             .flatMap({ $0.array?.flatMap { $0.int } }) else {
301                 
302                 return nil
303         }
304         
305         guard list.count - 1 == targetArraysArray.count else {
306             
307             return Logger.shared.log("api_df_list is wrong", value: nil)
308         }
309         
310         return targetArraysArray
311     }
312     
313     private func hogekiDamages(_ list: JSON) -> [[Int]]? {
314         
315         return list.array?.flatMap { $0.array?.flatMap { $0.int } }
316     }
317     
318     private func enemyFlags(_ list: JSON) -> [Int]? {
319         
320         return list.array?.flatMap { $0.int }.filter { $0 != -1 }
321     }
322     
323     private func isTargetFriend(eFlags: [Int]?, index: Int) -> Bool {
324         
325         if let eFlags = eFlags, 0..<eFlags.count ~= index {
326             
327             return eFlags[index] == 1
328         }
329         
330         return true
331     }
332     
333     private func validTargetPos(_ targetPos: Int, in battleFleet: BattleFleet) -> Bool {
334         
335         let upper = (battleFleet == .each ? 12 : 6)
336         
337         return 1...upper ~= targetPos
338     }
339     
340     private func position(_ pos: Int, in fleet: BattleFleet) -> Int? {
341         
342         let shipOffset = (fleet == .second) ? 6 : 0
343         
344         let damagePos = pos - 1 + shipOffset
345         
346         guard case 0..<damages.count = damagePos else { return nil }
347         
348         return damagePos
349     }
350     
351     private func calcHP(damage: Damage, receive: Int) {
352         
353         damage.hp -= receive
354         
355         if damage.hp > 0 { return }
356         
357         guard let ship = ServerDataStore.default.ship(by: damage.shipID) else { return }
358         
359         damage.hp = damageControlIfPossible(ship: ship)
360         if damage.hp != 0 {
361             
362             damage.useDamageControl = true
363         }
364     }
365     
366     private func calculateHogeki(baseKeyPath: String, _ bf: () -> BattleFleet) {
367         
368         calculateHogeki(baseKeyPath: baseKeyPath, battleFleet: bf())
369     }
370     
371     private func calculateHogeki(baseKeyPath: String, battleFleet: BattleFleet = .first) {
372         
373         let baseValue = json[baseKeyPath.components(separatedBy: ".")]
374         
375         guard let targetPosLists = hogekiTargets(baseValue["api_df_list"]),
376             let damageLists = hogekiDamages(baseValue["api_damage"]) else {
377                 
378                 Debug.print("Cound not find api_df_list or api_damage for \(baseKeyPath)", level: .full)
379                 return
380         }
381         
382         guard targetPosLists.count == damageLists.count else { return Logger.shared.log("api_damage is wrong.") }
383         
384         let eFlags = enemyFlags(baseValue["api_at_eflag"])
385         
386         Debug.print("Start Hougeki \(baseKeyPath)", level: .debug)
387         
388         zip(targetPosLists, damageLists).enumerated().forEach { (i, list) in
389             
390             if !isTargetFriend(eFlags: eFlags, index: i) {
391                 
392                 Debug.excute(level: .debug) {
393                     
394                     print("target is enemy")
395                 }
396                 
397                 return
398             }
399             
400             zip(list.0, list.1).forEach { (targetPos, damage) in
401                 
402                 guard validTargetPos(targetPos, in: battleFleet) else { return }
403                 
404                 guard let damagePos = position(targetPos, in: battleFleet) else {
405                     
406                     return Logger.shared.log("damage pos is larger than damage count")
407                 }
408                 
409                 calcHP(damage: damages[damagePos], receive: damage)
410                 
411                 Debug.excute(level: .debug) {
412                     
413                     let shipOffset = (battleFleet == .second) ? 6 : 0
414                     print("Hougeki \(targetPos + shipOffset) -> \(damage)")
415                 }
416             }
417         }
418     }
419     
420     private func calculateFDam(baseKeyPath: String, _ bf: () -> BattleFleet) {
421         
422         calculateFDam(baseKeyPath: baseKeyPath, battleFleet: bf())
423     }
424     
425     private func calculateFDam(baseKeyPath: String, battleFleet: BattleFleet = .first) {
426         
427         let baseValue = json[baseKeyPath.components(separatedBy: ".")]
428         
429         guard let fdamArray = baseValue["api_fdam"].arrayObject else {
430
431             Debug.print("Could not find api_fdam of \(baseKeyPath)", level: .full)
432             
433             return
434         }
435         
436         guard let IntFdamArray = fdamArray as? [IntConvertable] else {
437             
438             Debug.print("api_fdam value of \(baseKeyPath) is not [Int].", level: .debug)
439             Debug.print(baseValue, level: .debug)
440             
441             return
442         }
443         let frendDamages = IntFdamArray.map { $0.toInt() }
444         
445         Debug.print("Start FDam \(baseKeyPath)", level: .debug)
446         
447         frendDamages.enumerated().forEach { (idx, damage) in
448             
449             if idx == 0 { return }
450             
451             guard let damagePos = position(idx, in: battleFleet) else { return }
452             
453             calcHP(damage: damages[damagePos], receive: damage)
454             
455             Debug.excute(level: .debug) {
456                 
457                 let shipOffset = (battleFleet == .second) ? 6 : 0
458                 print("FDam \(idx + shipOffset) -> \(damage)")
459             }
460         }
461     }
462 }
463
464 // MARK: - Damage control
465 extension DamageCalculator {
466     
467     private func damageControlIfPossible(ship: Ship) -> Int {
468         
469         let store = ServerDataStore.default
470         
471         let damageControl = ship
472             .equippedItem
473             .lazy
474             .flatMap { $0 as? SlotItem }
475             .map { store.masterSlotItemID(by: $0.id) }
476             .flatMap { DamageControlID(rawValue: $0) }
477             .first
478         
479         if let validDamageControl = damageControl {
480             
481             switch validDamageControl {
482             case .damageControl:
483                 return Int(Double(ship.maxhp) * 0.2)
484                 
485             case .goddes:
486                 return ship.maxhp
487             }
488         }
489         
490         // check extra slot
491         let exItemId = store.masterSlotItemID(by: ship.slot_ex)
492         
493         guard let exType = DamageControlID(rawValue: exItemId) else { return 0 }
494         
495         switch exType {
496         case .damageControl:
497             return Int(Double(ship.maxhp) * 0.2)
498             
499         case .goddes:
500             return ship.maxhp
501         }
502     }
503 }