From 83c88d1fb9b8efbaec91aed6d701bae45daf560a Mon Sep 17 00:00:00 2001 From: Lanly109 <1094916227@qq.com> Date: Wed, 30 Oct 2024 19:30:41 +0800 Subject: [PATCH 01/14] fea: unit power calculate --- autopcr/core/datamgr.py | 3 + autopcr/db/database.py | 146 ++++++++++++++++++++++++++++++-- autopcr/db/methods.py | 62 +++++++++++++- autopcr/model/custom.py | 108 ++++++++++++++++++++++- autopcr/model/enums.py | 20 +++++ autopcr/module/modules/daily.py | 7 +- 6 files changed, 334 insertions(+), 12 deletions(-) diff --git a/autopcr/core/datamgr.py b/autopcr/core/datamgr.py index 1440fde4..a05d2854 100644 --- a/autopcr/core/datamgr.py +++ b/autopcr/core/datamgr.py @@ -457,3 +457,6 @@ async def request(self, request: Request[TResponse], next: RequestHandler) -> TR if resp: await resp.update(self, request) return resp + async def get_unit_power(self, unit_id: int) -> int: + power = db.calc_unit_power(self.unit[unit_id], set(self.read_story_ids)) + return int(power + 0.5) diff --git a/autopcr/db/database.py b/autopcr/db/database.py index 1939f848..973dd663 100644 --- a/autopcr/db/database.py +++ b/autopcr/db/database.py @@ -1,11 +1,11 @@ import time -from typing import List, Dict, Tuple, Union +from typing import List, Dict, Set, Tuple, Union import typing from ..model.enums import eCampaignCategory -from ..model.common import eInventoryType, RoomUserItem, InventoryInfo +from ..model.common import UnitData, eInventoryType, RoomUserItem, InventoryInfo from ..model.custom import ItemType import datetime -from collections import Counter +from collections import Counter, defaultdict from .dbmgr import dbmgr from .methods import * from .models import * @@ -82,6 +82,17 @@ def update(self, dbmgr: dbmgr): extra_drops.get(x.quest_id // 1000, Counter()) ) ) + + self.unique_equipment_data: Dict[int, UniqueEquipmentDatum] = ( + UniqueEquipmentDatum.query(db) + .to_dict(lambda x: x.equipment_id, lambda x: x) + ) + + self.unique_equip_enhance_rate: Dict[int, List[UniqueEquipEnhanceRate]] = ( + UniqueEquipEnhanceRate.query(db) + .group_by(lambda x: x.equipment_id) + .to_dict(lambda x: x.key, lambda x: x.to_list()) + ) self.unique_equip_rank: Dict[int, Dict[int, UniqueEquipmentEnhanceDatum]] = ( UniqueEquipmentEnhanceDatum.query(db) @@ -106,6 +117,18 @@ def update(self, dbmgr: dbmgr): ) ) + self.unit_status_coefficient: Dict[int, UnitStatusCoefficient] = ( + UnitStatusCoefficient.query(db) + .to_dict(lambda x: x.coefficient_id, lambda x: x) + ) + + self.promote_bonus: Dict[int, Dict[int, PromotionBonus]] = ( + PromotionBonus.query(db) + .group_by(lambda x: x.unit_id) + .to_dict(lambda x: x.key, lambda x: + x.to_dict(lambda x: x.promotion_level, lambda x: x)) + ) + self.unit_promotion: Dict[int, Dict[int, UnitPromotion]] = ( UnitPromotion.query(db) .group_by(lambda x: x.unit_id) @@ -115,6 +138,15 @@ def update(self, dbmgr: dbmgr): ) ) + self.unit_promotion_status: Dict[int, Dict[int, UnitPromotionStatus]] = ( + UnitPromotionStatus.query(db) + .group_by(lambda x: x.unit_id) + .to_dict(lambda x: x.key, lambda x: + x.to_dict(lambda y: y.promotion_level, lambda y: y + ) + ) + ) + self.unit_promotion_equip_count: Dict[int, Dict[int, typing.Counter[ItemType]]] = ( UnitPromotion.query(db) .group_by(lambda x: x.unit_id) @@ -225,6 +257,13 @@ def update(self, dbmgr: dbmgr): ExceedLevelUnit.query(db) .to_dict(lambda x: x.unit_id, lambda x: x) ) + + self.unit_rarity: Dict[int, Dict[int, UnitRarity]] = ( + UnitRarity.query(db) + .group_by(lambda x: x.unit_id) + .to_dict(lambda x: x.key, lambda x: x + .to_dict(lambda x: x.rarity, lambda x: x)) + ) self.rarity_up_required: Dict[int, Dict[int, typing.Counter[ItemType]]] = ( UnitRarity.query(db) @@ -363,6 +402,16 @@ def update(self, dbmgr: dbmgr): .concat(TrainingQuestDatum.query(db)) .to_dict(lambda x: x.quest_id, lambda x: x.quest_name) ) + + self.chara_story_status: Dict[int, CharaStoryStatus] = ( + CharaStoryStatus.query(db) + .to_dict(lambda x: x.story_id, lambda x: x) + ) + + self.chara2story: Dict[int, List[CharaStoryStatus]] = defaultdict(list) + for story in self.chara_story_status.values(): + for unit_id in story.get_effect_unit_ids(): + self.chara2story[unit_id].append(story) self.guild_story: List[StoryDetail] = ( StoryDetail.query(db) @@ -439,6 +488,11 @@ def update(self, dbmgr: dbmgr): x.to_dict(lambda x: x.equipment_enhance_level, lambda x: x)) ) + self.equipment_enhance_rate: Dict[int, EquipmentEnhanceRate] = ( + EquipmentEnhanceRate.query(db) + .to_dict(lambda x: x.equipment_id, lambda x: x) + ) + self.inventory_name: Dict[ItemType, str] = ( EquipmentDatum.query(db) .select(lambda x: (eInventoryType(eInventoryType.Equip), x.equipment_id, x.equipment_name)) @@ -598,16 +652,12 @@ def update(self, dbmgr: dbmgr): .to_list() ) - # self.exp_potion = self.exp_potion[::-1] - self.equip_enhance_stone: List[ItemDatum] = ( ItemDatum.query(db) .where(lambda x: x.item_id >= 22001 and x.item_id < 23000) .to_list() ) - # self.equip_enhance_stone = self.equip_enhance_stone[::-1] - self.quest_to_event: Dict[int, HatsuneQuest] = ( HatsuneQuest.query(db) .concat(ShioriQuest.query(db)) @@ -1108,4 +1158,86 @@ def get_gacha_prize_name(self, gacha_id: int, prize_rarity: int) -> str: return self.prizegacha_sp_detail[prize_rarity].name return f"{prize_rarity}等奖" + def is_unit_rank_bonus(self, unit_id: int, promotion_level: int) -> bool: + return unit_id in self.promote_bonus and promotion_level in self.promote_bonus[unit_id] + + def calc_unit_attribute(self, unit_data: UnitData, read_story: Set[int]) -> UnitAttribute: + unit_id = unit_data.id + promotion_level = unit_data.promotion_level.value + rarity = unit_data.battle_rarity if unit_data.battle_rarity else unit_data.unit_rarity + + base_attribute = UnitAttribute() + # 基础属性 + base_attribute += self.unit_rarity[unit_id][rarity].get_unit_attribute() + + # 等级属性 + base_attribute += self.unit_rarity[unit_id][rarity].get_unit_attribute_growth(unit_data.unit_level + promotion_level) + + #品级属性 + if promotion_level > 1: + base_attribute += self.unit_promotion_status[unit_id][promotion_level].get_unit_attribute() + + rb_attribute = UnitAttribute() + if self.is_unit_rank_bonus(unit_id, promotion_level): + rb_attribute += self.promote_bonus[unit_id][promotion_level].get_unit_attribute() + + equip_attribute = UnitAttribute() + # 装备属性 + for equip in unit_data.equip_slot: + if equip.is_slot: + equip_attribute += (self.equip_data[equip.id].get_unit_attribute() + self.equipment_enhance_rate[equip.id].get_unit_attribute(equip.enhancement_level)).ceil() + + unique_equip_attribute = UnitAttribute() + # 专武属性 + for unique_equip in unit_data.unique_equip_slot: + if unique_equip.is_slot: + unique_equip_attribute += self.unique_equipment_data[unique_equip.id].get_unit_attribute() + for enhance_rate in self.unique_equip_enhance_rate[unique_equip.id]: + unique_equip_attribute += enhance_rate.get_unit_attribute(unique_equip.enhancement_level) + + kizuna_attribute = UnitAttribute() + # 羁绊属性 + for story in self.chara2story[unit_id]: + if story.story_id in read_story: + kizuna_attribute += story.get_unit_attribute() + + unit_attribute = base_attribute.round() + rb_attribute.round() + equip_attribute.round() + unique_equip_attribute.ceil() + kizuna_attribute.round() + return unit_attribute + + def calc_unit_attribute_power(self, unit_data: UnitData, read_story: Set[int], coefficient: UnitStatusCoefficient) -> float: + unit_attribute = self.calc_unit_attribute(unit_data, read_story) + return unit_attribute.get_power(coefficient) + + def calc_skill_power(self, unit_data: UnitData, coefficient: UnitStatusCoefficient) -> float: + unit_rarity = unit_data.unit_rarity if not unit_data.battle_rarity else unit_data.battle_rarity + skill_power = 0 + for ub in unit_data.union_burst: + evolution = unit_rarity >= 6 + base = ub.skill_level + coef = coefficient.ub_evolution_slv_coefficient if evolution else 1 + extra = coefficient.ub_evolution_coefficient if evolution else 0 + skill_power += base * coef + extra + + for id, skill in enumerate(unit_data.main_skill): + evolution = len(unit_data.unique_equip_slot) > id and unit_data.unique_equip_slot[id].is_slot + base = skill.skill_level + coef = getattr(coefficient, f"skill{id+1}_evolution_slv_coefficient") if evolution else 1 + extra = getattr(coefficient, f"skill{id+1}_evolution_coefficient") if evolution else 0 + skill_power += base * coef + extra + + for ex in unit_data.ex_skill: + evolution = unit_rarity >= 5 + base = ex.skill_level + coef = 1 + extra = coefficient.exskill_evolution_coefficient if evolution else 0 + skill_power += base * coef + extra + + return skill_power * coefficient.skill_lv_coefficient + + def calc_unit_power(self, unit_data: UnitData, read_story: Set[int]) -> float: + coefficient = self.unit_status_coefficient[1] + attribute_power = self.calc_unit_attribute_power(unit_data, read_story, coefficient) + skill_power = self.calc_skill_power(unit_data, coefficient) + return attribute_power + skill_power + db = database() diff --git a/autopcr/db/methods.py b/autopcr/db/methods.py index 1b64dc9a..b3f0dd37 100644 --- a/autopcr/db/methods.py +++ b/autopcr/db/methods.py @@ -1,6 +1,6 @@ from typing import Iterator, Tuple, List from ..model.common import eInventoryType -from ..model.custom import ItemType +from ..model.custom import ItemType, UnitAttribute from . import models class Reward: @@ -16,6 +16,66 @@ def method(cls): setattr(base_cls, method_name, method_obj) return cls +@method +class PromotionBonus(models.PromotionBonus): + def get_unit_attribute(self) -> UnitAttribute: + return UnitAttribute.load(self) + +@method +class UnitPromotionStatus(models.UnitPromotionStatus): + def get_unit_attribute(self) -> UnitAttribute: + return UnitAttribute.load(self) + +@method +class UnitRarity(models.UnitRarity): + def get_unit_attribute(self) -> UnitAttribute: + return UnitAttribute.load(self) + + def get_unit_attribute_growth(self, level: int) -> UnitAttribute: + return UnitAttribute.load(self, suf='_growth') * level + +@method +class EquipmentDatum(models.EquipmentDatum): + def get_unit_attribute(self) -> UnitAttribute: + return UnitAttribute.load(self) + +@method +class EquipmentEnhanceRate(models.EquipmentEnhanceRate): + def get_unit_attribute(self, level: int) -> UnitAttribute: + return UnitAttribute.load(self) * level + +@method +class UniqueEquipmentDatum(models.UniqueEquipmentDatum): + def get_unit_attribute(self) -> UnitAttribute: + return UnitAttribute.load(self) + +@method +class UniqueEquipEnhanceRate(models.UniqueEquipEnhanceRate): + def get_unit_attribute(self, level: int) -> UnitAttribute: + rate = UnitAttribute.load(self) + if level < self.min_lv: + return rate * 0 + elif self.max_lv == -1: + return rate * (level - self.min_lv + 1) + else: + return rate * (min(self.max_lv, level) - self.min_lv + 1) + +@method +class CharaStoryStatus(models.CharaStoryStatus): + def get_unit_attribute(self) -> UnitAttribute: + ret = UnitAttribute() + for i in range(1, 5 + 1): + type = getattr(self, f"status_type_{i}") + value = getattr(self, f"status_rate_{i}") + ret.set_value(type, value) + return ret + + def get_effect_unit_ids(self) -> Iterator[int]: + for i in range(1, 20 + 1): + effect_unit_id = getattr(self, f"chara_id_{i}") + if effect_unit_id != 0: + yield effect_unit_id * 100 + 1 + @method class PrizegachaDatum(models.PrizegachaDatum): def get_prize_memory_id(self) -> Iterator[ItemType]: diff --git a/autopcr/model/custom.py b/autopcr/model/custom.py index 44665c82..a01820fd 100644 --- a/autopcr/model/custom.py +++ b/autopcr/model/custom.py @@ -1,11 +1,115 @@ from __future__ import annotations -from .enums import eInventoryType -from typing import List, Optional, Tuple, Counter as CounterType + +from ..db.models import UnitStatusCoefficient +from .enums import eInventoryType, eParamType +from typing import List, Optional, Tuple, Counter as CounterType, Union from collections import Counter from pydantic import BaseModel, Field +from dataclasses import dataclass +from decimal import ROUND_CEILING, Decimal, ROUND_HALF_UP ItemType = Tuple[eInventoryType, int] +@dataclass +class UnitAttribute: + # 浮点误差 + hp: Decimal = Decimal(0) + atk: Decimal = Decimal(0) + magic_str: Decimal = Decimal(0) + def_: Decimal = Decimal(0) + magic_def: Decimal = Decimal(0) + physical_critical: Decimal = Decimal(0) + magic_critical: Decimal = Decimal(0) + wave_hp_recovery: Decimal = Decimal(0) + wave_energy_recovery: Decimal = Decimal(0) + dodge: Decimal = Decimal(0) + physical_penetrate: Decimal = Decimal(0) + magic_penetrate: Decimal = Decimal(0) + life_steal: Decimal = Decimal(0) + hp_recovery_rate: Decimal = Decimal(0) + energy_recovery_rate: Decimal = Decimal(0) + energy_reduce_rate: Decimal = Decimal(0) + accuracy: Decimal = Decimal(0) + + index2name = { + eParamType.HP: "hp", + eParamType.ATK: "atk", + eParamType.MAGIC_ATK: "magic_str", + eParamType.DEF: "def_", + eParamType.MAGIC_DEF: "magic_def", + eParamType.PHYSICAL_CRITICAL: "physical_critical", + eParamType.MAGIC_CRITICAL: "magic_critical", + eParamType.WAVE_HP_RECOVERY: "wave_hp_recovery", + eParamType.WAVE_ENERGY_RECOVERY: "wave_energy_recovery", + eParamType.DODGE: "dodge", + eParamType.PHYSICAL_PENETRATE: "physical_penetrate", + eParamType.MAGIC_PENETRATE: "magic_penetrate", + eParamType.LIFE_STEAL: "life_steal", + eParamType.HP_RECOVERY_RATE: "hp_recovery_rate", + eParamType.ENERGY_RECOVERY_RATE: "energy_recovery_rate", + eParamType.ENERGY_REDUCE_RATE: "energy_reduce_rate", + eParamType.ACCURACY: "accuracy" + } + + def __add__(self, oth: UnitAttribute): + return UnitAttribute(**{key: getattr(self, key) + getattr(oth, key) for key in self.__annotations__}) + + def __iadd__(self, oth: UnitAttribute): + for key in self.__annotations__: + setattr(self, key, getattr(self, key) + getattr(oth, key)) + return self + + def __mul__(self, oth: Union[int, float, Decimal]): + if not isinstance(oth, Decimal): + oth = Decimal(str(oth)) + return UnitAttribute(**{key: getattr(self, key) * oth for key in self.__annotations__}) + + def round(self): + ret = UnitAttribute() + for key in self.__annotations__: + setattr(ret, key, getattr(self, key).quantize(Decimal(1), rounding=ROUND_HALF_UP)) + return ret + + def ceil(self): + ret = UnitAttribute() + for key in self.__annotations__: + setattr(ret, key, getattr(self, key).quantize(Decimal(1), rounding=ROUND_CEILING)) + return ret + + @staticmethod + def load(data: object, pre: str = '', suf: str = '') -> UnitAttribute: + ret = UnitAttribute() + for key in ret.__annotations__: + target = (pre + key.strip('_') + suf) if suf else key + setattr(ret, key, Decimal(str(getattr(data, target, 0)))) + return ret + + def set_value(self, type: int, value: Union[int, float, Decimal]): + if not isinstance(value, Decimal): + value = Decimal(str(value)) + if type in self.index2name: + setattr(self, self.index2name[type], value) + + def get_power(self, coefficient: UnitStatusCoefficient) -> float: + pow = self.hp.quantize(Decimal(1), rounding=ROUND_HALF_UP) * Decimal(coefficient.hp_coefficient) + pow += self.atk.quantize(Decimal(1), rounding=ROUND_HALF_UP) * Decimal(coefficient.atk_coefficient) + pow += self.magic_str.quantize(Decimal(1), rounding=ROUND_HALF_UP) * Decimal(coefficient.magic_str_coefficient) + pow += self.def_.quantize(Decimal(1), rounding=ROUND_HALF_UP) * Decimal(coefficient.def_coefficient) + pow += self.magic_def.quantize(Decimal(1), rounding=ROUND_HALF_UP) * Decimal(coefficient.magic_def_coefficient) + pow += self.physical_critical * Decimal(coefficient.physical_critical_coefficient) + pow += self.magic_critical * Decimal(coefficient.magic_critical_coefficient) + pow += self.dodge * Decimal(coefficient.dodge_coefficient) + pow += self.physical_penetrate * Decimal(coefficient.physical_penetrate_coefficient) + pow += self.magic_penetrate * Decimal(coefficient.magic_penetrate_coefficient) + pow += self.wave_hp_recovery * Decimal(coefficient.wave_hp_recovery_coefficient) + pow += self.wave_energy_recovery * Decimal(coefficient.wave_energy_recovery_coefficient) + pow += self.life_steal * Decimal(coefficient.life_steal_coefficient) + pow += self.hp_recovery_rate * Decimal(coefficient.hp_recovery_rate_coefficient) + pow += self.energy_recovery_rate * Decimal(coefficient.energy_recovery_rate_coefficient) + pow += self.energy_reduce_rate * Decimal(coefficient.energy_reduce_rate_coefficient) + pow += self.accuracy * Decimal(coefficient.accuracy_coefficient) + return float(pow) + class GachaReward(): reward_list: List = None new_unit: List = None diff --git a/autopcr/model/enums.py b/autopcr/model/enums.py index d7ac5453..9f6406cf 100644 --- a/autopcr/model/enums.py +++ b/autopcr/model/enums.py @@ -607,3 +607,23 @@ class eRoundEventResultType(IntEnum): FAILURE = 2 END = 3 +class eParamType(IntEnum): + NONE = 0 + HP = 1 + ATK = 2 + DEF = 3 + MAGIC_ATK = 4 + MAGIC_DEF = 5 + PHYSICAL_CRITICAL = 6 + MAGIC_CRITICAL = 7 + DODGE = 8 + LIFE_STEAL = 9 + WAVE_HP_RECOVERY = 10 + WAVE_ENERGY_RECOVERY = 11 + PHYSICAL_PENETRATE = 12 + MAGIC_PENETRATE = 13 + ENERGY_RECOVERY_RATE = 14 + HP_RECOVERY_RATE = 15 + ENERGY_REDUCE_RATE = 16 + ACCURACY = 17 + INVALID_VALUE = -1 diff --git a/autopcr/module/modules/daily.py b/autopcr/module/modules/daily.py index 0e64ce10..2cfa39c9 100644 --- a/autopcr/module/modules/daily.py +++ b/autopcr/module/modules/daily.py @@ -330,10 +330,13 @@ async def do_task(self, client: pcrclient): mana = client.data.gold.gold_id_free sweep_ticket = client.data.get_inventory((eInventoryType.Item, 23001)) pig = client.data.get_inventory((eInventoryType.Item, 90005)) + tot_power = sum([(await client.data.get_unit_power(unit)) for unit in client.data.unit]) if stamina >= max_stamina: self._warn(f"体力爆了!") - self._log(f"{name} 体力{stamina}({max_stamina}) 等级{level} 钻石{jewel} mana{mana} 扫荡券{sweep_ticket} 母猪石{pig}") - self._log(f"已购买体力数:{client.data.recover_stamina_exec_count}") + self._log(f"{name} 体力{stamina}({max_stamina}) 等级{level} 钻石{jewel}") + self._log(f"玛那{mana} 扫荡券{sweep_ticket} 母猪石{pig}") + self._log(f"全角色战力:{tot_power}") + self._log(f"已氪体数:{client.data.recover_stamina_exec_count}") self._log(f"清日常时间:{now}") From 3d762e2dedc48a6eecbaeea93e401be9948b9a2a Mon Sep 17 00:00:00 2001 From: Lanly109 <1094916227@qq.com> Date: Wed, 30 Oct 2024 19:31:35 +0800 Subject: [PATCH 02/14] fea: dispatch_solver --- autopcr/util/ilp_solver.py | 56 ++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/autopcr/util/ilp_solver.py b/autopcr/util/ilp_solver.py index 87281253..6b48d5c4 100644 --- a/autopcr/util/ilp_solver.py +++ b/autopcr/util/ilp_solver.py @@ -1,9 +1,17 @@ from typing import List, Tuple -from pulp import PULP_CBC_CMD, LpProblem, LpMinimize, LpStatus, LpVariable, lpSum, LpStatusOptimal, LpInteger, value +from pulp import PULP_CBC_CMD, LpProblem, LpMinimize, LpStatus, LpVariable, lpSum, LpStatusOptimal, LpInteger def ilp_solver(ub: List[int], target: int, limit: int, effect: List[int]) -> Tuple[bool, List[int]]: + ''' + 物品数量使用确定,使效果值尽可能小,处于给定上下界内 + :param ub: 物品数量上限 + :param target: 效果值下界 + :param limit: 效果值上界 + :param effect: 物品效果值 + :return: 是否有解,物品数量 + ''' prob = LpProblem(name='solve', sense=LpMinimize) - assert(len(ub) == len(effect)) + assert len(ub) == len(effect) n = len(ub) @@ -19,10 +27,46 @@ def ilp_solver(ub: List[int], target: int, limit: int, effect: List[int]) -> Tup result = {v.name: int(v.varValue) for v in prob.variables()} ret = [result[str(effect[i])] for i in range(n)] print(LpStatus[prob.status]) - print(ret) - print(effect) - print(target, limit, value(prob.objective)) + return prob.status == LpStatusOptimal, ret + +def dispatch_solver(start: List[int], candidate: List[int], choose: int) -> Tuple[bool, List[int]]: + ''' + 数字分配,使得不同组的数字和尽可能平均 + :param start: 组的初始数字和 + :param candidate: 候选数字 + :param choose: 每组选择的数字个数 + :return: 是否有解,数字分配结果 + ''' + def vname(i, j): + return f"x_{i}_{j}" + n = len(start) + m = len(candidate) + assert n * choose == m, "候选数需等于安排数" + prob = LpProblem(name='dispatch', sense=LpMinimize) + + x = [[LpVariable(vname(j, i), lowBound=0, upBound=1, cat=LpInteger) for i in range(n)] for j in range(m)] + min = LpVariable("min") + max = LpVariable("max") + + psum = [lpSum([x[i][j] * candidate[i] for i in range(m)]) + start[j] for j in range(n)] + prob += (max - min), "dispatch_power" + + for i in range(m): + prob += lpSum([x[i][j] for j in range(n)]) == 1, f"dispatch_once_{i}" + + for i in range(n): + prob += lpSum([x[j][i] for j in range(m)]) == choose, f"dispatch_{i}" + prob += max >= psum[i], f"max_{i}" + prob += min <= psum[i], f"min_{i}" + + prob.solve(PULP_CBC_CMD(msg=False, gapAbs = 200)) + result = {v.name: int(v.varValue) for v in prob.variables()} + ret = [next(i for i in range(n) if result[vname(j, i)] == 1) for j in range(m)] + print(LpStatus[prob.status]) return prob.status == LpStatusOptimal, ret if __name__ == '__main__': - ilp_solver([3, 1, 1], 100, 1000, [10, 20, 30]) + # ilp_solver([3, 1, 1], 100, 1000, [10, 20, 30]) + candidate = [61504,58688,58534,58201,57029,56686,56452,56348,56287,56238,56179,55966,55508,55117,55099,55039,54990,54661,54454,54400,54295,54055,53413,53290,53169,52944,52925,52235,51849,51713,51033,51026,50443,50233,49224,48957,48936,48890,48787,48603,48601,48600,48214,47903,47851,47507,47253,47129,46984,46881,46804,46765,46753,46600,46569,46567,46566,46476,46440,46297,45831,45808,45796,45783,45653,45626,45623,45560,45491,45472,45317,45206,45051,45038,44954,44741,44673,44601,44244,44108,43921,43838,43643,43532,43434,43259,43187,42857,42849,42791,42671,42534,42481,42309,42272,41880,41851,41755,41714,41481,41130,41034,40982,40966,40873,40686,40510,40188,39742,39522,39444,39153,38338,38107,37976,37776,37063,36895,36862,36653,35553,35303,34773,34740,34290,34198,33768,33102,32617,32519,31543,30843,30610,29648,29526,27902,27798,26901,26753,26283,26250,24512,23968,23779,22667,22347,22179,21796,21322,21019,20938,19611,19415,18988,16064,13391,12020,11845,4395,1721,1588,1443,1394,1384,1322,1316,1276,1233,1229,1223,1213,1206,1200,1180,1171,1162,1161,1146,1084,1083,725,689,685,628,628,621,601,601,599,594,498,497,496,465] + candidate = candidate[:24] + dispatch_solver([0,0,0], candidate, 8) From 01ec25dfd4f05b7dfd24c2f362a5b0839b71f202 Mon Sep 17 00:00:00 2001 From: Lanly109 <1094916227@qq.com> Date: Fri, 1 Nov 2024 00:22:33 +0800 Subject: [PATCH 03/14] fea: travel module --- autopcr/core/datamgr.py | 38 +++++++- autopcr/core/pcrclient.py | 49 ++++++++-- autopcr/db/database.py | 103 +++++++++++++++++++-- autopcr/db/methods.py | 10 +++ autopcr/model/handlers.py | 32 ++++++- autopcr/module/modules/__init__.py | 2 + autopcr/module/modules/autosweep.py | 24 +++-- autopcr/module/modules/sweep.py | 134 +++++++++++++++++++++++++++- autopcr/module/modules/tools.py | 118 +++++++++++++++++++++++- autopcr/module/modules/unit.py | 2 +- server.py | 5 ++ 11 files changed, 489 insertions(+), 28 deletions(-) diff --git a/autopcr/core/datamgr.py b/autopcr/core/datamgr.py index a05d2854..30e0d6b0 100644 --- a/autopcr/core/datamgr.py +++ b/autopcr/core/datamgr.py @@ -53,12 +53,14 @@ class datamgr(Component[apiclient]): dispatch_units: List[UnitDataForClanMember] = None event_sub_story: Dict[int, EventSubStory] = None user_gold_bank_info: UserBankGoldInfo = None + ex_equips: Dict[int, ExtraEquipInfo] = None def __init__(self): self.finishedQuest = set() self.hatsune_quest_dict = {} self._inventory = {} self.deck_list = {} + self.ex_equips = {} self.campaign_list = [] lck = Lock() @@ -253,8 +255,17 @@ def get_library_import_data(self) -> str: def _weight_mapper(cnt: int) -> float: return max(0, cnt) + max(0, cnt + 300) * .1 + max(0, cnt + 600) * .01 + max(0, cnt + 900) * .001 - def get_quest_weght(self, require_equip: typing.Counter[ItemType]) -> Dict[int, float]: # weight demand - + def get_ex_quest_weght(self, require_equip: typing.Counter[ItemType]) -> Dict[int, float]: + return ( + flow(db.travel_quest_data.items()) + .to_dict(lambda x: x[0], lambda x: + flow(x[1].get_rewards()) + .select(lambda y: require_equip.get(y.reward_item, 0)) + .sum() + ) + ) + + def get_quest_weght(self, require_equip: typing.Counter[ItemType]) -> Dict[int, float]: return ( flow(db.normal_quest_rewards.items()) .to_dict(lambda x: x[0], lambda x: @@ -322,6 +333,27 @@ def get_pure_memory_demand_gap(self) -> typing.Counter[ItemType]: # need -- >0 gap = self.get_demand_gap(demand, lambda x: db.is_unit_pure_memory(x)) return gap + def get_clan_ex_equip_demand(self, start_rank: Union[None, int] = None, like_unit_only: bool = False) -> typing.Counter[ItemType]: + ex_type: typing.Counter[int] = Counter() + for unit_id in self.unit: + if like_unit_only and not self.unit[unit_id].favorite_flag: + continue + if start_rank and self.unit[unit_id].promotion_level < start_rank: + continue + ex_type[db.unit_ex_equipment_slot[unit_id].slot_category_1] += 1 + ex_type[db.unit_ex_equipment_slot[unit_id].slot_category_2] += 1 + ex_type[db.unit_ex_equipment_slot[unit_id].slot_category_3] += 1 + result: typing.Counter[ItemType] = Counter({ + (eInventoryType.ExtraEquip, db.ex_equipment_type_to_clan_battle_ex[ex]): cnt + for ex, cnt in ex_type.items() + }) + return result + + def get_clan_ex_equip_gap(self, start_rank: Union[None, int] = None, like_unit_only: bool = False) -> typing.Counter[ItemType]: + demand = self.get_clan_ex_equip_demand(start_rank, like_unit_only) + gap = self.get_demand_gap(demand, lambda x: db.is_clan_ex_equip(x)) + return gap + def get_suixin_demand(self) -> Tuple[List[Tuple[ItemType, int]], int]: cnt = 0 result: List[Tuple[ItemType, int]] = [] @@ -388,6 +420,8 @@ def update_inventory(self, item: InventoryInfo): self.unit_love_data[unit_id].chara_id = unit_id self.unit_love_data[unit_id].chara_love = 0 self.unit_love_data[unit_id].love_level = 0 + elif item.type == eInventoryType.ExtraEquip: + self.ex_equips[item.id] = item.ex_equip else: self._inventory[token] = item.stock diff --git a/autopcr/core/pcrclient.py b/autopcr/core/pcrclient.py index 36917642..c97cee26 100644 --- a/autopcr/core/pcrclient.py +++ b/autopcr/core/pcrclient.py @@ -5,7 +5,7 @@ from .misc import errorhandler from .datamgr import datamgr from ..db.database import db -from typing import Tuple, Union +from typing import Callable, Tuple, Union import typing, math class pcrclient(apiclient): @@ -50,6 +50,45 @@ async def season_ticket_new_accept(self, season_id: int, mission_id: int): req.mission_id = mission_id return await self.request(req) + async def travel_top(self, travel_area_id: int, get_ex_equip_album_flag: int): + req = TravelTopRequest() + req.travel_area_id = travel_area_id + req.get_ex_equip_album_flag = get_ex_equip_album_flag + return await self.request(req) + + async def travel_start(self, start_travel_quest_list: List[TravelStartInfo], add_lap_travel_quest_list: List[TravelQuestAddLap], start_secret_travel_quest_list: List[SecretTravelStartInfo], action_type: eTravelStartType, current_currency_num: TravelCurrentCurrencyNum): + req = TravelStartRequest() + req.start_travel_quest_list = start_travel_quest_list + req.add_lap_travel_quest_list = add_lap_travel_quest_list + req.start_secret_travel_quest_list = start_secret_travel_quest_list + req.action_type = action_type + req.current_currency_num = current_currency_num + return await self.request(req) + + async def travel_receive_top_event_reward(self, top_event_appear_id: int, choice_number: int): + req = TravelReceiveTopEventRewardRequest() + req.top_event_appear_id = top_event_appear_id + req.choice_number = choice_number + return await self.request(req) + + async def travel_receive_all(self, ex_auto_recycle_option: TravelExtraEquipAutoRecycleOptionData): + req = TravelReceiveAllRequest() + req.ex_auto_recycle_option = ex_auto_recycle_option + return await self.request(req) + + async def travel_decrease_time(self, travel_quest_id: int, travel_id: int, decrease_time_item: TravelDecreaseItem, current_currency_num: TravelCurrentCurrencyNum): + req = TravelDecreaseTimeRequest() + req.travel_quest_id = travel_quest_id + req.travel_id = travel_id + req.decrease_time_item = decrease_time_item + req.current_currency_num = current_currency_num + return await self.request(req) + + async def travel_update_priority_unit_list(self, unit_id_list: List[int]): + req = TravelUpdatePriorityUnitListRequest() + req.unit_id_list = unit_id_list + return await self.request(req) + async def deck_update(self, deck_number: int, units: List[int]): req = DeckUpdateRequest() req.deck_number = deck_number @@ -746,11 +785,10 @@ async def serlize_gacha_reward(self, gacha: GachaReward, gacha_id: int = 0): return res - async def serlize_reward(self, reward_list: List[InventoryInfo], target: Union[ItemType, None] = None): - result = [] + async def serlize_reward(self, reward_list: List[InventoryInfo], target: Union[ItemType, None] = None, filter: Union[None, Callable[[ItemType],bool]] = None): rewards = {} for reward in reward_list: - if target is None or (reward.type == target[0] and reward.id == target[1]): + if target and (reward.type == target[0] and reward.id == target[1]) or filter and filter((reward.type, reward.id)) or not target and not filter: if (reward.id, reward.type) not in rewards: rewards[(reward.id, reward.type)] = [reward.count, reward.stock, reward] else: @@ -758,6 +796,7 @@ async def serlize_reward(self, reward_list: List[InventoryInfo], target: Union[I rewards[(reward.id, reward.type)][1] = max(reward.stock, rewards[(reward.id, reward.type)][1]) reward_item = list(rewards.values()) reward_item = sorted(reward_item, key = lambda x: x[0], reverse = True) + result = [] for value in reward_item: try: result.append(f"{db.get_inventory_name(value[2])}x{value[0]}({value[1]})") @@ -849,7 +888,7 @@ def set_stamina_recover_cnt(self, value: int): self.keys['stamina_recover_times'] = value async def quest_skip_aware(self, quest: int, times: int, recover: bool = False, is_total: bool = False): - name = db.quest_name[quest] if quest in db.quest_name else f"未知关卡{quest}" + name = db.get_quest_name(quest) if db.is_hatsune_quest(quest): event = db.quest_to_event[quest].event_id if not quest in self.data.hatsune_quest_dict[event]: diff --git a/autopcr/db/database.py b/autopcr/db/database.py index 973dd663..5250d4aa 100644 --- a/autopcr/db/database.py +++ b/autopcr/db/database.py @@ -19,6 +19,7 @@ class database(): xingqiubei: ItemType = (eInventoryType.Item, 25001) mana: ItemType = (eInventoryType.Gold, 94002) jewel: ItemType = (eInventoryType.Jewel, 91002) + travel_speed_up_paper: ItemType = (eInventoryType.Item, 23002) gacha_single_ticket: ItemType = (eInventoryType.Item, 24001) def update(self, dbmgr: dbmgr): @@ -395,14 +396,6 @@ def update(self, dbmgr: dbmgr): .to_dict(lambda x: x.quest_id, lambda x: x) ) - self.quest_name: Dict[int, str] = ( - QuestDatum.query(db) - .concat(HatsuneQuest.query(db)) - .concat(ShioriQuest.query(db)) - .concat(TrainingQuestDatum.query(db)) - .to_dict(lambda x: x.quest_id, lambda x: x.quest_name) - ) - self.chara_story_status: Dict[int, CharaStoryStatus] = ( CharaStoryStatus.query(db) .to_dict(lambda x: x.story_id, lambda x: x) @@ -516,6 +509,10 @@ def update(self, dbmgr: dbmgr): CustomMypage.query(db) .select(lambda x: (eInventoryType.CustomMypage, x.still_id, x.still_name)) ) + .concat( + ExEquipmentDatum.query(db) + .select(lambda x: (eInventoryType.ExtraEquip, x.ex_equipment_id, x.name)) + ) .to_dict(lambda x: (x[0], x[1]), lambda x: x[2]) ) @@ -548,6 +545,9 @@ def update(self, dbmgr: dbmgr): .group_by(lambda x: x.unit_material_id) .to_dict(lambda x: x.key, lambda x: x.first().unit_id) ) + self.unit_to_memory: Dict[int, int] = { + value: key for key, value in self.memory_to_unit.items() + } self.growth_parameter: Dict[int, GrowthParameter] = ( GrowthParameter.query(db) @@ -640,7 +640,6 @@ def update(self, dbmgr: dbmgr): self.love_cake: List[ItemDatum] = ( ItemDatum.query(db) .where(lambda x: x.item_id >= 50000 and x.item_id < 51000) - #.select(lambda x: (x.item_id, x.value)) .to_list() ) @@ -709,8 +708,56 @@ def update(self, dbmgr: dbmgr): .to_dict(lambda x: x.sub_story_id, lambda x: x) ) + self.ex_equipment_data: Dict[int, ExEquipmentDatum] = ( + ExEquipmentDatum.query(db) + .to_dict(lambda x: x.ex_equipment_id, lambda x: x) + ) + + self.unit_ex_equipment_slot: Dict[int, UnitExEquipmentSlot] = ( + UnitExEquipmentSlot.query(db) + .to_dict(lambda x: x.unit_id, lambda x: x) + ) + + self.ex_equipment_type_to_clan_battle_ex: Dict[int, int] = { # 只有每个类别的会战装备 + ex.category: ex.ex_equipment_id for ex in self.ex_equipment_data.values() if ex.clan_battle_equip_flag == 1 and ex.rarity == 3 + } + + self.ex_event_data: Dict[int, TravelExEventDatum] = ( + TravelExEventDatum.query(db) + .to_dict(lambda x: x.still_id, lambda x: x) + ) + + self.travel_area_data: Dict[int, TravelAreaDatum] = ( + TravelAreaDatum.query(db) + .to_dict(lambda x: x.travel_area_id, lambda x: x) + ) + + self.travel_quest_data: Dict[int, TravelQuestDatum] = ( + TravelQuestDatum.query(db) + .to_dict(lambda x: x.travel_quest_id, lambda x: x) + ) + + self.quest_name: Dict[int, str] = ( + QuestDatum.query(db) + .concat(HatsuneQuest.query(db)) + .concat(ShioriQuest.query(db)) + .concat(TrainingQuestDatum.query(db)) + .to_dict(lambda x: x.quest_id, lambda x: x.quest_name) + ) + self.quest_name.update( + {x.travel_quest_id :x.travel_quest_name for x in self.travel_quest_data.values()} + ) + + self.ex_rarity_name = { + 1: '铜装', + 2: '银装', + 3: '金装', + 4: '粉装' + } def get_inventory_name(self, item: InventoryInfo) -> str: try: + if item.type == eInventoryType.ExtraEquip: + return f"{self.ex_rarity_name[self.ex_equipment_data[item.id].rarity]}-" + self.inventory_name[(item.type, item.id)] return self.inventory_name[(item.type, item.id)] except: return f"未知物品({item.id})" @@ -745,9 +792,26 @@ def get_room_item_name(self, item_id: int) -> str: except: return f"未知房间物品({item_id})" + def get_quest_name(self, quest_id: int) -> str: + try: + if quest_id in self.travel_quest_data: + area = self.travel_quest_data[quest_id].travel_area_id % 10 + quest = self.travel_quest_data[quest_id].travel_quest_id % 10 + return f"{area}-{quest}图" + else: + return self.quest_name[quest_id] + except: + return f"未知关卡({quest_id})" + def is_daily_mission(self, mission_id: int) -> bool: return mission_id in self.daily_mission_data + def is_ex_equip(self, item: ItemType) -> bool: + return item[0] == eInventoryType.ExtraEquip + + def is_clan_ex_equip(self, item: ItemType) -> bool: + return item[0] == eInventoryType.ExtraEquip and self.ex_equipment_data[item[1]].clan_battle_equip_flag == 1 + def is_exp_upper(self, item: ItemType) -> bool: return item[0] == eInventoryType.Item and item[1] >= 20000 and item[1] < 21000 @@ -997,6 +1061,12 @@ def format_date(self, time: datetime.datetime) -> str: def format_time_safe(self, time: datetime.datetime) -> str: return time.strftime("%Y%m%d%H%M%S") + def format_second(self, total_seconds: int) -> str: + time_delta = datetime.timedelta(seconds=total_seconds) + hours, remainder = divmod(time_delta.total_seconds(), 3600) + minutes, seconds = divmod(remainder, 60) + return f"{int(hours)}:{int(minutes):02}:{int(seconds):02}" + def get_start_time(self, time: datetime.datetime) -> datetime.datetime: shift_time = datetime.timedelta(hours = 5); @@ -1108,6 +1178,13 @@ def get_unique_equip_pt_from_level(self, equip_slot: int, level: int): pt = self.unique_equipment_enhance_data[equip_slot][level].total_point if level in self.unique_equipment_enhance_data[equip_slot] else 0 return pt + def get_open_travel_area(self) -> List[int]: + return (flow(self.travel_area_data.values()) + .where(lambda x: self.is_target_time([(db.parse_time(x.start_time), db.parse_time(x.end_time))])) + .select(lambda x: x.travel_area_id) + .to_list() + ) + def deck_sort_unit(self, units: List[int]): return sorted(units, key=lambda x: self.unit_data[x].search_area_width if x in self.unit_data else 9999) @@ -1151,6 +1228,11 @@ def last_normal_quest_candidate(self): .select(lambda x: f"{x.quest_id}: {x.quest_name.split(' ')[1]}") \ .to_list() + def travel_quest_candidate(self): + return flow(self.travel_quest_data.values()) \ + .select(lambda x: f"{x.travel_quest_id}: {x.travel_quest_name}") \ + .to_list() + def get_gacha_prize_name(self, gacha_id: int, prize_rarity: int) -> str: if gacha_id in self.prizegacha_sp_data: prize_rarity = self.prizegacha_sp_data[gacha_id][prize_rarity].disp_rarity @@ -1240,4 +1322,7 @@ def calc_unit_power(self, unit_data: UnitData, read_story: Set[int]) -> float: skill_power = self.calc_skill_power(unit_data, coefficient) return attribute_power + skill_power + def calc_travel_once_time(self, power: int) -> int: + return max(12 * 60 * 60 - (power - 100000 + 199) // 200 * 3, 10 * 60 * 60) + db = database() diff --git a/autopcr/db/methods.py b/autopcr/db/methods.py index b3f0dd37..b6661c83 100644 --- a/autopcr/db/methods.py +++ b/autopcr/db/methods.py @@ -94,6 +94,7 @@ def get_rewards(self) -> Iterator[Reward]: yield Reward(self.reward_type_4, self.reward_id_4, self.reward_num_4, self.odds_4) yield Reward(self.reward_type_5, self.reward_id_5, self.reward_num_5, self.odds_5) + @method class EquipmentCraft(models.EquipmentCraft): def get_materials(self) -> Iterator[Tuple[ItemType, int]]: @@ -123,3 +124,12 @@ def get_drop_reward_ids(self) -> Iterator[int]: yield self.drop_reward_id_3 yield self.drop_reward_id_4 yield self.drop_reward_id_5 + +@method +class TravelQuestDatum(models.TravelQuestDatum): + def get_rewards(self) -> Iterator[Reward]: + yield Reward(eInventoryType.ExtraEquip, self.main_reward_1, 1, 1) + yield Reward(eInventoryType.ExtraEquip, self.main_reward_2, 1, 1) + yield Reward(eInventoryType.ExtraEquip, self.main_reward_3, 1, 1) + yield Reward(eInventoryType.ExtraEquip, self.main_reward_4, 1, 1) + yield Reward(eInventoryType.ExtraEquip, self.main_reward_5, 1, 1) diff --git a/autopcr/model/handlers.py b/autopcr/model/handlers.py index 69e044d6..183d98be 100644 --- a/autopcr/model/handlers.py +++ b/autopcr/model/handlers.py @@ -6,8 +6,8 @@ from ..core.datamgr import datamgr from ..db.database import db from .enums import eEventSubStoryStatus -from pydantic.fields import ModelField from typing import Optional +import json as json_module def handles(cls): cls.__base__.update = cls.update @@ -282,6 +282,8 @@ async def update(self, mgr: datamgr, request): if self.user_equip: for inv in self.user_equip: mgr.update_inventory(inv) + if self.user_ex_equip: + mgr.ex_equips = {equip.serial_id: equip for equip in self.user_ex_equip} mgr.unit = {unit.id: unit for unit in self.unit_list} if self.unit_list else {} mgr.unit_love_data = {unit.chara_id: unit for unit in self.user_chara_info} if self.user_chara_info else {} mgr.growth_unit = {unit.unit_id: unit for unit in self.growth_unit_list} if self.growth_unit_list else {} @@ -682,7 +684,35 @@ async def update(self, mgr: datamgr, request): for unit in self.unit_data_list: mgr.unit[unit.id] = unit +@handles +class TravelReceiveAllResponse(responses.TravelReceiveAllResponse): + async def update(self, mgr: datamgr, request): + if self.user_gold: + mgr.gold = self.user_gold + for result in self.travel_result: + for item in result.reward_list: + mgr.update_inventory(item) + +@handles +class TravelReceiveTopEventRewardResponse(responses.TravelReceiveTopEventRewardResponse): + async def update(self, mgr: datamgr, request): + if self.user_gold: + mgr.gold = self.user_gold + if self.user_jewel: + mgr.jewel = self.user_jewel + if self.stamina_info: + mgr.stamina = self.stamina_info.user_stamina + for item in self.reward_list: + mgr.update_inventory(item) + # 菜 就别玩 +def custom_dict(self, *args, **kwargs): + original_dict = super(TravelStartRequest, self).dict(*args, **kwargs) + if self.action_type is not None: + original_dict['action_type'] = {"value__": self.action_type.value} + return original_dict +TravelStartRequest.dict = custom_dict + HatsuneTopResponse.__annotations__['event_status'] = HatsuneEventStatus HatsuneTopResponse.__fields__['event_status'].type_ = Optional[HatsuneEventStatus] HatsuneTopResponse.__fields__['event_status'].outer_type_ = Optional[HatsuneEventStatus] diff --git a/autopcr/module/modules/__init__.py b/autopcr/module/modules/__init__.py index 4d54b897..2c2930b4 100644 --- a/autopcr/module/modules/__init__.py +++ b/autopcr/module/modules/__init__.py @@ -48,6 +48,7 @@ class ModuleList: room_accept_all, explore_exp, explore_mana, + travel_quest_sweep, underground_skip, special_underground_skip, tower_cloister_sweep, @@ -128,6 +129,7 @@ class ModuleList: 'tool', [ # cook_pudding, + travel_team_view, unit_promote, unit_set_unique_equip_growth, missing_unit, diff --git a/autopcr/module/modules/autosweep.py b/autopcr/module/modules/autosweep.py index 5e7e72ad..889f32ce 100644 --- a/autopcr/module/modules/autosweep.py +++ b/autopcr/module/modules/autosweep.py @@ -105,12 +105,12 @@ async def do_task(self, client: pcrclient): raise finally: if clean_cnt: - msg = '\n'.join((db.quest_name[quest] if quest in db.quest_name else f"未知关卡{quest}") + + msg = '\n'.join(db.get_quest_name(quest) + f": 刷取{cnt}次" for quest, cnt in clean_cnt.items()) self._log(msg) self._log("---------") if tmp: - self._log(await client.serlize_reward(tmp)) + self._log(await client.serlize_reward(tmp, filter=lambda x: db.is_equip(x))) class simple_demand_sweep_base(Module): @@ -118,6 +118,8 @@ class simple_demand_sweep_base(Module): async def get_need_list(self, client: pcrclient) -> List[Tuple[ItemType, int]]: ... def get_need_quest(self, token: ItemType) -> List[QuestDatum]: ... def get_max_times(self, client: pcrclient, quest_id: int) -> int: ... + def filter_reward_func(self) -> Callable[[ItemType], bool]: + return lambda x: True async def do_task(self, client: pcrclient): @@ -152,12 +154,12 @@ async def do_task(self, client: pcrclient): raise finally: if clean_cnt: - msg = '\n'.join((db.quest_name[quest] if quest in db.quest_name else f"未知关卡{quest}") + + msg = '\n'.join(db.get_quest_name(quest) + f": 刷取{cnt}次" for quest, cnt in clean_cnt.items()) self._log(msg) self._log("---------") if tmp: - self._log(await client.serlize_reward(tmp)) + self._log(await client.serlize_reward(tmp, filter=self.filter_reward_func())) if not self.log and not self.warn: self._log("需刷取的图均无次数") raise SkipError() @@ -185,6 +187,9 @@ async def get_need_list(self, client: pcrclient) -> List[Tuple[ItemType, int]]: return need_list + def filter_reward_func(self) -> Callable[[ItemType], bool]: + return lambda x: db.is_unit_memory(x) + def get_need_quest(self, token: ItemType) -> List[QuestDatum]: return db.memory_hard_quest.get(token, []) @@ -211,6 +216,9 @@ async def get_need_list(self, client: pcrclient) -> List[Tuple[ItemType, int]]: return need_list + def filter_reward_func(self) -> Callable[[ItemType], bool]: + return lambda x: db.is_unit_memory(x) + def get_need_quest(self, token: ItemType) -> List[ShioriQuest]: return db.memory_shiori_quest.get(token, []) @@ -261,6 +269,9 @@ async def get_need_list(self, client: pcrclient) -> List[Tuple[ItemType, int]]: raise SkipError("所有纯净碎片均已盈余") return need_list + def filter_reward_func(self) -> Callable[[ItemType], bool]: + return lambda x: db.is_unit_pure_memory(x) + def get_need_quest(self, token: ItemType) -> List[QuestDatum]: return db.pure_memory_quest.get(token, []) @@ -284,6 +295,9 @@ async def get_need_list(self, client: pcrclient) -> List[Tuple[ItemType, int]]: return need_list + def filter_reward_func(self) -> Callable[[ItemType], bool]: + return lambda x: db.is_unit_pure_memory(x) + def get_need_quest(self, token: ItemType) -> List[QuestDatum]: return db.pure_memory_quest.get(token, []) @@ -332,7 +346,7 @@ def _sweep(): break if clean_cnt: - msg = '\n'.join((db.quest_name[quest] if quest in db.quest_name else f"未知关卡{quest}") + + msg = '\n'.join(db.get_quest_name(quest) + f": 刷取{cnt}次" for quest, cnt in clean_cnt.items()) self._log(msg) self._log("---------") diff --git a/autopcr/module/modules/sweep.py b/autopcr/module/modules/sweep.py index 0e29d670..8a714f7a 100644 --- a/autopcr/module/modules/sweep.py +++ b/autopcr/module/modules/sweep.py @@ -7,7 +7,137 @@ from ...model.enums import * from ...model.custom import ItemType from ...util.linq import flow -import datetime +from ...model.common import InventoryInfo, TravelAppearEventData, TravelCurrentCurrencyNum, TravelDecreaseItem, TravelExtraEquipAutoRecycleOptionData, TravelQuestAddLap, TravelQuestInfo, TravelStartInfo +import time +import random +from collections import Counter + + +@description(''' +仅支持阅读特殊事件+续派遣次数到最大派遣数。 +装备事件为 60%三金装40%一金装 或 100%二金装 选项 +代币事件为 30%1000代币70%200代币 或 100%400代币 选项 +赌狗策略为前者,保守策略为后者 +'''.strip()) +@singlechoice('travel_quest_gold_event_strategy', "代币事件策略", ['随机'], ['保守','赌狗','随机']) +@singlechoice('travel_quest_equip_event_strategy', "装备事件策略", ['随机'], ['保守','赌狗','随机']) +@inttype('travel_quest_max_round', "最大派遣数", 3, [2,3,4,5]) +@name("探险") +@default(True) +class travel_quest_sweep(Module): + def can_receive_count(self, quest: TravelQuestInfo) -> int: + st = quest.travel_start_time + ed = quest.travel_end_time + decrease_time = quest.decrease_time + once_time = db.calc_travel_once_time(quest.total_power) + now = int(time.time()) + received_count = quest.received_count + cnt = (min(now, ed) - st + decrease_time) // once_time - received_count + return cnt + + def quest_still_round(self, quest: TravelQuestInfo) -> int: + return quest.total_lap_count - quest.received_count + + def get_choice(self, strategy: str) -> int: + if strategy == '赌狗': + return 1 + elif strategy == '保守': + return 2 + else: + return random.randint(1, 2) + + async def do_task(self, client: pcrclient): + travel_quest_max_round: int = int(self.get_config("travel_quest_max_round")) + travel_quest_equip_event_strategy: str = self.get_config("travel_quest_equip_event_strategy") + travel_quest_gold_event_strategy: str = self.get_config("travel_quest_gold_event_strategy") + reward: List[InventoryInfo] = [] + + def get_strategy(event_id: int) -> str: + if event_id == 4007: + return travel_quest_equip_event_strategy + elif event_id == 4009: + return travel_quest_gold_event_strategy + else: + raise ValueError(f"未知可选项事件{event_id}") + + top = await client.travel_top(max(db.get_open_travel_area()), 1) + + if len(top.travel_quest_list) < 3: + self._warn(f"正在探险的小队数为{len(top.travel_quest_list)}<3,请上号开始新的探险!") + + if top.top_event_list: + for top_event in top.top_event_list: + try: + chooice = 0 + if top_event.top_event_choice_flag: + strategy = get_strategy(top_event.top_event_id) + chooice = self.get_choice(strategy) + result = await client.travel_receive_top_event_reward(top_event.top_event_appear_id, chooice) + reward.extend(result.reward_list) + except Exception as e: + self._warn(f"处理特殊事件{top_event.top_event_id}失败:{e}") + self._log(f"阅读{len(top.top_event_list)}个特殊事件") + result_count = {} + reward2: List[InventoryInfo] = [] + if any(self.can_receive_count(quest) for quest in top.travel_quest_list): + option = TravelExtraEquipAutoRecycleOptionData(rarity=[], frame=[], category=[]) + result = await client.travel_receive_all(option) + secret_travel: List[TravelAppearEventData] = [] + for quest in result.travel_result: + reward2.extend(quest.reward_list) + for event in quest.appear_event_list or []: + reward2.extend(event.reward_list) + secret_travel.append(event) + + result_count = Counter([quest.travel_quest_id for quest in result.travel_result]) + msg = '探索了:' + ' '.join(f"{db.get_quest_name(quest)} x{cnt}" for quest, cnt in result_count.items()) + self._log(msg) + if secret_travel: + msg = '触发了秘密探险:' + ' '.join(db.ex_event_data[event.still_id].title for event in secret_travel) + self._log(msg) + + if reward or reward2: + self._log(f"获得了:") + msg = (await client.serlize_reward(reward)).strip('无') + if msg: self._log(msg) + msg = (await client.serlize_reward(reward2, filter=lambda x: db.is_ex_equip(x) or db.is_unit_memory(x))).strip('无') + if msg: self._log(msg) + self._log("") + + add_lap_travel_quest_list: List[TravelQuestAddLap] = [] + add_lap_travel_quest_id: List[int] = [] + start_travel_quest_list: List[TravelStartInfo] = [] + for quest in top.travel_quest_list: + quest.received_count += result_count.get(quest.travel_quest_id, 0) + quest_still_round = self.quest_still_round(quest) + if not quest_still_round: # restart + start_item = TravelStartInfo( + travel_quest_id = quest.travel_quest_id, + travel_deck = quest.travel_deck, + decrease_time_item = TravelDecreaseItem(jewel = 0, item = 0), + total_lap_count = travel_quest_max_round, + ) + start_travel_quest_list.append(start_item) + else: + append_round = travel_quest_max_round - quest_still_round + if append_round > 0: + add_item = TravelQuestAddLap(travel_id = quest.travel_id, add_lap_count = append_round) + add_lap_travel_quest_list.append(add_item) + add_lap_travel_quest_id.append(quest.travel_quest_id) + + if start_travel_quest_list or add_lap_travel_quest_list: + jewel = client.data.jewel.free_jewel + client.data.jewel.jewel + speed_up_paper = client.data.get_inventory(db.travel_speed_up_paper) + msg = '\n'.join(f"继续派遣{db.get_quest_name(quest_id)} x{quest.add_lap_count}" for quest_id, quest in zip(add_lap_travel_quest_id, add_lap_travel_quest_list)) + msg += '\n'.join(f"重新派遣{db.get_quest_name(quest.travel_quest_id)} x{quest.total_lap_count}" for quest in start_travel_quest_list) + self._log(msg) + action_type = eTravelStartType.ADD_LAP if add_lap_travel_quest_list else eTravelStartType.RESTART + if start_travel_quest_list and add_lap_travel_quest_list: + action_type = eTravelStartType.RESTART_AND_ADD + await client.travel_start(start_travel_quest_list, add_lap_travel_quest_list, [], action_type, TravelCurrentCurrencyNum(jewel = jewel, item=speed_up_paper)) + + if not self.log and not reward: + raise SkipError("探险仍在继续...") class explore_sweep(Module): @abstractmethod @@ -26,7 +156,7 @@ async def do_task(self, client: pcrclient): max_quest = self.get_max_quest(client) if self.not_max_stop() and max_quest != quest_id: raise AbortError(f"最高级探索{max_quest}未通关,不扫荡\n如欲扫荡已通关的,请关闭【非最高不扫荡】") - name = db.quest_name[quest_id] + name = db.get_quest_name(quest_id) await client.training_quest_skip(quest_id, remain_cnt) self._log(f"{name}扫荡{remain_cnt}次") else: diff --git a/autopcr/module/modules/tools.py b/autopcr/module/modules/tools.py index 225fc990..0cdc0a44 100644 --- a/autopcr/module/modules/tools.py +++ b/autopcr/module/modules/tools.py @@ -1,9 +1,11 @@ from typing import List, Set -from ...model.common import ChangeRarityUnit, DeckListData, GrandArenaHistoryDetailInfo, GrandArenaHistoryInfo, GrandArenaSearchOpponent, ProfileUserInfo, RankingSearchOpponent, VersusResult, VersusResultDetail +from ...util.ilp_solver import dispatch_solver + +from ...model.common import ChangeRarityUnit, DeckListData, GrandArenaHistoryDetailInfo, GrandArenaHistoryInfo, GrandArenaSearchOpponent, ProfileUserInfo, RankingSearchOpponent, TravelCurrentCurrencyNum, TravelDecreaseItem, TravelStartInfo, VersusResult, VersusResultDetail from ...model.responses import PsyTopResponse from ...db.models import GachaExchangeLineup -from ...model.custom import ArenaQueryResult, ArenaRegion, GachaReward, ItemType +from ...model.custom import ArenaQueryResult, GachaReward, ItemType from ..modulebase import * from ..config import * from ...core.pcrclient import pcrclient @@ -15,6 +17,116 @@ import random from collections import Counter +@name('计算探险编队') +@default(True) +@booltype('travel_team_view_go', '探险出发', False) +@inttype('travel_team_view_go_cnt', '出发次数', 3, [1,2,3,4,5]) +@multichoice('travel_team_view_quest_id', '探险任务', [], db.travel_quest_candidate) +@booltype('travel_team_view_auto_memory', '自动设置记忆碎片', True) +@description('根据设定的记忆碎片优先级,从剩余可派遣角色中自动计算战力平衡编队,自动设置记忆碎片指记忆碎片优先度不足够派出队伍时,根据盈亏情况补充') +class travel_team_view(Module): + async def do_task(self, client: pcrclient): + travel_team_auto_memory = self.get_config('travel_team_view_auto_memory') + travel_team_go = self.get_config('travel_team_view_go') + travel_team_go_cnt: int = int(self.get_config('travel_team_view_go_cnt')) + travel_quest_id_raw: List[str] = self.get_config('travel_team_view_quest_id') + travel_quest_id = [int(x.split(':')[0]) for x in travel_quest_id_raw] + + top = await client.travel_top(max(db.get_open_travel_area()), 1) + unit_list = top.priority_unit_list + + memory_gap = client.data.get_memory_demand_gap() + if unit_list: + memory_need = [] + for unit in unit_list: + token = (eInventoryType.Item, db.unit_to_memory[unit]) + memory_need.append((token, memory_gap[token])) + msg = '\n'.join([f'{db.get_inventory_name_san(item[0])}: {"缺少" if item[1] > 0 else "盈余"}{abs(item[1])}片' for item in memory_need]) + self._log(f"记忆碎片优先级的盈缺情况:\n{msg}") + self._log("----") + + if top.travel_quest_list: + self._log('当前派遣区域:') + for quest in top.travel_quest_list: + import time + now = int(time.time()) + leave_time = int(quest.travel_end_time - quest.decrease_time - now) + self._log(f"{db.get_quest_name(quest.travel_quest_id)} -{db.format_second(leave_time)}") + if quest.travel_quest_id in travel_quest_id: travel_quest_id.remove(quest.travel_quest_id) + + teams_go = 3 - len(top.travel_quest_list) + if not teams_go: + raise AbortError("已经派遣了3支队伍") + memory_unit = 3 * teams_go + + forbid_unit = [] + for quest in top.travel_quest_list: + forbid_unit.extend(quest.travel_deck) + forbid_unit = set(forbid_unit) + + unit_list = [unit for unit in unit_list if unit not in forbid_unit][:memory_unit] + if len(unit_list) != memory_unit: + if not travel_team_auto_memory: + raise AbortError(f"设置的未派遣的记忆碎片优先级的角色不足{memory_unit}个,请前往游戏里设置更多角色") + else: + leave = memory_unit - len(unit_list) + new_unit = sorted( + [unit_id for unit_id in client.data.unit if unit_id not in unit_list and unit_id not in forbid_unit], + key=lambda x: memory_gap[(eInventoryType.Item, db.unit_to_memory[x])], + reverse=True)[:leave] + msg = f"设置的未派遣的记忆碎片优先级的角色不足{memory_unit}个,将根据盈亏情况补充以下角色:\n" + msg += ' '.join(f"{db.get_unit_name(unit)}" for unit in new_unit) + self._log(msg) + unit_list.extend(new_unit) + await client.travel_update_priority_unit_list(unit_list) + + unit_power = {unit: await client.data.get_unit_power(unit) for unit in client.data.unit} + unit_list.sort(key=lambda x: unit_power[x], reverse=True) + + teams = [ + unit_list[st::3] for st in range(teams_go) + ] + + start_power = [sum(unit_power[unit] for unit in teams[i]) for i in range(teams_go)] + candidate_unit_id = sorted([unit_id for unit_id in unit_power if unit_id not in unit_list and unit_id not in forbid_unit], key=lambda x: unit_power[x], reverse=True)[:teams_go * 7] + candidate_unit_power = [unit_power[unit] for unit in candidate_unit_id] + + ret, sol = dispatch_solver(start_power, candidate_unit_power, 7) + if not ret: + raise AbortError("无解!这不可能!") + for pos, unit in zip(sol, candidate_unit_id): + teams[pos].append(unit) + + teams_power = [sum(unit_power[unit] for unit in teams[i]) for i in range(teams_go)] + + for id, (team, power) in enumerate(zip(teams, teams_power), start=1): + time = db.format_second(db.calc_travel_once_time(power)) + self._log(f"第{id}队({time})总战力{power}=" + '+'.join(f"{unit_power[unit]}" for unit in team)) + self._log(' '.join(f"{db.get_unit_name(unit)}" for unit in team)) + + if travel_team_go: + self._log('----') + if len(travel_quest_id) != teams_go: + raise AbortError(f"队伍数量{teams_go}与派遣图数{travel_quest_id}不匹配") + + start_travel_quest_list: List[TravelStartInfo] = [] + for id, (team, quest) in enumerate(zip(teams, travel_quest_id), start = 1): + start_item = TravelStartInfo( + travel_quest_id = quest, + travel_deck = team, + decrease_time_item = TravelDecreaseItem(jewel = 0, item = 0), + total_lap_count = travel_team_go_cnt, + ) + start_travel_quest_list.append(start_item) + + jewel = client.data.jewel.free_jewel + client.data.jewel.jewel + speed_up_paper = client.data.get_inventory(db.travel_speed_up_paper) + action_type = eTravelStartType.NORMAL + msg = '\n'.join(f"派遣第{id}队到{db.get_quest_name(quest.travel_quest_id)}x{quest.total_lap_count}" for id, quest in enumerate(start_travel_quest_list, start = 1)) + self._log(msg) + await client.travel_start(start_travel_quest_list, [], [], action_type, TravelCurrentCurrencyNum(jewel = jewel, item=speed_up_paper)) + + @name('【活动限时】一键做布丁') @default(True) @description('一键做+吃布丁,直到清空你的材料库存。
顺便还能把剧情也看了。') @@ -652,7 +764,7 @@ async def do_task(self, client: pcrclient): tot = [] for i in range(10): id = quest_id[i] - name = db.quest_name[id] + name = db.get_quest_name(id) tokens: List[ItemType] = [i for i in db.normal_quest_rewards[id]] msg = f"{name}:\n" + '\n'.join([ (f'{db.get_inventory_name_san(token)}: {"缺少" if require_equip[token] > 0 else "盈余"}{abs(require_equip[token])}片') diff --git a/autopcr/module/modules/unit.py b/autopcr/module/modules/unit.py index dff3912d..80062cce 100644 --- a/autopcr/module/modules/unit.py +++ b/autopcr/module/modules/unit.py @@ -412,7 +412,7 @@ async def do_task(self, client: pcrclient): self.unit_id = int(unit_id) await self.set_unique_growth_unit() -@description('支持全部角色,装备星级-1表示不穿装备,自动拉等级指当前等级不足以穿装备或提升技能等级,将会提升角色等级') +@description('支持全部角色,装备星级-1表示不穿装备,自动拉等级指当前等级不足以穿装备或提升技能等级,将会提升角色等级,自动拉品级指当前品级不足以装备专武时,会提升角色品级') @name('拉角色练度') @booltype("unit_promote_rank_when_fail_to_unique_equip", "自动拉品级", False) @booltype("unit_promote_level_when_fail_to_equip_or_skill", "自动拉等级", False) diff --git a/server.py b/server.py index babc3d16..7169719a 100644 --- a/server.py +++ b/server.py @@ -44,6 +44,7 @@ - {prefix}日常报告 [0|1|2|3] 最近四次清日常报告 - {prefix}定时日志 查看定时运行状态 - {prefix}查缺角色 查看缺少的限定常驻角色 +- {prefix}查探险编队 根据记忆碎片角色编队战力相当的队伍 - {prefix}查心碎 查询缺口心碎 - {prefix}查纯净碎片 查询缺口纯净碎片,国服六星+日服二专需求 - {prefix}查记忆碎片 [可刷取|大师币] 查询缺口记忆碎片,可按地图可刷取或大师币商店过滤 @@ -787,6 +788,10 @@ async def pjjc_shuffle_team(botev: BotEvent): async def find_missing_unit(botev: BotEvent): return {} +@register_tool("查探险编队", "travel_team_view") +async def find_travel_team_view(botev: BotEvent): + return {} + # @register_tool("获取导入", "get_library_import_data") # async def get_library_import(botev: BotEvent): # return {} From 9567fea7bd21ac9132236a9c69ef6a88d669013d Mon Sep 17 00:00:00 2001 From: cc004 <1176321897@qq.com> Date: Fri, 1 Nov 2024 23:14:24 +0800 Subject: [PATCH 04/14] add module rounded auto travel --- autopcr/core/pcrclient.py | 13 ++++- autopcr/module/modules/__init__.py | 1 + autopcr/module/modules/sweep.py | 77 ++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/autopcr/core/pcrclient.py b/autopcr/core/pcrclient.py index c97cee26..14f2f48e 100644 --- a/autopcr/core/pcrclient.py +++ b/autopcr/core/pcrclient.py @@ -76,14 +76,23 @@ async def travel_receive_all(self, ex_auto_recycle_option: TravelExtraEquipAutoR req.ex_auto_recycle_option = ex_auto_recycle_option return await self.request(req) - async def travel_decrease_time(self, travel_quest_id: int, travel_id: int, decrease_time_item: TravelDecreaseItem, current_currency_num: TravelCurrentCurrencyNum): + async def travel_decrease_time(self, travel_quest_id: int, travel_id: int, decrease_time_item: TravelDecreaseItem): req = TravelDecreaseTimeRequest() req.travel_quest_id = travel_quest_id req.travel_id = travel_id req.decrease_time_item = decrease_time_item - req.current_currency_num = current_currency_num + req.current_currency_num = TravelCurrentCurrencyNum(jewel = self.data.jewel.free_jewel + self.data.jewel.jewel, item = self.data.get_inventory(db.travel_speed_up_paper)) return await self.request(req) + async def travel_retire(self, travel_quest: TravelQuestInfo, ex_auto_recycle_option: TravelExtraEquipAutoRecycleOptionData | None = None): + if ex_auto_recycle_option is None: + ex_auto_recycle_option = TravelExtraEquipAutoRecycleOptionData(rarity=[], frame=[], category=[]) + req = TravelRetireRequest() + req.travel_quest_id = travel_quest.travel_quest_id + req.travel_id = travel_quest.travel_id + req.ex_auto_recycle_option = ex_auto_recycle_option + return await self.request(req) + async def travel_update_priority_unit_list(self, unit_id_list: List[int]): req = TravelUpdatePriorityUnitListRequest() req.unit_id_list = unit_id_list diff --git a/autopcr/module/modules/__init__.py b/autopcr/module/modules/__init__.py index 2c2930b4..cc592d2f 100644 --- a/autopcr/module/modules/__init__.py +++ b/autopcr/module/modules/__init__.py @@ -49,6 +49,7 @@ class ModuleList: explore_exp, explore_mana, travel_quest_sweep, + auto_travel, underground_skip, special_underground_skip, tower_cloister_sweep, diff --git a/autopcr/module/modules/sweep.py b/autopcr/module/modules/sweep.py index 8a714f7a..f277bebc 100644 --- a/autopcr/module/modules/sweep.py +++ b/autopcr/module/modules/sweep.py @@ -11,6 +11,7 @@ import time import random from collections import Counter +import math @description(''' @@ -450,3 +451,79 @@ def quest_id(self) -> int: class starcup1_sweep(starcup_sweep): def quest_id(self) -> int: return 19001001 + +@multichoice("travel_target_quest1", "轮转目标1", [1], [1, 2, 3, 4, 5]) +@multichoice("travel_target_quest2", "轮转目标2", [4], [1, 2, 3, 4, 5]) +@multichoice("travel_target_quest3", "轮转目标3", [2, 3, 5], [1, 2, 3, 4, 5]) +@inttype("travel_target_day", "轮转时间", 7, [2, 3, 5, 7, 10]) +@name('自动探险') +@description(''' +自动根据轮转进行探险,按轮转时间进行目标切换。 +警告:可能会引起不定期上号,不建议使用。 +'''.strip()) +@default(False) +class auto_travel(Module): + def today_targets(self) -> List[int]: + n = time.localtime().tm_yday // int(self.get_config("travel_target_day")) + def get_team(lst, n): return lst[n % len(lst)] + return [ + get_team(self.get_config("travel_target_quest1"), n), + get_team(self.get_config("travel_target_quest2"), n), + get_team(self.get_config("travel_target_quest3"), n) + ] + async def do_task(self, client: pcrclient): + area_id = max(db.get_open_travel_area()) + top = await client.travel_top(area_id, 1) + targets = set( + area_id * 1000 + x for x in self.today_targets() + ) + + now_targets = { + quest.travel_quest_id: quest for quest in top.travel_quest_list + } + + to_delete = {k: v for k, v in now_targets.items() if k not in targets} + to_add = targets - set(now_targets.keys()) + + start_infos = [] + + for add_quest_id, (remove_quest_id, remove_quest) in zip(to_add, to_delete.items()): + quest_interval = (remove_quest.travel_end_time - remove_quest.travel_start_time) / remove_quest.total_lap_count + for i in range(remove_quest.total_lap_count): + if remove_quest.travel_start_time + i * quest_interval > client.server_time: + break + + delta_time = remove_quest.travel_start_time + i * quest_interval - client.server_time + ticket_to_use = math.ceil(delta_time / client.data.settings.travel.decrease_time_by_ticket) + if ticket_to_use > client.data.get_inventory(db.travel_speed_up_paper): + raise AbortError(f"没有足够的加速券,无法切换目标") + if ticket_to_use > top.remain_daily_decrease_count_ticket: + raise AbortError(f"本日剩余加速券不足,无法切换目标") + if top.remain_daily_retire_count <= 0: + raise AbortError("本日剩余撤退次数不足,无法切换目标") + + if remove_quest: + self._log(f"结束探险{remove_quest_id}") + + await client.travel_decrease_time(remove_quest.travel_quest_id, remove_quest.travel_id, TravelDecreaseItem(jewel = 0, item = ticket_to_use)) + await client.travel_retire(remove_quest) + + top.remain_daily_decrease_count_ticket -= ticket_to_use + top.remain_daily_retire_count -= 1 + + start_infos.append( + TravelStartInfo( + travel_quest_id = add_quest_id, + travel_deck = remove_quest.travel_deck, + decrease_time_item = TravelDecreaseItem(jewel = 0, item = 0), + total_lap_count = client.data.settings.travel.travel_quest_max_repeat_count + ) + ) + + await client.travel_start( + start_infos, + [], + [], + eTravelStartType.RESTART, + TravelCurrentCurrencyNum(jewel = client.data.jewel.free_jewel + client.data.jewel.jewel, item = client.data.get_inventory(db.travel_speed_up_paper)) + ) \ No newline at end of file From 9a5b04b40b56846b067bfdd385760f8eec7574dc Mon Sep 17 00:00:00 2001 From: cc004 <1176321897@qq.com> Date: Sat, 2 Nov 2024 01:55:17 +0800 Subject: [PATCH 05/14] enhance auto travel description --- autopcr/module/modules/sweep.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/autopcr/module/modules/sweep.py b/autopcr/module/modules/sweep.py index f277bebc..24c7f3d8 100644 --- a/autopcr/module/modules/sweep.py +++ b/autopcr/module/modules/sweep.py @@ -458,8 +458,7 @@ def quest_id(self) -> int: @inttype("travel_target_day", "轮转时间", 7, [2, 3, 5, 7, 10]) @name('自动探险') @description(''' -自动根据轮转进行探险,按轮转时间进行目标切换。 -警告:可能会引起不定期上号,不建议使用。 +自动根据轮转进行探险,按轮转时间进行目标切换,切换时自动用券进行尾轮加速。 '''.strip()) @default(False) class auto_travel(Module): From 385ec5c63972c44462f081065fad84e85a5a4459 Mon Sep 17 00:00:00 2001 From: Lanly109 <1094916227@qq.com> Date: Sat, 2 Nov 2024 17:53:10 +0800 Subject: [PATCH 06/14] fix: travel_round optimize --- autopcr/core/pcrclient.py | 22 ++-- autopcr/db/database.py | 7 +- autopcr/model/handlers.py | 28 +++++ autopcr/module/config.py | 5 +- autopcr/module/modulebase.py | 1 + autopcr/module/modules/__init__.py | 4 +- autopcr/module/modules/autosweep.py | 2 +- autopcr/module/modules/room.py | 2 +- autopcr/module/modules/sweep.py | 154 ++++++++++++++++------------ autopcr/module/modules/tools.py | 14 +-- 10 files changed, 154 insertions(+), 85 deletions(-) diff --git a/autopcr/core/pcrclient.py b/autopcr/core/pcrclient.py index 14f2f48e..c5f4182c 100644 --- a/autopcr/core/pcrclient.py +++ b/autopcr/core/pcrclient.py @@ -56,13 +56,13 @@ async def travel_top(self, travel_area_id: int, get_ex_equip_album_flag: int): req.get_ex_equip_album_flag = get_ex_equip_album_flag return await self.request(req) - async def travel_start(self, start_travel_quest_list: List[TravelStartInfo], add_lap_travel_quest_list: List[TravelQuestAddLap], start_secret_travel_quest_list: List[SecretTravelStartInfo], action_type: eTravelStartType, current_currency_num: TravelCurrentCurrencyNum): + async def travel_start(self, start_travel_quest_list: List[TravelStartInfo], add_lap_travel_quest_list: List[TravelQuestAddLap], start_secret_travel_quest_list: List[SecretTravelStartInfo], action_type: eTravelStartType): req = TravelStartRequest() req.start_travel_quest_list = start_travel_quest_list req.add_lap_travel_quest_list = add_lap_travel_quest_list req.start_secret_travel_quest_list = start_secret_travel_quest_list req.action_type = action_type - req.current_currency_num = current_currency_num + req.current_currency_num = TravelCurrentCurrencyNum(jewel = self.data.jewel.free_jewel + self.data.jewel.jewel, item = self.data.get_inventory(db.travel_speed_up_paper)) return await self.request(req) async def travel_receive_top_event_reward(self, top_event_appear_id: int, choice_number: int): @@ -71,7 +71,9 @@ async def travel_receive_top_event_reward(self, top_event_appear_id: int, choice req.choice_number = choice_number return await self.request(req) - async def travel_receive_all(self, ex_auto_recycle_option: TravelExtraEquipAutoRecycleOptionData): + async def travel_receive_all(self, ex_auto_recycle_option: Union[TravelExtraEquipAutoRecycleOptionData, None] = None): + if ex_auto_recycle_option is None: + ex_auto_recycle_option = TravelExtraEquipAutoRecycleOptionData(rarity=[], frame=[], category=[]) req = TravelReceiveAllRequest() req.ex_auto_recycle_option = ex_auto_recycle_option return await self.request(req) @@ -84,12 +86,20 @@ async def travel_decrease_time(self, travel_quest_id: int, travel_id: int, decre req.current_currency_num = TravelCurrentCurrencyNum(jewel = self.data.jewel.free_jewel + self.data.jewel.jewel, item = self.data.get_inventory(db.travel_speed_up_paper)) return await self.request(req) - async def travel_retire(self, travel_quest: TravelQuestInfo, ex_auto_recycle_option: TravelExtraEquipAutoRecycleOptionData | None = None): + async def travel_receive(self, travel_id: int, ex_auto_recycle_option: Union[TravelExtraEquipAutoRecycleOptionData, None] = None): + if ex_auto_recycle_option is None: + ex_auto_recycle_option = TravelExtraEquipAutoRecycleOptionData(rarity=[], frame=[], category=[]) + req = TravelReceiveRequest() + req.travel_quest_id = travel_id + req.ex_auto_recycle_option = ex_auto_recycle_option + return await self.request(req) + + async def travel_retire(self, travel_quest_id: int, travel_id: int, ex_auto_recycle_option: Union[TravelExtraEquipAutoRecycleOptionData, None] = None): if ex_auto_recycle_option is None: ex_auto_recycle_option = TravelExtraEquipAutoRecycleOptionData(rarity=[], frame=[], category=[]) req = TravelRetireRequest() - req.travel_quest_id = travel_quest.travel_quest_id - req.travel_id = travel_quest.travel_id + req.travel_quest_id = travel_quest_id + req.travel_id = travel_id req.ex_auto_recycle_option = ex_auto_recycle_option return await self.request(req) diff --git a/autopcr/db/database.py b/autopcr/db/database.py index 5250d4aa..05a68684 100644 --- a/autopcr/db/database.py +++ b/autopcr/db/database.py @@ -1230,9 +1230,14 @@ def last_normal_quest_candidate(self): def travel_quest_candidate(self): return flow(self.travel_quest_data.values()) \ - .select(lambda x: f"{x.travel_quest_id}: {x.travel_quest_name}") \ + .select(lambda x: f"{x.travel_area_id % 10}-{x.travel_quest_id % 10}") \ .to_list() + def get_travel_quest_id_from_candidate(self, candidate: str): + area, quest = candidate.split('-') + ret = next(x.travel_quest_id for x in self.travel_quest_data.values() if x.travel_area_id % 10 == int(area) and x.travel_quest_id % 10 == int(quest)) + return ret + def get_gacha_prize_name(self, gacha_id: int, prize_rarity: int) -> str: if gacha_id in self.prizegacha_sp_data: prize_rarity = self.prizegacha_sp_data[gacha_id][prize_rarity].disp_rarity diff --git a/autopcr/model/handlers.py b/autopcr/model/handlers.py index 183d98be..7020df71 100644 --- a/autopcr/model/handlers.py +++ b/autopcr/model/handlers.py @@ -693,6 +693,34 @@ async def update(self, mgr: datamgr, request): for item in result.reward_list: mgr.update_inventory(item) +@handles +class TravelReceiveResponse(responses.TravelReceiveResponse): + async def update(self, mgr: datamgr, request): + if self.user_gold: + mgr.gold = self.user_gold + for result in self.travel_result: + for item in result.reward_list: + mgr.update_inventory(item) + +@handles +class TravelRetireResponse(responses.TravelRetireResponse): + async def update(self, mgr: datamgr, request): + if self.user_gold: + mgr.gold = self.user_gold + if self.travel_result: + for result in self.travel_result: + for item in result.reward_list: + mgr.update_inventory(item) + +@handles +class TravelDecreaseTimeResponse(responses.TravelDecreaseTimeResponse): + async def update(self, mgr: datamgr, request): + if self.item_list: + for item in self.item_list: + mgr.update_inventory(item) + if self.user_jewel: + mgr.jewel = self.user_jewel + @handles class TravelReceiveTopEventRewardResponse(responses.TravelReceiveTopEventRewardResponse): async def update(self, mgr: datamgr, request): diff --git a/autopcr/module/config.py b/autopcr/module/config.py index 715353be..3e78c22b 100644 --- a/autopcr/module/config.py +++ b/autopcr/module/config.py @@ -30,7 +30,10 @@ def default(self): else: candidates = self._candidates() ret = candidates[0] if candidates else "" - return ret if self.config_type != "multi" else [] + if self.config_type == 'multi': + ret = [item for item in candidates if item in self._default] + return ret + def dict(self) -> dict: ret = { diff --git a/autopcr/module/modulebase.py b/autopcr/module/modulebase.py index f143fccb..b08a08de 100644 --- a/autopcr/module/modulebase.py +++ b/autopcr/module/modulebase.py @@ -249,6 +249,7 @@ def _log(self, msg: str): self.log.append(msg) def _warn(self, msg: str): + self.log.append(msg) self.warn.append(msg) def _abort(self, msg: str = ""): diff --git a/autopcr/module/modules/__init__.py b/autopcr/module/modules/__init__.py index cc592d2f..f551e844 100644 --- a/autopcr/module/modules/__init__.py +++ b/autopcr/module/modules/__init__.py @@ -46,10 +46,10 @@ class ModuleList: normal_gacha, monthly_gacha, room_accept_all, + travel_round, + travel_quest_sweep, explore_exp, explore_mana, - travel_quest_sweep, - auto_travel, underground_skip, special_underground_skip, tower_cloister_sweep, diff --git a/autopcr/module/modules/autosweep.py b/autopcr/module/modules/autosweep.py index 889f32ce..0100c89e 100644 --- a/autopcr/module/modules/autosweep.py +++ b/autopcr/module/modules/autosweep.py @@ -160,7 +160,7 @@ async def do_task(self, client: pcrclient): self._log("---------") if tmp: self._log(await client.serlize_reward(tmp, filter=self.filter_reward_func())) - if not self.log and not self.warn: + if not self.log: self._log("需刷取的图均无次数") raise SkipError() diff --git a/autopcr/module/modules/room.py b/autopcr/module/modules/room.py index 3af6568b..caaafc6f 100644 --- a/autopcr/module/modules/room.py +++ b/autopcr/module/modules/room.py @@ -42,7 +42,7 @@ async def do_task(self, client: pcrclient): self._log(f"开始升级{db.get_room_item_name(x.room_item_id)}至{x.room_item_level + 1}级") await client.room_level_up_item(floors[x.serial_id], x) - if not self.log and not self.warn: + if not self.log: raise SkipError('没有可升级的家园物品。') @description('先回赞,再随机点赞') diff --git a/autopcr/module/modules/sweep.py b/autopcr/module/modules/sweep.py index 24c7f3d8..3399fb68 100644 --- a/autopcr/module/modules/sweep.py +++ b/autopcr/module/modules/sweep.py @@ -7,7 +7,7 @@ from ...model.enums import * from ...model.custom import ItemType from ...util.linq import flow -from ...model.common import InventoryInfo, TravelAppearEventData, TravelCurrentCurrencyNum, TravelDecreaseItem, TravelExtraEquipAutoRecycleOptionData, TravelQuestAddLap, TravelQuestInfo, TravelStartInfo +from ...model.common import InventoryInfo, TravelAppearEventData, TravelDecreaseItem, TravelQuestAddLap, TravelQuestInfo, TravelStartInfo import time import random from collections import Counter @@ -20,9 +20,8 @@ 代币事件为 30%1000代币70%200代币 或 100%400代币 选项 赌狗策略为前者,保守策略为后者 '''.strip()) -@singlechoice('travel_quest_gold_event_strategy', "代币事件策略", ['随机'], ['保守','赌狗','随机']) -@singlechoice('travel_quest_equip_event_strategy', "装备事件策略", ['随机'], ['保守','赌狗','随机']) -@inttype('travel_quest_max_round', "最大派遣数", 3, [2,3,4,5]) +@singlechoice('travel_quest_gold_event_strategy', "代币事件策略", '赌狗', ['保守','赌狗','随机']) +@singlechoice('travel_quest_equip_event_strategy', "装备事件策略", '赌狗', ['保守','赌狗','随机']) @name("探险") @default(True) class travel_quest_sweep(Module): @@ -48,7 +47,6 @@ def get_choice(self, strategy: str) -> int: return random.randint(1, 2) async def do_task(self, client: pcrclient): - travel_quest_max_round: int = int(self.get_config("travel_quest_max_round")) travel_quest_equip_event_strategy: str = self.get_config("travel_quest_equip_event_strategy") travel_quest_gold_event_strategy: str = self.get_config("travel_quest_gold_event_strategy") reward: List[InventoryInfo] = [] @@ -81,8 +79,7 @@ def get_strategy(event_id: int) -> str: result_count = {} reward2: List[InventoryInfo] = [] if any(self.can_receive_count(quest) for quest in top.travel_quest_list): - option = TravelExtraEquipAutoRecycleOptionData(rarity=[], frame=[], category=[]) - result = await client.travel_receive_all(option) + result = await client.travel_receive_all() secret_travel: List[TravelAppearEventData] = [] for quest in result.travel_result: reward2.extend(quest.reward_list) @@ -120,24 +117,22 @@ def get_strategy(event_id: int) -> str: ) start_travel_quest_list.append(start_item) else: - append_round = travel_quest_max_round - quest_still_round + append_round = client.data.settings.travel.travel_quest_max_repeat_count - quest_still_round if append_round > 0: add_item = TravelQuestAddLap(travel_id = quest.travel_id, add_lap_count = append_round) add_lap_travel_quest_list.append(add_item) add_lap_travel_quest_id.append(quest.travel_quest_id) if start_travel_quest_list or add_lap_travel_quest_list: - jewel = client.data.jewel.free_jewel + client.data.jewel.jewel - speed_up_paper = client.data.get_inventory(db.travel_speed_up_paper) msg = '\n'.join(f"继续派遣{db.get_quest_name(quest_id)} x{quest.add_lap_count}" for quest_id, quest in zip(add_lap_travel_quest_id, add_lap_travel_quest_list)) msg += '\n'.join(f"重新派遣{db.get_quest_name(quest.travel_quest_id)} x{quest.total_lap_count}" for quest in start_travel_quest_list) self._log(msg) action_type = eTravelStartType.ADD_LAP if add_lap_travel_quest_list else eTravelStartType.RESTART if start_travel_quest_list and add_lap_travel_quest_list: action_type = eTravelStartType.RESTART_AND_ADD - await client.travel_start(start_travel_quest_list, add_lap_travel_quest_list, [], action_type, TravelCurrentCurrencyNum(jewel = jewel, item=speed_up_paper)) + await client.travel_start(start_travel_quest_list, add_lap_travel_quest_list, [], action_type) - if not self.log and not reward: + if not self.log: raise SkipError("探险仍在继续...") class explore_sweep(Module): @@ -452,63 +447,91 @@ class starcup1_sweep(starcup_sweep): def quest_id(self) -> int: return 19001001 -@multichoice("travel_target_quest1", "轮转目标1", [1], [1, 2, 3, 4, 5]) -@multichoice("travel_target_quest2", "轮转目标2", [4], [1, 2, 3, 4, 5]) -@multichoice("travel_target_quest3", "轮转目标3", [2, 3, 5], [1, 2, 3, 4, 5]) -@inttype("travel_target_day", "轮转时间", 7, [2, 3, 5, 7, 10]) -@name('自动探险') +@inttype("travel_speed_up_paper_threshold", "加速阈值", 6, list(range(12))) +@inttype("travel_target_day", "轮转天数", 7, [2, 3, 5, 7, 10]) +@multichoice("travel_target_quest3", "轮转目标3", ['1-2','1-3','1-5'], db.travel_quest_candidate) +@multichoice("travel_target_quest2", "轮转目标2", ['1-4'], db.travel_quest_candidate) +@multichoice("travel_target_quest1", "轮转目标1", ['1-1'], db.travel_quest_candidate) +@name('探险轮转') @description(''' -自动根据轮转进行探险,按轮转时间进行目标切换,切换时自动用券进行尾轮加速。 +自动根据轮转进行探险,按轮转时间进行目标切换,需保持三支队探险。 +切换时若一轮剩余时间小于阈值且可加速时则加速,否则直接撤退。 '''.strip()) -@default(False) -class auto_travel(Module): +@default(True) +class travel_round(Module): + def is_finish_quest(self, quest: TravelQuestInfo) -> bool: + now = int(time.time()) + return now >= quest.travel_end_time - quest.decrease_time + def today_targets(self) -> List[int]: n = time.localtime().tm_yday // int(self.get_config("travel_target_day")) - def get_team(lst, n): return lst[n % len(lst)] + def get_quest_id(lst: List, n): return db.get_travel_quest_id_from_candidate(lst[n % len(lst)]) return [ - get_team(self.get_config("travel_target_quest1"), n), - get_team(self.get_config("travel_target_quest2"), n), - get_team(self.get_config("travel_target_quest3"), n) + get_quest_id(self.get_config("travel_target_quest1"), n), + get_quest_id(self.get_config("travel_target_quest2"), n), + get_quest_id(self.get_config("travel_target_quest3"), n) ] async def do_task(self, client: pcrclient): - area_id = max(db.get_open_travel_area()) - top = await client.travel_top(area_id, 1) - targets = set( - area_id * 1000 + x for x in self.today_targets() - ) - - now_targets = { + top = await client.travel_top(max(db.get_open_travel_area()), 1) + now_quest = { quest.travel_quest_id: quest for quest in top.travel_quest_list } + if len(now_quest) != 3: + raise AbortError(f"当前探险队数为{len(now_quest)}<3,不支持轮换!") - to_delete = {k: v for k, v in now_targets.items() if k not in targets} - to_add = targets - set(now_targets.keys()) - - start_infos = [] + travel_speed_up_paper_threshold = int(self.get_config("travel_speed_up_paper_threshold")) + target_quest = set(self.today_targets()) + self._log(f"当前探险为{', '.join(db.get_quest_name(quest) for quest in now_quest)}") + self._log(f"今日目标为{', '.join(db.get_quest_name(quest) for quest in target_quest)}") + to_delete = {k: v for k, v in now_quest.items() if k not in target_quest} + to_add = target_quest - set(now_quest.keys()) + + start_infos = [] + reward = [] for add_quest_id, (remove_quest_id, remove_quest) in zip(to_add, to_delete.items()): + self._log(f"{db.get_quest_name(remove_quest_id)}->{db.get_quest_name(add_quest_id)}") quest_interval = (remove_quest.travel_end_time - remove_quest.travel_start_time) / remove_quest.total_lap_count - for i in range(remove_quest.total_lap_count): - if remove_quest.travel_start_time + i * quest_interval > client.server_time: - break - - delta_time = remove_quest.travel_start_time + i * quest_interval - client.server_time - ticket_to_use = math.ceil(delta_time / client.data.settings.travel.decrease_time_by_ticket) - if ticket_to_use > client.data.get_inventory(db.travel_speed_up_paper): - raise AbortError(f"没有足够的加速券,无法切换目标") - if ticket_to_use > top.remain_daily_decrease_count_ticket: - raise AbortError(f"本日剩余加速券不足,无法切换目标") - if top.remain_daily_retire_count <= 0: - raise AbortError("本日剩余撤退次数不足,无法切换目标") - - if remove_quest: - self._log(f"结束探险{remove_quest_id}") - - await client.travel_decrease_time(remove_quest.travel_quest_id, remove_quest.travel_id, TravelDecreaseItem(jewel = 0, item = ticket_to_use)) - await client.travel_retire(remove_quest) - - top.remain_daily_decrease_count_ticket -= ticket_to_use - top.remain_daily_retire_count -= 1 + next_loop = next(i for i in range(remove_quest.received_count, remove_quest.total_lap_count + 1) + if remove_quest.travel_start_time + i * quest_interval - remove_quest.decrease_time > client.server_time) + + if next_loop < remove_quest.total_lap_count: + delta_time = int(remove_quest.travel_start_time + remove_quest.decrease_time + next_loop * quest_interval - client.server_time) + ticket_to_use = int(math.ceil(delta_time / client.data.settings.travel.decrease_time_by_ticket)) + if ticket_to_use < travel_speed_up_paper_threshold: + self._log(f"一轮剩余时间{db.format_second(delta_time)}小于使用阈值{travel_speed_up_paper_threshold}小时,加速一轮") + if ticket_to_use > client.data.get_inventory(db.travel_speed_up_paper): + ticket_to_use = 0 + self._warn(f"没有足够的加速券,无法加速") + if ticket_to_use > top.remain_daily_decrease_count_ticket: + ticket_to_use = 0 + self._warn(f"本日使用加速券次数不足,无法加速") + else: + ticket_to_use = 0 + self._log(f"一轮剩余{db.format_second(delta_time)}大于阈值{travel_speed_up_paper_threshold}小时,直接撤退") + + if top.remain_daily_retire_count <= 0: + self._warn(f"本日已无撤退次数,无法{db.get_quest_name(remove_quest_id)}->{db.get_quest_name(add_quest_id)}") + continue + + if ticket_to_use: + self._log(f"{db.get_quest_name(remove_quest_id)}使用加速券x{ticket_to_use}") + ret = await client.travel_decrease_time(remove_quest.travel_quest_id, remove_quest.travel_id, TravelDecreaseItem(jewel = 0, item = ticket_to_use)) + top.remain_daily_decrease_count_ticket = ret.remain_daily_decrease_count_ticket + remove_quest.decrease_time += ticket_to_use * client.data.settings.travel.decrease_time_by_ticket + + if self.is_finish_quest(remove_quest): + self._log(f"{db.get_quest_name(remove_quest_id)}完成") + ret = await client.travel_receive(remove_quest.travel_id) + for result in ret.travel_result: + reward.extend(result.reward_list) + else: + self._log(f"{db.get_quest_name(remove_quest_id)}撤退") + ret = await client.travel_retire(remove_quest.travel_quest_id, remove_quest.travel_id) + top.remain_daily_retire_count = ret.remain_daily_retire_count + if ret.travel_result: + for result in ret.travel_result: + reward.extend(result.reward_list) start_infos.append( TravelStartInfo( @@ -518,11 +541,14 @@ async def do_task(self, client: pcrclient): total_lap_count = client.data.settings.travel.travel_quest_max_repeat_count ) ) - - await client.travel_start( - start_infos, - [], - [], - eTravelStartType.RESTART, - TravelCurrentCurrencyNum(jewel = client.data.jewel.free_jewel + client.data.jewel.jewel, item = client.data.get_inventory(db.travel_speed_up_paper)) - ) \ No newline at end of file + + if reward: + self._log(f"获得了:") + msg = (await client.serlize_reward(reward, filter=lambda x: db.is_ex_equip(x) or db.is_unit_memory(x))) + if msg: self._log(msg) + self._log("") + + if start_infos: + msg = '\n'.join(f"派遣{db.get_quest_name(quest.travel_quest_id)} x{quest.total_lap_count}" for quest in start_infos) + self._log(msg) + await client.travel_start(start_infos, [], [], eTravelStartType.NORMAL) diff --git a/autopcr/module/modules/tools.py b/autopcr/module/modules/tools.py index 0cdc0a44..406ef189 100644 --- a/autopcr/module/modules/tools.py +++ b/autopcr/module/modules/tools.py @@ -2,7 +2,7 @@ from ...util.ilp_solver import dispatch_solver -from ...model.common import ChangeRarityUnit, DeckListData, GrandArenaHistoryDetailInfo, GrandArenaHistoryInfo, GrandArenaSearchOpponent, ProfileUserInfo, RankingSearchOpponent, TravelCurrentCurrencyNum, TravelDecreaseItem, TravelStartInfo, VersusResult, VersusResultDetail +from ...model.common import ChangeRarityUnit, DeckListData, GrandArenaHistoryDetailInfo, GrandArenaHistoryInfo, GrandArenaSearchOpponent, ProfileUserInfo, RankingSearchOpponent, TravelDecreaseItem, TravelStartInfo, VersusResult, VersusResultDetail from ...model.responses import PsyTopResponse from ...db.models import GachaExchangeLineup from ...model.custom import ArenaQueryResult, GachaReward, ItemType @@ -20,17 +20,15 @@ @name('计算探险编队') @default(True) @booltype('travel_team_view_go', '探险出发', False) -@inttype('travel_team_view_go_cnt', '出发次数', 3, [1,2,3,4,5]) @multichoice('travel_team_view_quest_id', '探险任务', [], db.travel_quest_candidate) @booltype('travel_team_view_auto_memory', '自动设置记忆碎片', True) -@description('根据设定的记忆碎片优先级,从剩余可派遣角色中自动计算战力平衡编队,自动设置记忆碎片指记忆碎片优先度不足够派出队伍时,根据盈亏情况补充') +@description('根据设定的记忆碎片优先级,从剩余可派遣角色中自动计算战力平衡编队,自动设置记忆碎片指记忆碎片优先度不足够派出队伍时,根据盈亏情况补充,探险出发指以计算出的编队出发') class travel_team_view(Module): async def do_task(self, client: pcrclient): travel_team_auto_memory = self.get_config('travel_team_view_auto_memory') travel_team_go = self.get_config('travel_team_view_go') - travel_team_go_cnt: int = int(self.get_config('travel_team_view_go_cnt')) travel_quest_id_raw: List[str] = self.get_config('travel_team_view_quest_id') - travel_quest_id = [int(x.split(':')[0]) for x in travel_quest_id_raw] + travel_quest_id = [db.get_travel_quest_id_from_candidate(x) for x in travel_quest_id_raw] top = await client.travel_top(max(db.get_open_travel_area()), 1) unit_list = top.priority_unit_list @@ -115,16 +113,14 @@ async def do_task(self, client: pcrclient): travel_quest_id = quest, travel_deck = team, decrease_time_item = TravelDecreaseItem(jewel = 0, item = 0), - total_lap_count = travel_team_go_cnt, + total_lap_count = client.data.settings.travel.travel_quest_max_repeat_count, ) start_travel_quest_list.append(start_item) - jewel = client.data.jewel.free_jewel + client.data.jewel.jewel - speed_up_paper = client.data.get_inventory(db.travel_speed_up_paper) action_type = eTravelStartType.NORMAL msg = '\n'.join(f"派遣第{id}队到{db.get_quest_name(quest.travel_quest_id)}x{quest.total_lap_count}" for id, quest in enumerate(start_travel_quest_list, start = 1)) self._log(msg) - await client.travel_start(start_travel_quest_list, [], [], action_type, TravelCurrentCurrencyNum(jewel = jewel, item=speed_up_paper)) + await client.travel_start(start_travel_quest_list, [], [], action_type) @name('【活动限时】一键做布丁') From 2aa3251af22a348fe42d2d5ec76ebd4c521adacf Mon Sep 17 00:00:00 2001 From: Lanly109 <1094916227@qq.com> Date: Sat, 2 Nov 2024 18:33:29 +0800 Subject: [PATCH 07/14] fix: overlapping options error --- autopcr/module/modules/sweep.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/autopcr/module/modules/sweep.py b/autopcr/module/modules/sweep.py index 3399fb68..4b451c39 100644 --- a/autopcr/module/modules/sweep.py +++ b/autopcr/module/modules/sweep.py @@ -22,7 +22,7 @@ '''.strip()) @singlechoice('travel_quest_gold_event_strategy', "代币事件策略", '赌狗', ['保守','赌狗','随机']) @singlechoice('travel_quest_equip_event_strategy', "装备事件策略", '赌狗', ['保守','赌狗','随机']) -@name("探险") +@name("探险续航") @default(True) class travel_quest_sweep(Module): def can_receive_count(self, quest: TravelQuestInfo) -> int: @@ -88,7 +88,7 @@ def get_strategy(event_id: int) -> str: secret_travel.append(event) result_count = Counter([quest.travel_quest_id for quest in result.travel_result]) - msg = '探索了:' + ' '.join(f"{db.get_quest_name(quest)} x{cnt}" for quest, cnt in result_count.items()) + msg = '探索了' + ' '.join(f"{db.get_quest_name(quest)}{cnt}次" for quest, cnt in result_count.items()) self._log(msg) if secret_travel: msg = '触发了秘密探险:' + ' '.join(db.ex_event_data[event.still_id].title for event in secret_travel) @@ -464,12 +464,18 @@ def is_finish_quest(self, quest: TravelQuestInfo) -> bool: return now >= quest.travel_end_time - quest.decrease_time def today_targets(self) -> List[int]: + target_quest1: List[str] = self.get_config("travel_target_quest1") + target_quest2: List[str] = self.get_config("travel_target_quest2") + target_quest3: List[str] = self.get_config("travel_target_quest3") + if set(target_quest1) & set(target_quest2) or set(target_quest1) & set(target_quest3) or set(target_quest2) & set(target_quest3): + raise AbortError("三个轮转目标有重叠!请修改!") + n = time.localtime().tm_yday // int(self.get_config("travel_target_day")) def get_quest_id(lst: List, n): return db.get_travel_quest_id_from_candidate(lst[n % len(lst)]) return [ - get_quest_id(self.get_config("travel_target_quest1"), n), - get_quest_id(self.get_config("travel_target_quest2"), n), - get_quest_id(self.get_config("travel_target_quest3"), n) + get_quest_id(target_quest1, n), + get_quest_id(target_quest2, n), + get_quest_id(target_quest3, n) ] async def do_task(self, client: pcrclient): top = await client.travel_top(max(db.get_open_travel_area()), 1) From 9f5b42ea2ece61f4ae9435b9fd450bc73ab7071e Mon Sep 17 00:00:00 2001 From: Lanly109 <1094916227@qq.com> Date: Sat, 2 Nov 2024 19:54:57 +0800 Subject: [PATCH 08/14] fix: travel lock error --- autopcr/core/datamgr.py | 10 +++++++--- autopcr/core/pcrclient.py | 2 ++ autopcr/module/modules/daily.py | 2 +- autopcr/module/modules/sweep.py | 2 ++ autopcr/module/modules/tools.py | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/autopcr/core/datamgr.py b/autopcr/core/datamgr.py index 30e0d6b0..6fa6f8bb 100644 --- a/autopcr/core/datamgr.py +++ b/autopcr/core/datamgr.py @@ -486,11 +486,15 @@ def get_not_enough_item(self, demand: typing.Counter[ItemType]) -> List[Tuple[It bad = [(item, cnt - self.get_inventory(item)) for item, cnt in demand.items() if cnt > self.get_inventory(item)] return bad + def get_unit_power(self, unit_id: int) -> int: + power = db.calc_unit_power(self.unit[unit_id], set(self.read_story_ids)) + return int(power + 0.5) + + def is_quest_cleared(self, quest: int) -> bool: + return quest in self.quest_dict and self.quest_dict[quest].result_type == eMissionStatusType.AlreadyReceive + async def request(self, request: Request[TResponse], next: RequestHandler) -> TResponse: resp = await next.request(request) if resp: await resp.update(self, request) return resp - async def get_unit_power(self, unit_id: int) -> int: - power = db.calc_unit_power(self.unit[unit_id], set(self.read_story_ids)) - return int(power + 0.5) diff --git a/autopcr/core/pcrclient.py b/autopcr/core/pcrclient.py index c5f4182c..32773935 100644 --- a/autopcr/core/pcrclient.py +++ b/autopcr/core/pcrclient.py @@ -51,6 +51,8 @@ async def season_ticket_new_accept(self, season_id: int, mission_id: int): return await self.request(req) async def travel_top(self, travel_area_id: int, get_ex_equip_album_flag: int): + if not self.data.is_quest_cleared(11018001): + raise SkipError("探险未解锁") req = TravelTopRequest() req.travel_area_id = travel_area_id req.get_ex_equip_album_flag = get_ex_equip_album_flag diff --git a/autopcr/module/modules/daily.py b/autopcr/module/modules/daily.py index 2cfa39c9..98f00b2c 100644 --- a/autopcr/module/modules/daily.py +++ b/autopcr/module/modules/daily.py @@ -330,7 +330,7 @@ async def do_task(self, client: pcrclient): mana = client.data.gold.gold_id_free sweep_ticket = client.data.get_inventory((eInventoryType.Item, 23001)) pig = client.data.get_inventory((eInventoryType.Item, 90005)) - tot_power = sum([(await client.data.get_unit_power(unit)) for unit in client.data.unit]) + tot_power = sum([client.data.get_unit_power(unit) for unit in client.data.unit]) if stamina >= max_stamina: self._warn(f"体力爆了!") diff --git a/autopcr/module/modules/sweep.py b/autopcr/module/modules/sweep.py index 4b451c39..9e831ed4 100644 --- a/autopcr/module/modules/sweep.py +++ b/autopcr/module/modules/sweep.py @@ -467,6 +467,8 @@ def today_targets(self) -> List[int]: target_quest1: List[str] = self.get_config("travel_target_quest1") target_quest2: List[str] = self.get_config("travel_target_quest2") target_quest3: List[str] = self.get_config("travel_target_quest3") + if not target_quest1 or not target_quest2 or not target_quest3: + raise AbortError("三个轮转目标未全设置!") if set(target_quest1) & set(target_quest2) or set(target_quest1) & set(target_quest3) or set(target_quest2) & set(target_quest3): raise AbortError("三个轮转目标有重叠!请修改!") diff --git a/autopcr/module/modules/tools.py b/autopcr/module/modules/tools.py index 406ef189..d80322f1 100644 --- a/autopcr/module/modules/tools.py +++ b/autopcr/module/modules/tools.py @@ -78,7 +78,7 @@ async def do_task(self, client: pcrclient): unit_list.extend(new_unit) await client.travel_update_priority_unit_list(unit_list) - unit_power = {unit: await client.data.get_unit_power(unit) for unit in client.data.unit} + unit_power = {unit: client.data.get_unit_power(unit) for unit in client.data.unit} unit_list.sort(key=lambda x: unit_power[x], reverse=True) teams = [ From e242b36eb6139d433252411c03d9e8c7e8dff23e Mon Sep 17 00:00:00 2001 From: Lanly109 <1094916227@qq.com> Date: Sat, 2 Nov 2024 22:45:31 +0800 Subject: [PATCH 09/14] fix: travel round speed up paper threshold --- autopcr/module/modules/sweep.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autopcr/module/modules/sweep.py b/autopcr/module/modules/sweep.py index 9e831ed4..1eb9c9e7 100644 --- a/autopcr/module/modules/sweep.py +++ b/autopcr/module/modules/sweep.py @@ -504,9 +504,9 @@ async def do_task(self, client: pcrclient): if remove_quest.travel_start_time + i * quest_interval - remove_quest.decrease_time > client.server_time) if next_loop < remove_quest.total_lap_count: - delta_time = int(remove_quest.travel_start_time + remove_quest.decrease_time + next_loop * quest_interval - client.server_time) + delta_time = int(remove_quest.travel_start_time + next_loop * quest_interval - remove_quest.decrease_time - client.server_time) ticket_to_use = int(math.ceil(delta_time / client.data.settings.travel.decrease_time_by_ticket)) - if ticket_to_use < travel_speed_up_paper_threshold: + if ticket_to_use <= travel_speed_up_paper_threshold: self._log(f"一轮剩余时间{db.format_second(delta_time)}小于使用阈值{travel_speed_up_paper_threshold}小时,加速一轮") if ticket_to_use > client.data.get_inventory(db.travel_speed_up_paper): ticket_to_use = 0 From be5ec314b65d65e64b56bbbd2745e8eb10b9d711 Mon Sep 17 00:00:00 2001 From: Lanly109 <1094916227@qq.com> Date: Sun, 3 Nov 2024 10:40:10 +0800 Subject: [PATCH 10/14] fix: travel_quest_sweep restart error --- autopcr/module/modules/sweep.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autopcr/module/modules/sweep.py b/autopcr/module/modules/sweep.py index 1eb9c9e7..0ee7db2a 100644 --- a/autopcr/module/modules/sweep.py +++ b/autopcr/module/modules/sweep.py @@ -113,7 +113,7 @@ def get_strategy(event_id: int) -> str: travel_quest_id = quest.travel_quest_id, travel_deck = quest.travel_deck, decrease_time_item = TravelDecreaseItem(jewel = 0, item = 0), - total_lap_count = travel_quest_max_round, + total_lap_count = client.data.settings.travel.travel_quest_max_repeat_count, ) start_travel_quest_list.append(start_item) else: From ec2bbdf624ca464ee3fa7273607710df0f306a24 Mon Sep 17 00:00:00 2001 From: Lanly109 <1094916227@qq.com> Date: Sun, 3 Nov 2024 13:40:55 +0800 Subject: [PATCH 11/14] fea: travel_quest_speed_up_paper ues in travel quest sweep --- autopcr/module/modules/sweep.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/autopcr/module/modules/sweep.py b/autopcr/module/modules/sweep.py index 0ee7db2a..a8d5d548 100644 --- a/autopcr/module/modules/sweep.py +++ b/autopcr/module/modules/sweep.py @@ -16,12 +16,14 @@ @description(''' 仅支持阅读特殊事件+续派遣次数到最大派遣数。 +加速券大于保留值则会加速,建议与探险轮转的加速阈值保持一致。 装备事件为 60%三金装40%一金装 或 100%二金装 选项 代币事件为 30%1000代币70%200代币 或 100%400代币 选项 赌狗策略为前者,保守策略为后者 '''.strip()) @singlechoice('travel_quest_gold_event_strategy', "代币事件策略", '赌狗', ['保守','赌狗','随机']) @singlechoice('travel_quest_equip_event_strategy', "装备事件策略", '赌狗', ['保守','赌狗','随机']) +@inttype('travel_quest_speed_up_paper_hold', "加速券保留", 6, list(range(37))) @name("探险续航") @default(True) class travel_quest_sweep(Module): @@ -49,6 +51,7 @@ def get_choice(self, strategy: str) -> int: async def do_task(self, client: pcrclient): travel_quest_equip_event_strategy: str = self.get_config("travel_quest_equip_event_strategy") travel_quest_gold_event_strategy: str = self.get_config("travel_quest_gold_event_strategy") + travel_quest_speed_up_paper_hold: int = self.get_config("travel_quest_speed_up_paper_hold") reward: List[InventoryInfo] = [] def get_strategy(event_id: int) -> str: @@ -61,8 +64,10 @@ def get_strategy(event_id: int) -> str: top = await client.travel_top(max(db.get_open_travel_area()), 1) - if len(top.travel_quest_list) < 3: - self._warn(f"正在探险的小队数为{len(top.travel_quest_list)}<3,请上号开始新的探险!") + team_count = len(top.travel_quest_list) + + if team_count < 3: + self._warn(f"正在探险的小队数为{team_count}<3,请上号开始新的探险!") if top.top_event_list: for top_event in top.top_event_list: @@ -105,6 +110,7 @@ def get_strategy(event_id: int) -> str: add_lap_travel_quest_list: List[TravelQuestAddLap] = [] add_lap_travel_quest_id: List[int] = [] start_travel_quest_list: List[TravelStartInfo] = [] + new_quest_list: List[TravelQuestInfo] = [] # avoid api call again for quest in top.travel_quest_list: quest.received_count += result_count.get(quest.travel_quest_id, 0) quest_still_round = self.quest_still_round(quest) @@ -122,6 +128,8 @@ def get_strategy(event_id: int) -> str: add_item = TravelQuestAddLap(travel_id = quest.travel_id, add_lap_count = append_round) add_lap_travel_quest_list.append(add_item) add_lap_travel_quest_id.append(quest.travel_quest_id) + else: + new_quest_list.append(quest) if start_travel_quest_list or add_lap_travel_quest_list: msg = '\n'.join(f"继续派遣{db.get_quest_name(quest_id)} x{quest.add_lap_count}" for quest_id, quest in zip(add_lap_travel_quest_id, add_lap_travel_quest_list)) @@ -130,7 +138,21 @@ def get_strategy(event_id: int) -> str: action_type = eTravelStartType.ADD_LAP if add_lap_travel_quest_list else eTravelStartType.RESTART if start_travel_quest_list and add_lap_travel_quest_list: action_type = eTravelStartType.RESTART_AND_ADD - await client.travel_start(start_travel_quest_list, add_lap_travel_quest_list, [], action_type) + ret = await client.travel_start(start_travel_quest_list, add_lap_travel_quest_list, [], action_type) + new_quest_list.extend(ret.travel_quest_list) + + total_use = max( + min(top.remain_daily_decrease_count_ticket, client.data.get_inventory(db.travel_speed_up_paper)) + - travel_quest_speed_up_paper_hold, 0) + if team_count and total_use: # avoid divide by zero + self._log(f"可使用加速券{total_use}张") + quest_use = [total_use // team_count + (i < total_use % team_count) for i in range(team_count)] + for quest, use in zip(new_quest_list, quest_use): + if use: + self._log(f"{db.get_quest_name(quest.travel_quest_id)}使用加速券x{use}") + ret = await client.travel_decrease_time(quest.travel_quest_id, quest.travel_id, TravelDecreaseItem(jewel = 0, item = use)) + top.remain_daily_decrease_count_ticket = ret.remain_daily_decrease_count_ticket + # need receive again? if not self.log: raise SkipError("探险仍在继续...") @@ -472,7 +494,7 @@ def today_targets(self) -> List[int]: if set(target_quest1) & set(target_quest2) or set(target_quest1) & set(target_quest3) or set(target_quest2) & set(target_quest3): raise AbortError("三个轮转目标有重叠!请修改!") - n = time.localtime().tm_yday // int(self.get_config("travel_target_day")) + n = db.get_today_start_time().timetuple().tm_yday // int(self.get_config("travel_target_day")) def get_quest_id(lst: List, n): return db.get_travel_quest_id_from_candidate(lst[n % len(lst)]) return [ get_quest_id(target_quest1, n), From 24ffcc4dbef4392f05fcfbd0966338195eb293a3 Mon Sep 17 00:00:00 2001 From: Lanly109 <1094916227@qq.com> Date: Sun, 3 Nov 2024 16:11:33 +0800 Subject: [PATCH 12/14] fix: travel_quest_sweep stop iterator --- autopcr/module/modules/sweep.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/autopcr/module/modules/sweep.py b/autopcr/module/modules/sweep.py index a8d5d548..364525a3 100644 --- a/autopcr/module/modules/sweep.py +++ b/autopcr/module/modules/sweep.py @@ -133,7 +133,8 @@ def get_strategy(event_id: int) -> str: if start_travel_quest_list or add_lap_travel_quest_list: msg = '\n'.join(f"继续派遣{db.get_quest_name(quest_id)} x{quest.add_lap_count}" for quest_id, quest in zip(add_lap_travel_quest_id, add_lap_travel_quest_list)) - msg += '\n'.join(f"重新派遣{db.get_quest_name(quest.travel_quest_id)} x{quest.total_lap_count}" for quest in start_travel_quest_list) + self._log(msg) + msg = '\n'.join(f"重新派遣{db.get_quest_name(quest.travel_quest_id)} x{quest.total_lap_count}" for quest in start_travel_quest_list) self._log(msg) action_type = eTravelStartType.ADD_LAP if add_lap_travel_quest_list else eTravelStartType.RESTART if start_travel_quest_list and add_lap_travel_quest_list: @@ -523,7 +524,8 @@ async def do_task(self, client: pcrclient): self._log(f"{db.get_quest_name(remove_quest_id)}->{db.get_quest_name(add_quest_id)}") quest_interval = (remove_quest.travel_end_time - remove_quest.travel_start_time) / remove_quest.total_lap_count next_loop = next(i for i in range(remove_quest.received_count, remove_quest.total_lap_count + 1) - if remove_quest.travel_start_time + i * quest_interval - remove_quest.decrease_time > client.server_time) + if i == remove_quest.total_lap_count + or remove_quest.travel_start_time + i * quest_interval - remove_quest.decrease_time > client.server_time) if next_loop < remove_quest.total_lap_count: delta_time = int(remove_quest.travel_start_time + next_loop * quest_interval - remove_quest.decrease_time - client.server_time) From 4e2f59c6f5ecbffefdf2e3798812eeed216b5da5 Mon Sep 17 00:00:00 2001 From: Lanly109 <1094916227@qq.com> Date: Sun, 3 Nov 2024 19:20:39 +0800 Subject: [PATCH 13/14] fix: arena & grand arena lock error --- autopcr/core/pcrclient.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/autopcr/core/pcrclient.py b/autopcr/core/pcrclient.py index 32773935..603b3d60 100644 --- a/autopcr/core/pcrclient.py +++ b/autopcr/core/pcrclient.py @@ -755,10 +755,14 @@ async def get_grand_arena_history_detail(self, log_id: int): return await self.request(req) async def get_arena_info(self): + if not self.data.is_quest_cleared(11004006): + raise SkipError("未解锁竞技场") req = ArenaInfoRequest() return await self.request(req) async def get_grand_arena_info(self): + if not self.data.is_quest_cleared(11008015): + raise SkipError("未解锁公主竞技场") req = GrandArenaInfoRequest() return await self.request(req) From c3369e25a69c7157e7109c95203e698e4678bc01 Mon Sep 17 00:00:00 2001 From: Lanly109 <1094916227@qq.com> Date: Mon, 4 Nov 2024 09:33:53 +0800 Subject: [PATCH 14/14] fea: expand the number of speed up paper reserved --- autopcr/module/modules/sweep.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autopcr/module/modules/sweep.py b/autopcr/module/modules/sweep.py index 364525a3..e760f873 100644 --- a/autopcr/module/modules/sweep.py +++ b/autopcr/module/modules/sweep.py @@ -23,7 +23,7 @@ '''.strip()) @singlechoice('travel_quest_gold_event_strategy', "代币事件策略", '赌狗', ['保守','赌狗','随机']) @singlechoice('travel_quest_equip_event_strategy', "装备事件策略", '赌狗', ['保守','赌狗','随机']) -@inttype('travel_quest_speed_up_paper_hold', "加速券保留", 6, list(range(37))) +@inttype('travel_quest_speed_up_paper_hold', "加速券保留", 6, list(range(3001))) @name("探险续航") @default(True) class travel_quest_sweep(Module):