OSDN Git Service

Swift4.1に更新
[kcd/KCD.git] / KCD / FleetViewController.swift
1 //
2 //  FleetViewController.swift
3 //  KCD
4 //
5 //  Created by Hori,Masaki on 2016/12/27.
6 //  Copyright © 2016年 Hori,Masaki. All rights reserved.
7 //
8
9 import Cocoa
10
11 enum FleetViewType: Int {
12     
13     case detailViewType = 0
14     case minimumViewType = 1
15     case miniVierticalType = 2
16 }
17
18 private var shipKeysContext: Int = 0
19 private var shipsContext: Int = 0
20
21 protocol FleetViewControllerDelegate: class {
22     
23     func changeShowsExtShip(_ fleetViewController: FleetViewController, showsExtShip: Bool)
24 }
25
26 final class FleetViewController: NSViewController {
27     
28     enum ShipOrder: Int {
29         
30         case doubleLine = 0
31         case leftToRight = 1
32     }
33     
34     enum SakutekiType: Int {
35         
36         case adding = 0
37         
38         case formula33 = 100
39         case formula33Parameter1 = 101
40         case formula33Parameter3 = 103
41         case formula33Parameter4 = 104
42     }
43     
44     enum SakutekiCalclationSterategy: Int {
45         
46         case total
47         case formula33
48     }
49     
50     static let oldStyleFleetViewHeight: CGFloat = 128.0
51     static let detailViewHeight: CGFloat = 320.0
52     static let heightDifference: CGFloat = detailViewHeight - oldStyleFleetViewHeight
53     
54     private static let maxFleetNumber: Int = 4
55     
56     private let details: [ShipDetailViewController]
57     private let shipKeys = [#keyPath(Deck.ship_0), #keyPath(Deck.ship_1), #keyPath(Deck.ship_2), #keyPath(Deck.ship_3),
58                             #keyPath(Deck.ship_4), #keyPath(Deck.ship_5), #keyPath(Deck.ship_6)]
59     private let type: FleetViewType
60     private let fleetController = NSObjectController()
61     private let shipObserveKeys = [#keyPath(Ship.seiku), #keyPath(Ship.lv), #keyPath(Ship.equippedItem)]
62     
63     private var notificationObserver = NotificationObserver()
64     
65     init?(viewType: FleetViewType) {
66         
67         type = viewType
68         
69         let shipiewType: ShipDetailViewType = {
70             
71             switch viewType {
72             case .detailViewType: return .full
73             case .minimumViewType: return .medium
74             case .miniVierticalType: return .minimum
75             }
76         }()
77         details = (1...6).map {
78             
79             guard let res = ShipDetailViewController(type: shipiewType) else { fatalError("Can not create ShipDetailViewController") }
80             
81             res.title = "\($0)"
82             
83             return res
84         }
85         
86         let nibName: NSNib.Name = {
87             switch viewType {
88             case .detailViewType: return FleetViewController.nibName
89             case .minimumViewType: return NSNib.Name("FleetMinimumViewController")
90             case .miniVierticalType: return NSNib.Name("VerticalFleetViewController")
91             }
92         }()
93         
94         super.init(nibName: nibName, bundle: nil)
95     }
96     
97     required init?(coder: NSCoder) {
98         
99         fatalError("init(coder:) has not been implemented")
100     }
101     
102     @IBOutlet private weak var placeholder01: NSView!
103     @IBOutlet private weak var placeholder02: NSView!
104     @IBOutlet private weak var placeholder03: NSView!
105     @IBOutlet private weak var placeholder04: NSView!
106     @IBOutlet private weak var placeholder05: NSView!
107     @IBOutlet private weak var placeholder06: NSView!
108     
109     @objc dynamic var fleetNumber: Int = 1 {
110         
111         didSet {
112             ServerDataStore.default
113                 .deck(by: fleetNumber)
114                 .map { fleet = $0 }
115         }
116     }
117     
118     @objc dynamic var fleet: Deck? {
119         
120         get { return representedObject as? Deck }
121         set {
122             representedObject = newValue
123             title = newValue?.name
124             checkExtShip()
125             setupShips()
126         }
127     }
128     
129     private var extDetail: ShipDetailViewController?
130     private var extShipAnimating: Bool = false
131     weak var delegate: FleetViewControllerDelegate?
132     
133     var enableAnimation: Bool = false
134     
135     var shipOrder: FleetViewController.ShipOrder = .doubleLine {
136         
137         willSet {
138             if shipOrder == newValue { return }
139             
140             switch newValue {
141             case .doubleLine: reorderShipToDoubleLine()
142             case .leftToRight: reorderShipToLeftToRight()
143             }
144         }
145     }
146     
147     var canDivide: Bool { return type == .detailViewType }
148     
149     var normalHeight: CGFloat {
150         
151         switch type {
152         case .detailViewType: return FleetViewController.detailViewHeight
153         case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
154         case .miniVierticalType: return 0.0
155         }
156     }
157     
158     var upsideHeight: CGFloat {
159         
160         switch type {
161         case .detailViewType: return 175.0
162         case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
163         case .miniVierticalType: return 0.0
164         }
165     }
166     
167     var sakutekiCalculator: SakutekiCalculator = SimpleCalculator() {
168         
169         didSet {
170             switch sakutekiCalculator {
171             case _ as SimpleCalculator:
172                 UserDefaults.standard[.sakutekiCalclationSterategy] = .total
173                 
174             case let f as Formula33:
175                 UserDefaults.standard[.sakutekiCalclationSterategy] = .formula33
176                 UserDefaults.standard[.formula33Factor] = Double(f.condition)
177                 
178             default: ()
179             }
180         }
181     }
182     
183     @objc private var totalSakuteki: Double { return sakutekiCalculator.calculate(ships) }
184     @objc var totalSeiku: Int { return ships.reduce(0) { $0 + $1.seiku } }
185     @objc var totalCalclatedSeiku: Int { return ships.reduce(0) { $0 + totalSeiku(of: $1) } }
186     @objc private var totalLevel: Int { return ships.reduce(0) { $0 + $1.lv } }
187     @objc private var totalDrums: Int { return ships.reduce(0) { $0 + totalDrums(of: $1) } }
188     @objc var totalTPValue: Int {
189         
190         return ships
191             .map { ShipTPValueCalculator($0).value }
192             .reduce(0, +)
193     }
194     @objc private var totalBRankTPValue: Int { return Int(floor(Double(totalTPValue) * 0.7)) }
195     
196     private func totalSeiku(of ship: Ship) -> Int {
197         
198         return SeikuCalclator(ship: ship).totalSeiku
199     }
200     private func totalDrums(of ship: Ship) -> Int {
201         
202         return (0...4).compactMap(ship.slotItem).filter { $0.slotitem_id == 75 }.count
203     }
204     
205     private var ships: [Ship] = [] {
206         
207         willSet {
208             ships.forEach { ship in
209                 
210                 shipObserveKeys.forEach { ship.removeObserver(self, forKeyPath: $0) }
211             }
212         }
213         didSet {
214             ships.forEach { ship in
215                 
216                 shipObserveKeys.forEach {
217                     
218                     ship.addObserver(self, forKeyPath: $0, context: &shipsContext)
219                 }
220             }
221         }
222     }
223     
224     private(set) var anchorageRepair = AnchorageRepairManager.default
225     
226     @objc dynamic private(set) var repairTime: NSNumber?
227     
228     override func viewDidLoad() {
229         
230         super.viewDidLoad()
231         
232         switch UserDefaults.standard[.sakutekiCalclationSterategy] {
233         case .total:
234             sakutekiCalculator = SimpleCalculator()
235             
236         case .formula33:
237             let factor = UserDefaults.standard[.formula33Factor]
238             sakutekiCalculator = Formula33(Int(factor))
239         }
240         
241         fleetController.bind(NSBindingName(#keyPath(NSArrayController.content)), to: self, withKeyPath: #keyPath(fleet))
242         fleetController.addObserver(self, forKeyPath: "selection.name", context: nil)
243         shipKeys.forEach {
244             
245             let keyPath = "selection.\($0)"
246             fleetController.addObserver(self, forKeyPath: keyPath, context: &shipKeysContext)
247         }
248         
249         buildAnchorageRepairHolder()
250         
251         zip([placeholder01, placeholder02, placeholder03, placeholder04, placeholder05, placeholder06], details)
252             .forEach { view, detail in
253                 
254                 guard let view = view else { return }
255                 
256                 detail.view.frame = view.frame
257                 detail.view.autoresizingMask = view.autoresizingMask
258                 self.view.replaceSubview(view, with: detail.view)
259                 
260         }
261         fleetNumber = 1
262         
263         // 初回起動時などデータがまだない時はportAPIを受信後設定する
264         NotificationCenter.default
265             .addObserverOnce(forName: .PortAPIReceived, object: nil, queue: .main) { [weak self] _ in
266                 
267                 if let current = self?.fleetNumber {
268                     
269                     self?.fleetNumber = current
270                 } else {
271                     
272                     self?.fleetNumber = 1
273                 }
274         }
275         
276         notificationObserver
277             .addObserver(forName: .DidUpdateGuardEscape, object: nil, queue: .main) { [weak self] _ in
278                 
279                 self?.notifyChangeValue(forKey: #keyPath(totalSeiku))
280                 self?.notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
281                 self?.notifyChangeValue(forKey: #keyPath(totalSakuteki))
282                 self?.notifyChangeValue(forKey: #keyPath(totalDrums))
283                 self?.notifyChangeValue(forKey: #keyPath(totalTPValue))
284                 self?.notifyChangeValue(forKey: #keyPath(totalBRankTPValue))
285         }
286     }
287     
288     override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
289         
290         
291         if keyPath == "selection.name" {
292             
293             title = fleet?.name
294             
295             return
296         }
297         
298         if context == &shipKeysContext {
299             
300             checkExtShip()
301             setupShips()
302             
303             return
304         }
305         
306         if let keyPath = keyPath {
307             
308             if context == &shipsContext {
309                 
310                 switch keyPath {
311                 case #keyPath(Ship.equippedItem):
312                     notifyChangeValue(forKey: #keyPath(totalSakuteki))
313                     notifyChangeValue(forKey: #keyPath(totalDrums))
314                     notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
315                     notifyChangeValue(forKey: #keyPath(totalTPValue))
316                     notifyChangeValue(forKey: #keyPath(totalBRankTPValue))
317
318                 case #keyPath(Ship.seiku):
319                     notifyChangeValue(forKey: #keyPath(totalSeiku))
320                     notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
321                     
322                 case #keyPath(Ship.lv):
323                     notifyChangeValue(forKey: #keyPath(totalLevel))
324                     
325                 default: break
326                 }
327                 
328                 return
329             }
330         }
331         
332         super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
333     }
334     
335     @IBAction func selectNextFleet(_ sender: AnyObject?) {
336         
337         let next = fleetNumber + 1
338         fleetNumber = (next <= FleetViewController.maxFleetNumber ? next : 1)
339     }
340     
341     @IBAction func selectPreviousFleet(_ sender: AnyObject?) {
342         
343         let prev = fleetNumber - 1
344         fleetNumber = (prev > 0 ? prev : 4)
345     }
346     
347     @IBAction func changeSakutekiCalculator(_ sender: Any?) {
348         
349         guard let menuItem = sender as? NSMenuItem else { return }
350         
351         switch menuItem.tag {
352         case 0:
353             sakutekiCalculator = SimpleCalculator()
354             
355         case 101...199:
356             sakutekiCalculator = Formula33(menuItem.tag - 100)
357             
358         case 100:
359             askCalcutaionTurnPoint()
360             
361         default: return
362         }
363         
364         notifyChangeValue(forKey: #keyPath(totalSakuteki))
365     }
366     
367     private func askCalcutaionTurnPoint() {
368         
369         guard let window = self.view.window else { return }
370         
371         let current = (sakutekiCalculator as? Formula33)?.condition ?? 1
372         
373         let wc = CalculateConditionPanelController()
374         wc.condition = Double(current)
375         wc.beginModal(for: window) {
376             
377             self.sakutekiCalculator = Formula33(Int($0))
378             
379             self.notifyChangeValue(forKey: #keyPath(totalSakuteki))
380         }
381     }
382     
383     private func setupShips() {
384         
385         let array: [Ship?] = (0..<6).map { fleet?[$0] }
386         zip(details, array).forEach { $0.0.ship = $0.1 }
387         
388         let extShip = fleet?[6]
389         extShip.map { extDetail?.ship = $0 }
390         ships = array.compactMap { $0 } + [extShip].compactMap { $0 }
391         
392         [#keyPath(totalSakuteki), #keyPath(totalSeiku), #keyPath(totalCalclatedSeiku),
393          #keyPath(totalLevel), #keyPath(totalDrums), #keyPath(repairable),
394          #keyPath(totalTPValue), #keyPath(totalBRankTPValue)]
395             .forEach(notifyChangeValue(forKey:))
396     }
397 }
398
399 extension FleetViewController {
400     
401     override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
402         
403         guard let action = menuItem.action else { return false }
404         
405         switch action {
406             
407         case #selector(changeSakutekiCalculator(_:)):
408             
409             if let _ = sakutekiCalculator as? SimpleCalculator {
410                 
411                 menuItem.state = (menuItem.tag == 0 ? .on : .off)
412                 
413                 return true
414                 
415             } else if let sakuObj = sakutekiCalculator as? Formula33 {
416                 
417                 let cond = 100 + sakuObj.condition
418                 
419                 menuItem.state = (menuItem.tag == cond ? .on : .off)
420                 
421                 return true
422             }
423             
424         default: ()
425         }
426         
427         return false
428     }
429 }
430
431 extension FleetViewController {
432     
433     private func reorder(order: [Int]) {
434         
435         guard order.count == 6 else { return Logger.shared.log("FleetViewController: order count is not 6.") }
436         
437         let views = details.map { $0.view }
438         let options = views.map { $0.autoresizingMask }
439         let reorderedOptions = order.map { options[$0] }
440         zip(views, reorderedOptions).forEach { $0.0.autoresizingMask = $0.1 }
441         
442         let frames = views.map { $0.frame }
443         let reorderedFrames = order.map { frames[$0] }
444         zip(views, reorderedFrames).forEach { $0.0.setFrame($0.1, animate: enableAnimation) }
445     }
446     
447     private func reorderShipToDoubleLine() {
448         
449         reorder(order: [0, 3, 1, 4, 2, 5])
450     }
451     
452     private func reorderShipToLeftToRight() {
453         
454         reorder(order: [0, 2, 4, 1, 3, 5])
455     }
456 }
457
458 extension FleetViewController {
459     
460     private func buildAnchorageRepairHolder() {
461         
462         AppDelegate.shared.addCounterUpdate { [weak self] in
463             
464             guard let `self` = self else { return }
465             
466             self.repairTime = self.calcRepairTime()
467         }
468     }
469     
470     private func calcRepairTime() -> NSNumber? {
471         
472         let time = anchorageRepair.repairTime
473         let complete = time.timeIntervalSince1970
474         let now = Date(timeIntervalSinceNow: 0.0)
475         let diff = complete - now.timeIntervalSince1970
476         
477         return NSNumber(value: diff + 20.0 * 60)
478     }
479     
480     private var repairShipIds: [Int] { return [19] }
481     
482     @objc private dynamic var repairable: Bool {
483         
484         guard let flagShip = fleet?[0] else { return false }
485         
486         return repairShipIds.contains(flagShip.master_ship.stype.id)
487     }
488 }
489
490 extension FleetViewController {
491     
492     var shipViewSize: NSSize { return details[0].view.frame.size }
493     
494     private func showExtShip() {
495         
496         guard type == .detailViewType else { return }
497         guard extDetail == nil else { return }
498         guard let _ = fleet?[6] else { return }
499         
500         if extShipAnimating {
501             DispatchQueue.main.async(execute: showExtShip)
502             return
503         }
504         
505         extShipAnimating = true
506         
507         NSAnimationContext.runAnimationGroup({ _ in
508             
509             extDetail = ShipDetailViewController(type: .full)
510             guard let extDetail = extDetail else { return }
511             
512             extDetail.title = "7"
513             extDetail.view.autoresizingMask = [.minXMargin]
514             
515             let width = extDetail.view.frame.width - 1
516             var frame = view.frame
517             frame.size.width += width
518             
519             extDetail.view.alphaValue = 0.0
520             extDetail.view.animator().alphaValue = 1.0
521             
522             extDetail.view.frame = details[5].view.frame
523             view.addSubview(extDetail.view, positioned: .below, relativeTo: details[5].view)
524             view.animator().frame = frame
525             
526         }, completionHandler: { [weak self] in
527             
528             self?.extShipAnimating = false
529         })
530         
531         delegate?.changeShowsExtShip(self, showsExtShip: true)
532     }
533     
534     private func hideExtShip() {
535         
536         guard type == .detailViewType else { return }
537         
538         if extShipAnimating {
539             DispatchQueue.main.async(execute: hideExtShip)
540             return
541         }
542         
543         guard let extDetail = extDetail else { return }
544         
545         extShipAnimating = true
546         
547         NSAnimationContext.runAnimationGroup({ _ in
548             
549             extDetail.view.animator().alphaValue = 0.0
550             
551             var frame = view.frame
552             frame.size.width -= extDetail.view.frame.width - 1
553             view.animator().frame = frame
554             
555             ships.removeLast()
556             
557         }, completionHandler: { [weak self] in
558             
559             extDetail.view.removeFromSuperview()
560             
561             self?.extShipAnimating = false
562             
563         })
564         
565         self.extDetail = nil
566         
567         delegate?.changeShowsExtShip(self, showsExtShip: false)
568     }
569     
570     private func checkExtShip() {
571                 
572         guard type == .detailViewType else { return }
573         
574         if fleet?[6] != nil {
575             
576             showExtShip()
577             
578         } else {
579             
580             hideExtShip()
581         }
582     }
583 }
584
585 extension FleetViewController: NibLoadable {}