# inventory_expansion_system.gd —— 40250 官方背包槽位解锁凭证与扩展系统 1:1 # 严格对照: # metin2/src/server/common/length.h (INVENTORY_MAX_NUM = 90, 45格/页) # metin2/src/server/share/conf/item_proto.txt (72319 / 72320 储物锁匙) # ClientVS22/source/UserInterface/GameType.h (INVENTORY_PAGE_SIZE = 45, 5x9 grid) extends RefCounted const PAGE_SIZE := 45 # 每页 45 格 (5 列 x 9 行) const BASE_UNLOCKED_PAGES := 2 # 默认前 2 页解锁 (0..89 共 90 格) const MAX_PAGES := 4 # 最大 4 页 (共 180 格) const TOTAL_SLOTS := 180 # 45 * 4 = 180 const SLOTS_PER_STAGE := 5 # 每个解锁阶段解锁 5 格 const TOTAL_EXPANSION_STAGES := 18 # 后 2 页共 90 格,划分为 18 个阶段 (90 / 5 = 18) const EXPANSION_KEY_VNUM := 72319 # 储物锁匙 # 18 个阶段的锁匙消耗曲线 (1:1 官方阶梯消耗) # 阶段 1~6 (90..119 格): 1 匙/段 # 阶段 7~12 (120..149 格): 2 匙/段 # 阶段 13~17 (150..174 格): 3 匙/段 # 阶段 18 (175..179 格): 4 匙/段 const STAGE_KEY_COSTS := [ 1, 1, 1, 1, 1, 1, # Stage 1..6 2, 2, 2, 2, 2, 2, # Stage 7..12 3, 3, 3, 3, 3, # Stage 13..17 4 # Stage 18 ] # 当前已解锁的扩展阶段数 (0..18) var unlocked_stages: int = 0 # 背包槽位存储 (slot 0..179 -> item_data Dictionary or null) var inventory_slots: Array = [] func _init(initial_unlocked_stages: int = 0) -> void: unlocked_stages = clamp(initial_unlocked_stages, 0, TOTAL_EXPANSION_STAGES) inventory_slots.clear() for i in range(TOTAL_SLOTS): inventory_slots.append(null) # 获取当前已解锁的最大格位数 (基础 90 格 + 解锁阶段数 * 5) func get_unlocked_slot_count() -> int: return (BASE_UNLOCKED_PAGES * PAGE_SIZE) + (unlocked_stages * SLOTS_PER_STAGE) # 检查特定格位是否已解锁 func is_slot_unlocked(slot_idx: int) -> bool: if slot_idx < 0 or slot_idx >= TOTAL_SLOTS: return false return slot_idx < get_unlocked_slot_count() # 获取下一阶段所需的储物锁匙数量 func get_next_stage_key_cost() -> int: if unlocked_stages >= TOTAL_EXPANSION_STAGES: return 0 return STAGE_KEY_COSTS[unlocked_stages] # 解锁下一阶段背包槽位 (5 格) func unlock_next_stage(player_keys: int) -> Dictionary: var res := { "success": false, "stage": unlocked_stages, "keys_deducted": 0, "remaining_keys": player_keys, "unlocked_slots": get_unlocked_slot_count(), "is_all_unlocked": false, "error_code": "OK", "message": "" } if unlocked_stages >= TOTAL_EXPANSION_STAGES: res["is_all_unlocked"] = true res["error_code"] = "ALREADY_MAX_EXPANDED" res["message"] = "背包所有 4 页 180 格位已全部解锁,达到上限!" return res var needed_keys := get_next_stage_key_cost() if player_keys < needed_keys: res["error_code"] = "INSUFFICIENT_KEYS" res["message"] = "储物锁匙不足!解锁阶段 %d 需要 %d 枚储物锁匙。" % [unlocked_stages + 1, needed_keys] return res unlocked_stages += 1 res["success"] = true res["stage"] = unlocked_stages res["keys_deducted"] = needed_keys res["remaining_keys"] = player_keys - needed_keys res["unlocked_slots"] = get_unlocked_slot_count() res["is_all_unlocked"] = (unlocked_stages >= TOTAL_EXPANSION_STAGES) res["message"] = "解锁成功!阶段 %d 已解除封印,新增 5 个可用格位 (当前可用格数: %d)。" % [ unlocked_stages, res["unlocked_slots"] ] return res # 检查物品是否能放置在指定格位 (考虑物品高度 height 与跨页/封印限制) # 在 Metin2 中,背包每行 5 格,高度占据 slot, slot+5, slot+10 func can_place_item_at(slot_idx: int, item_height: int = 1) -> Dictionary: var res := { "can_place": false, "error_code": "OK", "message": "" } if slot_idx < 0 or slot_idx >= TOTAL_SLOTS: res["error_code"] = "OUT_OF_BOUNDS" res["message"] = "超出背包格位边界。" return res var start_page := slot_idx / PAGE_SIZE var page_local_slot := slot_idx % PAGE_SIZE var row := page_local_slot / 5 # 跨页检查 (单物品不能从第 1 页跨到底部溢出到第 2 页) if row + item_height > 9: res["error_code"] = "PAGE_OVERFLOW" res["message"] = "物品长度超出单页底部边界。" return res # 检查高度占用的每一个单元格 for h in range(item_height): var check_slot := slot_idx + (h * 5) if not is_slot_unlocked(check_slot): res["error_code"] = "SLOT_LOCKED" res["message"] = "目标单元格 [%d] 处于锁链封印状态,需使用储物锁匙解锁。" % check_slot return res if inventory_slots[check_slot] != null: res["error_code"] = "SLOT_OCCUPIED" res["message"] = "目标单元格已被占用。" return res res["can_place"] = true return res # 放置物品到背包格位 func put_item(slot_idx: int, item_data: Dictionary, item_height: int = 1) -> Dictionary: var check := can_place_item_at(slot_idx, item_height) if not check["can_place"]: return check var it_copy := item_data.duplicate(true) it_copy["size"] = item_height it_copy["head_slot"] = slot_idx for h in range(item_height): var cell := slot_idx + (h * 5) inventory_slots[cell] = it_copy return { "success": true, "error_code": "OK", "slot": slot_idx } # 从背包移除物品 func remove_item(slot_idx: int) -> Dictionary: var res := { "success": false, "item": null, "error_code": "OK" } if slot_idx < 0 or slot_idx >= TOTAL_SLOTS or inventory_slots[slot_idx] == null: res["error_code"] = "EMPTY_SLOT" return res var it: Dictionary = inventory_slots[slot_idx] var head_slot: int = it.get("head_slot", slot_idx) var size: int = it.get("size", 1) for h in range(size): var cell := head_slot + (h * 5) if cell < TOTAL_SLOTS: inventory_slots[cell] = null res["success"] = true res["item"] = it return res