2 // FleetViewController.swift
5 // Created by Hori,Masaki on 2016/12/27.
6 // Copyright © 2016年 Hori,Masaki. All rights reserved.
11 enum FleetViewType: Int {
13 case detailViewType = 0
15 case minimumViewType = 1
17 case miniVierticalType = 2
20 private var shipKeysContext: Int = 0
21 private var shipsContext: Int = 0
23 protocol FleetViewControllerDelegate: class {
25 func changeShowsExtShip(_ fleetViewController: FleetViewController, showsExtShip: Bool)
28 final class FleetViewController: NSViewController {
37 enum SakutekiType: Int {
44 case formula33Parameter1 = 101
46 case formula33Parameter3 = 103
48 case formula33Parameter4 = 104
51 enum SakutekiCalclationSterategy: Int {
58 static let oldStyleFleetViewHeight: CGFloat = 128.0
59 static let detailViewHeight: CGFloat = 320.0
60 static let heightDifference: CGFloat = detailViewHeight - oldStyleFleetViewHeight
62 private static let maxFleetNumber: Int = 4
64 private let details: [ShipDetailViewController]
65 private let shipKeys = [#keyPath(Deck.ship_0), #keyPath(Deck.ship_1), #keyPath(Deck.ship_2), #keyPath(Deck.ship_3),
66 #keyPath(Deck.ship_4), #keyPath(Deck.ship_5), #keyPath(Deck.ship_6)]
67 private let type: FleetViewType
68 private let fleetController = NSObjectController()
69 private let shipObserveKeys = [#keyPath(Ship.seiku), #keyPath(Ship.lv), #keyPath(Ship.equippedItem)]
71 private var notificationObserver = NotificationObserver()
73 init?(viewType: FleetViewType) {
77 let shipiewType: ShipDetailViewType = {
81 case .detailViewType: return .full
83 case .minimumViewType: return .medium
85 case .miniVierticalType: return .minimum
89 details = (1...6).map {
91 guard let res = ShipDetailViewController(type: shipiewType) else {
93 fatalError("Can not create ShipDetailViewController")
101 let nibName: NSNib.Name = {
105 case .detailViewType: return FleetViewController.nibName
107 case .minimumViewType: return NSNib.Name("FleetMinimumViewController")
109 case .miniVierticalType: return NSNib.Name("VerticalFleetViewController")
114 super.init(nibName: nibName, bundle: nil)
117 required init?(coder: NSCoder) {
119 fatalError("init(coder:) has not been implemented")
122 @IBOutlet private weak var placeholder01: NSView!
123 @IBOutlet private weak var placeholder02: NSView!
124 @IBOutlet private weak var placeholder03: NSView!
125 @IBOutlet private weak var placeholder04: NSView!
126 @IBOutlet private weak var placeholder05: NSView!
127 @IBOutlet private weak var placeholder06: NSView!
129 @objc dynamic var fleetNumber: Int = 1 {
133 ServerDataStore.default
134 .deck(by: fleetNumber)
139 @objc dynamic var fleet: Deck? {
141 get { return representedObject as? Deck }
144 representedObject = newValue
145 title = newValue?.name
151 private var extDetail: ShipDetailViewController?
152 private var extShipAnimating: Bool = false
153 weak var delegate: FleetViewControllerDelegate?
155 var enableAnimation: Bool = false
157 var shipOrder: FleetViewController.ShipOrder = .doubleLine {
161 if shipOrder == newValue {
168 case .doubleLine: reorderShipToDoubleLine()
170 case .leftToRight: reorderShipToLeftToRight()
176 var canDivide: Bool { return type == .detailViewType }
178 var normalHeight: CGFloat {
182 case .detailViewType: return FleetViewController.detailViewHeight
184 case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
186 case .miniVierticalType: return 0.0
191 var upsideHeight: CGFloat {
195 case .detailViewType: return 175.0
197 case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
199 case .miniVierticalType: return 0.0
204 var sakutekiCalculator: SakutekiCalculator = SimpleCalculator() {
208 switch sakutekiCalculator {
210 case _ as SimpleCalculator:
211 UserDefaults.standard[.sakutekiCalclationSterategy] = .total
213 case let f as Formula33:
214 UserDefaults.standard[.sakutekiCalclationSterategy] = .formula33
215 UserDefaults.standard[.formula33Factor] = Double(f.condition)
223 @objc private var totalSakuteki: Double { return sakutekiCalculator.calculate(ships) }
224 @objc var totalSeiku: Int { return ships.reduce(0) { $0 + $1.seiku } }
225 @objc var totalCalclatedSeiku: Int { return ships.reduce(0) { $0 + totalSeiku(of: $1) } }
226 @objc private var totalLevel: Int { return ships.reduce(0) { $0 + $1.lv } }
227 @objc private var totalDrums: Int { return ships.reduce(0) { $0 + totalDrums(of: $1) } }
228 @objc var totalTPValue: Int {
231 .map { ShipTPValueCalculator($0).value }
234 @objc private var totalBRankTPValue: Int { return Int(floor(Double(totalTPValue) * 0.7)) }
236 private func totalSeiku(of ship: Ship) -> Int {
238 return SeikuCalclator(ship: ship).totalSeiku
240 private func totalDrums(of ship: Ship) -> Int {
242 return (0...4).compactMap(ship.slotItem).filter { $0.slotitem_id == 75 }.count
245 private var ships: [Ship] = [] {
249 ships.forEach { ship in
251 shipObserveKeys.forEach { ship.removeObserver(self, forKeyPath: $0) }
257 ships.forEach { ship in
259 shipObserveKeys.forEach {
261 ship.addObserver(self, forKeyPath: $0, context: &shipsContext)
267 private(set) var anchorageRepair = AnchorageRepairManager.default
269 @objc dynamic private(set) var repairTime: NSNumber?
271 override func viewDidLoad() {
275 switch UserDefaults.standard[.sakutekiCalclationSterategy] {
278 sakutekiCalculator = SimpleCalculator()
281 let factor = UserDefaults.standard[.formula33Factor]
282 sakutekiCalculator = Formula33(Int(factor))
286 fleetController.bind(NSBindingName(#keyPath(NSArrayController.content)), to: self, withKeyPath: #keyPath(fleet))
287 fleetController.addObserver(self, forKeyPath: "selection.name", context: nil)
290 let keyPath = "selection.\($0)"
291 fleetController.addObserver(self, forKeyPath: keyPath, context: &shipKeysContext)
294 buildAnchorageRepairHolder()
296 zip([placeholder01, placeholder02, placeholder03, placeholder04, placeholder05, placeholder06], details)
297 .forEach { view, detail in
299 guard let view = view else {
304 detail.view.frame = view.frame
305 detail.view.autoresizingMask = view.autoresizingMask
306 self.view.replaceSubview(view, with: detail.view)
311 // 初回起動時などデータがまだない時はportAPIを受信後設定する
312 NotificationCenter.default
313 .addObserverOnce(forName: .PortAPIReceived, object: nil, queue: .main) { [weak self] _ in
315 if let current = self?.fleetNumber {
317 self?.fleetNumber = current
321 self?.fleetNumber = 1
326 .addObserver(forName: .DidUpdateGuardEscape, object: nil, queue: .main) { [weak self] _ in
328 self?.notifyChangeValue(forKey: #keyPath(totalSeiku))
329 self?.notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
330 self?.notifyChangeValue(forKey: #keyPath(totalSakuteki))
331 self?.notifyChangeValue(forKey: #keyPath(totalDrums))
332 self?.notifyChangeValue(forKey: #keyPath(totalTPValue))
333 self?.notifyChangeValue(forKey: #keyPath(totalBRankTPValue))
337 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
340 if keyPath == "selection.name" {
347 if context == &shipKeysContext {
355 if let keyPath = keyPath {
357 if context == &shipsContext {
361 case #keyPath(Ship.equippedItem):
362 notifyChangeValue(forKey: #keyPath(totalSakuteki))
363 notifyChangeValue(forKey: #keyPath(totalDrums))
364 notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
365 notifyChangeValue(forKey: #keyPath(totalTPValue))
366 notifyChangeValue(forKey: #keyPath(totalBRankTPValue))
368 case #keyPath(Ship.seiku):
369 notifyChangeValue(forKey: #keyPath(totalSeiku))
370 notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
372 case #keyPath(Ship.lv):
373 notifyChangeValue(forKey: #keyPath(totalLevel))
383 super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
386 @IBAction func selectNextFleet(_ sender: AnyObject?) {
388 let next = fleetNumber + 1
389 fleetNumber = (next <= FleetViewController.maxFleetNumber ? next : 1)
392 @IBAction func selectPreviousFleet(_ sender: AnyObject?) {
394 let prev = fleetNumber - 1
395 fleetNumber = (prev > 0 ? prev : 4)
398 @IBAction func changeSakutekiCalculator(_ sender: Any?) {
400 guard let menuItem = sender as? NSMenuItem else {
405 switch menuItem.tag {
408 sakutekiCalculator = SimpleCalculator()
411 sakutekiCalculator = Formula33(menuItem.tag - 100)
414 askCalcutaionTurnPoint()
420 notifyChangeValue(forKey: #keyPath(totalSakuteki))
423 private func askCalcutaionTurnPoint() {
425 guard let window = self.view.window else {
430 let current = (sakutekiCalculator as? Formula33)?.condition ?? 1
432 let wc = CalculateConditionPanelController()
433 wc.condition = Double(current)
434 wc.beginModal(for: window) {
436 self.sakutekiCalculator = Formula33(Int($0))
438 self.notifyChangeValue(forKey: #keyPath(totalSakuteki))
442 private func setupShips() {
444 let array: [Ship?] = (0..<6).map { fleet?[$0] }
445 zip(details, array).forEach { $0.0.ship = $0.1 }
447 let extShip = fleet?[6]
448 extShip.map { extDetail?.ship = $0 }
449 ships = array.compactMap { $0 } + [extShip].compactMap { $0 }
451 [#keyPath(totalSakuteki), #keyPath(totalSeiku), #keyPath(totalCalclatedSeiku),
452 #keyPath(totalLevel), #keyPath(totalDrums), #keyPath(repairable),
453 #keyPath(totalTPValue), #keyPath(totalBRankTPValue)]
454 .forEach(notifyChangeValue(forKey:))
458 extension FleetViewController {
460 override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
462 guard let action = menuItem.action else {
469 case #selector(changeSakutekiCalculator(_:)):
471 if let _ = sakutekiCalculator as? SimpleCalculator {
473 menuItem.state = (menuItem.tag == 0 ? .on : .off)
477 } else if let sakuObj = sakutekiCalculator as? Formula33 {
479 let cond = 100 + sakuObj.condition
481 menuItem.state = (menuItem.tag == cond ? .on : .off)
494 extension FleetViewController {
496 private func reorder(order: [Int]) {
498 guard order.count == 6 else {
500 Logger.shared.log("FleetViewController: order count is not 6.")
505 let views = details.map { $0.view }
506 let options = views.map { $0.autoresizingMask }
507 let reorderedOptions = order.map { options[$0] }
508 zip(views, reorderedOptions).forEach { $0.0.autoresizingMask = $0.1 }
510 let frames = views.map { $0.frame }
511 let reorderedFrames = order.map { frames[$0] }
512 zip(views, reorderedFrames).forEach { $0.0.setFrame($0.1, animate: enableAnimation) }
515 private func reorderShipToDoubleLine() {
517 reorder(order: [0, 3, 1, 4, 2, 5])
520 private func reorderShipToLeftToRight() {
522 reorder(order: [0, 2, 4, 1, 3, 5])
526 extension FleetViewController {
528 private func buildAnchorageRepairHolder() {
530 AppDelegate.shared.addCounterUpdate { [weak self] in
532 guard let `self` = self else {
537 self.repairTime = self.calcRepairTime()
541 private func calcRepairTime() -> NSNumber? {
543 let time = anchorageRepair.repairTime
544 let complete = time.timeIntervalSince1970
545 let now = Date(timeIntervalSinceNow: 0.0)
546 let diff = complete - now.timeIntervalSince1970
548 return NSNumber(value: diff + 20.0 * 60)
551 private var repairShipIds: [Int] { return [19] }
553 @objc private dynamic var repairable: Bool {
555 guard let flagShip = fleet?[0] else {
560 return repairShipIds.contains(flagShip.master_ship.stype.id)
564 extension FleetViewController {
566 var shipViewSize: NSSize { return details[0].view.frame.size }
568 private func showExtShip() {
570 guard type == .detailViewType else {
574 guard extDetail == nil else {
578 guard let _ = fleet?[6] else {
583 if extShipAnimating {
585 DispatchQueue.main.async(execute: showExtShip)
590 extShipAnimating = true
592 NSAnimationContext.runAnimationGroup({ _ in
594 extDetail = ShipDetailViewController(type: .full)
595 guard let extDetail = extDetail else {
600 extDetail.title = "7"
601 extDetail.view.autoresizingMask = [.minXMargin]
603 let width = extDetail.view.frame.width - 1
604 var frame = view.frame
605 frame.size.width += width
607 extDetail.view.alphaValue = 0.0
608 extDetail.view.animator().alphaValue = 1.0
610 extDetail.view.frame = details[5].view.frame
611 view.addSubview(extDetail.view, positioned: .below, relativeTo: details[5].view)
612 view.animator().frame = frame
614 }, completionHandler: { [weak self] in
616 self?.extShipAnimating = false
619 delegate?.changeShowsExtShip(self, showsExtShip: true)
622 private func hideExtShip() {
624 guard type == .detailViewType else {
629 if extShipAnimating {
631 DispatchQueue.main.async(execute: hideExtShip)
636 guard let extDetail = extDetail else {
641 extShipAnimating = true
643 NSAnimationContext.runAnimationGroup({ _ in
645 extDetail.view.animator().alphaValue = 0.0
647 var frame = view.frame
648 frame.size.width -= extDetail.view.frame.width - 1
649 view.animator().frame = frame
653 }, completionHandler: { [weak self] in
655 extDetail.view.removeFromSuperview()
657 self?.extShipAnimating = false
663 delegate?.changeShowsExtShip(self, showsExtShip: false)
666 private func checkExtShip() {
668 guard type == .detailViewType else {
673 if fleet?[6] != nil {
684 extension FleetViewController: NibLoadable {}