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.
@@ -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
+ }
@@ -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
@@ -0,0 +1,3 @@
1
+ import { reducer } from './reducer'
2
+
3
+ export { reducer }
@@ -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
+ )