OSDN Git Service

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