OSDN Git Service

バージョンを1.9b33に更新
[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) }
195             .map { $0.value }
196             .reduce(0, +)
197     }
198     
199     func totalSeiku(of ship: Ship) -> Int {
200         
201         return SeikuCalclator(ship: ship).totalSeiku
202     }
203     func totalDrums(of ship: Ship) -> Int {
204         
205         return (0...4).flatMap(ship.slotItem).filter { $0.slotitem_id == 75 }.count
206     }
207     
208     private var ships: [Ship] = [] {
209         
210         willSet {
211             ships.forEach { ship in
212                 
213                 shipObserveKeys.forEach { ship.removeObserver(self, forKeyPath: $0) }
214             }
215         }
216         didSet {
217             ships.forEach { ship in
218                 
219                 shipObserveKeys.forEach {
220                     
221                     ship.addObserver(self, forKeyPath: $0, context: &shipsContext)
222                 }
223             }
224         }
225     }
226     
227     private(set) var anchorageRepair = AnchorageRepairManager.default
228     
229     @objc dynamic private(set) var repairTime: NSNumber?
230     
231     override func viewDidLoad() {
232         
233         super.viewDidLoad()
234         
235         switch UserDefaults.standard[.sakutekiCalclationSterategy] {
236         case .total:
237             sakutekiCalculator = SimpleCalculator()
238             
239         case .formula33:
240             let factor = UserDefaults.standard[.formula33Factor]
241             sakutekiCalculator = Formula33(Int(factor))
242         }
243         
244         fleetController.bind(NSBindingName(#keyPath(NSArrayController.content)), to: self, withKeyPath: #keyPath(fleet), options: nil)
245         fleetController.addObserver(self, forKeyPath: "selection.name", context: nil)
246         shipKeys.forEach {
247             
248             let keyPath = "selection.\($0)"
249             fleetController.addObserver(self, forKeyPath: keyPath, context: &shipKeysContext)
250         }
251         
252         buildAnchorageRepairHolder()
253         
254         [placeholder01, placeholder02, placeholder03, placeholder04, placeholder05, placeholder06]
255             .enumerated()
256             .forEach {
257                 
258                 guard let view = $0.element else { return }
259                 
260                 let detail = details[$0.offset]
261                 detail.view.frame = view.frame
262                 detail.view.autoresizingMask = view.autoresizingMask
263                 self.view.replaceSubview(view, with: detail.view)
264         }
265         fleetNumber = 1
266         
267         // 初回起動時などデータがまだない時はportAPIを受信後設定する
268         NotificationCenter.default
269             .addObserverOnce(forName: .PortAPIReceived, object: nil, queue: nil) { [weak self] _ in
270                 
271                 if let current = self?.fleetNumber {
272                     
273                     self?.fleetNumber = current
274                 } else {
275                     
276                     self?.fleetNumber = 1
277                 }
278         }
279         
280         NotificationCenter
281             .default
282             .addObserver(forName: .DidUpdateGuardEscape, object: nil, queue: nil) { [weak self] _ in
283                 
284                 self?.notifyChangeValue(forKey: #keyPath(totalSeiku))
285                 self?.notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
286                 self?.notifyChangeValue(forKey: #keyPath(totalSakuteki))
287                 self?.notifyChangeValue(forKey: #keyPath(totalDrums))
288                 self?.notifyChangeValue(forKey: #keyPath(totalTPValue))
289         }
290     }
291     
292     override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
293         
294         
295         if keyPath == "selection.name" {
296             
297             title = fleet?.name
298             
299             return
300         }
301         
302         if context == &shipKeysContext {
303             
304             checkExtShip()
305             setupShips()
306             
307             return
308         }
309         
310         if let keyPath = keyPath {
311             
312             if context == &shipsContext {
313                 
314                 switch keyPath {
315                 case #keyPath(Ship.equippedItem):
316                     notifyChangeValue(forKey: #keyPath(totalSakuteki))
317                     notifyChangeValue(forKey: #keyPath(totalDrums))
318                     notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
319                     notifyChangeValue(forKey: #keyPath(totalTPValue))
320                     
321                 case #keyPath(Ship.seiku):
322                     notifyChangeValue(forKey: #keyPath(totalSeiku))
323                     notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
324                     
325                 case #keyPath(Ship.lv):
326                     notifyChangeValue(forKey: #keyPath(totalLevel))
327                     
328                 default: break
329                 }
330                 
331                 return
332             }
333         }
334         
335         super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
336     }
337     
338     @IBAction func selectNextFleet(_ sender: AnyObject?) {
339         
340         let next = fleetNumber + 1
341         fleetNumber = (next <= FleetViewController.maxFleetNumber ? next : 1)
342     }
343     
344     @IBAction func selectPreviousFleet(_ sender: AnyObject?) {
345         
346         let prev = fleetNumber - 1
347         fleetNumber = (prev > 0 ? prev : 4)
348     }
349     
350     @IBAction func changeSakutekiCalculator(_ sender: Any?) {
351         
352         guard let menuItem = sender as? NSMenuItem else { return }
353         
354         switch menuItem.tag {
355         case 0:
356             sakutekiCalculator = SimpleCalculator()
357             
358         case 101...199:
359             sakutekiCalculator = Formula33(menuItem.tag - 100)
360             
361         case 100:
362             askCalcutaionTurnPoint()
363             
364         default: return
365         }
366         
367         notifyChangeValue(forKey: #keyPath(totalSakuteki))
368     }
369     
370     private func askCalcutaionTurnPoint() {
371         
372         guard let window = self.view.window else { return }
373         
374         let current = (sakutekiCalculator as? Formula33)?.condition ?? 1
375         
376         let wc = CalculateConditionPanelController()
377         wc.condition = Double(current)
378         wc.beginModal(for: window) {
379             
380             self.sakutekiCalculator = Formula33(Int($0))
381             
382             self.notifyChangeValue(forKey: #keyPath(totalSakuteki))
383         }
384     }
385     
386     private func setupShips() {
387         
388         let array: [Ship?] = (0..<6).map { fleet?[$0] }
389         zip(details, array).forEach { $0.0.ship = $0.1 }
390         
391         let extShip = fleet?[6]
392         extShip.map { extDetail?.ship = $0 }
393         ships = array.flatMap { $0 } + [extShip].flatMap { $0 }
394         
395         [#keyPath(totalSakuteki), #keyPath(totalSeiku), #keyPath(totalCalclatedSeiku),
396          #keyPath(totalLevel), #keyPath(totalDrums), #keyPath(repairable),
397          #keyPath(totalTPValue)]
398             .forEach(notifyChangeValue(forKey:))
399     }
400 }
401
402 extension FleetViewController {
403     
404     override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
405         
406         guard let action = menuItem.action else { return false }
407         
408         switch action {
409             
410         case #selector(changeSakutekiCalculator(_:)):
411             
412             if let _ = sakutekiCalculator as? SimpleCalculator {
413                 
414                 menuItem.state = (menuItem.tag == 0 ? .on : .off)
415                 
416                 return true
417                 
418             } else if let sakuObj = sakutekiCalculator as? Formula33 {
419                 
420                 let cond = 100 + sakuObj.condition
421                 
422                 menuItem.state = (menuItem.tag == cond ? .on : .off)
423                 
424                 return true
425             }
426             
427         default: ()
428         }
429         
430         return false
431     }
432 }
433
434 extension FleetViewController {
435     
436     private func reorder(order: [Int]) {
437         
438         guard order.count == 6 else { return Logger.shared.log("FleetViewController: order count is not 6.") }
439         
440         let views = details.map { $0.view }
441         let options = views.map { $0.autoresizingMask }
442         let reorderedOptions = order.map { options[$0] }
443         zip(views, reorderedOptions).forEach { $0.0.autoresizingMask = $0.1 }
444         
445         let frames = views.map { $0.frame }
446         let reorderedFrames = order.map { frames[$0] }
447         zip(views, reorderedFrames).forEach { $0.0.setFrame($0.1, animate: enableAnimation) }
448     }
449     
450     private func reorderShipToDoubleLine() {
451         
452         reorder(order: [0, 3, 1, 4, 2, 5])
453     }
454     
455     private func reorderShipToLeftToRight() {
456         
457         reorder(order: [0, 2, 4, 1, 3, 5])
458     }
459 }
460
461 extension FleetViewController {
462     
463     func buildAnchorageRepairHolder() {
464         
465         AppDelegate.shared.addCounterUpdate { [weak self] in
466             
467             guard let `self` = self else { return }
468             
469             self.repairTime = self.calcRepairTime()
470         }
471     }
472     
473     private func calcRepairTime() -> NSNumber? {
474         
475         let time = anchorageRepair.repairTime
476         let complete = time.timeIntervalSince1970
477         let now = Date(timeIntervalSinceNow: 0.0)
478         let diff = complete - now.timeIntervalSince1970
479         
480         return NSNumber(value: diff + 20.0 * 60)
481     }
482     
483     private var repairShipIds: [Int] { return [19] }
484     
485     @objc dynamic var repairable: Bool {
486         
487         guard let flagShip = fleet?[0] else { return false }
488         
489         return repairShipIds.contains(flagShip.master_ship.stype.id)
490     }
491 }
492
493 extension FleetViewController {
494     
495     var shipViewSize: NSSize { return details[0].view.frame.size }
496     
497     private func showExtShip() {
498         
499         guard type == .detailViewType else { return }
500         guard extDetail == nil else { return }
501         guard let _ = fleet?[6] else { return }
502         
503         if extShipAnimating {
504             DispatchQueue.main.async(execute: showExtShip)
505             return
506         }
507         
508         extShipAnimating = true
509         
510         NSAnimationContext.runAnimationGroup({ _ in
511             
512             extDetail = ShipDetailViewController(type: .full)
513             guard let extDetail = extDetail else { return }
514             
515             extDetail.title = "7"
516             extDetail.view.autoresizingMask = [.minXMargin]
517             
518             let width = extDetail.view.frame.width - 1
519             var frame = view.frame
520             frame.size.width += width
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             var frame = view.frame
550             frame.size.width -= extDetail.view.frame.width - 1
551             view.animator().frame = frame
552             
553             ships.removeLast()
554             
555         }, completionHandler: { [weak self] in
556             
557             extDetail.view.removeFromSuperview()
558             
559             self?.extShipAnimating = false
560             
561         })
562         
563         self.extDetail = nil
564         
565         delegate?.changeShowsExtShip(self, showsExtShip: false)
566     }
567     
568     private func checkExtShip() {
569                 
570         guard type == .detailViewType else { return }
571         
572         if fleet?[6] != nil {
573             
574             showExtShip()
575             
576         } else {
577             
578             hideExtShip()
579         }
580     }
581 }
582
583 extension FleetViewController: NibLoadable {}