import Cocoa
enum FleetViewType: Int {
+
case detailViewType = 0
case minimumViewType = 1
case miniVierticalType = 2
}
-fileprivate var shipKeysContext: Int = 0
-fileprivate var shipsContext: Int = 0
+private var shipKeysContext: Int = 0
+private var shipsContext: Int = 0
-class FleetViewController: NSViewController {
+protocol FleetViewControllerDelegate: class {
+
+ func changeShowsExtShip(_ fleetViewController: FleetViewController, showsExtShip: Bool)
+}
+
+final class FleetViewController: NSViewController {
+
enum ShipOrder: Int {
+
case doubleLine = 0
case leftToRight = 1
}
+ enum SakutekiType: Int {
+
+ case adding = 0
+
+ case formula33 = 100
+ case formula33Parameter1 = 101
+ case formula33Parameter3 = 103
+ case formula33Parameter4 = 104
+ }
+
+ enum SakutekiCalclationSterategy: Int {
+
+ case total
+ case formula33
+ }
+
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 = ["sakuteki_0", "seiku", "totalSeiku", "lv", "totalDrums"]
+ private let shipObserveKeys = [#keyPath(Ship.seiku), #keyPath(Ship.lv), #keyPath(Ship.equippedItem)]
+
+ private var notificationObserver = NotificationObserver()
init?(viewType: FleetViewType) {
+
type = viewType
let shipiewType: ShipDetailViewType = {
+
switch viewType {
case .detailViewType: return .full
case .minimumViewType: return .medium
}
}()
details = (1...6).map {
- let res = ShipDetailViewController(type: shipiewType)!
+
+ 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")
}
}()
+
super.init(nibName: nibName, bundle: nil)
}
+
required init?(coder: NSCoder) {
+
fatalError("init(coder:) has not been implemented")
}
- deinit {
- NotificationCenter.default.removeObserver(self)
- }
- @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!
+ @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!
- dynamic var fleetNumber: Int = 1 {
+ @objc dynamic var fleetNumber: Int = 1 {
+
didSet {
ServerDataStore.default
- .deck(byId: fleetNumber)
+ .deck(by: fleetNumber)
.map { fleet = $0 }
}
}
- 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 {
+
willSet {
if shipOrder == newValue { return }
+
switch newValue {
case .doubleLine: reorderShipToDoubleLine()
case .leftToRight: reorderShipToLeftToRight()
}
}
}
+
var canDivide: Bool { return type == .detailViewType }
+
var normalHeight: CGFloat {
+
switch type {
case .detailViewType: return FleetViewController.detailViewHeight
case .minimumViewType: return FleetViewController.oldStyleFleetViewHeight
case .miniVierticalType: return 0.0
}
}
+
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
}
}
- var totalSakuteki: Int { return ships.reduce(0) { $0 + $1.sakuteki_0 } }
- var totalSeiku: Int { return ships.reduce(0) { $0 + $1.seiku } }
- var totalCalclatedSeiku: Int { return ships.reduce(0) { $0 + $1.totalSeiku } }
- var totalLevel: Int { return ships.reduce(0) { $0 + $1.lv } }
- var totalDrums: Int { return ships.reduce(0) { $0 + $1.totalDrums } }
- fileprivate var ships: [Ship] = [] {
+ var sakutekiCalculator: SakutekiCalculator = SimpleCalculator() {
+
+ didSet {
+ switch sakutekiCalculator {
+ case _ as SimpleCalculator:
+ UserDefaults.standard[.sakutekiCalclationSterategy] = .total
+
+ case let f as Formula33:
+ UserDefaults.standard[.sakutekiCalclationSterategy] = .formula33
+ UserDefaults.standard[.formula33Factor] = Double(f.condition)
+
+ default: ()
+ }
+ }
+ }
+
+ @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)) }
+
+ private func totalSeiku(of ship: Ship) -> Int {
+
+ return SeikuCalclator(ship: ship).totalSeiku
+ }
+ private func totalDrums(of ship: Ship) -> Int {
+
+ return (0...4).compactMap(ship.slotItem).filter { $0.slotitem_id == 75 }.count
+ }
+
+ private var ships: [Ship] = [] {
+
willSet {
- ships.forEach { (ship) in
- shipObserveKeys.forEach {
- ship.removeObserver(self, forKeyPath: $0)
- }
+ ships.forEach { ship in
+
+ shipObserveKeys.forEach { ship.removeObserver(self, forKeyPath: $0) }
}
}
didSet {
- ships.forEach { (ship) in
+ ships.forEach { ship in
+
shipObserveKeys.forEach {
+
ship.addObserver(self, forKeyPath: $0, context: &shipsContext)
}
}
}
}
+
private(set) var anchorageRepair = AnchorageRepairManager.default
- dynamic fileprivate(set) var repairTime: NSNumber?
+
+ @objc dynamic private(set) var repairTime: NSNumber?
override func viewDidLoad() {
+
super.viewDidLoad()
- fleetController.bind("content", to:self, withKeyPath:"fleet", options:nil)
- fleetController.addObserver(self, forKeyPath:"selection.name", context:nil)
+ switch UserDefaults.standard[.sakutekiCalclationSterategy] {
+ case .total:
+ sakutekiCalculator = SimpleCalculator()
+
+ case .formula33:
+ let factor = UserDefaults.standard[.formula33Factor]
+ sakutekiCalculator = Formula33(Int(factor))
+ }
+
+ 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()
- [NSView?]()
- .appended { placeholder01 }
- .appended { placeholder02 }
- .appended { placeholder03 }
- .appended { placeholder04 }
- .appended { placeholder05 }
- .appended { placeholder06 }
- .enumerated()
- .forEach {
- guard let view = $0.element else { return }
- let detail = details[$0.offset]
+ zip([placeholder01, placeholder02, placeholder03, placeholder04, placeholder05, placeholder06], details)
+ .forEach { view, detail in
+
+ guard let view = view else { return }
+
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) { _ in
- self.willChangeValue(forKey: "fleetNumber")
- self.didChangeValue(forKey: "fleetNumber")
+ .addObserverOnce(forName: .PortAPIReceived, object: nil, queue: .main) { [weak self] _ in
+
+ if let current = self?.fleetNumber {
+
+ self?.fleetNumber = current
+ } else {
+
+ self?.fleetNumber = 1
+ }
+ }
+
+ 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(totalTPValue))
+ self?.notifyChangeValue(forKey: #keyPath(totalBRankTPValue))
}
}
- override func observeValue(forKeyPath keyPath: String?,
- of object: Any?,
- change: [NSKeyValueChangeKey: Any]?,
- context: UnsafeMutableRawPointer?) {
+
+ override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
+
if keyPath == "selection.name" {
+
title = fleet?.name
+
return
}
if context == &shipKeysContext {
+
+ checkExtShip()
setupShips()
+
return
}
if let keyPath = keyPath {
+
if context == &shipsContext {
+
switch keyPath {
- case "sakuteki_0":
- willChangeValue(forKey: "totalSakuteki")
- didChangeValue(forKey: "totalSakuteki")
- case "seiku":
- willChangeValue(forKey: "totalSeiku")
- didChangeValue(forKey: "totalSeiku")
- case "totalSeiku":
- willChangeValue(forKey: "totalCalclatedSeiku")
- didChangeValue(forKey: "totalCalclatedSeiku")
- case "lv":
- willChangeValue(forKey: "totalLevel")
- didChangeValue(forKey: "totalLevel")
- case "totalDrums":
- willChangeValue(forKey: "totalDrums")
- didChangeValue(forKey: "totalDrums")
+ case #keyPath(Ship.equippedItem):
+ notifyChangeValue(forKey: #keyPath(totalSakuteki))
+ notifyChangeValue(forKey: #keyPath(totalDrums))
+ notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
+ notifyChangeValue(forKey: #keyPath(totalTPValue))
+ notifyChangeValue(forKey: #keyPath(totalBRankTPValue))
+
+ case #keyPath(Ship.seiku):
+ notifyChangeValue(forKey: #keyPath(totalSeiku))
+ notifyChangeValue(forKey: #keyPath(totalCalclatedSeiku))
+
+ case #keyPath(Ship.lv):
+ notifyChangeValue(forKey: #keyPath(totalLevel))
+
default: break
}
+
return
}
}
}
@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 }
+
+ switch menuItem.tag {
+ case 0:
+ sakutekiCalculator = SimpleCalculator()
+
+ case 101...199:
+ sakutekiCalculator = Formula33(menuItem.tag - 100)
+
+ case 100:
+ askCalcutaionTurnPoint()
+
+ default: return
+ }
+
+ notifyChangeValue(forKey: #keyPath(totalSakuteki))
+ }
+
+ private func askCalcutaionTurnPoint() {
+
+ guard let window = self.view.window else { return }
+
+ let current = (sakutekiCalculator as? Formula33)?.condition ?? 1
+
+ let wc = CalculateConditionPanelController()
+ wc.condition = Double(current)
+ wc.beginModal(for: window) {
+
+ self.sakutekiCalculator = Formula33(Int($0))
+
+ self.notifyChangeValue(forKey: #keyPath(totalSakuteki))
+ }
}
private func setupShips() {
+
let array: [Ship?] = (0..<6).map { fleet?[$0] }
zip(details, array).forEach { $0.0.ship = $0.1 }
- ships = array.flatMap { $0 }
-
- [String]()
- .appended { "totalSakuteki" }
- .appended { "totalSeiku" }
- .appended { "totalCalclatedSeiku" }
- .appended { "totalLevel" }
- .appended { "totalDrums" }
- .appended { "repairable" }
- .forEach {
- willChangeValue(forKey: $0)
- didChangeValue(forKey: $0)
+
+ let extShip = fleet?[6]
+ extShip.map { extDetail?.ship = $0 }
+ ships = array.compactMap { $0 } + [extShip].compactMap { $0 }
+
+ [#keyPath(totalSakuteki), #keyPath(totalSeiku), #keyPath(totalCalclatedSeiku),
+ #keyPath(totalLevel), #keyPath(totalDrums), #keyPath(repairable),
+ #keyPath(totalTPValue), #keyPath(totalBRankTPValue)]
+ .forEach(notifyChangeValue(forKey:))
+ }
+}
+
+extension FleetViewController {
+
+ override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
+
+ guard let action = menuItem.action else { return false }
+
+ switch action {
+
+ case #selector(changeSakutekiCalculator(_:)):
+
+ if let _ = sakutekiCalculator as? SimpleCalculator {
+
+ menuItem.state = (menuItem.tag == 0 ? .on : .off)
+
+ return true
+
+ } else if let sakuObj = sakutekiCalculator as? Formula33 {
+
+ let cond = 100 + sakuObj.condition
+
+ menuItem.state = (menuItem.tag == cond ? .on : .off)
+
+ return true
+ }
+
+ default: ()
}
+
+ return false
}
}
extension FleetViewController {
+
private func reorder(order: [Int]) {
- guard order.count == 6 else {
- print("FleetViewController: order count is not 6.")
- return
- }
- let views: [NSView] = details.map { $0.view }
- let options: [NSAutoresizingMaskOptions] = views.map { $0.autoresizingMask }
+
+ guard order.count == 6 else { return Logger.shared.log("FleetViewController: order count is not 6.") }
+
+ 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])
}
}
extension FleetViewController {
- func buildAnchorageRepairHolder() {
+
+ private func buildAnchorageRepairHolder() {
+
AppDelegate.shared.addCounterUpdate { [weak self] in
+
guard let `self` = self else { return }
+
self.repairTime = self.calcRepairTime()
}
}
+
private func calcRepairTime() -> NSNumber? {
+
let time = anchorageRepair.repairTime
let complete = time.timeIntervalSince1970
let now = Date(timeIntervalSinceNow: 0.0)
let diff = complete - now.timeIntervalSince1970
+
return NSNumber(value: diff + 20.0 * 60)
}
+
private var repairShipIds: [Int] { return [19] }
- dynamic var repairable: Bool {
- guard let flagShip = fleet?[0]
- else { return false }
+
+ @objc private dynamic var repairable: Bool {
+
+ 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 {}