Skip to content
218 changes: 195 additions & 23 deletions autopcr/module/modules/travel.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,231 @@
from ...db.database import db
from ...model.enums import *
from ...model.common import InventoryInfo, TravelAppearEventData, TravelDecreaseItem, TravelQuestAddLap, TravelQuestInfo, TravelStartInfo
from typing import Set
from collections import Counter
from typing import Set
from ...util.ilp_solver import dispatch_solver
import math
import random
import time

@description('''
分解对应的EX装备,不分解已锁或已装备的EX装备
分解对应的EX装备,不分解已锁或已装备或5星的EX装备。
可设置每种金装保留数量,只分解未强化的装备。
开启"分解未满星装备"后,会将所有已强化但未满5星的装备纳入分解范围。
'''.strip())
@multichoice("ex_equip_recycle_category", "稀有度", ['普通铜', '普通银'], ['普通铜', '普通银', '会战银'])
@inttype("ex_equip_clan_gold_keep", "可分解会战金保留数量", 30, list(range(100)))
@inttype("ex_equip_normal_gold_keep", "可分解普通金保留数量", 30, list(range(100)))
@booltype("ex_equip_enable_keep", "启用保留数量限制", True)
@booltype("ex_equip_include_not_max_star", "分解未满星装备", False)
@booltype("ex_equip_show_detail", "显示详细分解信息", False)
@booltype("ex_equip_preview_only", "不分解,看消耗", False)
@multichoice("ex_equip_recycle_category", "分解稀有度", ['普通铜', '普通银', '会战银'], ['普通铜', '普通银', '会战银', '普通金', '会战金'])
@name("EX装备分解")
@default(True)
class ex_equip_recycle(Module):
task = {
'普通铜': lambda ex_id: db.ex_equipment_data[ex_id].rarity == 1 and db.ex_equipment_data[ex_id].clan_battle_equip_flag == 0,
'普通银': lambda ex_id: db.ex_equipment_data[ex_id].rarity == 2 and db.ex_equipment_data[ex_id].clan_battle_equip_flag == 0,
'会战银': lambda ex_id: db.ex_equipment_data[ex_id].rarity == 2 and db.ex_equipment_data[ex_id].clan_battle_equip_flag == 1,
'普通金': lambda ex_id: db.ex_equipment_data[ex_id].rarity == 3 and db.ex_equipment_data[ex_id].clan_battle_equip_flag == 0,
'会战金': lambda ex_id: db.ex_equipment_data[ex_id].rarity == 3 and db.ex_equipment_data[ex_id].clan_battle_equip_flag == 1,
}

async def do_task(self, client: pcrclient):
ex_equip_resolve_category: List[str] = self.get_config("ex_equip_recycle_category")
ex_equip_recycle_category: List[str] = self.get_config("ex_equip_recycle_category")
ex_equip_enable_keep: bool = self.get_config("ex_equip_enable_keep")
ex_equip_normal_gold_keep: int = self.get_config("ex_equip_normal_gold_keep")
ex_equip_clan_gold_keep: int = self.get_config("ex_equip_clan_gold_keep")
ex_equip_include_not_max_star: bool = self.get_config("ex_equip_include_not_max_star")
ex_equip_show_detail: bool = self.get_config("ex_equip_show_detail")
ex_equip_preview_only: bool = self.get_config("ex_equip_preview_only")

cnt = Counter()
rewards = []
preview_details = {} # 存储预览详情

# 获取已装备的EX装备
slot_ex = set(ex.serial_id for unit in client.data.unit.values() for ex in unit.ex_equip_slot if ex.serial_id) | \
set(ex.serial_id for unit in client.data.unit.values() for ex in unit.cb_ex_equip_slot if ex.serial_id)

# 获取保留配置
keep_config = {
'普通金': ex_equip_normal_gold_keep if ex_equip_enable_keep else 0,
'会战金': ex_equip_clan_gold_keep if ex_equip_enable_keep else 0,
'普通铜': 0,
'普通银': 0,
'会战银': 0,
}

slot_ex = set(ex.serial_id for unit in client.data.unit.values() for ex in unit.ex_equip_slot if ex.serial_id) | set(ex.serial_id for unit in client.data.unit.values() for ex in unit.cb_ex_equip_slot if ex.serial_id)

async def resolve(filter: Callable[[int], bool]):
serial_ids = [ex.serial_id for ex in client.data.ex_equips.values() if
filter(ex.ex_equipment_id) and
ex.protection_flag != 2 and
ex.serial_id not in slot_ex]
if serial_ids:
async def resolve_with_keep(filter_func: Callable[[int], bool], keep_count: int, category: str):
"""分解装备,保留指定数量"""

# 1. 先统计所有符合条件的装备
target_equips = []

for ex in client.data.ex_equips.values():
if not (filter_func(ex.ex_equipment_id) and \
ex.protection_flag != 2 and \
ex.serial_id not in slot_ex):
continue

# 判断是否应该纳入分解范围
should_include = False

if ex_equip_include_not_max_star:
# 开启"包含未满星装备":所有未满5星的装备都纳入(无论是否强化)
if ex.rank < 5:
should_include = True
else:
# 默认:只分解未强化的装备(rank == 0)
if ex.rank == 0:
should_include = True

