OSDN Git Service

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