poi-plugin-equips-farm 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +206 -0
- package/assets/ships.json +180 -0
- package/index.es +107 -0
- package/lib/data-processor.es +168 -0
- package/lib/search-utils.es +141 -0
- package/lib/utils.es +106 -0
- package/package.json +42 -0
- package/redux/actions.es +25 -0
- package/redux/index.es +3 -0
- package/redux/reducer.es +45 -0
- package/redux/selectors.es +61 -0
- package/views/components/EquipmentList.es +273 -0
- package/views/components/ShipList.es +145 -0
- package/views/index.es +179 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Search utility functions for multi-language support
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize string for comparison (lowercase, remove spaces)
|
|
5
|
+
*/
|
|
6
|
+
export function normalizeString(str) {
|
|
7
|
+
if (!str) return ''
|
|
8
|
+
return String(str).toLowerCase().replace(/\s+/g, '')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if query matches any of the provided name variants
|
|
13
|
+
* @param {string} query - User search query
|
|
14
|
+
* @param {Object} nameVariants - Object containing name, chinese_name, yomi, etc.
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
export function matchesSearch(query, nameVariants) {
|
|
18
|
+
if (!query) return true
|
|
19
|
+
|
|
20
|
+
const normalizedQuery = normalizeString(query)
|
|
21
|
+
|
|
22
|
+
// Check all available name fields
|
|
23
|
+
const fieldsToCheck = [
|
|
24
|
+
nameVariants.api_name, // Japanese name from game API
|
|
25
|
+
nameVariants.name, // Japanese name from WCTF
|
|
26
|
+
nameVariants.chinese_name, // Chinese name from WCTF
|
|
27
|
+
nameVariants.api_yomi, // Reading (hiragana) from game API
|
|
28
|
+
nameVariants.yomi, // Reading (hiragana) from WCTF Detail API
|
|
29
|
+
nameVariants.wiki_id, // Wiki ID
|
|
30
|
+
nameVariants.filename, // Filename (often romaji-based)
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
// Check direct matches
|
|
34
|
+
for (const field of fieldsToCheck) {
|
|
35
|
+
if (field && normalizeString(field).includes(normalizedQuery)) {
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if query might be romaji - convert Japanese reading to romaji
|
|
41
|
+
// Try both api_yomi (from game) and yomi (from WCTF)
|
|
42
|
+
const yomiField = nameVariants.yomi || nameVariants.api_yomi
|
|
43
|
+
if (yomiField) {
|
|
44
|
+
const romaji = hiraganaToRomaji(yomiField)
|
|
45
|
+
if (normalizeString(romaji).includes(normalizedQuery)) {
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Filename often contains romaji-like strings
|
|
51
|
+
if (nameVariants.filename) {
|
|
52
|
+
if (normalizeString(nameVariants.filename).includes(normalizedQuery)) {
|
|
53
|
+
return true
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Simple Hiragana to Romaji conversion
|
|
62
|
+
* Based on Hepburn romanization
|
|
63
|
+
*/
|
|
64
|
+
const HIRAGANA_TO_ROMAJI = {
|
|
65
|
+
'あ': 'a', 'い': 'i', 'う': 'u', 'え': 'e', 'お': 'o',
|
|
66
|
+
'か': 'ka', 'き': 'ki', 'く': 'ku', 'け': 'ke', 'こ': 'ko',
|
|
67
|
+
'が': 'ga', 'ぎ': 'gi', 'ぐ': 'gu', 'げ': 'ge', 'ご': 'go',
|
|
68
|
+
'さ': 'sa', 'し': 'shi', 'す': 'su', 'せ': 'se', 'そ': 'so',
|
|
69
|
+
'ざ': 'za', 'じ': 'ji', 'ず': 'zu', 'ぜ': 'ze', 'ぞ': 'zo',
|
|
70
|
+
'た': 'ta', 'ち': 'chi', 'つ': 'tsu', 'て': 'te', 'と': 'to',
|
|
71
|
+
'だ': 'da', 'ぢ': 'ji', 'づ': 'zu', 'で': 'de', 'ど': 'do',
|
|
72
|
+
'な': 'na', 'に': 'ni', 'ぬ': 'nu', 'ね': 'ne', 'の': 'no',
|
|
73
|
+
'は': 'ha', 'ひ': 'hi', 'ふ': 'fu', 'へ': 'he', 'ほ': 'ho',
|
|
74
|
+
'ば': 'ba', 'び': 'bi', 'ぶ': 'bu', 'べ': 'be', 'ぼ': 'bo',
|
|
75
|
+
'ぱ': 'pa', 'ぴ': 'pi', 'ぷ': 'pu', 'ぺ': 'pe', 'ぽ': 'po',
|
|
76
|
+
'ま': 'ma', 'み': 'mi', 'む': 'mu', 'め': 'me', 'も': 'mo',
|
|
77
|
+
'や': 'ya', 'ゆ': 'yu', 'よ': 'yo',
|
|
78
|
+
'ら': 'ra', 'り': 'ri', 'る': 'ru', 'れ': 're', 'ろ': 'ro',
|
|
79
|
+
'わ': 'wa', 'ゐ': 'wi', 'ゑ': 'we', 'を': 'wo', 'ん': 'n',
|
|
80
|
+
'きゃ': 'kya', 'きゅ': 'kyu', 'きょ': 'kyo',
|
|
81
|
+
'しゃ': 'sha', 'しゅ': 'shu', 'しょ': 'sho',
|
|
82
|
+
'ちゃ': 'cha', 'ちゅ': 'chu', 'ちょ': 'cho',
|
|
83
|
+
'にゃ': 'nya', 'にゅ': 'nyu', 'にょ': 'nyo',
|
|
84
|
+
'ひゃ': 'hya', 'ひゅ': 'hyu', 'ひょ': 'hyo',
|
|
85
|
+
'みゃ': 'mya', 'みゅ': 'myu', 'みょ': 'myo',
|
|
86
|
+
'りゃ': 'rya', 'りゅ': 'ryu', 'りょ': 'ryo',
|
|
87
|
+
'ぎゃ': 'gya', 'ぎゅ': 'gyu', 'ぎょ': 'gyo',
|
|
88
|
+
'じゃ': 'ja', 'じゅ': 'ju', 'じょ': 'jo',
|
|
89
|
+
'びゃ': 'bya', 'びゅ': 'byu', 'びょ': 'byo',
|
|
90
|
+
'ぴゃ': 'pya', 'ぴゅ': 'pyu', 'ぴょ': 'pyo',
|
|
91
|
+
'っ': '', // Small tsu (gemination)
|
|
92
|
+
'ー': '', // Long vowel mark
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function hiraganaToRomaji(hiragana) {
|
|
96
|
+
if (!hiragana) return ''
|
|
97
|
+
|
|
98
|
+
let result = ''
|
|
99
|
+
let i = 0
|
|
100
|
+
|
|
101
|
+
while (i < hiragana.length) {
|
|
102
|
+
// Try 2-character combinations first
|
|
103
|
+
const twoChar = hiragana.substring(i, i + 2)
|
|
104
|
+
if (HIRAGANA_TO_ROMAJI[twoChar]) {
|
|
105
|
+
result += HIRAGANA_TO_ROMAJI[twoChar]
|
|
106
|
+
i += 2
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Try single character
|
|
111
|
+
const oneChar = hiragana[i]
|
|
112
|
+
if (HIRAGANA_TO_ROMAJI[oneChar]) {
|
|
113
|
+
result += HIRAGANA_TO_ROMAJI[oneChar]
|
|
114
|
+
} else {
|
|
115
|
+
result += oneChar // Keep as-is if not found
|
|
116
|
+
}
|
|
117
|
+
i++
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get display name based on current language
|
|
125
|
+
* @param {Object} item - Ship or equipment data
|
|
126
|
+
* @param {string} language - Current language (zh-CN, en-US, ja-JP)
|
|
127
|
+
* @returns {string}
|
|
128
|
+
*/
|
|
129
|
+
export function getDisplayName(item, language = 'zh-CN') {
|
|
130
|
+
if (!item) return ''
|
|
131
|
+
|
|
132
|
+
// Priority order based on language
|
|
133
|
+
if (language === 'zh-CN' || language === 'zh-TW') {
|
|
134
|
+
return item.chinese_name || item.api_name || item.name || ''
|
|
135
|
+
} else if (language === 'ja-JP') {
|
|
136
|
+
return item.api_name || item.name || item.chinese_name || ''
|
|
137
|
+
} else {
|
|
138
|
+
// English - try to use romaji or fallback
|
|
139
|
+
return item.api_name || item.name || item.chinese_name || ''
|
|
140
|
+
}
|
|
141
|
+
}
|
package/lib/utils.es
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import shipsData from '../assets/ships.json'
|
|
2
|
+
|
|
3
|
+
// --- Icon URLs ---
|
|
4
|
+
const SHIP_ICON_BASE = 'http://74.120.174.162/static/image/common/ship_card'
|
|
5
|
+
const EQUIP_ICON_BASE = 'http://74.120.174.162/static/image/common/slotitem'
|
|
6
|
+
|
|
7
|
+
export const getShipIconUrl = (id) => `${SHIP_ICON_BASE}/${id}.png`
|
|
8
|
+
export const getEquipIconUrl = (id) => `${EQUIP_ICON_BASE}/${id}.png`
|
|
9
|
+
|
|
10
|
+
// --- Data Merging ---
|
|
11
|
+
|
|
12
|
+
// Returns a Map of Equipment ID -> { id, name, iconId, typeName, ships: [] }
|
|
13
|
+
// This needs Master Data ($ships, $equipments, $shipTypes, $equipTypes) to be passed in,
|
|
14
|
+
// because we shouldn't access store directly in pure utils if possible (or we use selectors in component).
|
|
15
|
+
export function prepareFarmingData($ships, $equipments, $shipTypes, $equipTypes) {
|
|
16
|
+
const equipmentMap = {}
|
|
17
|
+
|
|
18
|
+
// 1. Iterate over local farming config (ships.json)
|
|
19
|
+
shipsData.forEach(entry => {
|
|
20
|
+
const shipId = entry.shipId
|
|
21
|
+
const masterShip = $ships ? $ships[shipId] : null
|
|
22
|
+
const shipName = masterShip ? masterShip.api_name : `Ship#${shipId}`
|
|
23
|
+
const shipTypeId = masterShip ? masterShip.api_stype : 0
|
|
24
|
+
const shipTypeName = ($shipTypes && $shipTypes[shipTypeId]) ? $shipTypes[shipTypeId].api_name : '??'
|
|
25
|
+
|
|
26
|
+
entry.farming.forEach(farmItem => {
|
|
27
|
+
const equipId = farmItem.equipId
|
|
28
|
+
const masterEquip = $equipments ? $equipments[equipId] : null
|
|
29
|
+
|
|
30
|
+
// Fallback if master equip is missing
|
|
31
|
+
const equipName = masterEquip ? masterEquip.api_name : `Equip#${equipId}`
|
|
32
|
+
// Safe access to api_type
|
|
33
|
+
const typeId = (masterEquip && masterEquip.api_type) ? masterEquip.api_type[2] : 0
|
|
34
|
+
const iconId = (masterEquip && masterEquip.api_type) ? masterEquip.api_type[3] : 0
|
|
35
|
+
|
|
36
|
+
const typeName = ($equipTypes && $equipTypes[typeId]) ? $equipTypes[typeId].api_name : '??'
|
|
37
|
+
|
|
38
|
+
if (!equipmentMap[equipId]) {
|
|
39
|
+
equipmentMap[equipId] = {
|
|
40
|
+
id: equipId,
|
|
41
|
+
name: equipName,
|
|
42
|
+
iconId: iconId,
|
|
43
|
+
typeName: typeName,
|
|
44
|
+
typeId: typeId,
|
|
45
|
+
ships: []
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Add provider ship to this equipment
|
|
50
|
+
equipmentMap[equipId].ships.push({
|
|
51
|
+
shipId: shipId,
|
|
52
|
+
shipName: shipName,
|
|
53
|
+
shipType: shipTypeName,
|
|
54
|
+
shipTypeId: shipTypeId,
|
|
55
|
+
level: farmItem.level,
|
|
56
|
+
remodel: farmItem.remodel
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Convert map to array
|
|
62
|
+
const equipmentList = Object.values(equipmentMap)
|
|
63
|
+
|
|
64
|
+
return { equipmentList }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Logic for Drop Check
|
|
68
|
+
export function checkShipDrop(shipId, targetEquipIds) {
|
|
69
|
+
// This logic needs to access ships.json directly to be fast and independent of Master Data if possible,
|
|
70
|
+
// BUT since we minimized ships.json, we still have the IDs.
|
|
71
|
+
// So current ships.json structure is enough: [{shipId, farming: [{equipId}]}]
|
|
72
|
+
|
|
73
|
+
// We can assume drop check is simple: Does this ship provide anything?
|
|
74
|
+
// Note: ships.json contains remodeling forms (e.g. Isuzu Kai Ni is 12).
|
|
75
|
+
// If Isuzu (110) drops, we actually want to know if "Isuzu (110)" OR "Isuzu Kai Ni (12)" provides something?
|
|
76
|
+
// NO. If Isuzu drops, you have Isuzu. You need to level her.
|
|
77
|
+
// So if Isuzu (110) is in list, we match.
|
|
78
|
+
// If Isuzu Kai Ni (12) is in list, does dropping Isuzu count?
|
|
79
|
+
// Technically YES, because you can evolve it.
|
|
80
|
+
// BUT we need a "Base Ship -> Evolution" map to know that 110 becomes 12.
|
|
81
|
+
// POI's $ships[id].api_aftershipid tells you the next form.
|
|
82
|
+
// Implementing full evolution tree check is complex.
|
|
83
|
+
// Updated Logic: Strict match for now. If you farm Isuzu Kai Ni equipment, you probably added Isuzu Base to the list too?
|
|
84
|
+
// Or users expect to add "Type 21 Radar" and be told "Isuzu dropped, she gives it at Lv 50".
|
|
85
|
+
|
|
86
|
+
// Let's iterate ships.json.
|
|
87
|
+
const matches = []
|
|
88
|
+
|
|
89
|
+
// We need to match the Dropped Ship ID.
|
|
90
|
+
// AND we also strictly speaking need to know if the Dropped Ship *evolves* into a provider.
|
|
91
|
+
// For MVP, we'll check if the dropped ship ID exists in ships.json directly.
|
|
92
|
+
|
|
93
|
+
const shipEntry = shipsData.find(s => s.shipId === shipId)
|
|
94
|
+
if (shipEntry) {
|
|
95
|
+
shipEntry.farming.forEach(item => {
|
|
96
|
+
if (targetEquipIds.includes(item.equipId)) {
|
|
97
|
+
matches.push({
|
|
98
|
+
equipId: item.equipId,
|
|
99
|
+
level: item.level,
|
|
100
|
+
remodel: item.remodel
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
return matches
|
|
106
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "poi-plugin-equips-farm",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "A POI plugin to assist players in farming ships for specific equipment with enhanced multi-language search support",
|
|
5
|
+
"main": "index.es",
|
|
6
|
+
"poiPlugin": {
|
|
7
|
+
"title": "Farming Assistant",
|
|
8
|
+
"description": "Track and farm equipment from ship remodels",
|
|
9
|
+
"icon": "fa/wrench",
|
|
10
|
+
"priority": 100
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/Lanyangzhi/poi-plugin-equips-farm.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"poi",
|
|
21
|
+
"poi-plugin",
|
|
22
|
+
"kancolle",
|
|
23
|
+
"kantai-collection",
|
|
24
|
+
"equipment",
|
|
25
|
+
"farming",
|
|
26
|
+
"remodel"
|
|
27
|
+
],
|
|
28
|
+
"author": "Lanyangzhi",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"prop-types": "^15.7.2",
|
|
32
|
+
"classnames": "^2.3.1",
|
|
33
|
+
"@blueprintjs/core": "^3.51.3",
|
|
34
|
+
"react-bootstrap": "^0.32.4",
|
|
35
|
+
"semver": "^7.3.5"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react": "^16.8.0",
|
|
39
|
+
"react-dom": "^16.8.0",
|
|
40
|
+
"react-redux": "^7.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/redux/actions.es
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const ADD_TARGET = '@@poi-plugin-farming-assistant/ADD_TARGET'
|
|
2
|
+
export const REMOVE_TARGET = '@@poi-plugin-farming-assistant/REMOVE_TARGET'
|
|
3
|
+
export const SYNC_CONFIG = '@@poi-plugin-farming-assistant/SYNC_CONFIG'
|
|
4
|
+
|
|
5
|
+
export function addTarget(equipmentId, quota = 1) {
|
|
6
|
+
return {
|
|
7
|
+
type: ADD_TARGET,
|
|
8
|
+
equipmentId,
|
|
9
|
+
quota,
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function removeTarget(equipmentId) {
|
|
14
|
+
return {
|
|
15
|
+
type: REMOVE_TARGET,
|
|
16
|
+
equipmentId,
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function syncConfig(targets) {
|
|
21
|
+
return {
|
|
22
|
+
type: SYNC_CONFIG,
|
|
23
|
+
targets, // Can be Array (legacy) or Object
|
|
24
|
+
}
|
|
25
|
+
}
|
package/redux/index.es
ADDED
package/redux/reducer.es
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ADD_TARGET, REMOVE_TARGET, SYNC_CONFIG } from './actions'
|
|
2
|
+
|
|
3
|
+
const initialState = {
|
|
4
|
+
active: true,
|
|
5
|
+
targets: {}, // Changed from Array to Object: { [equipId]: quota }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function reducer(state = initialState, action) {
|
|
9
|
+
const { type, equipmentId, quota, targets } = action
|
|
10
|
+
switch (type) {
|
|
11
|
+
case ADD_TARGET:
|
|
12
|
+
// If adding existing, update quota
|
|
13
|
+
// Default quota 1 if not specified
|
|
14
|
+
const newTargets = {
|
|
15
|
+
...state.targets,
|
|
16
|
+
[equipmentId]: quota || (state.targets[equipmentId] ? state.targets[equipmentId] + 1 : 1)
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
...state,
|
|
20
|
+
targets: newTargets
|
|
21
|
+
}
|
|
22
|
+
case REMOVE_TARGET:
|
|
23
|
+
const updatedTargets = { ...state.targets }
|
|
24
|
+
delete updatedTargets[equipmentId]
|
|
25
|
+
return {
|
|
26
|
+
...state,
|
|
27
|
+
targets: updatedTargets,
|
|
28
|
+
}
|
|
29
|
+
case SYNC_CONFIG:
|
|
30
|
+
// Migration logic: If array (old config), convert to object
|
|
31
|
+
let migratedTargets = targets || {}
|
|
32
|
+
if (Array.isArray(targets)) {
|
|
33
|
+
migratedTargets = {}
|
|
34
|
+
targets.forEach(id => {
|
|
35
|
+
migratedTargets[id] = 1 // Default quota 1 for legacy array
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
...state,
|
|
40
|
+
targets: migratedTargets
|
|
41
|
+
}
|
|
42
|
+
default:
|
|
43
|
+
return state
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createSelector } from 'reselect'
|
|
2
|
+
import { extensionSelectorFactory } from 'views/utils/selectors'
|
|
3
|
+
import { wctfSelector } from 'views/utils/selectors' // POI provides this if available, otherwise we use state.wctf
|
|
4
|
+
|
|
5
|
+
const EXTENSION_KEY = 'poi-plugin-equips-farm'
|
|
6
|
+
|
|
7
|
+
// Plugin State - Direct access with POI's _ wrapper
|
|
8
|
+
export const farmingStateSelector = (state) => {
|
|
9
|
+
const extState = (state.ext && state.ext[EXTENSION_KEY]) || {}
|
|
10
|
+
// POI wraps reducer state in a '_' key
|
|
11
|
+
const pluginState = extState._ || extState || {}
|
|
12
|
+
return pluginState
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Targets is now an Object: { [equipId]: quotaCount }
|
|
16
|
+
export const targetsSelector = createSelector(
|
|
17
|
+
farmingStateSelector,
|
|
18
|
+
(state) => {
|
|
19
|
+
return state.targets || {}
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
// POI Master Data Selectors (Const)
|
|
24
|
+
export const constSelector = (state) => state.const || {}
|
|
25
|
+
|
|
26
|
+
export const masterShipsSelector = createSelector(
|
|
27
|
+
constSelector,
|
|
28
|
+
(state) => state.$ships || {}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export const masterEquipmentsSelector = createSelector(
|
|
32
|
+
constSelector,
|
|
33
|
+
(state) => state.$equips || state.$equipments || state.$slotitems || {}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
export const masterShipTypesSelector = createSelector(
|
|
37
|
+
constSelector,
|
|
38
|
+
(state) => state.$shipTypes || {}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
export const masterEquipTypesSelector = createSelector(
|
|
42
|
+
constSelector,
|
|
43
|
+
(state) => state.$equipTypes || {}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
// WCTF Data (WhoCallsTheFleet)
|
|
47
|
+
// state.wctf contains { ships, items }
|
|
48
|
+
export const wctfDataSelector = (state) => state.wctf || {}
|
|
49
|
+
|
|
50
|
+
// User Inventory Selectors (Info)
|
|
51
|
+
export const infoSelector = (state) => state.info || {}
|
|
52
|
+
|
|
53
|
+
export const userShipsSelector = createSelector(
|
|
54
|
+
infoSelector,
|
|
55
|
+
(info) => info.ships || {}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
export const userEquipsSelector = createSelector(
|
|
59
|
+
infoSelector,
|
|
60
|
+
(info) => info.equips || {}
|
|
61
|
+
)
|