add refine action

This commit is contained in:
bridge
2026-01-07 21:15:04 +08:00
parent 2c8f73d290
commit 7c69d612b0
10 changed files with 212 additions and 11 deletions

View File

@@ -37,6 +37,7 @@ from .switch_weapon import SwitchWeapon
from .assassinate import Assassinate
from .move_to_direction import MoveToDirection
from .cast import Cast
from .refine import Refine
from .buy import Buy
from .mine import Mine
@@ -72,6 +73,7 @@ register_action(actual=True)(SwitchWeapon)
register_action(actual=True)(Assassinate)
register_action(actual=True)(MoveToDirection)
register_action(actual=True)(Cast)
register_action(actual=True)(Refine)
register_action(actual=True)(Buy)
register_action(actual=True)(Mine)
# Talk 已移动到 mutual_action 模块,在那里注册
@@ -110,6 +112,7 @@ __all__ = [
"Assassinate",
"MoveToDirection",
"Cast",
"Refine",
"Buy",
"Mine",
]

View File

@@ -33,7 +33,7 @@ class Cast(TimedAction):
Realm.Nascent_Soul: 0.1,
}
DOABLES_REQUIREMENTS = f"拥有{COST}个同境界矿石材料"
DOABLES_REQUIREMENTS = f"拥有{COST}个同境界材料"
PARAMS = {"target_realm": "目标境界名称('练气''筑基''金丹''元婴'"}
IS_MAJOR = False
@@ -49,12 +49,12 @@ class Cast(TimedAction):
def _count_materials(self, realm: Realm) -> int:
"""
统计符合条件的材料数量。
注意:统计 Item 类的直接实例,且必须在 ORE_ITEM_IDS 中
注意:统计所有 Item 类的直接实例,不限于矿石
"""
count = 0
for item, qty in self.avatar.items.items():
# 增加判断item.id 必须在 ORE_ITEM_IDS 中
if type(item).__name__ == "Item" and item.realm == realm and item.id in ORE_ITEM_IDS:
# 只要是 Item 实例且境界符合即可
if type(item).__name__ == "Item" and item.realm == realm:
count += qty
return count
@@ -91,7 +91,7 @@ class Cast(TimedAction):
for item, qty in self.avatar.items.items():
if to_deduct <= 0:
break
if type(item).__name__ == "Item" and item.realm == self.target_realm and item.id in ORE_ITEM_IDS:
if type(item).__name__ == "Item" and item.realm == self.target_realm:
take = min(qty, to_deduct)
items_to_modify.append((item, take))
to_deduct -= take

View File

@@ -0,0 +1,175 @@
from __future__ import annotations
import random
from typing import Optional, TYPE_CHECKING, List
from src.classes.action import TimedAction
from src.classes.cultivation import Realm
from src.classes.event import Event
from src.classes.elixir import get_random_elixir_by_realm
from src.classes.single_choice import handle_item_exchange
from src.utils.resolution import resolve_query
if TYPE_CHECKING:
from src.classes.avatar import Avatar
class Refine(TimedAction):
"""
炼丹动作:消耗同阶材料,尝试炼制同阶丹药。
持续时间3个月
"""
ACTION_NAME = "炼丹"
EMOJI = "💊"
DESC = "消耗材料尝试炼制丹药"
COST = 3
SUCCESS_RATES = {
Realm.Qi_Refinement: 0.4,
Realm.Foundation_Establishment: 0.3,
Realm.Core_Formation: 0.2,
Realm.Nascent_Soul: 0.1,
}
DOABLES_REQUIREMENTS = f"拥有{COST}个同境界材料"
PARAMS = {"target_realm": "目标境界名称('练气''筑基''金丹''元婴'"}
IS_MAJOR = False
duration_months = 2
def __init__(self, avatar: Avatar, world):
super().__init__(avatar, world)
self.target_realm: Optional[Realm] = None
def _get_cost(self) -> int:
return self.COST
def _count_materials(self, realm: Realm) -> int:
"""
统计符合条件的材料数量。
注意:统计所有 Item 类的直接实例,不限于矿石。
"""
count = 0
for item, qty in self.avatar.items.items():
# 只要是 Item 实例且境界符合即可
if type(item).__name__ == "Item" and item.realm == realm:
count += qty
return count
def can_start(self, target_realm: str) -> tuple[bool, str]:
if not target_realm:
return False, "未指定目标境界"
res = resolve_query(target_realm, expected_types=[Realm])
if not res.is_valid:
return False, f"无效的境界: {target_realm}"
realm = res.obj
cost = self._get_cost()
count = self._count_materials(realm)
if count < cost:
return False, f"材料不足,需要 {cost}{target_realm}阶材料,当前拥有 {count}"
return True, ""
def start(self, target_realm: str) -> Event:
res = resolve_query(target_realm, expected_types=[Realm])
if res.is_valid:
self.target_realm = res.obj
cost = self._get_cost()
# 扣除材料逻辑
to_deduct = cost
items_to_modify = []
# 再次遍历寻找材料进行扣除
for item, qty in self.avatar.items.items():
if to_deduct <= 0:
break
if type(item).__name__ == "Item" and item.realm == self.target_realm:
take = min(qty, to_deduct)
items_to_modify.append((item, take))
to_deduct -= take
for item, take in items_to_modify:
self.avatar.remove_item(item, take)
realm_val = self.target_realm.value if self.target_realm else target_realm
return Event(
self.world.month_stamp,
f"{self.avatar.name} 开始尝试炼制{realm_val}阶丹药。",
related_avatars=[self.avatar.id]
)
def _execute(self) -> None:
# 持续过程中无特殊逻辑
pass
async def finish(self) -> list[Event]:
if self.target_realm is None:
return []
# 1. 计算成功率
base_rate = self.SUCCESS_RATES.get(self.target_realm, 0.1)
# 获取额外成功率(例如来自特质或功法)
extra_rate = float(self.avatar.effects.get("extra_refine_success_rate", 0.0))
success_rate = base_rate + extra_rate
events = []
# 2. 判定结果
if random.random() > success_rate:
# 失败
fail_event = Event(
self.world.month_stamp,
f"{self.avatar.name} 炼制{self.target_realm.value}阶丹药失败,所有材料化为灰烬。",
related_avatars=[self.avatar.id],
is_major=False
)
events.append(fail_event)
return events
# 3. 成功:生成物品
new_item = get_random_elixir_by_realm(self.target_realm)
if new_item is None:
# 理论上不应该发生,除非该境界没有配置丹药
fail_event = Event(
self.world.month_stamp,
f"{self.avatar.name} 炼制成功,但似乎没有产生任何已知的丹药。",
related_avatars=[self.avatar.id],
is_major=False
)
events.append(fail_event)
return events
# 4. 决策:保留还是卖出
base_desc = f"炼丹成功!获得了{self.target_realm.value}丹药『{new_item.name}』。"
# 事件1炼丹成功
events.append(Event(
self.world.month_stamp,
f"{self.avatar.name} 成功炼制{self.target_realm.value}丹药『{new_item.name}』。",
related_avatars=[self.avatar.id],
is_major=True
))
_, result_text = await handle_item_exchange(
avatar=self.avatar,
new_item=new_item,
item_type="elixir",
context_intro=base_desc,
can_sell_new=True
)
# 事件2处置结果
events.append(Event(
self.world.month_stamp,
result_text,
related_avatars=[self.avatar.id],
is_major=True
))
return events

