5 // Created by Hori,Masaki on 2017/01/29.
6 // Copyright © 2017年 Hori,Masaki. All rights reserved.
11 // swiftlint:disable variable_name
12 class Ship: KCManagedObject {
13 @NSManaged dynamic var bull: Int
14 @NSManaged dynamic var cond: Int
15 @NSManaged dynamic var exp: Int
16 @NSManaged dynamic var fleet: NSNumber?
17 @NSManaged dynamic var fuel: Int
18 @NSManaged dynamic var id: Int
19 @NSManaged dynamic var kaihi_0: Int
20 @NSManaged dynamic var kaihi_1: NSNumber?
21 @NSManaged dynamic var karyoku_0: Int
22 @NSManaged dynamic var karyoku_1: Int
23 @NSManaged dynamic var kyouka_0: Int
24 @NSManaged dynamic var kyouka_1: Int
25 @NSManaged dynamic var kyouka_2: Int
26 @NSManaged dynamic var kyouka_3: Int
27 @NSManaged dynamic var kyouka_4: Int
28 @NSManaged dynamic var locked: Int
29 @NSManaged dynamic var locked_equip: NSNumber?
30 @NSManaged dynamic var lucky_0: Int
31 @NSManaged dynamic var lucky_1: Int
32 @NSManaged dynamic var lv: Int
33 @NSManaged dynamic var maxhp: Int
34 @NSManaged dynamic var ndock_time: NSNumber?
35 @NSManaged dynamic var nowhp: Int
36 @NSManaged dynamic var onslot_0: Int
37 @NSManaged dynamic var onslot_1: Int
38 @NSManaged dynamic var onslot_2: Int
39 @NSManaged dynamic var onslot_3: Int
40 @NSManaged dynamic var onslot_4: Int
41 @NSManaged dynamic var raisou_0: Int
42 @NSManaged dynamic var raisou_1: Int
43 @NSManaged dynamic var sakuteki_0: Int
44 @NSManaged dynamic var sakuteki_1: NSNumber?
45 @NSManaged dynamic var sally_area: NSNumber?
46 @NSManaged dynamic var ship_id: Int
47 @NSManaged dynamic var slot_0: Int
48 @NSManaged dynamic var slot_1: Int
49 @NSManaged dynamic var slot_2: Int
50 @NSManaged dynamic var slot_3: Int
51 @NSManaged dynamic var slot_4: Int
52 @NSManaged dynamic var slot_ex: Int
53 @NSManaged dynamic var sortno: NSNumber?
54 @NSManaged dynamic var soukou_0: Int
55 @NSManaged dynamic var soukou_1: Int
56 @NSManaged dynamic var srate: NSNumber?
57 @NSManaged dynamic var taiku_0: Int
58 @NSManaged dynamic var taiku_1: Int
59 @NSManaged dynamic var taisen_0: Int
60 @NSManaged dynamic var taisen_1: NSNumber?
61 @NSManaged dynamic var equippedItem: NSOrderedSet
62 @NSManaged dynamic var master_ship: MasterShip
63 @NSManaged dynamic var extraItem: SlotItem?
65 // swiftlint:eable variable_name
67 fileprivate let shortSTypeNames: [String] = {
68 guard let url = Bundle.main.url(forResource: "STypeShortName", withExtension: "plist"),
69 let array = NSArray(contentsOf: url) as? [String]
71 print("Can not load STypeShortName.plist.")
76 fileprivate let levelUpExps: [Int] = {
77 guard let url = Bundle.main.url(forResource: "LevelUpExp", withExtension: "plist"),
78 let array = NSArray(contentsOf: url) as? [Int]
80 print("Can not load LevelUpExp.plist.")
87 class func keyPathsForValuesAffectingName() -> Set<String> {
90 dynamic var name: String { return master_ship.name }
92 class func keyPathsForValuesAffectingShortTypeName() -> Set<String> {
95 dynamic var shortTypeName: String? {
96 let index = master_ship.stype.id - 1
97 guard 0..<shortSTypeNames.count ~= index
99 return shortSTypeNames[index]
102 class func keyPathsForValuesAffectingNext() -> Set<String> {
105 dynamic var next: NSNumber? {
106 guard 0..<levelUpExps.count ~= lv else { return nil }
107 if lv == 99 { return nil }
108 let nextExp = levelUpExps[lv]
109 if lv < 99 { return (nextExp - exp) as NSNumber }
110 return (1_000_000 + nextExp - exp) as NSNumber
113 class func keyPathsForValuesAffectingStatus() -> Set<String> {
114 return ["nowhp", "maxph"]
116 dynamic var status: Int {
117 let n = Double(nowhp)
118 let m = Double(maxhp)
120 if stat <= 0.25 { return 3 }
121 if stat <= 0.5 { return 2 }
122 if stat <= 0.75 { return 1 }
126 class func keyPathsForValuesAffectingStatusColor() -> Set<String> {
129 dynamic var statusColor: NSColor {
131 case 0: return NSColor.controlTextColor
132 case 1: return NSColor.yellow
133 case 2: return NSColor.orange
134 case 3: return NSColor.red
135 default: fatalError("status is unknown value")
139 // class func keyPathsForValuesAffectingConditionColor() -> Set<String> {
142 dynamic var conditionColor: NSColor { return NSColor.controlTextColor }
144 class func keyPathsForValuesAffectingPlanColor() -> Set<String> {
145 return ["sally_area"]
147 dynamic var planColor: NSColor {
148 if !UserDefaults.standard.showsPlanColor { return NSColor.controlTextColor }
149 guard let sally = sally_area else { return NSColor.controlTextColor }
152 case 1: return UserDefaults.standard.plan01Color
153 case 2: return UserDefaults.standard.plan02Color
154 case 3: return UserDefaults.standard.plan03Color
155 case 4: return UserDefaults.standard.plan04Color
156 case 5: return UserDefaults.standard.plan05Color
157 case 6: return UserDefaults.standard.plan06Color
158 default: return NSColor.controlTextColor
165 dynamic var maxBull: Int { return master_ship.bull_max }
166 dynamic var maxFuel: Int { return master_ship.fuel_max }
168 class func keyPathsForValuesAffectingIsMaxKaryoku() -> Set<String> {
169 return ["karyoku_1", "kyouka_0"]
171 dynamic var isMaxKaryoku: Bool {
172 let initial = master_ship.houg_0
174 let growth = kyouka_0
175 return initial + growth >= max
178 class func keyPathsForValuesAffectingIsMaxRaisou() -> Set<String> {
179 return ["raisou_1", "kyouka_1"]
181 dynamic var isMaxRaisou: Bool {
182 let initial = master_ship.raig_0
184 let growth = kyouka_1
185 return initial + growth >= max
188 class func keyPathsForValuesAffectingIsMaxTaiku() -> Set<String> {
189 return ["taiku_1", "kyouka_2"]
191 dynamic var isMaxTaiku: Bool {
192 let initial = master_ship.tyku_0
194 let growth = kyouka_2
195 return initial + growth >= max
198 class func keyPathsForValuesAffectingIsMaxSoukou() -> Set<String> {
199 return ["soukou_1", "kyouka_3"]
201 dynamic var isMaxSoukou: Bool {
202 let initial = master_ship.souk_0
204 let growth = kyouka_3
205 return initial + growth >= max
208 class func keyPathsForValuesAffectingIsMaxLucky() -> Set<String> {
209 return ["lucky_1", "kyouka_4"]
211 dynamic var isMaxLucky: Bool {
212 let initial = master_ship.luck_0
214 let growth = kyouka_4
215 return initial + growth >= max
218 class func keyPathsForValuesAffectingUpgradeLevel() -> Set<String> {
221 dynamic var upgradeLevel: Int { return master_ship.afterlv }
223 class func keyPathsForValuesAffectingUpgradeExp() -> Set<String> {
226 dynamic var upgradeExp: NSNumber? {
227 let upgradeLv = upgradeLevel
228 if upgradeLv <= 0 { return nil }
229 if levelUpExps.count < upgradeLv { return nil }
230 let upExp = levelUpExps[upgradeLv - 1] - exp
231 return upExp < 0 ? 0 : upExp as NSNumber
234 dynamic var guardEscaped: Bool {
235 let store = TemporaryDataStore.default
236 guard let _ = store.ensuredGuardEscaped(byShipId: id)
237 else { return false }
241 class func keyPathsForValuesAffectingSteelRequiredInRepair() -> Set<String> {
244 dynamic var steelRequiredInRepair: Int {
245 return Int(Double(maxFuel) * 0.06 * Double(maxhp - nowhp))
248 class func keyPathsForValuesAffectingFuelRequiredInRepair() -> Set<String> {
251 dynamic var fuelRequiredInRepair: Int {
252 return Int(Double(maxFuel) * 0.032 * Double(maxhp - nowhp))
257 fileprivate let seikuEffectiveTypes: [Int] = [6, 7, 8, 11, 45, 56, 57, 58]
259 fileprivate let fighterTypes: [Int] = [6]
260 fileprivate let bomberTypes: [Int] = [7]
261 fileprivate let attackerTypes: [Int] = [8]
262 fileprivate let floatplaneBomberTypes: [Int] = [11]
263 fileprivate let floatplaneFighterTypes: [Int] = [45]
264 fileprivate let jetFighter: [Int] = [56]
265 fileprivate let jetBomberTypes: [Int] = [57]
266 fileprivate let jetAttackerTypes: [Int] = [58]
268 fileprivate let allPlaneTypes: [Int] = [6, 7, 8, 9, 10, 11, 25, 26, 41, 45, 56, 57, 58, 59]
270 fileprivate let fighterBonus: [Double] = [0, 0, 2, 5, 9, 14, 14, 22]
271 fileprivate let bomberBonus: [Double] = [0, 0, 0, 0, 0, 0, 0, 0]
272 fileprivate let attackerBonus: [Double] = [0, 0, 0, 0, 0, 0, 0, 0]
273 fileprivate let floatplaneBomberBonus: [Double] = [0, 0, 1, 1, 1, 3, 3, 6]
274 fileprivate let floatplaneFighterBonus: [Double] = [0, 0, 2, 5, 9, 14, 14, 22]
275 fileprivate let jetBomberBonus: [Double] = [0, 0, 0, 0, 0, 0, 0, 0]
277 // sqrt 0, 1, 2.5, 4, 5.5, 7, 8.5, 10
278 fileprivate let bonus: [Double] = [0, 1.000, 1.581, 2.000, 2.345, 2.645, 2.915, 3.162]
283 private func slotItemCount(_ index: Int) -> Int {
285 case 0: return onslot_0
286 case 1: return onslot_1
287 case 2: return onslot_2
288 case 3: return onslot_3
289 case 4: return onslot_4
293 private func slotItemId(_ index: Int) -> Int {
295 case 0: return slot_0
296 case 1: return slot_1
297 case 2: return slot_2
298 case 3: return slot_3
299 case 4: return slot_4
303 func setItem(_ id: Int, for slot: Int) {
310 default: fatalError("Ship: setItem out of bounds.")
313 private func slotItemMax(_ index: Int) -> Int {
315 case 0: return master_ship.maxeq_0
316 case 1: return master_ship.maxeq_1
317 case 2: return master_ship.maxeq_2
318 case 3: return master_ship.maxeq_3
319 case 4: return master_ship.maxeq_4
324 private func slotItem(_ index: Int) -> SlotItem? {
325 return ServerDataStore.default.slotItem(by: slotItemId(index))
327 private func typeBonus(_ type: Int) -> [Double]? {
329 case let t where fighterTypes.contains(t): return fighterBonus
330 case let t where bomberTypes.contains(t): return bomberBonus
331 case let t where attackerTypes.contains(t): return attackerBonus
332 case let t where floatplaneBomberTypes.contains(t): return floatplaneBomberBonus
333 case let t where floatplaneFighterTypes.contains(t): return floatplaneFighterBonus
334 case let t where jetBomberTypes.contains(t): return jetBomberBonus
338 private func normalSeiku(_ index: Int) -> Double {
339 let itemCount = slotItemCount(index)
340 if itemCount == 0 { return 0 }
341 guard let item = slotItem(index) else { return 0 }
342 let type2 = item.master_slotItem.type_2
343 guard seikuEffectiveTypes.contains(type2) else { return 0 }
344 let taiku = Double(item.master_slotItem.tyku)
345 let lv = Double(item.level)
346 let rate = bomberTypes.contains(type2) ? 0.25 : 0.2
347 return (taiku + lv * rate) * sqrt(Double(itemCount))
350 private func extraSeiku(_ index: Int) -> Double {
351 let itemCount = slotItemCount(index)
352 if itemCount == 0 { return 0 }
353 guard let item = slotItem(index) else { return 0 }
354 let type2 = item.master_slotItem.type_2
355 guard let typeBonus = typeBonus(type2) else { return 0 }
356 let airLevel = item.alv
357 return typeBonus[airLevel] + bonus[airLevel]
359 private func seiku(_ index: Int) -> Int {
360 return Int(normalSeiku(index) + extraSeiku(index))
363 dynamic var totalEquipment: Int {
364 return (0...4).map(slotItemMax).reduce(0, +)
367 class func keyPathsForValuesAffectingSeiku() -> Set<String> {
368 return ["slot_0", "slot_1", "slot_2", "slot_3", "slot_4",
369 "onslot_0", "onslot_1", "onslot_2", "onslot_3", "onslot_4"]
371 dynamic var seiku: Int {
372 return (0...4).map(normalSeiku).map { Int($0) }.reduce(0, +)
375 class func keyPathsForValuesAffectingExtraSeiku() -> Set<String> {
378 dynamic var extraSeiku: Int {
379 return (0...4).map(extraSeiku).map { Int($0) }.reduce(0, +)
382 class func keyPathsForValuesAffectingTotalSeiku() -> Set<String> {
383 return ["seiku", "extraSeiku"]
385 dynamic var totalSeiku: Int {
386 return (0...4).map(seiku).reduce(0, +)
389 class func keyPathsForValuesAffectingTotalDrums() -> Set<String> {
390 return ["slot_0", "slot_1", "slot_2", "slot_3", "slot_4"]
392 dynamic var totalDrums: Int {
393 return (0...4).flatMap(slotItem).filter { $0.slotitem_id == 75 }.count
396 // MARK: - Plane count strings
397 private enum PlaneState {
400 case equiped(Int, Int)
402 private func planState(_ index: Int) -> PlaneState {
403 let itemId = slotItemId(index)
404 let maxCount = slotItemMax(index)
405 if maxCount == 0 { return .cannotEquip }
406 if itemId == -1 { return .notEquip(maxCount) }
408 if let item = slotItem(index),
409 allPlaneTypes.contains(item.master_slotItem.type_2) {
410 return .equiped(slotItemCount(index), maxCount)
412 return .notEquip(maxCount)
414 private func planeString(_ index: Int) -> String? {
415 switch planState(index) {
418 case .notEquip(let max):
420 case .equiped(let count, let max):
421 return "\(count)/\(max)"
425 class func keyPathsForValuesAffectingPlaneString0() -> Set<String> {
426 return ["onslot_0", "master_ship.maxeq_0", "equippedItem"]
428 dynamic var planeString0: String? { return planeString(0) }
430 class func keyPathsForValuesAffectingPlaneString1() -> Set<String> {
431 return ["onslot_1", "master_ship.maxeq_1", "equippedItem"]
433 dynamic var planeString1: String? { return planeString(1) }
435 class func keyPathsForValuesAffectingPlaneString2() -> Set<String> {
436 return ["onslot_2", "master_ship.maxeq_2", "equippedItem"]
438 dynamic var planeString2: String? { return planeString(2) }
440 class func keyPathsForValuesAffectingPlaneString3() -> Set<String> {
441 return ["onslot_3", "master_ship.maxeq_3", "equippedItem"]
443 dynamic var planeString3: String? { return planeString(3) }
445 class func keyPathsForValuesAffectingPlaneString4() -> Set<String> {
446 return ["onslot_4", "master_ship.maxeq_4", "equippedItem"]
448 dynamic var planeString4: String? { return planeString(4) }
450 // MARK: - Plane count string color
451 private func planeStringColor(_ index: Int) -> NSColor {
452 switch planState(index) {
453 case .cannotEquip: return NSColor.controlTextColor
454 case .notEquip: return NSColor.disabledControlTextColor
455 case .equiped: return NSColor.controlTextColor
459 class func keyPathsForValuesAffectingPlaneString0Color() -> Set<String> {
460 return ["onslot_0", "master_ship.maxeq_0", "equippedItem"]
462 dynamic var planeString0Color: NSColor { return planeStringColor(0) }
464 class func keyPathsForValuesAffectingPlaneString1Color() -> Set<String> {
465 return ["onslot_1", "master_ship.maxeq_1", "equippedItem"]
467 dynamic var planeString1Color: NSColor { return planeStringColor(1) }
469 class func keyPathsForValuesAffectingPlaneString2Color() -> Set<String> {
470 return ["onslot_2", "master_ship.maxeq_2", "equippedItem"]
472 dynamic var planeString2Color: NSColor { return planeStringColor(2) }
474 class func keyPathsForValuesAffectingPlaneString3Color() -> Set<String> {
475 return ["onslot_3", "master_ship.maxeq_3", "equippedItem"]
477 dynamic var planeString3Color: NSColor { return planeStringColor(3) }
479 class func keyPathsForValuesAffectingPlaneString4Color() -> Set<String> {
480 return ["onslot_4", "master_ship.maxeq_4", "equippedItem"]
482 dynamic var planeString4Color: NSColor { return planeStringColor(4) }