OSDN Git Service

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