View File

@@ -16,6 +16,7 @@ from .consts import (
EXTRA_FORTUNE_PROBABILITY,
EXTRA_MISFORTUNE_PROBABILITY,
EXTRA_CAST_SUCCESS_RATE,
EXTRA_REFINE_SUCCESS_RATE,
EXTRA_WEAPON_PROFICIENCY_GAIN,
EXTRA_WEAPON_UPGRADE_CHANCE,
EXTRA_MAX_LIFESPAN,

View File

@@ -219,6 +219,18 @@ EXTRA_CAST_SUCCESS_RATE = "extra_cast_success_rate"
- 大量: 0.2+ (+20%)
"""
EXTRA_REFINE_SUCCESS_RATE = "extra_refine_success_rate"
"""
额外炼丹成功率
类型: float
结算: src/classes/action/refine.py
说明: 炼丹Refine动作的成功率加成。
数值参考:
- 微量: 0.05 (+5%)
- 中量: 0.1 (+10%)
- 大量: 0.2+ (+20%)
"""
# --- 兵器相关 ---
EXTRA_WEAPON_PROFICIENCY_GAIN = "extra_weapon_proficiency_gain"
"""
@@ -430,6 +442,7 @@ ALL_EFFECTS = [
# 铸造相关
"extra_cast_success_rate", # float - 额外铸造成功率
"extra_refine_success_rate", # float - 额外炼丹成功率
# 兵器相关
"extra_weapon_proficiency_gain", # float - 额外兵器熟练度增长倍率

View File

@@ -28,6 +28,7 @@ EFFECT_DESC_MAP = {
"realm_suppression_bonus": "境界压制",
"cultivate_duration_reduction": "修炼时长缩减",
"extra_cast_success_rate": "铸造成功率",
"extra_refine_success_rate": "炼丹成功率",
}
ACTION_DESC_MAP = {

View File

@@ -1,8 +1,10 @@
from __future__ import annotations
import random
import copy
from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, List, Union
from typing import Dict, List, Union, Optional
from src.utils.df import game_configs, get_str, get_int
from src.classes.effect import load_effect_from_str, format_effects_to_text
@@ -188,4 +190,10 @@ elixirs_by_id, elixirs_by_name = _load_elixirs()
def get_elixirs_by_realm(realm: Realm) -> List[Elixir]:
"""获取指定境界的所有丹药"""
return [e for e in elixirs_by_id.values() if e.realm == realm]
return [e for e in elixirs_by_id.values() if e.realm == realm]
def get_random_elixir_by_realm(realm: Realm) -> Optional[Elixir]:
"""获取指定境界的随机丹药"""
candidates = get_elixirs_by_realm(realm)
return copy.deepcopy(random.choice(candidates))