Files
NewWPSBot/Plugins/WPSCombatSystem/combat_plugin_base.py

673 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""战斗系统基础插件类"""
from __future__ import annotations
from PWF.Convention.Runtime.Architecture import *
from PWF.Convention.Runtime.GlobalConfig import ConsoleFrontColor, ProjectConfig
from PWF.CoreModules.plugin_interface import DatabaseModel
from Plugins.WPSAPI import GuideEntry, GuideSection, WPSAPI
from Plugins.WPSBackpackSystem import BackpackItemTier, WPSBackpackSystem
from Plugins.WPSAlchemyGame import WPSAlchemyGame
from Plugins.WPSStoreSystem import WPSStoreSystem
from Plugins.WPSConfigSystem import WPSConfigAPI
from Plugins.WPSFortuneSystem import WPSFortuneSystem
from .combat_models import (
ADVENTURE_MATERIALS,
ADVENTURE_SEEDS,
ADVENTURE_SOUVENIRS,
COMBAT_POTIONS,
EQUIPMENT_REGISTRY,
SKILL_REGISTRY,
SPARK_DUST_ITEM_ID,
EquipmentDefinition,
get_combat_db_models,
)
from .combat_service import CombatService, get_combat_service
from Plugins.WPSGardenSystem import (
GardenCropDefinition,
GardenExtraReward,
register_crop,
)
logger: ProjectConfig = Architecture.Get(ProjectConfig)
class WPSCombatBase(WPSAPI):
"""战斗系统基础插件类"""
_service: CombatService | None = None
_initialized: bool = False
def collect_additional_sections(self) -> Sequence[GuideSection]:
sections = list(super().collect_additional_sections())
equipment_entries = self._build_equipment_entries()
if equipment_entries:
sections.append(
GuideSection(
title="装备详单",
entries=equipment_entries,
layout="grid",
section_id="combat-equipment",
description="战斗系统预置的装备清单及属性效果。",
)
)
potion_entries = self._build_simple_item_entries(
COMBAT_POTIONS,
category="战斗药剂",
)
if potion_entries:
sections.append(
GuideSection(
title="药剂与增益",
entries=potion_entries,
layout="grid",
section_id="combat-potions",
description="药剂品质影响价格与效果,部分由冒险掉落。",
)
)
material_entries = self._build_simple_item_entries(
ADVENTURE_MATERIALS,
category="冒险材料",
)
if material_entries:
sections.append(
GuideSection(
title="冒险材料",
entries=material_entries,
layout="grid",
section_id="combat-materials",
description="材料主要来源于冒险战斗,稀有度决定获取概率。",
)
)
souvenir_entries = self._build_souvenir_entries()
if souvenir_entries:
sections.append(
GuideSection(
title="纪念品一览",
entries=souvenir_entries,
layout="grid",
section_id="combat-souvenirs",
description="纪念品可在营地或商店出售兑换积分。",
)
)
seed_entries = self._build_simple_item_entries(
ADVENTURE_SEEDS,
category="冒险种子",
)
if seed_entries:
sections.append(
GuideSection(
title="冒险种子",
entries=seed_entries,
layout="grid",
section_id="combat-seeds",
description="冒险专属种子,可在菜园种植获取增益或任务物资。",
)
)
skill_entries = self._build_skill_entries()
if skill_entries:
sections.append(
GuideSection(
title="技能图鉴",
entries=skill_entries,
layout="list",
section_id="combat-skills",
description="装备附带的技能可在战斗中释放,冷却时间以回合计算。",
)
)
return tuple(sections)
def _format_tier(self, tier: BackpackItemTier) -> str:
return f"{tier.display_name}"
def _build_equipment_entries(self) -> List[GuideEntry]:
entries: List[GuideEntry] = []
attr_names = {
"HP": "生命值",
"ATK": "攻击力",
"DEF": "防御力",
"SPD": "速度",
"CRIT": "暴击率",
"CRIT_DMG": "暴击伤害",
}
tier_icons = {
BackpackItemTier.COMMON: "⚔️",
BackpackItemTier.RARE: "🛡️",
BackpackItemTier.EPIC: "🔥",
BackpackItemTier.LEGENDARY: "🌟",
}
for eq in EQUIPMENT_REGISTRY.values():
attr_list = []
for key, value in eq.attributes.items():
label = attr_names.get(key, key)
suffix = "%" if key in ("CRIT", "CRIT_DMG") else ""
attr_list.append(f"{label}+{value}{suffix}")
skills = []
for skill_id in eq.skill_ids:
skill = SKILL_REGISTRY.get(skill_id)
if skill:
skills.append(f"{skill.icon} {skill.name}")
metadata = {
"槽位": eq.slot,
"稀有度": self._format_tier(eq.tier),
}
details: List[Dict[str, Any] | str] = []
if attr_list:
details.append({"type": "list", "items": attr_list})
if skills:
details.append({"type": "list", "items": [f"技能:{info}" for info in skills]})
if eq.description:
details.append(eq.description)
entries.append(
GuideEntry(
title=eq.name,
identifier=eq.item_id,
description=eq.description or "标准装备。",
category="装备",
metadata=metadata,
icon=tier_icons.get(eq.tier, "⚔️"),
tags=skills,
details=details,
)
)
return entries
def _build_simple_item_entries(
self,
registry: Dict[str, tuple],
*,
category: str,
) -> List[GuideEntry]:
entries: List[GuideEntry] = []
icon_map = {
"战斗药剂": "🧪",
"冒险材料": "🪨",
"纪念品": "🎖️",
"冒险种子": "🌱",
}
for item_id, payload in registry.items():
name, tier, *rest = payload
description = rest[-1] if rest else ""
metadata = {"稀有度": self._format_tier(tier)}
if category == "战斗药剂":
price_lookup = {
BackpackItemTier.COMMON: 50,
BackpackItemTier.RARE: 150,
BackpackItemTier.EPIC: 500,
}
price = price_lookup.get(tier)
if price:
metadata["默认售价"] = f"{price}"
entries.append(
GuideEntry(
title=name,
identifier=item_id,
description=description,
category=category,
metadata=metadata,
icon=icon_map.get(category, "📦"),
)
)
return entries
def _build_souvenir_entries(self) -> List[GuideEntry]:
entries: List[GuideEntry] = []
for item_id, (name, tier, price, desc) in ADVENTURE_SOUVENIRS.items():
entries.append(
GuideEntry(
title=name,
identifier=item_id,
description=desc,
category="纪念品",
metadata={
"稀有度": self._format_tier(tier),
"基础售价": f"{price}",
},
icon="🎖️",
)
)
return entries
def _build_skill_entries(self) -> List[GuideEntry]:
entries: List[GuideEntry] = []
for skill in SKILL_REGISTRY.values():
details: List[Union[str, Dict[str, Any]]] = [
{"type": "list", "items": [effect.get("description", str(effect)) for effect in skill.effects]},
]
if skill.cooldown:
details.append(f"冷却:{skill.cooldown} 回合")
entries.append(
GuideEntry(
title=skill.name,
identifier=skill.skill_id,
description=skill.description,
category="技能",
icon=skill.icon,
metadata={},
details=details,
)
)
return entries
def get_guide_subtitle(self) -> str:
return "冒险、战斗与装备体系的基础能力"
def get_guide_metadata(self) -> Dict[str, str]:
return {
"装备数量": str(len(EQUIPMENT_REGISTRY)),
"药剂数量": str(len(COMBAT_POTIONS)),
"纪念品数量": str(len(ADVENTURE_SOUVENIRS)),
"冒险材料": str(len(ADVENTURE_MATERIALS)),
}
def collect_item_entries(self) -> Sequence[GuideEntry]:
return (
{
"title": "装备库",
"description": f"{len(EQUIPMENT_REGISTRY)} 件装备,自动注册至背包并带有属性描述。",
},
{
"title": "药剂与材料",
"description": (
f"{len(COMBAT_POTIONS)} 种药剂、{len(ADVENTURE_MATERIALS)} 种冒险素材,"
"部分可在商店购买或冒险获得。"
),
},
{
"title": "纪念品",
"description": f"{len(ADVENTURE_SOUVENIRS)} 种纪念品可在营地出售换取积分。",
},
)
def collect_guide_entries(self) -> Sequence[GuideEntry]:
return (
{
"title": "冒险流程",
"description": "冒险按阶段推进,使用食物缩短时间并依赖运势与装备强度影响结果。",
},
{
"title": "装备体系",
"description": "装备提供属性与技能加成,通过 `装备`/`战斗属性` 等指令查看详情。",
},
{
"title": "积分与资源",
"description": "冒险和战斗会产出积分、装备、材料等,通过商店与营地完成循环。",
},
)
@classmethod
def service(cls) -> CombatService:
"""获取共享的战斗服务实例"""
if cls._service is None:
cls._service = get_combat_service()
return cls._service
def dependencies(self) -> List[Type]:
"""声明依赖的插件"""
return [
WPSAPI,
WPSConfigAPI,
WPSBackpackSystem,
WPSStoreSystem,
WPSFortuneSystem,
WPSAlchemyGame,
]
def register_db_model(self) -> List[DatabaseModel]:
"""注册数据库表"""
return get_combat_db_models()
def wake_up(self) -> None:
"""插件初始化(只执行一次)"""
if WPSCombatBase._initialized:
return
WPSCombatBase._initialized = True
logger.Log(
"Info",
f"{ConsoleFrontColor.GREEN}WPSCombat 系统开始初始化{ConsoleFrontColor.RESET}"
)
backpack: WPSBackpackSystem = Architecture.Get(WPSBackpackSystem)
store: WPSStoreSystem = Architecture.Get(WPSStoreSystem)
# 1. 注册所有装备
for equipment in EQUIPMENT_REGISTRY.values():
# 生成包含属性数值和技能信息的描述
enhanced_description = self._generate_equipment_description(equipment)
self._safe_register_item(
backpack,
equipment.item_id,
equipment.name,
equipment.tier,
enhanced_description,
)
# 装备价格根据品质和属性计算
price = self._calculate_equipment_price(equipment)
# 只有普通和稀有装备可以出售
if equipment.tier == BackpackItemTier.COMMON or equipment.tier == BackpackItemTier.RARE:
self._safe_register_store(store, equipment.item_id, price, limit=3)
# 2. 注册材料
for item_id, (name, tier, desc) in ADVENTURE_MATERIALS.items():
self._safe_register_item(backpack, item_id, name, tier, desc)
# 材料可以在商店出售(但不购买)
# 3. 注册纪念品
for item_id, (name, tier, sell_price, desc) in ADVENTURE_SOUVENIRS.items():
self._safe_register_item(backpack, item_id, name, tier, desc)
# 纪念品只能出售
# 4. 注册药剂
for item_id, (name, tier, desc) in COMBAT_POTIONS.items():
self._safe_register_item(backpack, item_id, name, tier, desc)
# 药剂价格根据品质
potion_prices = {
BackpackItemTier.COMMON: 50,
BackpackItemTier.RARE: 150,
BackpackItemTier.EPIC: 500,
}
price = potion_prices.get(tier, 100)
self._safe_register_store(store, item_id, price, limit=10)
# 5. 注册冒险种子
for item_id, (name, tier, desc) in ADVENTURE_SEEDS.items():
self._safe_register_item(backpack, item_id, name, tier, desc)
# 种子只能通过冒险获得
# 5.1. 注册冒险种子到菜园系统
self._register_adventure_seeds_to_garden()
self._register_legendary_alchemy_recipes()
# 6. 恢复过期任务和超时战斗
try:
service = self.service()
service.recover_overdue_adventures()
service.check_battle_timeout()
except Exception as e:
logger.Log(
"Warning",
f"{ConsoleFrontColor.YELLOW}恢复任务时出错: {e}{ConsoleFrontColor.RESET}"
)
logger.Log(
"Info",
f"{ConsoleFrontColor.GREEN}WPSCombat 系统初始化完成:{len(EQUIPMENT_REGISTRY)}件装备、"
f"{len(COMBAT_POTIONS)}种药剂已注册{ConsoleFrontColor.RESET}"
)
# ========================================================================
# 辅助方法
# ========================================================================
def _safe_register_item(
self,
backpack: WPSBackpackSystem,
item_id: str,
name: str,
tier: BackpackItemTier,
description: str,
) -> None:
"""安全注册物品到背包系统"""
try:
backpack.register_item(item_id, name, tier, description)
except Exception as e:
logger.Log(
"Warning",
f"{ConsoleFrontColor.YELLOW}注册物品 {item_id} 时出错: {e}{ConsoleFrontColor.RESET}"
)
def _safe_register_store(
self,
store: WPSStoreSystem,
item_id: str,
price: int,
*,
limit: int = 5,
) -> None:
"""安全注册物品到商店系统"""
try:
store.register_mode(
item_id=item_id,
price=price,
limit_amount=limit,
)
except Exception as e:
logger.Log(
"Warning",
f"{ConsoleFrontColor.YELLOW}注册商店物品 {item_id} 时出错: {e}{ConsoleFrontColor.RESET}"
)
def _generate_equipment_description(self, equipment:EquipmentDefinition) -> str:
"""生成包含属性数值和技能信息的装备描述"""
parts = []
# 基础描述
if equipment.description:
parts.append(equipment.description)
# 属性信息
if equipment.attributes:
attr_parts = []
attr_names = {
"HP": "生命值",
"ATK": "攻击力",
"DEF": "防御力",
"SPD": "速度",
"CRIT": "暴击率",
"CRIT_DMG": "暴击伤害",
}
for attr_key, attr_value in sorted(equipment.attributes.items()):
attr_name = attr_names.get(attr_key, attr_key)
if attr_key in ["CRIT", "CRIT_DMG"]:
attr_parts.append(f"{attr_name}+{attr_value}%")
else:
attr_parts.append(f"{attr_name}+{attr_value}")
if attr_parts:
parts.append(f"属性:{', '.join(attr_parts)}")
# 技能信息
if equipment.skill_ids:
skill_names = []
for skill_id in equipment.skill_ids:
skill = SKILL_REGISTRY.get(skill_id)
if skill:
skill_names.append(skill.name)
if skill_names:
parts.append(f"附带技能:{', '.join(skill_names)}")
return " | ".join(parts) if parts else equipment.description
def _calculate_equipment_price(self, equipment: EquipmentDefinition) -> int:
"""根据装备品质和属性计算价格"""
# 基础价格
base_prices = {
BackpackItemTier.COMMON: 100,
BackpackItemTier.RARE: 500,
BackpackItemTier.EPIC: 2000,
BackpackItemTier.LEGENDARY: 10000,
}
base_price = base_prices.get(equipment.tier, 100)
# 属性加成
attr_sum = sum(equipment.attributes.values())
price = base_price + attr_sum * 5
# 技能加成
skill_bonus = len(equipment.skill_ids) * 200
return price + skill_bonus
def _register_adventure_seeds_to_garden(self) -> None:
"""注册冒险种子到菜园系统"""
try:
backpack: WPSBackpackSystem = Architecture.Get(WPSBackpackSystem)
# 战斗之花种子EPIC
battle_flower_crop = GardenCropDefinition(
seed_id="combat_seed_battle_flower",
fruit_id="combat_fruit_battle_flower",
display_name="战斗之花",
tier="epic",
growth_minutes=240, # 4小时比稀有树木更长
seed_price=300, # 比稀有树木更贵
base_yield=2, # 产量较低,体现稀有性
extra_reward=GardenExtraReward(
kind="points",
payload={"min": 500, "max": 2000},
base_rate=0.35, # 较低的基础概率,体现稀有性
),
wine_item_id=None, # 暂不设置果酒
wine_tier=None,
)
register_crop(battle_flower_crop)
# 注册果实到背包系统
self._safe_register_item(
backpack,
battle_flower_crop.fruit_id,
"战斗之花的果实",
BackpackItemTier.EPIC,
"战斗之花成熟后的果实,可食用或售出换取积分。",
)
# 胜利之树种子LEGENDARY
victory_tree_crop = GardenCropDefinition(
seed_id="combat_seed_victory_tree",
fruit_id="combat_fruit_victory_tree",
display_name="胜利之树",
tier="legendary",
growth_minutes=480, # 8小时最长的生长时间
seed_price=800, # 最贵的种子
base_yield=1, # 最低产量,体现传说级稀有性
extra_reward=GardenExtraReward(
kind="points",
payload={"min": 2000, "max": 5000},
base_rate=0.25, # 最低的基础概率
),
wine_item_id="garden_wine_victory_tree",
wine_tier="legendary",
)
register_crop(victory_tree_crop)
# 注册果实到背包系统
self._safe_register_item(
backpack,
victory_tree_crop.fruit_id,
"胜利之树的果实",
BackpackItemTier.LEGENDARY,
"胜利之树成熟后的果实,可食用或售出换取积分。",
)
# 注册胜利之树的果酒物品和配方
if victory_tree_crop.wine_item_id and victory_tree_crop.wine_tier:
alchemy: WPSAlchemyGame = Architecture.Get(WPSAlchemyGame)
wine_tier = getattr(BackpackItemTier, victory_tree_crop.wine_tier.upper(), BackpackItemTier.LEGENDARY)
wine_name = f"{victory_tree_crop.display_name}的果酒"
wine_desc = self._generate_wine_description(victory_tree_crop.display_name, victory_tree_crop.wine_item_id)
self._safe_register_item(
backpack,
victory_tree_crop.wine_item_id,
wine_name,
wine_tier,
wine_desc,
)
# 注册果酒配方
try:
alchemy.register_recipe(
(victory_tree_crop.fruit_id, victory_tree_crop.fruit_id, victory_tree_crop.fruit_id),
victory_tree_crop.wine_item_id,
"garden_item_rot_fruit",
0.75,
)
except Exception as exc:
logger.Log(
"Warning",
f"{ConsoleFrontColor.YELLOW}注册胜利之树果酒配方失败: {exc}{ConsoleFrontColor.RESET}"
)
logger.Log(
"Info",
f"{ConsoleFrontColor.GREEN}成功注册 {len(ADVENTURE_SEEDS)} 种冒险种子到菜园系统{ConsoleFrontColor.RESET}"
)
except Exception as e:
logger.Log(
"Warning",
f"{ConsoleFrontColor.YELLOW}注册冒险种子到菜园系统时出错: {e}{ConsoleFrontColor.RESET}"
)
def _register_legendary_alchemy_recipes(self) -> None:
"""注册传说装备的炼金链条"""
alchemy: WPSAlchemyGame = Architecture.Get(WPSAlchemyGame)
recipe_definitions = (
# 护甲链
(("combat_material_ore", "garden_wood_maple", "combat_armor_chain"), "combat_armor_plate", 0.70),
(("combat_material_gem", "combat_material_crystal", "combat_armor_plate"), "combat_armor_sentinel", 0.50),
(("combat_armor_sentinel", "garden_wood_sakura", "combat_material_crystal"), "combat_armor_dragonheart", 0.30),
(("combat_armor_dragonheart", "combat_material_essence", "combat_armor_plate"), "combat_armor_guardian", 0.10),
# 鞋子链
(("combat_material_ore", "garden_wood_ginkgo", "combat_boots_leather"), "combat_boots_rapid", 0.70),
(("combat_boots_rapid", "combat_material_gem", "combat_material_crystal"), "combat_boots_wind", 0.50),
(("combat_boots_wind", "combat_material_crystal", "garden_wood_ginkgo"), "combat_boots_tempest", 0.30),
(("combat_boots_tempest", "combat_material_essence", "garden_wine_maple"), "combat_boots_starlight", 0.10),
# 饰品链
(("combat_accessory_ring_str", "combat_material_gem", "garden_wood_sakura"), "combat_accessory_barrier", 0.70),
(("combat_accessory_barrier", "combat_material_crystal", "garden_wood_sakura"), "combat_accessory_amulet", 0.50),
(("combat_accessory_amulet", "combat_material_crystal", "garden_wine_sakura"), "combat_accessory_sanctum", 0.30),
(("combat_accessory_sanctum", "combat_material_essence", "combat_souvenir_relic"), "combat_accessory_aegis", 0.10),
)
for materials, success_item_id, success_rate in recipe_definitions:
try:
alchemy.register_recipe(materials, success_item_id, SPARK_DUST_ITEM_ID, success_rate)
except Exception as exc:
logger.Log(
"Warning",
f"{ConsoleFrontColor.YELLOW}注册炼金配方 {materials} -> {success_item_id} 失败: {exc}{ConsoleFrontColor.RESET}",
)
def _generate_wine_description(self, crop_name: str, wine_item_id: str) -> str:
"""生成包含buff加成信息的果酒描述"""
try:
from .combat_models import WINE_BUFFS
buffs = WINE_BUFFS.get(wine_item_id, {})
except ImportError:
buffs = {}
parts = [f"{crop_name}酿制的果酒,饮用后可触发战斗增益。"]
if buffs:
buff_parts = []
buff_names = {
"time_reduction": "冒险时间",
"reward_boost": "冒险收益",
"success_rate": "冒险成功率",
"atk_boost": "攻击力",
"def_boost": "防御力",
"crit_boost": "暴击率",
}
for buff_key, buff_value in sorted(buffs.items()):
buff_name = buff_names.get(buff_key, buff_key)
if buff_key == "time_reduction":
buff_parts.append(f"{buff_name}-{buff_value*100:.0f}%")
else:
buff_parts.append(f"{buff_name}+{buff_value*100:.0f}%")
if buff_parts:
parts.append(f"效果:{', '.join(buff_parts)}")
return " | ".join(parts)
__all__ = ["WPSCombatBase"]