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 final class FleetViewController: NSViewController {
29 enum SakutekiType: Int {
34 case formula33Parameter1 = 101
35 case formula33Parameter3 = 103
36 case formula33Parameter4 = 104
39 enum SakutekiCalclationSterategy: Int {
45 static let oldStyleFleetViewHeight: CGFloat = 128.0
46 static let detailViewHeight: CGFloat = 288.0
47 static let heightDifference: CGFloat = detailViewHeight - oldStyleFleetViewHeight
49 private static let maxFleetNumber: Int = 4
51 private let details: [ShipDetailViewController]
52 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)]
53 private let type: FleetViewType
54 private let fleetController = NSObjectController()
55 private let shipObserveKeys = [#keyPath(Ship.seiku), #keyPath(Ship.lv), #keyPath(Ship.equippedItem)]
57 init?(viewType: FleetViewType) {
61 let shipiewType: ShipDetailViewType = {
64 case .detailViewType: return .full
65 case .minimumViewType: return .medium
66 case .miniVierticalType: return .minimum
69 details = (1...6).map {
71 guard let res = ShipDetailViewController(type: shipiewType) else { fatalError("Can not create ShipDetailViewController") }
78 let nibName: NSNib.Name = {
80 case .detailViewType: return FleetViewController.nibName
81 case .minimumViewType: return NSNib.Name("FleetMinimumViewController")
82 case .miniVierticalType: return NSNib.Name("VerticalFleetViewController")
86 super.init(nibName: nibName, bundle: nil)
89 required init?(coder: NSCoder) {
91 fatalError("init(coder:) has not been implemented")
96 NotificationCenter.default.removeObserver(self)
99 @IBOutlet weak var placeholder01: NSView!
100 @IBOutlet weak var placeholder02: NSView!
101 @IBOutlet weak var placeholder03: NSView!
102 @IBOutlet weak var placeholder04: NSView!
103 @IBOutlet weak var placeholder05: NSView!
104 @IBOutlet weak var placeholder06: NSView!
106 @objc dynamic var fleetNumber: Int = 1 {
109 ServerDataStore.default
110 .deck(by: fleetNumber)
115 @objc dynamic var fleet: Deck? {
117 get { return representedObject as? Deck }
119 representedObject = newValue
120 title = newValue?.name
125 var enableAnimation: Bool = false
127 var shipOrder: FleetViewController.ShipOrder = .doubleLine {
130 if shipOrder == newValue { return }
133 case .doubleLine: reorderShipToDoubleLine()
134 case .leftToRight: reorderShipToLeftToRight()
139 var canDivide: Bool { return type == .detailViewType }
141 var normalHeight: CGFloat {
144 case .detailViewType: return FleetViewController.detailViewHeight
145 case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
146 case .miniVierticalType: return 0.0
150 var upsideHeight: CGFloat {
153 case .detailViewType: return 159.0
154 case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
155 case .miniVierticalType: return 0.0
159 var sakutekiCalculator: SakutekiCalculator = SimpleCalculator() {
162 switch sakutekiCalculator {
163 case _ as SimpleCalculator:
164 UserDefaults.standard[.sakutekiCalclationSterategy] = .total
166 case let f as Formula33:
167 UserDefaults.standard[.sakutekiCalclationSterategy] = .formula33
168 UserDefaults.standard[.formula33Factor] = Double(f.condition)
175 @objc var totalSakuteki: Double { return sakutekiCalculator.calculate(ships) }
176 @objc var totalSeiku: Int { return ships.reduce(0) { $0 + $1.seiku } }
177 @objc var totalCalclatedSeiku: Int { return ships.reduce(0) { $0 + totalSeiku(of: $1) } }
178 @objc var totalLevel: Int { return ships.reduce(0) { $0 + $1.lv } }
179 @objc var totalDrums: Int { return ships.reduce(0) { $0 + totalDrums(of: $1) } }
180 @objc var totalTPValue: Int {
183 .map { ShipTPValueCalculator($0) }
189 func totalSeiku(of ship: Ship) -> Int {
191 return SeikuCalclator(ship: ship).totalSeiku
193 func totalDrums(of ship: Ship) -> Int {
195 return (0...4).flatMap(ship.slotItem).filter { $0.slotitem_id == 75 }.count
198 private var ships: [Ship] = [] {
201 ships.forEach { ship in
203 shipObserveKeys.forEach { ship.removeObserver(self, forKeyPath: $0) }
207 ships.forEach { ship in
209 shipObserveKeys.forEach {
211 ship.addObserver(self, forKeyPath: $0, context: &shipsContext)
217 private(set) var anchorageRepair = AnchorageRepairManager.default
219 @objc dynamic private(set) var repairTime: NSNumber?
221 override func viewDidLoad() {
225 switch UserDefaults.standard[.sakutekiCalclationSterategy] {
227 sakutekiCalculator = SimpleCalculator()
230 let factor = UserDefaults.standard[.formula33Factor]
231 sakutekiCalculator = Formula33(Int(factor))
234 fleetController.bind(NSBindingName(#keyPath(NSArrayController.content)), to: self, withKeyPath: #keyPath(fleet), options: nil)
235 fleetController.addObserver(self, forKeyPath: "selection.name", context: nil)
238 let keyPath = "selection.\($0)"
239 fleetController.addObserver(self, forKeyPath: keyPath, context: &shipKeysContext)
242 buildAnchorageRepairHolder()
244 [placeholder01, placeholder02, placeholder03, placeholder04, placeholder05, placeholder06]
248 guard let view = $0.element else { return }
250 let detail = details[$0.offset]
251 detail.view.frame = view.frame
252 detail.view.autoresizingMask = view.autoresizingMask
253 self.view.replaceSubview(view, with: detail.view)
257 // 初回起動時などデータがまだない時はportAPIを受信後設定する
258 NotificationCenter.default
259 .addObserverOnce(forName: .PortAPIReceived, object: nil, queue: nil) { [weak self] _ in
261 self?.fleetNumber = 1
266 .addObserver(forName: .DidUpdateGuardEscape, object: nil, queue: nil) { [weak self] _ in
268 self?.notifyChangeValue(forKey: #keyPath(totalSeiku))
269 self?.notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
270 self?.notifyChangeValue(forKey: #keyPath(totalSakuteki))
271 self?.notifyChangeValue(forKey: #keyPath(totalDrums))
272 self?.notifyChangeValue(forKey: #keyPath(totalTPValue))
276 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
279 if keyPath == "selection.name" {
286 if context == &shipKeysContext {
293 if let keyPath = keyPath {
295 if context == &shipsContext {
298 case #keyPath(Ship.equippedItem):
299 notifyChangeValue(forKey: #keyPath(totalSakuteki))
300 notifyChangeValue(forKey: #keyPath(totalDrums))
301 notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
302 notifyChangeValue(forKey: #keyPath(totalTPValue))
304 case #keyPath(Ship.seiku):
305 notifyChangeValue(forKey: #keyPath(totalSeiku))
306 notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
308 case #keyPath(Ship.lv):
309 notifyChangeValue(forKey: #keyPath(totalLevel))
318 super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
321 @IBAction func selectNextFleet(_ sender: AnyObject?) {
323 let next = fleetNumber + 1
324 fleetNumber = (next <= FleetViewController.maxFleetNumber ? next : 1)
327 @IBAction func selectPreviousFleet(_ sender: AnyObject?) {
329 let prev = fleetNumber - 1
330 fleetNumber = (prev > 0 ? prev : 4)
333 @IBAction func changeSakutekiCalculator(_ sender: Any?) {
335 guard let menuItem = sender as? NSMenuItem else { return }
337 switch menuItem.tag {
339 sakutekiCalculator = SimpleCalculator()
342 sakutekiCalculator = Formula33(menuItem.tag - 100)
345 askCalcutaionTurnPoint()
350 notifyChangeValue(forKey: #keyPath(totalSakuteki))
353 private func askCalcutaionTurnPoint() {
355 guard let window = self.view.window else { return }
357 let current = (sakutekiCalculator as? Formula33)?.condition ?? 1
359 let wc = CalculateConditionPanelController()
360 wc.condition = Double(current)
361 wc.beginModal(for: window) {
363 self.sakutekiCalculator = Formula33(Int($0))
365 self.notifyChangeValue(forKey: #keyPath(totalSakuteki))
369 private func setupShips() {
371 let array: [Ship?] = (0..<6).map { fleet?[$0] }
372 zip(details, array).forEach { $0.0.ship = $0.1 }
373 ships = array.flatMap { $0 }
375 [#keyPath(totalSakuteki), #keyPath(totalSeiku), #keyPath(totalCalclatedSeiku),
376 #keyPath(totalLevel), #keyPath(totalDrums), #keyPath(repairable),
377 #keyPath(totalTPValue)]
378 .forEach(notifyChangeValue(forKey:))
382 extension FleetViewController {
384 override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
386 guard let action = menuItem.action else { return false }
390 case #selector(changeSakutekiCalculator(_:)):
392 if let _ = sakutekiCalculator as? SimpleCalculator {
394 menuItem.state = (menuItem.tag == 0 ? .on : .off)
398 } else if let sakuObj = sakutekiCalculator as? Formula33 {
400 let cond = 100 + sakuObj.condition
402 menuItem.state = (menuItem.tag == cond ? .on : .off)
414 extension FleetViewController {
416 private func reorder(order: [Int]) {
418 guard order.count == 6 else { return Logger.shared.log("FleetViewController: order count is not 6.") }
420 let views = details.map { $0.view }
421 let options = views.map { $0.autoresizingMask }
422 let reorderedOptions = order.map { options[$0] }
423 zip(views, reorderedOptions).forEach { $0.0.autoresizingMask = $0.1 }
425 let frames = views.map { $0.frame }
426 let reorderedFrames = order.map { frames[$0] }
427 zip(views, reorderedFrames).forEach { $0.0.setFrame($0.1, animate: enableAnimation) }
430 private func reorderShipToDoubleLine() {
432 reorder(order: [0, 3, 1, 4, 2, 5])
435 private func reorderShipToLeftToRight() {
437 reorder(order: [0, 2, 4, 1, 3, 5])
441 extension FleetViewController {
443 func buildAnchorageRepairHolder() {
445 AppDelegate.shared.addCounterUpdate { [weak self] in
447 guard let `self` = self else { return }
449 self.repairTime = self.calcRepairTime()
453 private func calcRepairTime() -> NSNumber? {
455 let time = anchorageRepair.repairTime
456 let complete = time.timeIntervalSince1970
457 let now = Date(timeIntervalSinceNow: 0.0)
458 let diff = complete - now.timeIntervalSince1970
460 return NSNumber(value: diff + 20.0 * 60)
463 private var repairShipIds: [Int] { return [19] }
465 @objc dynamic var repairable: Bool {
467 guard let flagShip = fleet?[0] else { return false }
469 return repairShipIds.contains(flagShip.master_ship.stype.id)
473 extension FleetViewController: NibLoadable {}