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 = 288.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 init?(viewType: FleetViewType) {
67 let shipiewType: ShipDetailViewType = {
70 case .detailViewType: return .full
71 case .minimumViewType: return .medium
72 case .miniVierticalType: return .minimum
75 details = (1...6).map {
77 guard let res = ShipDetailViewController(type: shipiewType) else { fatalError("Can not create ShipDetailViewController") }
84 let nibName: NSNib.Name = {
86 case .detailViewType: return FleetViewController.nibName
87 case .minimumViewType: return NSNib.Name("FleetMinimumViewController")
88 case .miniVierticalType: return NSNib.Name("VerticalFleetViewController")
92 super.init(nibName: nibName, bundle: nil)
95 required init?(coder: NSCoder) {
97 fatalError("init(coder:) has not been implemented")
102 NotificationCenter.default.removeObserver(self)
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!
112 @objc dynamic var fleetNumber: Int = 1 {
115 ServerDataStore.default
116 .deck(by: fleetNumber)
121 @objc dynamic var fleet: Deck? {
123 get { return representedObject as? Deck }
125 representedObject = newValue
126 title = newValue?.name
132 var extDetail: ShipDetailViewController?
133 var extShipAnimating: Bool = false
134 weak var delegate: FleetViewControllerDelegate?
136 var enableAnimation: Bool = false
138 var shipOrder: FleetViewController.ShipOrder = .doubleLine {
141 if shipOrder == newValue { return }
144 case .doubleLine: reorderShipToDoubleLine()
145 case .leftToRight: reorderShipToLeftToRight()
150 var canDivide: Bool { return type == .detailViewType }
152 var normalHeight: CGFloat {
155 case .detailViewType: return FleetViewController.detailViewHeight
156 case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
157 case .miniVierticalType: return 0.0
161 var upsideHeight: CGFloat {
164 case .detailViewType: return 159.0
165 case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
166 case .miniVierticalType: return 0.0
170 var sakutekiCalculator: SakutekiCalculator = SimpleCalculator() {
173 switch sakutekiCalculator {
174 case _ as SimpleCalculator:
175 UserDefaults.standard[.sakutekiCalclationSterategy] = .total
177 case let f as Formula33:
178 UserDefaults.standard[.sakutekiCalclationSterategy] = .formula33
179 UserDefaults.standard[.formula33Factor] = Double(f.condition)
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 {
194 .map { ShipTPValueCalculator($0).value }
198 func totalSeiku(of ship: Ship) -> Int {
200 return SeikuCalclator(ship: ship).totalSeiku
202 func totalDrums(of ship: Ship) -> Int {
204 return (0...4).flatMap(ship.slotItem).filter { $0.slotitem_id == 75 }.count
207 private var ships: [Ship] = [] {
210 ships.forEach { ship in
212 shipObserveKeys.forEach { ship.removeObserver(self, forKeyPath: $0) }
216 ships.forEach { ship in
218 shipObserveKeys.forEach {
220 ship.addObserver(self, forKeyPath: $0, context: &shipsContext)
226 private(set) var anchorageRepair = AnchorageRepairManager.default
228 @objc dynamic private(set) var repairTime: NSNumber?
230 override func viewDidLoad() {
234 switch UserDefaults.standard[.sakutekiCalclationSterategy] {
236 sakutekiCalculator = SimpleCalculator()
239 let factor = UserDefaults.standard[.formula33Factor]
240 sakutekiCalculator = Formula33(Int(factor))
243 fleetController.bind(NSBindingName(#keyPath(NSArrayController.content)), to: self, withKeyPath: #keyPath(fleet), options: nil)
244 fleetController.addObserver(self, forKeyPath: "selection.name", context: nil)
247 let keyPath = "selection.\($0)"
248 fleetController.addObserver(self, forKeyPath: keyPath, context: &shipKeysContext)
251 buildAnchorageRepairHolder()
253 [placeholder01, placeholder02, placeholder03, placeholder04, placeholder05, placeholder06]
257 guard let view = $0.element else { return }
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)
266 // 初回起動時などデータがまだない時はportAPIを受信後設定する
267 NotificationCenter.default
268 .addObserverOnce(forName: .PortAPIReceived, object: nil, queue: nil) { [weak self] _ in
270 if let current = self?.fleetNumber {
272 self?.fleetNumber = current
275 self?.fleetNumber = 1
281 .addObserver(forName: .DidUpdateGuardEscape, object: nil, queue: nil) { [weak self] _ in
283 self?.notifyChangeValue(forKey: #keyPath(totalSeiku))
284 self?.notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
285 self?.notifyChangeValue(forKey: #keyPath(totalSakuteki))
286 self?.notifyChangeValue(forKey: #keyPath(totalDrums))
287 self?.notifyChangeValue(forKey: #keyPath(totalTPValue))
291 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
294 if keyPath == "selection.name" {
301 if context == &shipKeysContext {
309 if let keyPath = keyPath {
311 if context == &shipsContext {
314 case #keyPath(Ship.equippedItem):
315 notifyChangeValue(forKey: #keyPath(totalSakuteki))
316 notifyChangeValue(forKey: #keyPath(totalDrums))
317 notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
318 notifyChangeValue(forKey: #keyPath(totalTPValue))
320 case #keyPath(Ship.seiku):
321 notifyChangeValue(forKey: #keyPath(totalSeiku))
322 notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
324 case #keyPath(Ship.lv):
325 notifyChangeValue(forKey: #keyPath(totalLevel))
334 super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
337 @IBAction func selectNextFleet(_ sender: AnyObject?) {
339 let next = fleetNumber + 1
340 fleetNumber = (next <= FleetViewController.maxFleetNumber ? next : 1)
343 @IBAction func selectPreviousFleet(_ sender: AnyObject?) {
345 let prev = fleetNumber - 1
346 fleetNumber = (prev > 0 ? prev : 4)
349 @IBAction func changeSakutekiCalculator(_ sender: Any?) {
351 guard let menuItem = sender as? NSMenuItem else { return }
353 switch menuItem.tag {
355 sakutekiCalculator = SimpleCalculator()
358 sakutekiCalculator = Formula33(menuItem.tag - 100)
361 askCalcutaionTurnPoint()
366 notifyChangeValue(forKey: #keyPath(totalSakuteki))
369 private func askCalcutaionTurnPoint() {
371 guard let window = self.view.window else { return }
373 let current = (sakutekiCalculator as? Formula33)?.condition ?? 1
375 let wc = CalculateConditionPanelController()
376 wc.condition = Double(current)
377 wc.beginModal(for: window) {
379 self.sakutekiCalculator = Formula33(Int($0))
381 self.notifyChangeValue(forKey: #keyPath(totalSakuteki))
385 private func setupShips() {
387 let array: [Ship?] = (0..<6).map { fleet?[$0] }
388 zip(details, array).forEach { $0.0.ship = $0.1 }
390 let extShip = fleet?[6]
391 extShip.map { extDetail?.ship = $0 }
392 ships = array.flatMap { $0 } + [extShip].flatMap { $0 }
394 [#keyPath(totalSakuteki), #keyPath(totalSeiku), #keyPath(totalCalclatedSeiku),
395 #keyPath(totalLevel), #keyPath(totalDrums), #keyPath(repairable),
396 #keyPath(totalTPValue)]
397 .forEach(notifyChangeValue(forKey:))
401 extension FleetViewController {
403 override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
405 guard let action = menuItem.action else { return false }
409 case #selector(changeSakutekiCalculator(_:)):
411 if let _ = sakutekiCalculator as? SimpleCalculator {
413 menuItem.state = (menuItem.tag == 0 ? .on : .off)
417 } else if let sakuObj = sakutekiCalculator as? Formula33 {
419 let cond = 100 + sakuObj.condition
421 menuItem.state = (menuItem.tag == cond ? .on : .off)
433 extension FleetViewController {
435 private func reorder(order: [Int]) {
437 guard order.count == 6 else { return Logger.shared.log("FleetViewController: order count is not 6.") }
439 let views = details.map { $0.view }
440 let options = views.map { $0.autoresizingMask }
441 let reorderedOptions = order.map { options[$0] }
442 zip(views, reorderedOptions).forEach { $0.0.autoresizingMask = $0.1 }
444 let frames = views.map { $0.frame }
445 let reorderedFrames = order.map { frames[$0] }
446 zip(views, reorderedFrames).forEach { $0.0.setFrame($0.1, animate: enableAnimation) }
449 private func reorderShipToDoubleLine() {
451 reorder(order: [0, 3, 1, 4, 2, 5])
454 private func reorderShipToLeftToRight() {
456 reorder(order: [0, 2, 4, 1, 3, 5])
460 extension FleetViewController {
462 func buildAnchorageRepairHolder() {
464 AppDelegate.shared.addCounterUpdate { [weak self] in
466 guard let `self` = self else { return }
468 self.repairTime = self.calcRepairTime()
472 private func calcRepairTime() -> NSNumber? {
474 let time = anchorageRepair.repairTime
475 let complete = time.timeIntervalSince1970
476 let now = Date(timeIntervalSinceNow: 0.0)
477 let diff = complete - now.timeIntervalSince1970
479 return NSNumber(value: diff + 20.0 * 60)
482 private var repairShipIds: [Int] { return [19] }
484 @objc dynamic var repairable: Bool {
486 guard let flagShip = fleet?[0] else { return false }
488 return repairShipIds.contains(flagShip.master_ship.stype.id)
492 extension FleetViewController {
494 var shipViewSize: NSSize { return details[0].view.frame.size }
496 private func showExtShip() {
498 guard type == .detailViewType else { return }
499 guard extDetail == nil else { return }
500 guard let _ = fleet?[6] else { return }
502 if extShipAnimating {
503 DispatchQueue.main.async(execute: showExtShip)
507 extShipAnimating = true
509 NSAnimationContext.runAnimationGroup({ _ in
511 extDetail = ShipDetailViewController(type: .full)
512 guard let extDetail = extDetail else { return }
514 extDetail.title = "7"
515 extDetail.view.autoresizingMask = [.minXMargin]
517 let width = extDetail.view.frame.width - 1
518 var frame = view.frame
519 frame.size.width += width
521 extDetail.view.frame = details[5].view.frame
522 view.addSubview(extDetail.view, positioned: .below, relativeTo: details[5].view)
523 view.animator().frame = frame
525 }, completionHandler: { [weak self] in
527 self?.extShipAnimating = false
530 delegate?.changeShowsExtShip(self, showsExtShip: true)
533 private func hideExtShip() {
535 guard type == .detailViewType else { return }
537 if extShipAnimating {
538 DispatchQueue.main.async(execute: hideExtShip)
542 guard let extDetail = extDetail else { return }
544 extShipAnimating = true
546 NSAnimationContext.runAnimationGroup({ _ in
548 var frame = view.frame
549 frame.size.width -= extDetail.view.frame.width - 1
550 view.animator().frame = frame
554 }, completionHandler: { [weak self] in
556 extDetail.view.removeFromSuperview()
558 self?.extShipAnimating = false
564 delegate?.changeShowsExtShip(self, showsExtShip: false)
567 private func checkExtShip() {
569 guard type == .detailViewType else { return }
571 if fleet?[6] != nil {
582 extension FleetViewController: NibLoadable {}