if should_include:
target_equips.append(ex)

if not target_equips:
if ex_equip_show_detail:
status_text = "未满星" if ex_equip_include_not_max_star else "未强化"
self._log(f"【{category}】没有可分解的{status_text}装备")
return

# 2. 按装备ID分组统计目标装备
equip_count = {}
for ex in target_equips:
equip_id = ex.ex_equipment_id
equip_count[equip_id] = equip_count.get(equip_id, 0) + 1


# 3. 计算每种装备需要分解的数量
to_decompose_serial_ids = []
total_decompose = 0
category_details = []

for equip_id, count in equip_count.items():
equip_name = db.get_ex_equip_name(equip_id)

# 如果总数 <= 保留数量,全部保留
if count <= keep_count:
if ex_equip_show_detail or ex_equip_preview_only:
self._log(f" {equip_name}: {count}件 ≤ 保留{keep_count}件,全部保留")
continue

# 否则分解 count - keep_count 件
decompose_count = count - keep_count
total_decompose += decompose_count
cnt[category] += decompose_count

# 记录预览详情
category_details.append({
'name': equip_name,
'total': count,
'keep': keep_count,
'decompose': decompose_count,
'equip_id': equip_id
})

if ex_equip_show_detail or ex_equip_preview_only:
self._log(f" {equip_name}: {count}件,保留{keep_count}件,分解{decompose_count}件")

# 获取该装备的serial_id列表
equip_serial_ids = [
ex.serial_id for ex in target_equips
if ex.ex_equipment_id == equip_id
]
# 按等级排序,优先分解低等级的
equip_serial_ids_sorted = sorted(
equip_serial_ids,
key=lambda sid: client.data.ex_equips[sid].rank
)
# 取需要分解的数量
to_decompose_serial_ids.extend(equip_serial_ids_sorted[:decompose_count])

# 存储预览详情
if ex_equip_preview_only:
preview_details[category] = {
'details': category_details,
'total_count': total_decompose,
'serial_ids': to_decompose_serial_ids.copy()
}

if not to_decompose_serial_ids:
if ex_equip_show_detail:
self._log(f" {category}: 无需分解")
return

# 4. 批量分解(仅在非预览模式下执行)
if to_decompose_serial_ids and not ex_equip_preview_only:
gap = client.data.settings.ex_equip.ex_equip_limit_consume_num
cnt[category] = len(serial_ids)
for i in range(0, len(serial_ids), gap):
ret = await client.item_recycle_ex(serial_ids[i:i+gap])
if ex_equip_show_detail:
self._log(f" 分解了 {category},共{len(to_decompose_serial_ids)}件")

for i in range(0, len(to_decompose_serial_ids), gap):
ret = await client.item_recycle_ex(to_decompose_serial_ids[i:i+gap])
rewards.extend(ret.item_list)
elif ex_equip_preview_only:
# 预览模式下,记录将要分解的数量
if ex_equip_show_detail:
self._log(f" 将分解 {category},共{len(to_decompose_serial_ids)}件")

for category in ex_equip_resolve_category:
# 执行分解
for category in ex_equip_recycle_category:
if category not in self.task:
self._warn(f"未知的稀有度{category}")
continue

keep_count = keep_config.get(category, 0)

if ex_equip_show_detail or ex_equip_preview_only:
self._log(f"\n{'='*40}")
status_text = "未满星" if ex_equip_include_not_max_star else "未强化"
preview_text = "【看消耗】" if ex_equip_preview_only else ""
if ex_equip_enable_keep and category in ['普通金', '会战金']:
self._log(f"{preview_text}处理 {category}({status_text},保留数量: {keep_count}件)")
else:
self._log(f"{preview_text}处理 {category}({status_text},全部分解)")

await resolve_with_keep(self.task[category], keep_count, category)

# 输出结果
if ex_equip_preview_only:
# 预览模式:只显示预览信息,不执行分解
self._log(f"\n{'='*40}")
self._log("以下装备将被分解:")

total_preview = 0
for category, info in preview_details.items():
if info['details']:
self._log(f"\n{category}:")
for detail in info['details']:
self._log(f" {detail['name']}: {detail['decompose']}件")
total_preview += detail['decompose']

if total_preview > 0:
self._log(f"\n总计将分解 {total_preview} 件装备")
else:
await resolve(self.task[category])

self._log("没有需要分解的装备")
return # 预览模式直接返回

# 正常执行模式
if cnt:
msg = "分解了" + ' '.join(f"{category}x{cnt}" for category, cnt in cnt.items())
self._log(msg)
msg = "获得了:\n" + (await client.serialize_reward_summary(rewards)).strip('无')
self._log(msg)
if ex_equip_show_detail:
self._log(f"\n{'='*40}")

decompose_msg = ' '.join(f"{category}x{count}" for category, count in cnt.items())
self._log(f"分解了 {decompose_msg}")

if rewards:
reward_summary = await client.serialize_reward_summary(rewards)
if reward_summary and reward_summary.strip('无'):
self._log(f"获得了:\n{reward_summary.strip('无')}")
else:
self._log("获得了: 无")
else:
self._log("获得了: 无")
else:
raise SkipError("没有可分解的装备")

class eEventReward(IntEnum):
Unknown = 0
Coin = 1
Expand Down