721 lines
24 KiB
JavaScript
721 lines
24 KiB
JavaScript
// 背包系統核心
|
||
import { apiService } from './api-service.js'
|
||
import { ITEM_TYPES, ITEM_RARITY, EQUIPMENT_SLOTS } from '../data/items.js'
|
||
|
||
export class InventorySystem {
|
||
constructor(petSystem, eventSystem, api = apiService) {
|
||
this.petSystem = petSystem
|
||
this.eventSystem = eventSystem
|
||
this.api = api
|
||
this.items = {} // 從 API 載入的道具數據
|
||
this.itemTypes = ITEM_TYPES
|
||
this.itemRarity = ITEM_RARITY
|
||
this.equipmentSlots = EQUIPMENT_SLOTS
|
||
|
||
// 背包結構:{ itemId: { count: number, item: ItemData } }
|
||
this.inventory = {}
|
||
|
||
// 裝備槽位:每個槽位可以同時裝備實際套件和外觀套件
|
||
// { slot: { equipment: itemId/object, appearance: itemId/object } }
|
||
this.equipped = {
|
||
weapon: { equipment: null, appearance: null },
|
||
armor: { equipment: null, appearance: null },
|
||
hat: { equipment: null, appearance: null },
|
||
accessory: { equipment: null, appearance: null },
|
||
talisman: { equipment: null, appearance: null },
|
||
special: { equipment: null, appearance: null }
|
||
}
|
||
|
||
// 外觀設定(從外觀套件或實際裝備中提取,外觀套件優先)
|
||
this.appearance = {}
|
||
}
|
||
|
||
// 初始化(從 API 載入道具數據、背包和裝備)
|
||
async initialize() {
|
||
try {
|
||
// 從 API 載入道具列表
|
||
const itemsList = await this.api.getItems()
|
||
if (itemsList && Array.isArray(itemsList)) {
|
||
// 將數組轉換為對象,以 itemId 為 key
|
||
this.items = {}
|
||
for (const item of itemsList) {
|
||
if (item.id) {
|
||
this.items[item.id] = item
|
||
}
|
||
}
|
||
console.log(`[InventorySystem] 從 API 載入 ${Object.keys(this.items).length} 種道具`)
|
||
} else {
|
||
// 降級到本地數據
|
||
const { ITEMS } = await import('../data/items.js')
|
||
this.items = ITEMS
|
||
console.log(`[InventorySystem] 使用本地道具數據,共 ${Object.keys(this.items).length} 種道具`)
|
||
}
|
||
} catch (error) {
|
||
console.warn('[InventorySystem] 載入道具列表失敗,使用本地數據:', error)
|
||
// 降級到本地數據
|
||
const { ITEMS } = await import('../data/items.js')
|
||
this.items = ITEMS
|
||
}
|
||
|
||
try {
|
||
// 載入背包和裝備
|
||
const saved = await this.api.getInventory()
|
||
if (saved) {
|
||
this.inventory = saved.inventory || {}
|
||
// 兼容舊格式:如果是舊格式(單一值),轉換為新格式
|
||
if (saved.equipped) {
|
||
for (const [slot, equipped] of Object.entries(saved.equipped)) {
|
||
if (this.equipped[slot]) {
|
||
// 新格式:已有 equipment 和 appearance 結構
|
||
if (equipped && typeof equipped === 'object' && (equipped.equipment !== undefined || equipped.appearance !== undefined)) {
|
||
this.equipped[slot] = {
|
||
equipment: equipped.equipment || null,
|
||
appearance: equipped.appearance || null
|
||
}
|
||
} else if (equipped) {
|
||
// 舊格式:單一值,判斷是裝備還是外觀
|
||
const itemId = typeof equipped === 'string' ? equipped : equipped.itemId
|
||
const item = this.items[itemId]
|
||
if (item) {
|
||
if (item.type === 'appearance') {
|
||
this.equipped[slot] = { equipment: null, appearance: equipped }
|
||
} else {
|
||
this.equipped[slot] = { equipment: equipped, appearance: null }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
this.appearance = saved.appearance || {}
|
||
}
|
||
|
||
// 重新應用裝備效果
|
||
await this.reapplyEquipmentEffects()
|
||
|
||
console.log(`[InventorySystem] 載入背包,共 ${Object.keys(this.inventory).length} 種道具`)
|
||
} catch (error) {
|
||
console.error('[InventorySystem] 載入背包失敗:', error)
|
||
// 降級到本地載入
|
||
const saved = localStorage.getItem('inventory')
|
||
if (saved) {
|
||
const data = JSON.parse(saved)
|
||
this.inventory = data.inventory || {}
|
||
this.equipped = { ...this.equipped, ...data.equipped }
|
||
this.appearance = data.appearance || {}
|
||
await this.reapplyEquipmentEffects()
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加道具到背包
|
||
async addItem(itemId, count = 1) {
|
||
const item = this.items[itemId]
|
||
if (!item) {
|
||
console.warn(`[InventorySystem] 找不到道具: ${itemId}`)
|
||
return { success: false, message: '道具不存在' }
|
||
}
|
||
|
||
// 只有消耗品可以堆疊,其他道具(裝備、外觀、護身符、特殊)每件都要分開
|
||
if (item.type === 'consumable') {
|
||
// 消耗品可以堆疊
|
||
if (!this.inventory[itemId]) {
|
||
this.inventory[itemId] = {
|
||
count: 0,
|
||
item: { ...item }
|
||
}
|
||
}
|
||
this.inventory[itemId].count += count
|
||
} else {
|
||
// 裝備類道具(equipment, appearance, talisman, special)每件分開
|
||
if (!this.inventory[itemId]) {
|
||
this.inventory[itemId] = {
|
||
count: 0,
|
||
items: [] // 多個相同裝備,每個有獨立耐久度
|
||
}
|
||
}
|
||
|
||
// 添加新裝備實例(每件都是獨立的)
|
||
for (let i = 0; i < count; i++) {
|
||
this.inventory[itemId].items = this.inventory[itemId].items || []
|
||
this.inventory[itemId].items.push({
|
||
id: `${itemId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||
itemId,
|
||
durability: item.durability !== undefined ? item.durability : Infinity,
|
||
maxDurability: item.maxDurability !== undefined ? item.maxDurability : Infinity
|
||
})
|
||
this.inventory[itemId].count++
|
||
}
|
||
}
|
||
|
||
// 同步到 API
|
||
await this.saveInventory()
|
||
|
||
console.log(`[InventorySystem] 獲得 ${count} 個 ${item.name}`)
|
||
return { success: true, item, count }
|
||
}
|
||
|
||
// 移除道具(會自動卸下如果正在裝備中)
|
||
async removeItem(itemId, count = 1) {
|
||
// 檢查是否正在裝備中,如果是則先卸下
|
||
for (const [slot, slotData] of Object.entries(this.equipped)) {
|
||
if (typeof slotData === 'object' && slotData !== null) {
|
||
if (slotData.equipment) {
|
||
const eqItemId = typeof slotData.equipment === 'string' ? slotData.equipment : slotData.equipment.itemId
|
||
if (eqItemId === itemId) {
|
||
await this.unequipItem(slot, 'equipment')
|
||
}
|
||
}
|
||
if (slotData.appearance) {
|
||
const apItemId = typeof slotData.appearance === 'string' ? slotData.appearance : slotData.appearance.itemId
|
||
if (apItemId === itemId) {
|
||
await this.unequipItem(slot, 'appearance')
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!this.inventory[itemId]) {
|
||
return { success: false, message: '道具不存在' }
|
||
}
|
||
|
||
const item = this.items[itemId]
|
||
|
||
// 只有消耗品可以堆疊
|
||
if (item.type === 'consumable') {
|
||
if (this.inventory[itemId].count < count) {
|
||
return { success: false, message: '道具數量不足' }
|
||
}
|
||
this.inventory[itemId].count -= count
|
||
if (this.inventory[itemId].count <= 0) {
|
||
delete this.inventory[itemId]
|
||
}
|
||
} else {
|
||
// 裝備類道具,每件分開
|
||
if (this.inventory[itemId].count < count) {
|
||
return { success: false, message: '道具數量不足' }
|
||
}
|
||
|
||
// 移除指定數量的裝備實例(從未裝備的開始移除)
|
||
this.inventory[itemId].items = this.inventory[itemId].items || []
|
||
|
||
// 找出未裝備的實例
|
||
const equippedInstances = new Set()
|
||
for (const [slot, slotData] of Object.entries(this.equipped)) {
|
||
if (slotData && typeof slotData === 'object') {
|
||
if (slotData.equipment && typeof slotData.equipment === 'object' && slotData.equipment.itemId === itemId) {
|
||
equippedInstances.add(slotData.equipment.instanceId)
|
||
}
|
||
if (slotData.appearance && typeof slotData.appearance === 'object' && slotData.appearance.itemId === itemId) {
|
||
equippedInstances.add(slotData.appearance.instanceId)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 先移除未裝備的實例
|
||
let removed = 0
|
||
for (let i = this.inventory[itemId].items.length - 1; i >= 0 && removed < count; i--) {
|
||
if (!equippedInstances.has(this.inventory[itemId].items[i].id)) {
|
||
this.inventory[itemId].items.splice(i, 1)
|
||
removed++
|
||
}
|
||
}
|
||
|
||
this.inventory[itemId].count -= removed
|
||
|
||
if (this.inventory[itemId].count <= 0) {
|
||
delete this.inventory[itemId]
|
||
}
|
||
}
|
||
|
||
await this.saveInventory()
|
||
console.log(`[InventorySystem] 移除道具: ${itemId} x${count}`)
|
||
return { success: true }
|
||
}
|
||
|
||
// 重置背包(清除所有道具和裝備)
|
||
async resetInventory() {
|
||
this.inventory = {}
|
||
this.equipped = {
|
||
weapon: { equipment: null, appearance: null },
|
||
armor: { equipment: null, appearance: null },
|
||
hat: { equipment: null, appearance: null },
|
||
accessory: { equipment: null, appearance: null },
|
||
talisman: { equipment: null, appearance: null },
|
||
special: { equipment: null, appearance: null }
|
||
}
|
||
this.appearance = {}
|
||
|
||
// 清除裝備效果
|
||
await this.petSystem.updateState({ equipmentBuffs: { flat: {}, percent: {} } })
|
||
this.petSystem.calculateCombatStats()
|
||
|
||
// 清除 localStorage
|
||
localStorage.removeItem('inventory')
|
||
|
||
// 同步到 API
|
||
await this.saveInventory()
|
||
|
||
console.log('[InventorySystem] 背包已重置')
|
||
return { success: true }
|
||
}
|
||
|
||
// 使用消耗品
|
||
async useItem(itemId, count = 1) {
|
||
const item = this.items[itemId]
|
||
if (!item) {
|
||
return { success: false, message: '道具不存在' }
|
||
}
|
||
|
||
if (item.type !== 'consumable') {
|
||
return { success: false, message: '此道具不是消耗品' }
|
||
}
|
||
|
||
// 檢查數量
|
||
if (!this.inventory[itemId] || this.inventory[itemId].count < count) {
|
||
return { success: false, message: '道具數量不足' }
|
||
}
|
||
|
||
const results = []
|
||
|
||
// 執行效果
|
||
for (let i = 0; i < count; i++) {
|
||
const result = await this.executeItemEffects(item)
|
||
results.push(result)
|
||
}
|
||
|
||
// 移除道具
|
||
await this.removeItem(itemId, count)
|
||
|
||
// 同步到 API
|
||
try {
|
||
await this.api.useItem(itemId, count)
|
||
} catch (error) {
|
||
console.warn('[InventorySystem] API 同步失敗:', error)
|
||
}
|
||
|
||
return { success: true, results }
|
||
}
|
||
|
||
// 執行道具效果
|
||
async executeItemEffects(item) {
|
||
const results = []
|
||
const state = this.petSystem.getState()
|
||
|
||
if (item.effects) {
|
||
// 修改屬性
|
||
if (item.effects.modifyStats) {
|
||
const updates = {}
|
||
for (const [key, value] of Object.entries(item.effects.modifyStats)) {
|
||
if (key === 'health') {
|
||
const maxHealth = this.petSystem.getMaxHealth()
|
||
updates[key] = Math.min(maxHealth, (state[key] || 0) + value)
|
||
} else if (key === 'hunger' || key === 'happiness') {
|
||
updates[key] = Math.min(100, Math.max(0, (state[key] || 0) + value))
|
||
} else {
|
||
updates[key] = (state[key] || 0) + value
|
||
}
|
||
}
|
||
await this.petSystem.updateState(updates)
|
||
results.push({ type: 'modifyStats', updates })
|
||
}
|
||
|
||
// 治癒疾病
|
||
if (item.effects.cureSickness && state.isSick) {
|
||
await this.petSystem.updateState({ isSick: false })
|
||
results.push({ type: 'cureSickness' })
|
||
}
|
||
|
||
// 添加 Buff
|
||
if (item.effects.addBuff) {
|
||
await this.eventSystem.addBuff(item.effects.addBuff)
|
||
results.push({ type: 'addBuff', buff: item.effects.addBuff })
|
||
}
|
||
}
|
||
|
||
return results
|
||
}
|
||
|
||
// 裝備道具
|
||
// equipType: 'equipment' | 'appearance' | 'auto' (自動判斷)
|
||
async equipItem(itemId, slot = null, equipType = 'auto') {
|
||
const item = this.items[itemId]
|
||
if (!item) {
|
||
return { success: false, message: '道具不存在' }
|
||
}
|
||
|
||
// 檢查是否可裝備
|
||
if (item.type !== 'equipment' && item.type !== 'appearance' && item.type !== 'talisman' && item.type !== 'special') {
|
||
return { success: false, message: '此道具無法裝備' }
|
||
}
|
||
|
||
// 確定槽位
|
||
const targetSlot = slot || item.slot
|
||
if (!targetSlot) {
|
||
return { success: false, message: '道具沒有指定槽位' }
|
||
}
|
||
|
||
// 檢查是否有該道具
|
||
if (!this.inventory[itemId] || this.inventory[itemId].count < 1) {
|
||
return { success: false, message: '背包中沒有此道具' }
|
||
}
|
||
|
||
// 確定裝備類型
|
||
let targetType = equipType
|
||
if (targetType === 'auto') {
|
||
// 自動判斷:appearance 類型裝備到 appearance 槽位,其他裝備到 equipment 槽位
|
||
if (item.type === 'appearance') {
|
||
targetType = 'appearance'
|
||
} else {
|
||
targetType = 'equipment'
|
||
}
|
||
}
|
||
|
||
// 檢查槽位結構
|
||
if (!this.equipped[targetSlot] || typeof this.equipped[targetSlot] !== 'object') {
|
||
this.equipped[targetSlot] = { equipment: null, appearance: null }
|
||
}
|
||
|
||
// 如果該子槽位已有裝備,先卸下
|
||
if (this.equipped[targetSlot][targetType]) {
|
||
await this.unequipItem(targetSlot, targetType)
|
||
}
|
||
|
||
// 裝備道具
|
||
let equippedData = null
|
||
if (item.type === 'consumable') {
|
||
// 消耗品不應該裝備
|
||
return { success: false, message: '消耗品無法裝備' }
|
||
} else {
|
||
// 裝備類道具,使用指定的實例或選擇第一個未裝備的
|
||
if (!instanceId) {
|
||
// 選擇第一個未裝備的實例
|
||
const instances = this.inventory[itemId].items || []
|
||
if (instances.length === 0) {
|
||
return { success: false, message: '裝備實例不存在' }
|
||
}
|
||
|
||
// 找出已裝備的實例
|
||
const equippedInstances = new Set()
|
||
for (const [s, slotData] of Object.entries(this.equipped)) {
|
||
if (slotData && typeof slotData === 'object') {
|
||
if (slotData.equipment && typeof slotData.equipment === 'object' && slotData.equipment.itemId === itemId) {
|
||
equippedInstances.add(slotData.equipment.instanceId)
|
||
}
|
||
if (slotData.appearance && typeof slotData.appearance === 'object' && slotData.appearance.itemId === itemId) {
|
||
equippedInstances.add(slotData.appearance.instanceId)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 選擇第一個未裝備的實例
|
||
const availableInstance = instances.find(i => !equippedInstances.has(i.id))
|
||
if (!availableInstance) {
|
||
return { success: false, message: '所有裝備實例都已裝備' }
|
||
}
|
||
instanceId = availableInstance.id
|
||
}
|
||
|
||
equippedData = {
|
||
itemId,
|
||
instanceId: instanceId
|
||
}
|
||
}
|
||
|
||
// 裝備到對應槽位
|
||
this.equipped[targetSlot][targetType] = equippedData
|
||
|
||
// 更新外觀(優先使用外觀套件,如果沒有則使用實際裝備的外觀)
|
||
await this.updateAppearance()
|
||
|
||
// 應用裝備效果(只有實際裝備才有效果)
|
||
await this.applyEquipmentEffects()
|
||
|
||
// 同步到 API
|
||
await this.saveInventory()
|
||
|
||
const slotName = EQUIPMENT_SLOTS[targetSlot]?.name || targetSlot
|
||
const typeName = targetType === 'appearance' ? '外觀' : '實際'
|
||
console.log(`[InventorySystem] 裝備 ${item.name} 到 ${slotName} (${typeName})`)
|
||
return { success: true, item, slot: targetSlot, type: targetType }
|
||
}
|
||
|
||
// 卸下裝備
|
||
// type: 'equipment' | 'appearance' | null (卸下全部)
|
||
async unequipItem(slot, type = null) {
|
||
if (!this.equipped[slot]) {
|
||
return { success: false, message: '該槽位沒有裝備' }
|
||
}
|
||
|
||
// 如果沒有指定類型,卸下全部(兼容舊代碼)
|
||
if (type === null) {
|
||
const results = []
|
||
if (this.equipped[slot].equipment) {
|
||
const result = await this.unequipItem(slot, 'equipment')
|
||
if (result.success) results.push(result)
|
||
}
|
||
if (this.equipped[slot].appearance) {
|
||
const result = await this.unequipItem(slot, 'appearance')
|
||
if (result.success) results.push(result)
|
||
}
|
||
return { success: results.length > 0, results }
|
||
}
|
||
|
||
// 卸下指定類型的裝備
|
||
const equipped = this.equipped[slot][type]
|
||
if (!equipped) {
|
||
return { success: false, message: `該槽位的${type === 'appearance' ? '外觀' : '實際'}裝備為空` }
|
||
}
|
||
|
||
const itemId = typeof equipped === 'string' ? equipped : equipped.itemId
|
||
const item = this.items[itemId]
|
||
|
||
// 清除裝備
|
||
this.equipped[slot][type] = null
|
||
|
||
// 更新外觀
|
||
await this.updateAppearance()
|
||
|
||
// 重新計算屬性(只有卸下實際裝備才需要)
|
||
if (type === 'equipment') {
|
||
await this.applyEquipmentEffects()
|
||
}
|
||
|
||
// 同步到 API
|
||
await this.saveInventory()
|
||
|
||
console.log(`[InventorySystem] 卸下 ${item?.name || itemId} (${type === 'appearance' ? '外觀' : '實際'})`)
|
||
return { success: true, item, type }
|
||
}
|
||
|
||
// 更新外觀(優先使用外觀套件,如果沒有則使用實際裝備的外觀)
|
||
async updateAppearance() {
|
||
this.appearance = {}
|
||
|
||
// 遍歷所有槽位
|
||
for (const [slot, equipped] of Object.entries(this.equipped)) {
|
||
if (!equipped) continue
|
||
|
||
// 優先使用外觀套件
|
||
if (equipped.appearance) {
|
||
const itemId = typeof equipped.appearance === 'string' ? equipped.appearance : equipped.appearance.itemId
|
||
const item = this.items[itemId]
|
||
if (item && item.appearance) {
|
||
this.appearance = { ...this.appearance, ...item.appearance }
|
||
}
|
||
} else if (equipped.equipment) {
|
||
// 如果沒有外觀套件,使用實際裝備的外觀
|
||
const itemId = typeof equipped.equipment === 'string' ? equipped.equipment : equipped.equipment.itemId
|
||
const item = this.items[itemId]
|
||
if (item && item.appearance) {
|
||
this.appearance = { ...this.appearance, ...item.appearance }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 應用裝備效果(只應用實際裝備的效果,外觀套件不影響數值)
|
||
async applyEquipmentEffects() {
|
||
const state = this.petSystem.getState()
|
||
const equipmentBuffs = {}
|
||
|
||
// 收集所有實際裝備的加成(不包括外觀套件)
|
||
for (const [slot, equipped] of Object.entries(this.equipped)) {
|
||
if (!equipped || !equipped.equipment) continue
|
||
|
||
const itemId = typeof equipped.equipment === 'string' ? equipped.equipment : equipped.equipment.itemId
|
||
const item = this.items[itemId]
|
||
if (!item) continue
|
||
|
||
// 檢查耐久度(如果是裝備類且有耐久度)
|
||
if (item.type === 'equipment' && item.durability !== Infinity) {
|
||
const instanceId = typeof equipped.equipment === 'object' ? equipped.equipment.instanceId : null
|
||
if (instanceId) {
|
||
const instance = this.inventory[itemId]?.items?.find(i => i.id === instanceId)
|
||
if (instance && instance.durability <= 0) {
|
||
// 裝備已損壞,自動卸下
|
||
console.log(`[InventorySystem] ${item.name} 已損壞,自動卸下`)
|
||
this.equipped[slot].equipment = null
|
||
continue
|
||
}
|
||
}
|
||
}
|
||
|
||
// 應用裝備效果(只有實際裝備才有效果)
|
||
if (item.effects) {
|
||
if (item.effects.flat) {
|
||
for (const [key, value] of Object.entries(item.effects.flat)) {
|
||
if (!equipmentBuffs.flat) equipmentBuffs.flat = {}
|
||
equipmentBuffs.flat[key] = (equipmentBuffs.flat[key] || 0) + value
|
||
}
|
||
}
|
||
if (item.effects.percent) {
|
||
for (const [key, value] of Object.entries(item.effects.percent)) {
|
||
if (!equipmentBuffs.percent) equipmentBuffs.percent = {}
|
||
equipmentBuffs.percent[key] = (equipmentBuffs.percent[key] || 0) + value
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 更新寵物狀態(確保結構正確)
|
||
const finalBuffs = {
|
||
flat: equipmentBuffs.flat || {},
|
||
percent: equipmentBuffs.percent || {}
|
||
}
|
||
|
||
await this.petSystem.updateState({ equipmentBuffs: finalBuffs })
|
||
|
||
// 重新計算戰鬥數值
|
||
this.petSystem.calculateCombatStats()
|
||
|
||
return finalBuffs
|
||
}
|
||
|
||
// 重新應用裝備效果(初始化時使用)
|
||
async reapplyEquipmentEffects() {
|
||
await this.applyEquipmentEffects()
|
||
}
|
||
|
||
// 減少裝備耐久度(只減少實際裝備的耐久度)
|
||
async reduceDurability(slot, amount = 1) {
|
||
const equipped = this.equipped[slot]
|
||
if (!equipped || !equipped.equipment) return
|
||
|
||
const itemId = typeof equipped.equipment === 'string' ? equipped.equipment : equipped.equipment.itemId
|
||
const item = this.items[itemId]
|
||
|
||
// 只有裝備類且有耐久度的才會減少
|
||
if (item.type !== 'equipment' || item.durability === Infinity) return
|
||
|
||
const instanceId = typeof equipped.equipment === 'object' ? equipped.equipment.instanceId : null
|
||
if (!instanceId) return
|
||
|
||
// 找到裝備實例
|
||
const instance = this.inventory[itemId]?.items?.find(i => i.id === instanceId)
|
||
if (!instance) return
|
||
|
||
// 減少耐久度
|
||
instance.durability = Math.max(0, instance.durability - amount)
|
||
|
||
// 如果耐久度歸零,自動卸下
|
||
if (instance.durability <= 0) {
|
||
console.log(`[InventorySystem] ${item.name} 耐久度歸零,已損壞`)
|
||
await this.unequipItem(slot, 'equipment')
|
||
}
|
||
|
||
await this.saveInventory()
|
||
}
|
||
|
||
// 修復裝備
|
||
async repairItem(itemId, instanceId = null) {
|
||
if (!this.inventory[itemId]) {
|
||
return { success: false, message: '道具不存在' }
|
||
}
|
||
|
||
const item = this.items[itemId]
|
||
if (item.durability === Infinity) {
|
||
return { success: false, message: '此道具無法修復(永久裝備)' }
|
||
}
|
||
|
||
if (item.stackable) {
|
||
return { success: false, message: '此道具無法修復' }
|
||
}
|
||
|
||
const instances = this.inventory[itemId].items || []
|
||
let repaired = false
|
||
|
||
if (instanceId) {
|
||
// 修復指定實例
|
||
const instance = instances.find(i => i.id === instanceId)
|
||
if (instance) {
|
||
instance.durability = instance.maxDurability || item.maxDurability
|
||
repaired = true
|
||
}
|
||
} else {
|
||
// 修復所有實例
|
||
instances.forEach(instance => {
|
||
instance.durability = instance.maxDurability || item.maxDurability
|
||
})
|
||
repaired = instances.length > 0
|
||
}
|
||
|
||
if (repaired) {
|
||
await this.saveInventory()
|
||
await this.applyEquipmentEffects() // 重新應用效果
|
||
return { success: true, message: '裝備已修復' }
|
||
}
|
||
|
||
return { success: false, message: '修復失敗' }
|
||
}
|
||
|
||
// 獲取背包
|
||
getInventory() {
|
||
return { ...this.inventory }
|
||
}
|
||
|
||
// 獲取裝備
|
||
getEquipped() {
|
||
return { ...this.equipped }
|
||
}
|
||
|
||
// 獲取外觀
|
||
getAppearance() {
|
||
return { ...this.appearance }
|
||
}
|
||
|
||
// 檢查是否有道具
|
||
hasItem(itemId, count = 1) {
|
||
if (!this.inventory[itemId]) return false
|
||
return this.inventory[itemId].count >= count
|
||
}
|
||
|
||
// 獲取道具數量
|
||
getItemCount(itemId) {
|
||
return this.inventory[itemId]?.count || 0
|
||
}
|
||
|
||
// 獲取裝備的道具
|
||
// type: 'equipment' | 'appearance' | null (返回兩個)
|
||
getEquippedItem(slot, type = null) {
|
||
const equipped = this.equipped[slot]
|
||
if (!equipped) return null
|
||
|
||
if (type) {
|
||
const equippedData = equipped[type]
|
||
if (!equippedData) return null
|
||
const itemId = typeof equippedData === 'string' ? equippedData : equippedData.itemId
|
||
return this.items[itemId] || null
|
||
} else {
|
||
// 返回兩個
|
||
return {
|
||
equipment: equipped.equipment ? (() => {
|
||
const itemId = typeof equipped.equipment === 'string' ? equipped.equipment : equipped.equipment.itemId
|
||
return this.items[itemId] || null
|
||
})() : null,
|
||
appearance: equipped.appearance ? (() => {
|
||
const itemId = typeof equipped.appearance === 'string' ? equipped.appearance : equipped.appearance.itemId
|
||
return this.items[itemId] || null
|
||
})() : null
|
||
}
|
||
}
|
||
}
|
||
|
||
// 保存背包(同步到 API)
|
||
async saveInventory() {
|
||
const data = {
|
||
inventory: this.inventory,
|
||
equipped: this.equipped,
|
||
appearance: this.appearance
|
||
}
|
||
|
||
try {
|
||
await this.api.saveInventory(data)
|
||
} catch (error) {
|
||
console.warn('[InventorySystem] API 同步失敗:', error)
|
||
// 降級到 localStorage
|
||
localStorage.setItem('inventory', JSON.stringify(data))
|
||
}
|
||
}
|
||
}
|
||
|