OSDN Git Service

処理を簡素化
[kcd/KCD.git] / KCD / Ship.swift
1 //
2 //  [
3 //  KCD
4 //
5 //  Created by Hori,Masaki on 2017/01/29.
6 //  Copyright © 2017年 Hori,Masaki. All rights reserved.
7 //
8
9 import Cocoa
10
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?
64 }
65 // swiftlint:eable variable_name
66
67 fileprivate let shortSTypeNames: [String] = {
68     guard let url = Bundle.main.url(forResource: "STypeShortName", withExtension: "plist"),
69         let array = NSArray(contentsOf: url) as? [String]
70         else {
71             print("Can not load STypeShortName.plist.")
72             return []
73     }
74     return array
75 }()
76 fileprivate let levelUpExps: [Int] = {
77     guard let url = Bundle.main.url(forResource: "LevelUpExp", withExtension: "plist"),
78         let array = NSArray(contentsOf: url) as? [Int]
79         else {
80             print("Can not load LevelUpExp.plist.")
81             return []
82     }
83     return array
84 }()
85
86 extension Ship {
87     class func keyPathsForValuesAffectingName() -> Set<String> {
88         return ["ship_id"]
89     }
90     dynamic var name: String { return master_ship.name }
91     
92     class func keyPathsForValuesAffectingShortTypeName() -> Set<String> {
93         return ["ship_id"]
94     }
95     dynamic var shortTypeName: String? {
96         let index = master_ship.stype.id - 1
97         guard 0..<shortSTypeNames.count ~= index
98             else { return nil }
99         return shortSTypeNames[index]
100     }
101     
102     class func keyPathsForValuesAffectingNext() -> Set<String> {
103         return ["exp"]
104     }
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
111     }
112     
113     class func keyPathsForValuesAffectingStatus() -> Set<String> {
114         return ["nowhp", "maxph"]
115     }
116     dynamic var status: Int {
117         let n = Double(nowhp)
118         let m = Double(maxhp)
119         let stat = n / m
120         if stat <= 0.25 { return 3 }
121         if stat <= 0.5 { return 2 }
122         if stat <= 0.75 { return 1 }
123         return 0
124     }
125     
126     class func keyPathsForValuesAffectingStatusColor() -> Set<String> {
127         return ["status"]
128     }
129     dynamic var statusColor: NSColor {
130         switch status {
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")
136         }
137     }
138     
139 //    class func keyPathsForValuesAffectingConditionColor() -> Set<String> {
140 //        return ["cond"]
141 //    }
142     dynamic var conditionColor: NSColor { return NSColor.controlTextColor }
143     
144     class func keyPathsForValuesAffectingPlanColor() -> Set<String> {
145         return ["sally_area"]
146     }
147     dynamic var planColor: NSColor {
148         if !UserDefaults.standard.showsPlanColor { return NSColor.controlTextColor }
149         guard let sally = sally_area else { return NSColor.controlTextColor }
150         
151         switch sally {
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
159         }
160     }
161 }
162
163
164 extension Ship {
165     dynamic var maxBull: Int { return master_ship.bull_max }
166     dynamic var maxFuel: Int { return master_ship.fuel_max }
167     
168     class func keyPathsForValuesAffectingIsMaxKaryoku() -> Set<String> {
169         return ["karyoku_1", "kyouka_0"]
170     }
171     dynamic var isMaxKaryoku: Bool {
172         let initial = master_ship.houg_0
173         let max = karyoku_1
174         let growth = kyouka_0
175         return initial + growth >= max
176     }
177     
178     class func keyPathsForValuesAffectingIsMaxRaisou() -> Set<String> {
179         return ["raisou_1", "kyouka_1"]
180     }
181     dynamic var isMaxRaisou: Bool {
182         let initial = master_ship.raig_0
183         let max = raisou_1
184         let growth = kyouka_1
185         return initial + growth >= max
186     }
187     
188     class func keyPathsForValuesAffectingIsMaxTaiku() -> Set<String> {
189         return ["taiku_1", "kyouka_2"]
190     }
191     dynamic var isMaxTaiku: Bool {
192         let initial = master_ship.tyku_0
193         let max = taiku_1
194         let growth = kyouka_2
195         return initial + growth >= max
196     }
197     
198     class func keyPathsForValuesAffectingIsMaxSoukou() -> Set<String> {
199         return ["soukou_1", "kyouka_3"]
200     }
201     dynamic var isMaxSoukou: Bool {
202         let initial = master_ship.souk_0
203         let max = soukou_1
204         let growth = kyouka_3
205         return initial + growth >= max
206     }
207     
208     class func keyPathsForValuesAffectingIsMaxLucky() -> Set<String> {
209         return ["lucky_1", "kyouka_4"]
210     }
211     dynamic var isMaxLucky: Bool {
212         let initial = master_ship.luck_0
213         let max = lucky_1
214         let growth = kyouka_4
215         return initial + growth >= max
216     }
217     
218     class func keyPathsForValuesAffectingUpgradeLevel() -> Set<String> {
219         return ["ship_id"]
220     }
221     dynamic var upgradeLevel: Int { return master_ship.afterlv }
222     
223     class func keyPathsForValuesAffectingUpgradeExp() -> Set<String> {
224         return ["exp"]
225     }
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
232     }
233     
234     dynamic var guardEscaped: Bool {
235         let store = TemporaryDataStore.default
236         guard let _ = store.ensuredGuardEscaped(byShipId: id)
237             else { return false }
238         return true
239     }
240     
241     class func keyPathsForValuesAffectingSteelRequiredInRepair() -> Set<String> {
242         return ["nowhp"]
243     }
244     dynamic var steelRequiredInRepair: Int {
245         return Int(Double(maxFuel) * 0.06 * Double(maxhp - nowhp))
246     }
247     
248     class func keyPathsForValuesAffectingFuelRequiredInRepair() -> Set<String> {
249         return ["nowhp"]
250     }
251     dynamic var fuelRequiredInRepair: Int {
252         return Int(Double(maxFuel) * 0.032 * Double(maxhp - nowhp))
253     }
254 }
255
256
257 fileprivate let seikuEffectiveTypes: [Int] = [6, 7, 8, 11, 45, 56, 57, 58]
258
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]
267
268 fileprivate let allPlaneTypes: [Int] = [6, 7, 8, 9, 10, 11, 25, 26, 41, 45, 56, 57, 58, 59]
269
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]
276
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]
279
280
281 extension Ship {
282     
283     private func slotItemCount(_ index: Int) -> Int {
284         switch index {
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
290         default: return 0
291         }
292     }
293     private func slotItemId(_ index: Int) -> Int {
294         switch index {
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
300         default: return 0
301         }
302     }
303     func setItem(_ id: Int, for slot: Int) {
304         switch slot {
305         case 0: slot_0 = id
306         case 1: slot_1 = id
307         case 2: slot_2 = id
308         case 3: slot_3 = id
309         case 4: slot_4 = id
310         default: fatalError("Ship: setItem out of bounds.")
311         }
312     }
313     private func slotItemMax(_ index: Int) -> Int {
314         switch index {
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
320         default: return 0
321         }
322     }
323     
324     private func slotItem(_ index: Int) -> SlotItem? {
325         return ServerDataStore.default.slotItem(by: slotItemId(index))
326     }
327     private func typeBonus(_ type: Int) -> [Double]? {
328         switch type {
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
335         default: return nil
336         }
337     }
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))
348         
349     }
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]
358     }
359     private func seiku(_ index: Int) -> Int {
360         return Int(normalSeiku(index) + extraSeiku(index))
361     }
362     
363     dynamic var totalEquipment: Int {
364         return (0...4).map(slotItemMax).reduce(0, +)
365     }
366     
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"]
370     }
371     dynamic var seiku: Int {
372         return (0...4).map(normalSeiku).map { Int($0) }.reduce(0, +)
373     }
374     
375     class func keyPathsForValuesAffectingExtraSeiku() -> Set<String> {
376         return ["seiku"]
377     }
378     dynamic var extraSeiku: Int {
379         return (0...4).map(extraSeiku).map { Int($0) }.reduce(0, +)
380     }
381     
382     class func keyPathsForValuesAffectingTotalSeiku() -> Set<String> {
383         return ["seiku", "extraSeiku"]
384     }
385     dynamic var totalSeiku: Int {
386         return (0...4).map(seiku).reduce(0, +)
387     }
388     
389     class func keyPathsForValuesAffectingTotalDrums() -> Set<String> {
390         return ["slot_0", "slot_1", "slot_2", "slot_3", "slot_4"]
391     }
392     dynamic var totalDrums: Int {
393         return (0...4).flatMap(slotItem).filter { $0.slotitem_id == 75 }.count
394     }
395     
396     // MARK: - Plane count strings
397     private enum PlaneState {
398         case cannotEquip
399         case notEquip(Int)
400         case equiped(Int, Int)
401     }
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) }
407         
408         if let item = slotItem(index),
409             allPlaneTypes.contains(item.master_slotItem.type_2) {
410             return .equiped(slotItemCount(index), maxCount)
411         }
412         return .notEquip(maxCount)
413     }
414     private func planeString(_ index: Int) -> String? {
415         switch planState(index) {
416         case .cannotEquip:
417             return nil
418         case .notEquip(let max):
419             return "\(max)"
420         case .equiped(let count, let max):
421             return "\(count)/\(max)"
422         }
423     }
424     
425     class func keyPathsForValuesAffectingPlaneString0() -> Set<String> {
426         return ["onslot_0", "master_ship.maxeq_0", "equippedItem"]
427     }
428     dynamic var planeString0: String? { return planeString(0) }
429     
430     class func keyPathsForValuesAffectingPlaneString1() -> Set<String> {
431         return ["onslot_1", "master_ship.maxeq_1", "equippedItem"]
432     }
433     dynamic var planeString1: String? { return planeString(1) }
434     
435     class func keyPathsForValuesAffectingPlaneString2() -> Set<String> {
436         return ["onslot_2", "master_ship.maxeq_2", "equippedItem"]
437     }
438     dynamic var planeString2: String? { return planeString(2) }
439     
440     class func keyPathsForValuesAffectingPlaneString3() -> Set<String> {
441         return ["onslot_3", "master_ship.maxeq_3", "equippedItem"]
442     }
443     dynamic var planeString3: String? { return planeString(3) }
444     
445     class func keyPathsForValuesAffectingPlaneString4() -> Set<String> {
446         return ["onslot_4", "master_ship.maxeq_4", "equippedItem"]
447     }
448     dynamic var planeString4: String? { return planeString(4) }
449     
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
456         }
457     }
458     
459     class func keyPathsForValuesAffectingPlaneString0Color() -> Set<String> {
460         return ["onslot_0", "master_ship.maxeq_0", "equippedItem"]
461     }
462     dynamic var planeString0Color: NSColor { return planeStringColor(0) }
463     
464     class func keyPathsForValuesAffectingPlaneString1Color() -> Set<String> {
465         return ["onslot_1", "master_ship.maxeq_1", "equippedItem"]
466     }
467     dynamic var planeString1Color: NSColor { return planeStringColor(1) }
468     
469     class func keyPathsForValuesAffectingPlaneString2Color() -> Set<String> {
470         return ["onslot_2", "master_ship.maxeq_2", "equippedItem"]
471     }
472     dynamic var planeString2Color: NSColor { return planeStringColor(2) }
473     
474     class func keyPathsForValuesAffectingPlaneString3Color() -> Set<String> {
475         return ["onslot_3", "master_ship.maxeq_3", "equippedItem"]
476     }
477     dynamic var planeString3Color: NSColor { return planeStringColor(3) }
478     
479     class func keyPathsForValuesAffectingPlaneString4Color() -> Set<String> {
480         return ["onslot_4", "master_ship.maxeq_4", "equippedItem"]
481     }
482     dynamic var planeString4Color: NSColor { return planeStringColor(4) }
483 }