OSDN Git Service

洋上補給の補強増設用のショートネームをつけた
[kcd/KCD.git] / KCD / FleetViewController.swift
index bd26204..a18629b 100644 (file)
@@ -15,8 +15,13 @@ enum FleetViewType: Int {
     case miniVierticalType = 2
 }
 
-fileprivate var shipKeysContext: Int = 0
-fileprivate var shipsContext: Int = 0
+private var shipKeysContext: Int = 0
+private var shipsContext: Int = 0
+
+protocol FleetViewControllerDelegate: class {
+    
+    func changeShowsExtShip(_ fleetViewController: FleetViewController, showsExtShip: Bool)
+}
 
 final class FleetViewController: NSViewController {
     
@@ -34,7 +39,6 @@ final class FleetViewController: NSViewController {
         case formula33Parameter1 = 101
         case formula33Parameter3 = 103
         case formula33Parameter4 = 104
-        
     }
     
     enum SakutekiCalclationSterategy: Int {
@@ -44,16 +48,19 @@ final class FleetViewController: NSViewController {
     }
     
     static let oldStyleFleetViewHeight: CGFloat = 128.0
-    static let detailViewHeight: CGFloat = 288.0
+    static let detailViewHeight: CGFloat = 320.0
     static let heightDifference: CGFloat = detailViewHeight - oldStyleFleetViewHeight
     
     private static let maxFleetNumber: Int = 4
     
-    fileprivate let details: [ShipDetailViewController]
-    private let shipKeys = ["ship_0", "ship_1", "ship_2", "ship_3", "ship_4", "ship_5"]
+    private let details: [ShipDetailViewController]
+    private let shipKeys = [#keyPath(Deck.ship_0), #keyPath(Deck.ship_1), #keyPath(Deck.ship_2), #keyPath(Deck.ship_3),
+                            #keyPath(Deck.ship_4), #keyPath(Deck.ship_5), #keyPath(Deck.ship_6)]
     private let type: FleetViewType
     private let fleetController = NSObjectController()
-    private let shipObserveKeys = ["seiku", "lv", "equippedItem"]
+    private let shipObserveKeys = [#keyPath(Ship.seiku), #keyPath(Ship.lv), #keyPath(Ship.equippedItem)]
+    
+    private var notificationObserver = NotificationObserver()
     
     init?(viewType: FleetViewType) {
         
@@ -69,19 +76,18 @@ final class FleetViewController: NSViewController {
         }()
         details = (1...6).map {
             
-            guard let res = ShipDetailViewController(type: shipiewType)
-                else { fatalError("Can not create ShipDetailViewController") }
+            guard let res = ShipDetailViewController(type: shipiewType) else { fatalError("Can not create ShipDetailViewController") }
             
             res.title = "\($0)"
             
             return res
         }
         
-        let nibName: String = {
+        let nibName: NSNib.Name = {
             switch viewType {
-            case .detailViewType: return "FleetViewController"
-            case .minimumViewType: return "FleetMinimumViewController"
-            case .miniVierticalType: return "VerticalFleetViewController"
+            case .detailViewType: return FleetViewController.nibName
+            case .minimumViewType: return NSNib.Name("FleetMinimumViewController")
+            case .miniVierticalType: return NSNib.Name("VerticalFleetViewController")
             }
         }()
         
@@ -93,19 +99,14 @@ final class FleetViewController: NSViewController {
         fatalError("init(coder:) has not been implemented")
     }
     
-    deinit {
-        
-        NotificationCenter.default.removeObserver(self)
-    }
+    @IBOutlet private weak var placeholder01: NSView!
+    @IBOutlet private weak var placeholder02: NSView!
+    @IBOutlet private weak var placeholder03: NSView!
+    @IBOutlet private weak var placeholder04: NSView!
+    @IBOutlet private weak var placeholder05: NSView!
+    @IBOutlet private weak var placeholder06: NSView!
     
-    @IBOutlet weak var placeholder01: NSView!
-    @IBOutlet weak var placeholder02: NSView!
-    @IBOutlet weak var placeholder03: NSView!
-    @IBOutlet weak var placeholder04: NSView!
-    @IBOutlet weak var placeholder05: NSView!
-    @IBOutlet weak var placeholder06: NSView!
-    
-    dynamic var fleetNumber: Int = 1 {
+    @objc dynamic var fleetNumber: Int = 1 {
         
         didSet {
             ServerDataStore.default
@@ -114,16 +115,21 @@ final class FleetViewController: NSViewController {
         }
     }
     
-    dynamic var fleet: Deck? {
+    @objc dynamic var fleet: Deck? {
         
         get { return representedObject as? Deck }
         set {
             representedObject = newValue
             title = newValue?.name
+            checkExtShip()
             setupShips()
         }
     }
     
+    private var extDetail: ShipDetailViewController?
+    private var extShipAnimating: Bool = false
+    weak var delegate: FleetViewControllerDelegate?
+    
     var enableAnimation: Bool = false
     
     var shipOrder: FleetViewController.ShipOrder = .doubleLine {
@@ -152,7 +158,7 @@ final class FleetViewController: NSViewController {
     var upsideHeight: CGFloat {
         
         switch type {
-        case .detailViewType: return 159.0
+        case .detailViewType: return 175.0
         case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
         case .miniVierticalType: return 0.0
         }
@@ -174,30 +180,34 @@ final class FleetViewController: NSViewController {
         }
     }
     
-    var totalSakuteki: Double { return sakutekiCalculator.calculate(ships) }
-    var totalSeiku: Int { return ships.reduce(0) { $0 + $1.seiku } }
-    var totalCalclatedSeiku: Int { return ships.reduce(0) { $0 + totalSeiku(of: $1) } }
-    var totalLevel: Int { return ships.reduce(0) { $0 + $1.lv } }
-    var totalDrums: Int { return ships.reduce(0) { $0 + totalDrums(of: $1) } }
+    @objc private var totalSakuteki: Double { return sakutekiCalculator.calculate(ships) }
+    @objc var totalSeiku: Int { return ships.reduce(0) { $0 + $1.seiku } }
+    @objc var totalCalclatedSeiku: Int { return ships.reduce(0) { $0 + totalSeiku(of: $1) } }
+    @objc private var totalLevel: Int { return ships.reduce(0) { $0 + $1.lv } }
+    @objc private var totalDrums: Int { return ships.reduce(0) { $0 + totalDrums(of: $1) } }
+    @objc var totalTPValue: Int {
+        
+        return ships
+            .map { ShipTPValueCalculator($0).value }
+            .reduce(0, +)
+    }
+    @objc private var totalBRankTPValue: Int { return Int(floor(Double(totalTPValue) * 0.7)) }
     
-    func totalSeiku(of ship: Ship) -> Int {
+    private func totalSeiku(of ship: Ship) -> Int {
         
         return SeikuCalclator(ship: ship).totalSeiku
     }
-    func totalDrums(of ship: Ship) -> Int {
+    private func totalDrums(of ship: Ship) -> Int {
         
         return (0...4).flatMap(ship.slotItem).filter { $0.slotitem_id == 75 }.count
     }
     
-    fileprivate var ships: [Ship] = [] {
+    private var ships: [Ship] = [] {
         
         willSet {
             ships.forEach { ship in
                 
-                shipObserveKeys.forEach {
-                    
-                    ship.removeObserver(self, forKeyPath: $0)
-                }
+                shipObserveKeys.forEach { ship.removeObserver(self, forKeyPath: $0) }
             }
         }
         didSet {
@@ -213,7 +223,7 @@ final class FleetViewController: NSViewController {
     
     private(set) var anchorageRepair = AnchorageRepairManager.default
     
-    dynamic fileprivate(set) var repairTime: NSNumber?
+    @objc dynamic private(set) var repairTime: NSNumber?
     
     override func viewDidLoad() {
         
@@ -228,48 +238,50 @@ final class FleetViewController: NSViewController {
             sakutekiCalculator = Formula33(Int(factor))
         }
         
-        fleetController.bind("content", to:self, withKeyPath:#keyPath(fleet), options:nil)
-        fleetController.addObserver(self, forKeyPath:"selection.name", context:nil)
+        fleetController.bind(NSBindingName(#keyPath(NSArrayController.content)), to: self, withKeyPath: #keyPath(fleet))
+        fleetController.addObserver(self, forKeyPath: "selection.name", context: nil)
         shipKeys.forEach {
             
             let keyPath = "selection.\($0)"
-            fleetController.addObserver(self, forKeyPath:keyPath, context:&shipKeysContext)
+            fleetController.addObserver(self, forKeyPath: keyPath, context: &shipKeysContext)
         }
         
         buildAnchorageRepairHolder()
         
-        [placeholder01, placeholder02, placeholder03,
-         placeholder04, placeholder05, placeholder06]
-            .enumerated()
-            .forEach {
+        zip([placeholder01, placeholder02, placeholder03, placeholder04, placeholder05, placeholder06], details)
+            .forEach { view, detail in
                 
-                guard let view = $0.element else { return }
+                guard let view = view else { return }
                 
-                let detail = details[$0.offset]
                 detail.view.frame = view.frame
                 detail.view.autoresizingMask = view.autoresizingMask
                 self.view.replaceSubview(view, with: detail.view)
+                
         }
         fleetNumber = 1
         
+        // 初回起動時などデータがまだない時はportAPIを受信後設定する
         NotificationCenter.default
-            .addObserver(forName: .DidPrepareFleet, object: nil, queue: nil) { [weak self] _ in
-                
-                guard let `self` = self else { return }
+            .addObserverOnce(forName: .PortAPIReceived, object: nil, queue: .main) { [weak self] _ in
                 
-                self.notifyChangeValue(forKey: #keyPath(fleetNumber))
+                if let current = self?.fleetNumber {
+                    
+                    self?.fleetNumber = current
+                } else {
+                    
+                    self?.fleetNumber = 1
+                }
         }
         
-        NotificationCenter
-            .default
-            .addObserver(forName: .DidUpdateGuardEscape, object: nil, queue: nil) { [weak self] _ in
-                
-                guard let `self` = self else { return }
+        notificationObserver
+            .addObserver(forName: .DidUpdateGuardEscape, object: nil, queue: .main) { [weak self] _ in
                 
-                self.notifyChangeValue(forKey: #keyPath(totalSeiku))
-                self.notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
-                self.notifyChangeValue(forKey: #keyPath(totalSakuteki))
-                self.notifyChangeValue(forKey: #keyPath(totalDrums))
+                self?.notifyChangeValue(forKey: #keyPath(totalSeiku))
+                self?.notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
+                self?.notifyChangeValue(forKey: #keyPath(totalSakuteki))
+                self?.notifyChangeValue(forKey: #keyPath(totalDrums))
+                self?.notifyChangeValue(forKey: #keyPath(totalTPValue))
+                self?.notifyChangeValue(forKey: #keyPath(totalBRankTPValue))
         }
     }
     
@@ -285,6 +297,7 @@ final class FleetViewController: NSViewController {
         
         if context == &shipKeysContext {
             
+            checkExtShip()
             setupShips()
             
             return
@@ -295,16 +308,18 @@ final class FleetViewController: NSViewController {
             if context == &shipsContext {
                 
                 switch keyPath {
-                case "equippedItem":
+                case #keyPath(Ship.equippedItem):
                     notifyChangeValue(forKey: #keyPath(totalSakuteki))
                     notifyChangeValue(forKey: #keyPath(totalDrums))
                     notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
-                    
-                case "seiku":
+                    notifyChangeValue(forKey: #keyPath(totalTPValue))
+                    notifyChangeValue(forKey: #keyPath(totalBRankTPValue))
+
+                case #keyPath(Ship.seiku):
                     notifyChangeValue(forKey: #keyPath(totalSeiku))
                     notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
                     
-                case "lv":
+                case #keyPath(Ship.lv):
                     notifyChangeValue(forKey: #keyPath(totalLevel))
                     
                 default: break
@@ -320,19 +335,18 @@ final class FleetViewController: NSViewController {
     @IBAction func selectNextFleet(_ sender: AnyObject?) {
         
         let next = fleetNumber + 1
-        fleetNumber = next <= FleetViewController.maxFleetNumber ? next : 1
+        fleetNumber = (next <= FleetViewController.maxFleetNumber ? next : 1)
     }
     
     @IBAction func selectPreviousFleet(_ sender: AnyObject?) {
         
         let prev = fleetNumber - 1
-        fleetNumber = prev > 0 ? prev : 4
+        fleetNumber = (prev > 0 ? prev : 4)
     }
     
     @IBAction func changeSakutekiCalculator(_ sender: Any?) {
         
-        guard let menuItem = sender as? NSMenuItem
-            else { return }
+        guard let menuItem = sender as? NSMenuItem else { return }
         
         switch menuItem.tag {
         case 0:
@@ -370,11 +384,15 @@ final class FleetViewController: NSViewController {
         
         let array: [Ship?] = (0..<6).map { fleet?[$0] }
         zip(details, array).forEach { $0.0.ship = $0.1 }
-        ships = array.flatMap { $0 }
+        
+        let extShip = fleet?[6]
+        extShip.map { extDetail?.ship = $0 }
+        ships = array.flatMap { $0 } + [extShip].flatMap { $0 }
         
         [#keyPath(totalSakuteki), #keyPath(totalSeiku), #keyPath(totalCalclatedSeiku),
-         #keyPath(totalLevel), #keyPath(totalDrums), #keyPath(repairable)]
-            .forEach { notifyChangeValue(forKey: $0) }
+         #keyPath(totalLevel), #keyPath(totalDrums), #keyPath(repairable),
+         #keyPath(totalTPValue), #keyPath(totalBRankTPValue)]
+            .forEach(notifyChangeValue(forKey:))
     }
 }
 
@@ -382,8 +400,7 @@ extension FleetViewController {
     
     override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
         
-        guard let action = menuItem.action
-            else { return false }
+        guard let action = menuItem.action else { return false }
         
         switch action {
             
@@ -391,7 +408,7 @@ extension FleetViewController {
             
             if let _ = sakutekiCalculator as? SimpleCalculator {
                 
-                menuItem.state = menuItem.tag == 0 ? NSOnState : NSOffState
+                menuItem.state = (menuItem.tag == 0 ? .on : .off)
                 
                 return true
                 
@@ -399,7 +416,7 @@ extension FleetViewController {
                 
                 let cond = 100 + sakuObj.condition
                 
-                menuItem.state = menuItem.tag == cond ? NSOnState : NSOffState
+                menuItem.state = (menuItem.tag == cond ? .on : .off)
                 
                 return true
             }
@@ -415,28 +432,24 @@ extension FleetViewController {
     
     private func reorder(order: [Int]) {
         
-        guard order.count == 6 else {
-            print("FleetViewController: order count is not 6.")
-            return
-        }
+        guard order.count == 6 else { return Logger.shared.log("FleetViewController: order count is not 6.") }
         
-        let views: [NSView] = details.map { $0.view }
-        let options: [NSAutoresizingMaskOptions] = views.map { $0.autoresizingMask }
+        let views = details.map { $0.view }
+        let options = views.map { $0.autoresizingMask }
         let reorderedOptions = order.map { options[$0] }
         zip(views, reorderedOptions).forEach { $0.0.autoresizingMask = $0.1 }
         
-        let frames: [NSRect] = views.map { $0.frame }
+        let frames = views.map { $0.frame }
         let reorderedFrames = order.map { frames[$0] }
-        zip(views, reorderedFrames)
-            .forEach { $0.0.setFrame($0.1, animate: enableAnimation) }
+        zip(views, reorderedFrames).forEach { $0.0.setFrame($0.1, animate: enableAnimation) }
     }
     
-    fileprivate func reorderShipToDoubleLine() {
+    private func reorderShipToDoubleLine() {
         
         reorder(order: [0, 3, 1, 4, 2, 5])
     }
     
-    fileprivate func reorderShipToLeftToRight() {
+    private func reorderShipToLeftToRight() {
         
         reorder(order: [0, 2, 4, 1, 3, 5])
     }
@@ -444,7 +457,7 @@ extension FleetViewController {
 
 extension FleetViewController {
     
-    func buildAnchorageRepairHolder() {
+    private func buildAnchorageRepairHolder() {
         
         AppDelegate.shared.addCounterUpdate { [weak self] in
             
@@ -466,11 +479,107 @@ extension FleetViewController {
     
     private var repairShipIds: [Int] { return [19] }
     
-    dynamic var repairable: Bool {
+    @objc private dynamic var repairable: Bool {
         
-        guard let flagShip = fleet?[0]
-            else { return false }
+        guard let flagShip = fleet?[0] else { return false }
         
         return repairShipIds.contains(flagShip.master_ship.stype.id)
     }
 }
+
+extension FleetViewController {
+    
+    var shipViewSize: NSSize { return details[0].view.frame.size }
+    
+    private func showExtShip() {
+        
+        guard type == .detailViewType else { return }
+        guard extDetail == nil else { return }
+        guard let _ = fleet?[6] else { return }
+        
+        if extShipAnimating {
+            DispatchQueue.main.async(execute: showExtShip)
+            return
+        }
+        
+        extShipAnimating = true
+        
+        NSAnimationContext.runAnimationGroup({ _ in
+            
+            extDetail = ShipDetailViewController(type: .full)
+            guard let extDetail = extDetail else { return }
+            
+            extDetail.title = "7"
+            extDetail.view.autoresizingMask = [.minXMargin]
+            
+            let width = extDetail.view.frame.width - 1
+            var frame = view.frame
+            frame.size.width += width
+            
+            extDetail.view.alphaValue = 0.0
+            extDetail.view.animator().alphaValue = 1.0
+            
+            extDetail.view.frame = details[5].view.frame
+            view.addSubview(extDetail.view, positioned: .below, relativeTo: details[5].view)
+            view.animator().frame = frame
+            
+        }, completionHandler: { [weak self] in
+            
+            self?.extShipAnimating = false
+        })
+        
+        delegate?.changeShowsExtShip(self, showsExtShip: true)
+    }
+    
+    private func hideExtShip() {
+        
+        guard type == .detailViewType else { return }
+        
+        if extShipAnimating {
+            DispatchQueue.main.async(execute: hideExtShip)
+            return
+        }
+        
+        guard let extDetail = extDetail else { return }
+        
+        extShipAnimating = true
+        
+        NSAnimationContext.runAnimationGroup({ _ in
+            
+            extDetail.view.animator().alphaValue = 0.0
+            
+            var frame = view.frame
+            frame.size.width -= extDetail.view.frame.width - 1
+            view.animator().frame = frame
+            
+            ships.removeLast()
+            
+        }, completionHandler: { [weak self] in
+            
+            extDetail.view.removeFromSuperview()
+            
+            self?.extShipAnimating = false
+            
+        })
+        
+        self.extDetail = nil
+        
+        delegate?.changeShowsExtShip(self, showsExtShip: false)
+    }
+    
+    private func checkExtShip() {
+                
+        guard type == .detailViewType else { return }
+        
+        if fleet?[6] != nil {
+            
+            showExtShip()
+            
+        } else {
+            
+            hideExtShip()
+        }
+    }
+}
+
+extension FleetViewController: NibLoadable {}