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
14 case minimumViewType = 1
15 case miniVierticalType = 2
18 private var shipKeysContext: Int = 0
19 private var shipsContext: Int = 0
21 protocol FleetViewControllerDelegate: class {
23 func changeShowsExtShip(_ fleetViewController: FleetViewController, showsExtShip: Bool)
26 final class FleetViewController: NSViewController {
34 enum SakutekiType: Int {
39 case formula33Parameter1 = 101
40 case formula33Parameter3 = 103
41 case formula33Parameter4 = 104
44 enum SakutekiCalclationSterategy: Int {
50 static let oldStyleFleetViewHeight: CGFloat = 128.0
51 static let detailViewHeight: CGFloat = 320.0
52 static let heightDifference: CGFloat = detailViewHeight - oldStyleFleetViewHeight
54 private static let maxFleetNumber: Int = 4
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)]
63 private var notificationObserver = NotificationObserver()
65 init?(viewType: FleetViewType) {
69 let shipiewType: ShipDetailViewType = {
72 case .detailViewType: return .full
73 case .minimumViewType: return .medium
74 case .miniVierticalType: return .minimum
77 details = (1...6).map {
79 guard let res = ShipDetailViewController(type: shipiewType) else { fatalError("Can not create ShipDetailViewController") }
86 let nibName: NSNib.Name = {
88 case .detailViewType: return FleetViewController.nibName
89 case .minimumViewType: return NSNib.Name("FleetMinimumViewController")
90 case .miniVierticalType: return NSNib.Name("VerticalFleetViewController")
94 super.init(nibName: nibName, bundle: nil)
97 required init?(coder: NSCoder) {
99 fatalError("init(coder:) has not been implemented")
102 @IBOutlet private weak var placeholder01: NSView!
103 @IBOutlet private weak var placeholder02: NSView!
104 @IBOutlet private weak var placeholder03: NSView!
105 @IBOutlet private weak var placeholder04: NSView!
106 @IBOutlet private weak var placeholder05: NSView!
107 @IBOutlet private weak var placeholder06: NSView!
109 @objc dynamic var fleetNumber: Int = 1 {
112 ServerDataStore.default
113 .deck(by: fleetNumber)
118 @objc dynamic var fleet: Deck? {
120 get { return representedObject as? Deck }
122 representedObject = newValue
123 title = newValue?.name
129 private var extDetail: ShipDetailViewController?
130 private var extShipAnimating: Bool = false
131 weak var delegate: FleetViewControllerDelegate?
133 var enableAnimation: Bool = false
135 var shipOrder: FleetViewController.ShipOrder = .doubleLine {
138 if shipOrder == newValue { return }
141 case .doubleLine: reorderShipToDoubleLine()
142 case .leftToRight: reorderShipToLeftToRight()
147 var canDivide: Bool { return type == .detailViewType }
149 var normalHeight: CGFloat {
152 case .detailViewType: return FleetViewController.detailViewHeight
153 case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
154 case .miniVierticalType: return 0.0
158 var upsideHeight: CGFloat {
161 case .detailViewType: return 175.0
162 case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
163 case .miniVierticalType: return 0.0
167 var sakutekiCalculator: SakutekiCalculator = SimpleCalculator() {
170 switch sakutekiCalculator {
171 case _ as SimpleCalculator:
172 UserDefaults.standard[.sakutekiCalclationSterategy] = .total
174 case let f as Formula33:
175 UserDefaults.standard[.sakutekiCalclationSterategy] = .formula33
176 UserDefaults.standard[.formula33Factor] = Double(f.condition)
183 @objc private var totalSakuteki: Double { return sakutekiCalculator.calculate(ships) }
184 @objc var totalSeiku: Int { return ships.reduce(0) { $0 + $1.seiku } }
185 @objc var totalCalclatedSeiku: Int { return ships.reduce(0) { $0 + totalSeiku(of: $1) } }
186 @objc private var totalLevel: Int { return ships.reduce(0) { $0 + $1.lv } }
187 @objc private var totalDrums: Int { return ships.reduce(0) { $0 + totalDrums(of: $1) } }
188 @objc var totalTPValue: Int {
191 .map { ShipTPValueCalculator($0).value }
194 @objc private var totalBRankTPValue: Int { return Int(floor(Double(totalTPValue) * 0.7)) }
196 private func totalSeiku(of ship: Ship) -> Int {
198 return SeikuCalclator(ship: ship).totalSeiku
200 private func totalDrums(of ship: Ship) -> Int {
202 return (0...4).compactMap(ship.slotItem).filter { $0.slotitem_id == 75 }.count
205 private var ships: [Ship] = [] {
208 ships.forEach { ship in
210 shipObserveKeys.forEach { ship.removeObserver(self, forKeyPath: $0) }
214 ships.forEach { ship in
216 shipObserveKeys.forEach {
218 ship.addObserver(self, forKeyPath: $0, context: &shipsContext)
224 private(set) var anchorageRepair = AnchorageRepairManager.default
226 @objc dynamic private(set) var repairTime: NSNumber?
228 override func viewDidLoad() {
232 switch UserDefaults.standard[.sakutekiCalclationSterategy] {
234 sakutekiCalculator = SimpleCalculator()
237 let factor = UserDefaults.standard[.formula33Factor]
238 sakutekiCalculator = Formula33(Int(factor))
241 fleetController.bind(NSBindingName(#keyPath(NSArrayController.content)), to: self, withKeyPath: #keyPath(fleet))
242 fleetController.addObserver(self, forKeyPath: "selection.name", context: nil)
245 let keyPath = "selection.\($0)"
246 fleetController.addObserver(self, forKeyPath: keyPath, context: &shipKeysContext)
249 buildAnchorageRepairHolder()
251 zip([placeholder01, placeholder02, placeholder03, placeholder04, placeholder05, placeholder06], details)
252 .forEach { view, detail in
254 guard let view = view else { return }
256 detail.view.frame = view.frame
257 detail.view.autoresizingMask = view.autoresizingMask
258 self.view.replaceSubview(view, with: detail.view)
263 // 初回起動時などデータがまだない時はportAPIを受信後設定する
264 NotificationCenter.default
265 .addObserverOnce(forName: .PortAPIReceived, object: nil, queue: .main) { [weak self] _ in
267 if let current = self?.fleetNumber {
269 self?.fleetNumber = current
272 self?.fleetNumber = 1
277 .addObserver(forName: .DidUpdateGuardEscape, object: nil, queue: .main) { [weak self] _ in
279 self?.notifyChangeValue(forKey: #keyPath(totalSeiku))
280 self?.notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
281 self?.notifyChangeValue(forKey: #keyPath(totalSakuteki))
282 self?.notifyChangeValue(forKey: #keyPath(totalDrums))
283 self?.notifyChangeValue(forKey: #keyPath(totalTPValue))
284 self?.notifyChangeValue(forKey: #keyPath(totalBRankTPValue))
288 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
291 if keyPath == "selection.name" {
298 if context == &shipKeysContext {
306 if let keyPath = keyPath {
308 if context == &shipsContext {
311 case #keyPath(Ship.equippedItem):
312 notifyChangeValue(forKey: #keyPath(totalSakuteki))
313 notifyChangeValue(forKey: #keyPath(totalDrums))
314 notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
315 notifyChangeValue(forKey: #keyPath(totalTPValue))
316 notifyChangeValue(forKey: #keyPath(totalBRankTPValue))
318 case #keyPath(Ship.seiku):
319 notifyChangeValue(forKey: #keyPath(totalSeiku))
320 notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
322 case #keyPath(Ship.lv):
323 notifyChangeValue(forKey: #keyPath(totalLevel))
332 super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
335 @IBAction func selectNextFleet(_ sender: AnyObject?) {
337 let next = fleetNumber + 1
338 fleetNumber = (next <= FleetViewController.maxFleetNumber ? next : 1)
341 @IBAction func selectPreviousFleet(_ sender: AnyObject?) {
343 let prev = fleetNumber - 1
344 fleetNumber = (prev > 0 ? prev : 4)
347 @IBAction func changeSakutekiCalculator(_ sender: Any?) {
349 guard let menuItem = sender as? NSMenuItem else { return }
351 switch menuItem.tag {
353 sakutekiCalculator = SimpleCalculator()
356 sakutekiCalculator = Formula33(menuItem.tag - 100)
359 askCalcutaionTurnPoint()
364 notifyChangeValue(forKey: #keyPath(totalSakuteki))
367 private func askCalcutaionTurnPoint() {
369 guard let window = self.view.window else { return }
371 let current = (sakutekiCalculator as? Formula33)?.condition ?? 1
373 let wc = CalculateConditionPanelController()
374 wc.condition = Double(current)
375 wc.beginModal(for: window) {
377 self.sakutekiCalculator = Formula33(Int($0))
379 self.notifyChangeValue(forKey: #keyPath(totalSakuteki))
383 private func setupShips() {
385 let array: [Ship?] = (0..<6).map { fleet?[$0] }
386 zip(details, array).forEach { $0.0.ship = $0.1 }
388 let extShip = fleet?[6]
389 extShip.map { extDetail?.ship = $0 }
390 ships = array.compactMap { $0 } + [extShip].compactMap { $0 }
392 [#keyPath(totalSakuteki), #keyPath(totalSeiku), #keyPath(totalCalclatedSeiku),
393 #keyPath(totalLevel), #keyPath(totalDrums), #keyPath(repairable),
394 #keyPath(totalTPValue), #keyPath(totalBRankTPValue)]
395 .forEach(notifyChangeValue(forKey:))
399 extension FleetViewController {
401 override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
403 guard let action = menuItem.action else { return false }
407 case #selector(changeSakutekiCalculator(_:)):
409 if let _ = sakutekiCalculator as? SimpleCalculator {
411 menuItem.state = (menuItem.tag == 0 ? .on : .off)
415 } else if let sakuObj = sakutekiCalculator as? Formula33 {
417 let cond = 100 + sakuObj.condition
419 menuItem.state = (menuItem.tag == cond ? .on : .off)
431 extension FleetViewController {
433 private func reorder(order: [Int]) {
435 guard order.count == 6 else { return Logger.shared.log("FleetViewController: order count is not 6.") }
437 let views = details.map { $0.view }
438 let options = views.map { $0.autoresizingMask }
439 let reorderedOptions = order.map { options[$0] }
440 zip(views, reorderedOptions).forEach { $0.0.autoresizingMask = $0.1 }
442 let frames = views.map { $0.frame }
443 let reorderedFrames = order.map { frames[$0] }
444 zip(views, reorderedFrames).forEach { $0.0.setFrame($0.1, animate: enableAnimation) }
447 private func reorderShipToDoubleLine() {
449 reorder(order: [0, 3, 1, 4, 2, 5])
452 private func reorderShipToLeftToRight() {
454 reorder(order: [0, 2, 4, 1, 3, 5])
458 extension FleetViewController {
460 private func buildAnchorageRepairHolder() {
462 AppDelegate.shared.addCounterUpdate { [weak self] in
464 guard let `self` = self else { return }
466 self.repairTime = self.calcRepairTime()
470 private func calcRepairTime() -> NSNumber? {
472 let time = anchorageRepair.repairTime
473 let complete = time.timeIntervalSince1970
474 let now = Date(timeIntervalSinceNow: 0.0)
475 let diff = complete - now.timeIntervalSince1970
477 return NSNumber(value: diff + 20.0 * 60)
480 private var repairShipIds: [Int] { return [19] }
482 @objc private dynamic var repairable: Bool {
484 guard let flagShip = fleet?[0] else { return false }
486 return repairShipIds.contains(flagShip.master_ship.stype.id)
490 extension FleetViewController {
492 var shipViewSize: NSSize { return details[0].view.frame.size }
494 private func showExtShip() {
496 guard type == .detailViewType else { return }
497 guard extDetail == nil else { return }
498 guard let _ = fleet?[6] else { return }
500 if extShipAnimating {
501 DispatchQueue.main.async(execute: showExtShip)
505 extShipAnimating = true
507 NSAnimationContext.runAnimationGroup({ _ in
509 extDetail = ShipDetailViewController(type: .full)
510 guard let extDetail = extDetail else { return }
512 extDetail.title = "7"
513 extDetail.view.autoresizingMask = [.minXMargin]
515 let width = extDetail.view.frame.width - 1
516 var frame = view.frame
517 frame.size.width += width
519 extDetail.view.alphaValue = 0.0
520 extDetail.view.animator().alphaValue = 1.0
522 extDetail.view.frame = details[5].view.frame
523 view.addSubview(extDetail.view, positioned: .below, relativeTo: details[5].view)
524 view.animator().frame = frame
526 }, completionHandler: { [weak self] in
528 self?.extShipAnimating = false
531 delegate?.changeShowsExtShip(self, showsExtShip: true)
534 private func hideExtShip() {
536 guard type == .detailViewType else { return }
538 if extShipAnimating {
539 DispatchQueue.main.async(execute: hideExtShip)
543 guard let extDetail = extDetail else { return }
545 extShipAnimating = true
547 NSAnimationContext.runAnimationGroup({ _ in
549 extDetail.view.animator().alphaValue = 0.0
551 var frame = view.frame
552 frame.size.width -= extDetail.view.frame.width - 1
553 view.animator().frame = frame
557 }, completionHandler: { [weak self] in
559 extDetail.view.removeFromSuperview()
561 self?.extShipAnimating = false
567 delegate?.changeShowsExtShip(self, showsExtShip: false)
570 private func checkExtShip() {
572 guard type == .detailViewType else { return }
574 if fleet?[6] != nil {
585 extension FleetViewController: NibLoadable {}