// // [ // KCD // // Created by Hori,Masaki on 2017/01/29. // Copyright © 2017年 Hori,Masaki. All rights reserved. // import Cocoa class Ship: KCManagedObject { @NSManaged dynamic var bull: Int @NSManaged dynamic var cond: Int @NSManaged dynamic var exp: Int @NSManaged dynamic var fleet: NSNumber? @NSManaged dynamic var fuel: Int @NSManaged dynamic var id: Int @NSManaged dynamic var kaihi_0: Int @NSManaged dynamic var kaihi_1: NSNumber? @NSManaged dynamic var karyoku_0: Int @NSManaged dynamic var karyoku_1: Int @NSManaged dynamic var kyouka_0: Int @NSManaged dynamic var kyouka_1: Int @NSManaged dynamic var kyouka_2: Int @NSManaged dynamic var kyouka_3: Int @NSManaged dynamic var kyouka_4: Int @NSManaged dynamic var locked: Int @NSManaged dynamic var locked_equip: NSNumber? @NSManaged dynamic var lucky_0: Int @NSManaged dynamic var lucky_1: Int @NSManaged dynamic var lv: Int @NSManaged dynamic var maxhp: Int @NSManaged dynamic var ndock_time: NSNumber? @NSManaged dynamic var nowhp: Int @NSManaged dynamic var onslot_0: Int @NSManaged dynamic var onslot_1: Int @NSManaged dynamic var onslot_2: Int @NSManaged dynamic var onslot_3: Int @NSManaged dynamic var onslot_4: Int @NSManaged dynamic var raisou_0: Int @NSManaged dynamic var raisou_1: Int @NSManaged dynamic var sakuteki_0: Int @NSManaged dynamic var sakuteki_1: NSNumber? @NSManaged dynamic var sally_area: NSNumber? @NSManaged dynamic var ship_id: Int @NSManaged dynamic var slot_0: Int @NSManaged dynamic var slot_1: Int @NSManaged dynamic var slot_2: Int @NSManaged dynamic var slot_3: Int @NSManaged dynamic var slot_4: Int @NSManaged dynamic var slot_ex: Int @NSManaged dynamic var sortno: NSNumber? @NSManaged dynamic var soukou_0: Int @NSManaged dynamic var soukou_1: Int @NSManaged dynamic var srate: NSNumber? @NSManaged dynamic var taiku_0: Int @NSManaged dynamic var taiku_1: Int @NSManaged dynamic var taisen_0: Int @NSManaged dynamic var taisen_1: NSNumber? @NSManaged dynamic var equippedItem: NSOrderedSet @NSManaged dynamic var master_ship: MasterShip @NSManaged dynamic var extraItem: SlotItem? } fileprivate let shortSTypeNames: [String] = { guard let url = Bundle.main.url(forResource: "STypeShortName", withExtension: "plist"), let array = NSArray(contentsOf: url) as? [String] else { print("Can not load STypeShortName.plist.") return [] } return array }() fileprivate let levelUpExps: [Int] = { guard let url = Bundle.main.url(forResource: "LevelUpExp", withExtension: "plist"), let array = NSArray(contentsOf: url) as? [Int] else { print("Can not load LevelUpExp.plist.") return [] } return array }() extension Ship { class func keyPathsForValuesAffectingName() -> Set { return ["ship_id"] } dynamic var name: String { return master_ship.name } class func keyPathsForValuesAffectingShortTypeName() -> Set { return ["ship_id"] } dynamic var shortTypeName: String? { if shortSTypeNames.count < master_ship.stype.id { return nil } return shortSTypeNames[master_ship.stype.id - 1] } class func keyPathsForValuesAffectingNext() -> Set { return ["exp"] } dynamic var next: NSNumber? { if lv >= levelUpExps.count { return nil } if lv == 99 { return nil } let nextExp = levelUpExps[lv] if lv < 99 { return (nextExp - exp) as NSNumber } return (1_000_000 + nextExp - exp) as NSNumber } class func keyPathsForValuesAffectingStatus() -> Set { return ["nowhp", "maxph"] } dynamic var status: Int { let n = Double(nowhp) let m = Double(maxhp) let stat = n / m if stat <= 0.25 { return 3 } if stat <= 0.5 { return 2 } if stat <= 0.75 { return 1 } return 0 } class func keyPathsForValuesAffectingStatusColor() -> Set { return ["status"] } dynamic var statusColor: NSColor { switch status { case 0: return NSColor.controlTextColor case 1: return NSColor.yellow case 2: return NSColor.orange case 3: return NSColor.red default: fatalError("status is unknown value") } } // class func keyPathsForValuesAffectingConditionColor() -> Set { // return ["cond"] // } dynamic var conditionColor: NSColor { return NSColor.controlTextColor } class func keyPathsForValuesAffectingPlanColor() -> Set { return ["sally_area"] } dynamic var planColor: NSColor { if !UserDefaults.standard.showsPlanColor { return NSColor.controlTextColor } guard let sally = sally_area else { return NSColor.controlTextColor } switch sally { case 1: return UserDefaults.standard.plan01Color case 2: return UserDefaults.standard.plan02Color case 3: return UserDefaults.standard.plan03Color case 4: return UserDefaults.standard.plan04Color case 5: return UserDefaults.standard.plan05Color case 6: return UserDefaults.standard.plan06Color default: return NSColor.controlTextColor } } } extension Ship { dynamic var maxBull: Int { return master_ship.bull_max } dynamic var maxFuel: Int { return master_ship.fuel_max } class func keyPathsForValuesAffectingIsMaxKaryoku() -> Set { return ["karyoku_1", "kyouka_0"] } dynamic var isMaxKaryoku: Bool { let initial = master_ship.houg_0 let max = karyoku_1 let growth = kyouka_0 return initial + growth >= max } class func keyPathsForValuesAffectingIsMaxRaisou() -> Set { return ["raisou_1", "kyouka_1"] } dynamic var isMaxRaisou: Bool { let initial = master_ship.raig_0 let max = raisou_1 let growth = kyouka_1 return initial + growth >= max } class func keyPathsForValuesAffectingIsMaxTaiku() -> Set { return ["taiku_1", "kyouka_2"] } dynamic var isMaxTaiku: Bool { let initial = master_ship.tyku_0 let max = taiku_1 let growth = kyouka_2 return initial + growth >= max } class func keyPathsForValuesAffectingIsMaxSoukou() -> Set { return ["soukou_1", "kyouka_3"] } dynamic var isMaxSoukou: Bool { let initial = master_ship.souk_0 let max = soukou_1 let growth = kyouka_3 return initial + growth >= max } class func keyPathsForValuesAffectingIsMaxLucky() -> Set { return ["lucky_1", "kyouka_4"] } dynamic var isMaxLucky: Bool { let initial = master_ship.luck_0 let max = lucky_1 let growth = kyouka_4 return initial + growth >= max } class func keyPathsForValuesAffectingUpgradeLevel() -> Set { return ["ship_id"] } dynamic var upgradeLevel: Int { return master_ship.afterlv } class func keyPathsForValuesAffectingUpgradeExp() -> Set { return ["exp"] } dynamic var upgradeExp: NSNumber? { let upgradeLv = upgradeLevel if upgradeLv <= 0 { return nil } if levelUpExps.count < upgradeLv { return nil } let upExp = levelUpExps[upgradeLv - 1] - exp return upExp < 0 ? 0 : upExp as NSNumber } dynamic var guardEscaped: Bool { let store = TemporaryDataStore.default guard let _ = store.ensuredGuardEscaped(byShipId: id) else { return false } return true } class func keyPathsForValuesAffectingSteelRequiredInRepair() -> Set { return ["nowhp"] } dynamic var steelRequiredInRepair: Int { return Int(Double(maxFuel) * 0.06 * Double(maxhp - nowhp)) } class func keyPathsForValuesAffectingFuelRequiredInRepair() -> Set { return ["nowhp"] } dynamic var fuelRequiredInRepair: Int { return Int(Double(maxFuel) * 0.032 * Double(maxhp - nowhp)) } } fileprivate let seikuEffectiveTypes: [Int] = [6, 7, 8, 11, 45] fileprivate let fighterTypes: [Int] = [6] fileprivate let bomberTypes: [Int] = [7] fileprivate let attackerTypes: [Int] = [8] fileprivate let floatplaneBomberTypes: [Int] = [11] fileprivate let floatplaneFighterTypes: [Int] = [45] fileprivate let jetFighter: [Int] = [56] fileprivate let jetBomberTypes: [Int] = [57] fileprivate let jetAttackerTypes: [Int] = [58] fileprivate let allPlaneTypes: [Int] = [6, 7, 8, 9, 10, 11, 25, 26, 41, 45, 56, 57, 58, 59] fileprivate let fighterBonus: [Double] = [0, 0, 2, 5, 9, 14, 14, 22] fileprivate let bomberBonus: [Double] = [0, 0, 0, 0, 0, 0, 0, 0] fileprivate let attackerBonus: [Double] = [0, 0, 0, 0, 0, 0, 0, 0] fileprivate let floatplaneBomberBonus: [Double] = [0, 0, 1, 1, 1, 3, 3, 6] fileprivate let floatplaneFighterBonus: [Double] = [0, 0, 2, 5, 9, 14, 14, 22] fileprivate let jetBomberBonus: [Double] = [0, 0, 0, 0, 0, 0, 0, 0] // sqrt 0, 1, 2.5, 4, 5.5, 7, 8.5, 10 fileprivate let bonus: [Double] = [0, 1.000, 1.581, 2.000, 2.345, 2.645, 2.915, 3.162] extension Ship { private func slotItemCount(_ index: Int) -> Int { switch index { case 0: return onslot_0 case 1: return onslot_1 case 2: return onslot_2 case 3: return onslot_3 case 4: return onslot_4 default: return 0 } } private func slotItemId(_ index: Int) -> Int { switch index { case 0: return slot_0 case 1: return slot_1 case 2: return slot_2 case 3: return slot_3 case 4: return slot_4 default: return 0 } } private func slotItemMax(_ index: Int) -> Int { switch index { case 0: return master_ship.maxeq_0 case 1: return master_ship.maxeq_1 case 2: return master_ship.maxeq_2 case 3: return master_ship.maxeq_3 case 4: return master_ship.maxeq_4 default: return 0 } } private func slotItem(_ index: Int) -> SlotItem? { return ServerDataStore.default.slotItem(byId: slotItemId(index)) } private func typeBonus(_ type: Int) -> [Double]? { switch type { case let t where fighterTypes.contains(t): return fighterBonus case let t where bomberTypes.contains(t): return bomberBonus case let t where attackerTypes.contains(t): return attackerBonus case let t where floatplaneBomberTypes.contains(t): return floatplaneBomberBonus case let t where floatplaneFighterTypes.contains(t): return floatplaneFighterBonus case let t where jetBomberTypes.contains(t): return jetBomberBonus default: return nil } } private func normalSeiku(_ index: Int) -> Double { let itemCount = slotItemCount(index) if itemCount == 0 { return 0 } guard let item = slotItem(index) else { return 0 } let type2 = item.master_slotItem.type_2 guard seikuEffectiveTypes.contains(type2) else { return 0 } let taiku = Double(item.master_slotItem.tyku) let lv = Double(item.level) let rate = bomberTypes.contains(type2) ? 0.25 : 0.2 return (taiku + lv * rate) * sqrt(Double(itemCount)) } private func extraSeiku(_ index: Int) -> Double { let itemCount = slotItemCount(index) if itemCount == 0 { return 0 } guard let item = slotItem(index) else { return 0 } let type2 = item.master_slotItem.type_2 guard let typeBonus = typeBonus(type2) else { return 0 } let airLevel = item.alv return typeBonus[airLevel] + bonus[airLevel] } private func seiku(_ index: Int) -> Int { return Int(normalSeiku(index) + extraSeiku(index)) } dynamic var totalEquipment: Int { return (0...4).reduce(0) { $0 + slotItemMax($1) } } class func keyPathsForValuesAffectingSeiku() -> Set { return ["slot_0", "slot_1", "slot_2", "slot_3", "slot_4", "onslot_0", "onslot_1", "onslot_2", "onslot_3", "onslot_4"] } dynamic var seiku: Int { return (0...4).reduce(0) { $0 + Int(normalSeiku($1)) } } class func keyPathsForValuesAffectingExtraSeiku() -> Set { return ["seiku"] } dynamic var extraSeiku: Int { return (0...4).reduce(0) { $0 + Int(extraSeiku($1)) } } class func keyPathsForValuesAffectingTotalSeiku() -> Set { return ["seiku", "extraSeiku"] } dynamic var totalSeiku: Int { return (0...4).reduce(0) { $0 + seiku($1) } } class func keyPathsForValuesAffectingTotalDrums() -> Set { return ["slot_0", "slot_1", "slot_2", "slot_3", "slot_4"] } dynamic var totalDrums: Int { return (0...4).reduce(0) { guard let item = slotItem($1), item.slotitem_id == 75 else { return $0 } return $0 + 1 } } // MARK: - Plane count strings private enum PlaneState { case cannotEquip case notEquip(Int) case equiped(Int, Int) } private func planState(_ index: Int) -> PlaneState { let itemId = slotItemId(index) let maxCount = slotItemMax(index) if maxCount == 0 { return .cannotEquip } if itemId == -1 { return .notEquip(maxCount) } if let item = slotItem(index), allPlaneTypes.contains(item.master_slotItem.type_2) { return .equiped(slotItemCount(index), maxCount) } return .notEquip(maxCount) } private func planeString(_ index: Int) -> String? { switch planState(index) { case .cannotEquip: return nil case .notEquip(let max): return "\(max)" case .equiped(let count, let max): return "\(count)/\(max)" } } class func keyPathsForValuesAffectingPlaneString0() -> Set { return ["onslot_0", "master_ship.maxeq_0", "equippedItem"] } dynamic var planeString0: String? { return planeString(0) } class func keyPathsForValuesAffectingPlaneString1() -> Set { return ["onslot_1", "master_ship.maxeq_1", "equippedItem"] } dynamic var planeString1: String? { return planeString(1) } class func keyPathsForValuesAffectingPlaneString2() -> Set { return ["onslot_2", "master_ship.maxeq_2", "equippedItem"] } dynamic var planeString2: String? { return planeString(2) } class func keyPathsForValuesAffectingPlaneString3() -> Set { return ["onslot_3", "master_ship.maxeq_3", "equippedItem"] } dynamic var planeString3: String? { return planeString(3) } class func keyPathsForValuesAffectingPlaneString4() -> Set { return ["onslot_4", "master_ship.maxeq_4", "equippedItem"] } dynamic var planeString4: String? { return planeString(4) } // MARK: - Plane count string color private func planeStringColor(_ index: Int) -> NSColor { switch planState(index) { case .cannotEquip: return NSColor.controlTextColor case .notEquip: return NSColor.disabledControlTextColor case .equiped: return NSColor.controlTextColor } } class func keyPathsForValuesAffectingPlaneString0Color() -> Set { return ["onslot_0", "master_ship.maxeq_0", "equippedItem"] } dynamic var planeString0Color: NSColor { return planeStringColor(0) } class func keyPathsForValuesAffectingPlaneString1Color() -> Set { return ["onslot_1", "master_ship.maxeq_1", "equippedItem"] } dynamic var planeString1Color: NSColor { return planeStringColor(1) } class func keyPathsForValuesAffectingPlaneString2Color() -> Set { return ["onslot_2", "master_ship.maxeq_2", "equippedItem"] } dynamic var planeString2Color: NSColor { return planeStringColor(2) } class func keyPathsForValuesAffectingPlaneString3Color() -> Set { return ["onslot_3", "master_ship.maxeq_3", "equippedItem"] } dynamic var planeString3Color: NSColor { return planeStringColor(3) } class func keyPathsForValuesAffectingPlaneString4Color() -> Set { return ["onslot_4", "master_ship.maxeq_4", "equippedItem"] } dynamic var planeString4Color: NSColor { return planeStringColor(4) } }