OSDN Git Service

a254d4920d5f72d6e896346e9b1fd381c7a0db67
[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: [KCDamage] {
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? KCShipObject {
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 calculateHogeki(baseKeyPath: String, _ bf: () -> BattleFleet) {
149         calculateHogeki(baseKeyPath: baseKeyPath, battleFleet: bf())
150     }
151     private func calculateHogeki(baseKeyPath: String, battleFleet: BattleFleet = .first) {
152         let j = json as NSDictionary
153         guard let data = j.value(forKeyPath: baseKeyPath) as? [String: Any],
154             let tt = data["api_df_list"] as? [Any],
155             let dd = data["api_damage"] as? [Any]
156             else { return }
157         let ttt = tt.filter { $0 is [Int] }
158         let ddd = dd.filter { $0 is [Int] }
159         guard let targetArraysArray = ttt as? [[Int]],
160             tt.count - 1 == targetArraysArray.count
161             else { return print("api_df_list is wrong") }
162         guard let hougeki1Damages = ddd as? [[Int]],
163             targetArraysArray.count == hougeki1Damages.count
164             else { return print("api_damage is wrong") }
165
166         let eFlags: [Int]? = (data["api_at_eflag"] as? [Int])?.filter { $0 != -1 }
167         
168         Debug.print("Start Hougeki \(baseKeyPath)", level: .debug)
169         let shipOffset = (battleFleet == .second) ? 6 : 0
170         targetArraysArray.enumerated().forEach { (i, targetArray) in
171             targetArray.enumerated().forEach { (j, targetPosition) in
172                 // targetは自軍か?
173                 if let e = eFlags, 0..<e.count ~= i {
174                     if e[i] != 1 { return }
175                 }
176                 else {
177                     if battleFleet == .each {
178                         guard 1...12 ~= targetPosition else { return }
179                     } else {
180                         guard 1...6 ~= targetPosition else { return }
181                     }
182                 }
183                 
184                 let damagePos = targetPosition - 1 + shipOffset
185                 guard 0..<damages.count ~= damagePos
186                     else { return print("damage pos is larger than damage count") }
187                 let damageObject = damages[damagePos]
188                 guard 0..<hougeki1Damages[i].count ~= j
189                     else { return print("target pos is larger than damages") }
190                 let damage = hougeki1Damages[i][j]
191                 let hp = damageObject.hp
192                 var newHP = (hp as Int) - damage
193                 if newHP <= 0 {
194                     let shipId = damageObject.shipID
195                     if let ship = ServerDataStore.default.ship(byId: shipId) {
196                         let efectiveHP = damageControlIfPossible(nowhp: newHP, ship: ship)
197                         if efectiveHP != 0 && efectiveHP != newHP {
198                             damageObject.useDamageControl = true
199                         }
200                         newHP = efectiveHP
201                     }
202                 }
203                 damageObject.hp = newHP
204                 
205                 Debug.print("Hougeki \(targetPosition + shipOffset) -> \(damage)", level: .debug)
206             }
207         }
208     }
209     private func calculateFDam(baseKeyPath: String, _ bf: () -> BattleFleet) {
210         calculateFDam(baseKeyPath: baseKeyPath, battleFleet: bf())
211     }
212     private func calculateFDam(baseKeyPath: String, battleFleet: BattleFleet = .first) {
213         let j = json as NSDictionary
214         guard let data = j.value(forKeyPath: baseKeyPath) as? [String: Any],
215             let koukuDamages = data["api_fdam"] as? [Int]
216             else { return }
217         
218         Debug.print("Start FDam \(baseKeyPath)", level: .debug)
219         
220         let shipOffset = (battleFleet == .second) ? 6 : 0
221         koukuDamages.enumerated().forEach { (idx, damage) in
222             if idx == 0 { return }
223             
224             let damagePos = idx - 1 + shipOffset
225             guard 0..<damages.count ~= damagePos
226                 else { return }
227             let damageObject = damages[damagePos]
228             var newHP = damageObject.hp - damage
229             if newHP <= 0 {
230                 let shipId = damageObject.shipID
231                 if let ship = ServerDataStore.default.ship(byId: shipId) {
232                     let efectiveHP = damageControlIfPossible(nowhp: newHP, ship: ship)
233                     if efectiveHP != 0 && efectiveHP != newHP {
234                         damageObject.useDamageControl = true
235                     }
236                     newHP = efectiveHP
237                 }
238                 damageObject.hp = newHP
239             }
240             damageObject.hp = newHP
241             
242             Debug.print("FDam \(idx + shipOffset) -> \(damage)", level: .debug)
243         }
244     }
245     
246     // MARK: - Battle phase
247     private func calcKouku() {
248         calculateFDam(baseKeyPath: "api_data.api_kouku.api_stage3")
249         calculateFDam(baseKeyPath: "api_data.api_kouku2.api_stage3")
250         
251         // 艦隊 戦闘艦隊
252         // 連合vs通常(水上) 第2
253         // 連合vs通常(機動) 第2
254         // 連合vs連合(水上) 第2 全体 use kouku nor kouku2
255         // 連合vs連合(機動) 第1 全体 use kouku nor kouku2
256         let bf: () -> BattleFleet = {
257             switch self.battleType {
258             case .combinedWater, .combinedAir,
259                  .eachCombinedWater, .eachCombinedAir:
260                 return .second
261             default: return .first
262             }
263         }
264         calculateFDam(baseKeyPath: "api_data.api_kouku.api_stage3_combined", bf)
265         calculateFDam(baseKeyPath: "api_data.api_kouku2.api_stage3_combined", bf)
266         
267     }
268     private func calcOpeningAttack() {
269         // 艦隊 戦闘艦隊
270         // 連合vs通常(水上) 第2
271         // 連合vs通常(機動) 第2
272         // 連合vs連合(水上) 第2 全体
273         // 連合vs連合(機動) 第2 全体
274         calculateFDam(baseKeyPath: "api_data.api_opening_atack") {
275             switch battleType {
276             case .combinedWater, .combinedAir: return .second
277             case .eachCombinedWater, .eachCombinedAir: return .each
278             default: return .first
279             }
280         }
281     }
282     private func calcOpeningTaisen() {
283         calculateHogeki(baseKeyPath: "api_data.api_opening_taisen") {
284             isCombinedBattle ? .second : .first
285         }
286     }
287     private func calcHougeki1() {
288         // 艦隊 戦闘艦隊
289         // 連合vs通常(水上) 第1
290         // 連合vs通常(機動) 第2
291         // 連合vs連合(水上) 第1
292         // 連合vs連合(機動) 第1
293         calculateHogeki(baseKeyPath: "api_data.api_hougeki1") {
294             switch battleType {
295             case .combinedAir: return .second
296             default: return .first
297             }
298         }
299     }
300     private func calcHougeki2() {
301         // 艦隊 戦闘艦隊
302         // 連合vs通常(水上) 第1
303         // 連合vs通常(機動) 第1
304         // 連合vs連合(水上) 第1 全体
305         // 連合vs連合(機動) 第2
306         calculateHogeki(baseKeyPath: "api_data.api_hougeki2") {
307             switch battleType {
308             case .eachCombinedWater: return .each
309 //            case .eachCombinedAir: return .second  // 1~12
310             default: return .first
311             }
312         }
313     }
314     private func calcHougeki3() {
315         // 艦隊 戦闘艦隊
316         // 連合vs通常(水上) 第2
317         // 連合vs通常(機動) 第1
318         // 連合vs連合(水上) 第2
319         // 連合vs連合(機動) 第1 全体
320         calculateHogeki(baseKeyPath: "api_data.api_hougeki3") {
321             switch battleType {
322             case .combinedWater: return .second
323 //            case .eachCombinedWater: return .second  // 1~12
324             case .eachCombinedAir: return .each
325             default: return .first
326             }
327         }
328     }
329     private func calcRaigeki() {
330         // 艦隊 戦闘艦隊
331         // 連合vs通常(水上) 第2
332         // 連合vs通常(機動) 第2
333         // 連合vs連合(水上) 第2 全体
334         // 連合vs連合(機動) 第2 全体
335         calculateFDam(baseKeyPath: "api_data.api_raigeki") {
336             switch battleType {
337             case .combinedWater, .combinedAir: return .second
338             case .eachCombinedWater, .eachCombinedAir: return .each
339             default: return .first
340             }
341         }
342     }
343     
344     private func calculateMidnightBattle() {
345         // 艦隊 戦闘艦隊
346         // 連合vs通常(水上) 第2
347         // 連合vs通常(機動) 第2
348         // 連合vs連合(水上) 第2
349         // 連合vs連合(機動) 第2
350         calculateHogeki(baseKeyPath: "api_data.api_hougeki") {
351             isCombinedBattle ? .second : .first
352         }
353     }
354     
355     // MARK: - Battle type
356     private func calculateBattle() {
357         updateBattleCell()
358         
359         calcKouku()
360         calcOpeningTaisen()
361         calcOpeningAttack()
362         calcHougeki1()
363         calcHougeki2()
364         calcHougeki3()
365         calcRaigeki()
366     }
367     private func calcCombinedBattleAir() {
368         updateBattleCell()
369         
370         calcKouku()
371         calcOpeningTaisen()
372         calcOpeningAttack()
373         calcHougeki1()
374         calcRaigeki()
375         calcHougeki2()
376         calcHougeki3()
377     }
378     private func calcEachBattleAir() {
379         updateBattleCell()
380         
381         calcKouku()
382         calcOpeningTaisen()
383         calcOpeningAttack()
384         calcHougeki1()
385         calcHougeki2()
386         calcRaigeki()
387         calcHougeki3()
388     }
389     private func calcEnemyCombinedBattle() {
390         // same phase as combined air
391         calcCombinedBattleAir()
392     }
393     
394     // MARK: - Damage control
395     private func damageControlIfPossible(nowhp: Int, ship: KCShipObject) -> Int {
396         var nowHp = nowhp
397         if nowHp < 0 { nowHp = 0 }
398         let maxhp = ship.maxhp
399         let store = ServerDataStore.default
400         var useDamageControl = false
401         ship.equippedItem.forEach {
402             if useDamageControl { return }
403             guard let master = $0 as? KCSlotItemObject else { return }
404             let masterSlotItemId = store.masterSlotItemID(bySlotItemId: master.id)
405             guard let type = DamageControlID(rawValue: masterSlotItemId)
406                 else { return }
407             switch type {
408             case .damageControl:
409                 nowHp = Int(Double(maxhp) * 0.2)
410                 useDamageControl = true
411             case .goddes:
412                 nowHp = maxhp
413                 useDamageControl = true
414             }
415         }
416         if useDamageControl { return nowHp }
417         
418         let exItemId = store.masterSlotItemID(bySlotItemId: ship.slot_ex)
419         guard let exType = DamageControlID(rawValue: exItemId)
420             else { return nowHp }
421         switch exType {
422         case .damageControl:
423             nowHp = Int(Double(maxhp) * 0.2)
424         case .goddes:
425             nowHp = maxhp
426         }
427         return nowHp
428     }
429     private func removeFirstDamageControl(of ship: KCShipObject) {
430         let equiped = ship.equippedItem
431         let newEquiped = equiped.mutableCopy() as! NSMutableOrderedSet
432         let store = ServerDataStore.default
433         var useDamageControl = false
434         equiped.forEach {
435             if useDamageControl { return }
436             guard let master = $0 as? KCSlotItemObject else { return }
437             let masterSlotItemId = store.masterSlotItemID(bySlotItemId: master.id)
438             guard let type = DamageControlID(rawValue: masterSlotItemId)
439                 else { return }
440             switch type {
441             case .goddes:
442                 ship.fuel = ship.maxFuel
443                 ship.bull = ship.maxBull
444                 fallthrough
445             case .damageControl:
446                 newEquiped.remove(master)
447                 useDamageControl = true
448             }
449         }
450         if useDamageControl {
451             ship.equippedItem = newEquiped
452             return
453         }
454         
455         let exItemId = store.masterSlotItemID(bySlotItemId: ship.slot_ex)
456         guard let exType = DamageControlID(rawValue: exItemId)
457             else { return }
458         switch exType {
459         case .goddes:
460             ship.fuel = ship.maxFuel
461             ship.bull = ship.maxBull
462             fallthrough
463         case .damageControl:
464             ship.slot_ex = -1
465         }
466     }
467 }