OSDN Git Service

7隻の艦隊表示を作成
[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     weak var delegate: FleetViewControllerDelegate?
134     
135     var enableAnimation: Bool = false
136     
137     var shipOrder: FleetViewController.ShipOrder = .doubleLine {
138         
139         willSet {
140             if shipOrder == newValue { return }
141             
142             switch newValue {
143             case .doubleLine: reorderShipToDoubleLine()
144             case .leftToRight: reorderShipToLeftToRight()
145             }
146         }
147     }
148     
149     var canDivide: Bool { return type == .detailViewType }
150     
151     var normalHeight: CGFloat {
152         
153         switch type {
154         case .detailViewType: return FleetViewController.detailViewHeight
155         case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
156         case .miniVierticalType: return 0.0
157         }
158     }
159     
160     var upsideHeight: CGFloat {
161         
162         switch type {
163         case .detailViewType: return 159.0
164         case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
165         case .miniVierticalType: return 0.0
166         }
167     }
168     
169     var sakutekiCalculator: SakutekiCalculator = SimpleCalculator() {
170         
171         didSet {
172             switch sakutekiCalculator {
173             case _ as SimpleCalculator:
174                 UserDefaults.standard[.sakutekiCalclationSterategy] = .total
175                 
176             case let f as Formula33:
177                 UserDefaults.standard[.sakutekiCalclationSterategy] = .formula33
178                 UserDefaults.standard[.formula33Factor] = Double(f.condition)
179                 
180             default: ()
181             }
182         }
183     }
184     
185     @objc var totalSakuteki: Double { return sakutekiCalculator.calculate(ships) }
186     @objc var totalSeiku: Int { return ships.reduce(0) { $0 + $1.seiku } }
187     @objc var totalCalclatedSeiku: Int { return ships.reduce(0) { $0 + totalSeiku(of: $1) } }
188     @objc var totalLevel: Int { return ships.reduce(0) { $0 + $1.lv } }
189     @objc var totalDrums: Int { return ships.reduce(0) { $0 + totalDrums(of: $1) } }
190     @objc var totalTPValue: Int {
191         
192         return ships
193             .map { ShipTPValueCalculator($0) }
194             .map { $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                 self?.fleetNumber = 1
271         }
272         
273         NotificationCenter
274             .default
275             .addObserver(forName: .DidUpdateGuardEscape, object: nil, queue: nil) { [weak self] _ in
276                 
277                 self?.notifyChangeValue(forKey: #keyPath(totalSeiku))
278                 self?.notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
279                 self?.notifyChangeValue(forKey: #keyPath(totalSakuteki))
280                 self?.notifyChangeValue(forKey: #keyPath(totalDrums))
281                 self?.notifyChangeValue(forKey: #keyPath(totalTPValue))
282         }
283     }
284     
285     override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
286         
287         
288         if keyPath == "selection.name" {
289             
290             title = fleet?.name
291             
292             return
293         }
294         
295         if context == &shipKeysContext {
296             
297             checkExtShip()
298             setupShips()
299             
300             return
301         }
302         
303         if let keyPath = keyPath {
304             
305             if context == &shipsContext {
306                 
307                 switch keyPath {
308                 case #keyPath(Ship.equippedItem):
309                     notifyChangeValue(forKey: #keyPath(totalSakuteki))
310                     notifyChangeValue(forKey: #keyPath(totalDrums))
311                     notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
312                     notifyChangeValue(forKey: #keyPath(totalTPValue))
313                     
314                 case #keyPath(Ship.seiku):
315                     notifyChangeValue(forKey: #keyPath(totalSeiku))
316                     notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
317                     
318                 case #keyPath(Ship.lv):
319                     notifyChangeValue(forKey: #keyPath(totalLevel))
320                     
321                 default: break
322                 }
323                 
324                 return
325             }
326         }
327         
328         super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
329     }
330     
331     @IBAction func selectNextFleet(_ sender: AnyObject?) {
332         
333         let next = fleetNumber + 1
334         fleetNumber = (next <= FleetViewController.maxFleetNumber ? next : 1)
335     }
336     
337     @IBAction func selectPreviousFleet(_ sender: AnyObject?) {
338         
339         let prev = fleetNumber - 1
340         fleetNumber = (prev > 0 ? prev : 4)
341     }
342     
343     @IBAction func changeSakutekiCalculator(_ sender: Any?) {
344         
345         guard let menuItem = sender as? NSMenuItem else { return }
346         
347         switch menuItem.tag {
348         case 0:
349             sakutekiCalculator = SimpleCalculator()
350             
351         case 101...199:
352             sakutekiCalculator = Formula33(menuItem.tag - 100)
353             
354         case 100:
355             askCalcutaionTurnPoint()
356             
357         default: return
358         }
359         
360         notifyChangeValue(forKey: #keyPath(totalSakuteki))
361     }
362     
363     private func askCalcutaionTurnPoint() {
364         
365         guard let window = self.view.window else { return }
366         
367         let current = (sakutekiCalculator as? Formula33)?.condition ?? 1
368         
369         let wc = CalculateConditionPanelController()
370         wc.condition = Double(current)
371         wc.beginModal(for: window) {
372             
373             self.sakutekiCalculator = Formula33(Int($0))
374             
375             self.notifyChangeValue(forKey: #keyPath(totalSakuteki))
376         }
377     }
378     
379     private func setupShips() {
380         
381         let array: [Ship?] = (0..<6).map { fleet?[$0] }
382         zip(details, array).forEach { $0.0.ship = $0.1 }
383         
384         let extShip = fleet?[6]
385         extShip.map { extDetail?.ship = $0 }
386         ships = array.flatMap { $0 } + [extShip].flatMap { $0 }
387         
388         [#keyPath(totalSakuteki), #keyPath(totalSeiku), #keyPath(totalCalclatedSeiku),
389          #keyPath(totalLevel), #keyPath(totalDrums), #keyPath(repairable),
390          #keyPath(totalTPValue)]
391             .forEach(notifyChangeValue(forKey:))
392     }
393 }
394
395 extension FleetViewController {
396     
397     override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
398         
399         guard let action = menuItem.action else { return false }
400         
401         switch action {
402             
403         case #selector(changeSakutekiCalculator(_:)):
404             
405             if let _ = sakutekiCalculator as? SimpleCalculator {
406                 
407                 menuItem.state = (menuItem.tag == 0 ? .on : .off)
408                 
409                 return true
410                 
411             } else if let sakuObj = sakutekiCalculator as? Formula33 {
412                 
413                 let cond = 100 + sakuObj.condition
414                 
415                 menuItem.state = (menuItem.tag == cond ? .on : .off)
416                 
417                 return true
418             }
419             
420         default: ()
421         }
422         
423         return false
424     }
425 }
426
427 extension FleetViewController {
428     
429     private func reorder(order: [Int]) {
430         
431         guard order.count == 6 else { return Logger.shared.log("FleetViewController: order count is not 6.") }
432         
433         let views = details.map { $0.view }
434         let options = views.map { $0.autoresizingMask }
435         let reorderedOptions = order.map { options[$0] }
436         zip(views, reorderedOptions).forEach { $0.0.autoresizingMask = $0.1 }
437         
438         let frames = views.map { $0.frame }
439         let reorderedFrames = order.map { frames[$0] }
440         zip(views, reorderedFrames).forEach { $0.0.setFrame($0.1, animate: enableAnimation) }
441     }
442     
443     private func reorderShipToDoubleLine() {
444         
445         reorder(order: [0, 3, 1, 4, 2, 5])
446     }
447     
448     private func reorderShipToLeftToRight() {
449         
450         reorder(order: [0, 2, 4, 1, 3, 5])
451     }
452 }
453
454 extension FleetViewController {
455     
456     func buildAnchorageRepairHolder() {
457         
458         AppDelegate.shared.addCounterUpdate { [weak self] in
459             
460             guard let `self` = self else { return }
461             
462             self.repairTime = self.calcRepairTime()
463         }
464     }
465     
466     private func calcRepairTime() -> NSNumber? {
467         
468         let time = anchorageRepair.repairTime
469         let complete = time.timeIntervalSince1970
470         let now = Date(timeIntervalSinceNow: 0.0)
471         let diff = complete - now.timeIntervalSince1970
472         
473         return NSNumber(value: diff + 20.0 * 60)
474     }
475     
476     private var repairShipIds: [Int] { return [19] }
477     
478     @objc dynamic var repairable: Bool {
479         
480         guard let flagShip = fleet?[0] else { return false }
481         
482         return repairShipIds.contains(flagShip.master_ship.stype.id)
483     }
484 }
485
486 extension FleetViewController {
487     
488     var shipViewSize: NSSize { return details[0].view.frame.size }
489     
490     private func showExtShip() {
491         
492         print("show ext shp")
493         
494         guard type == .detailViewType else { return }
495         guard extDetail == nil else { return }
496         guard let _ = fleet?[6] else { return }
497         
498         extDetail = ShipDetailViewController(type: .full)
499         guard let extDetail = extDetail else { return }
500         
501         extDetail.title = "7"
502         extDetail.view.autoresizingMask = [.minXMargin]
503         
504         let width = extDetail.view.frame.width - 1
505         var frame = view.frame
506         frame.size.width += width
507         
508         extDetail.view.frame = details[5].view.frame
509         view.addSubview(extDetail.view, positioned: .below, relativeTo: details[5].view)
510         view.animator().frame = frame
511         
512         delegate?.changeShowsExtShip(self, showsExtShip: true)
513     }
514     
515     private func hideExtShip() {
516         
517         print("hide ext ship")
518         guard type == .detailViewType else { return }
519         
520         guard let extDetail = extDetail else { return }
521         
522         var frame = view.frame
523         frame.size.width -= extDetail.view.frame.width - 1
524         view.animator().frame = frame
525         
526         ships.removeLast()
527         
528         DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
529             extDetail.view.removeFromSuperview()
530         }
531         
532         self.extDetail = nil
533         
534         delegate?.changeShowsExtShip(self, showsExtShip: false)
535     }
536     
537     private func checkExtShip() {
538         
539         fleet.map { print($0) }
540         
541         guard type == .detailViewType else { return }
542         
543         if fleet?[6] != nil {
544             
545             showExtShip()
546             
547         } else {
548             
549             hideExtShip()
550         }
551     }
552 }
553
554 extension FleetViewController: NibLoadable {}