rename item -> material & refactor buying action

This commit is contained in:
bridge
2026-01-07 22:43:26 +08:00
parent 73c50286b7
commit b2a021bf8a
43 changed files with 795 additions and 580 deletions

View File

@@ -26,7 +26,7 @@ from src.classes.event import Event
from src.classes.action_runtime import ActionPlan, ActionInstance
from src.classes.alignment import Alignment
from src.classes.persona import Persona, get_random_compatible_personas
from src.classes.item import Item
from src.classes.material import Material
from src.classes.weapon import Weapon
from src.classes.auxiliary import Auxiliary
from src.classes.magic_stone import MagicStone
@@ -96,7 +96,7 @@ class Avatar(
short_term_objective: str = ""
long_term_objective: Optional[LongTermObjective] = None
magic_stone: MagicStone = field(default_factory=lambda: MagicStone(0))
items: dict[Item, int] = field(default_factory=dict)
materials: dict[Material, int] = field(default_factory=dict)
hp: HP = field(default_factory=lambda: HP(0, 0))
relations: dict["Avatar", Relation] = field(default_factory=dict)
alignment: Alignment | None = None

View File

@@ -50,7 +50,7 @@ def get_avatar_info(avatar: "Avatar", detailed: bool = False) -> dict:
technique_info = avatar.technique.get_detailed_info() if avatar.technique is not None else ""
cultivation_info = avatar.cultivation_progress.get_detailed_info()
personas_info = ", ".join([p.get_detailed_info() for p in avatar.personas]) if avatar.personas else ""
items_info = "".join([f"{item.get_detailed_info()}x{quantity}" for item, quantity in avatar.items.items()]) if avatar.items else ""
materials_info = "".join([f"{mat.get_detailed_info()}x{quantity}" for mat, quantity in avatar.materials.items()]) if avatar.materials else ""
appearance_info = avatar.appearance.get_detailed_info(avatar.gender)
spirit_animal_info = avatar.spirit_animal.get_info() if avatar.spirit_animal is not None else ""
else:
@@ -63,7 +63,7 @@ def get_avatar_info(avatar: "Avatar", detailed: bool = False) -> dict:
technique_info = avatar.technique.get_info() if avatar.technique is not None else ""
cultivation_info = avatar.cultivation_progress.get_info()
personas_info = ", ".join([p.get_detailed_info() for p in avatar.personas]) if avatar.personas else ""
items_info = "".join([f"{item.get_info()}x{quantity}" for item, quantity in avatar.items.items()]) if avatar.items else ""
materials_info = "".join([f"{mat.get_info()}x{quantity}" for mat, quantity in avatar.materials.items()]) if avatar.materials else ""
appearance_info = avatar.appearance.get_info()
spirit_animal_info = avatar.spirit_animal.get_info() if avatar.spirit_animal is not None else ""
@@ -81,7 +81,7 @@ def get_avatar_info(avatar: "Avatar", detailed: bool = False) -> dict:
"功法": technique_info,
"境界": cultivation_info,
"特质": personas_info,
"物品": items_info,
"材料": materials_info,
"外貌": appearance_info,
"兵器": weapon_info,
"辅助装备": auxiliary_info,
@@ -185,13 +185,13 @@ def get_avatar_structured_info(avatar: "Avatar") -> dict:
else:
info["auxiliary"] = None
# 5. 物品 (Items)
items_list = []
for item, count in avatar.items.items():
i_info = item.get_structured_info()
i_info["count"] = count
items_list.append(i_info)
info["items"] = items_list
# 5. 材料 (Materials)
materials_list = []
for material, count in avatar.materials.items():
m_info = material.get_structured_info()
m_info["count"] = count
materials_list.append(m_info)
info["materials"] = materials_list
# 6. 关系 (Relations)
relations_list = []

View File

@@ -3,73 +3,74 @@ Avatar 物品与装备管理 Mixin
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Optional, Any, Union
if TYPE_CHECKING:
from src.classes.avatar.core import Avatar
from src.classes.item import Item
from src.classes.material import Material
from src.classes.weapon import Weapon
from src.classes.auxiliary import Auxiliary
from src.classes.elixir import Elixir
class InventoryMixin:
"""物品与装备管理相关方法"""
def add_item(self: "Avatar", item: "Item", quantity: int = 1) -> None:
def add_material(self: "Avatar", material: "Material", quantity: int = 1) -> None:
"""
添加物品到背包
Args:
item: 要添加的物品
material: 要添加的物品
quantity: 添加数量默认为1
"""
if quantity <= 0:
return
if item in self.items:
self.items[item] += quantity
if material in self.materials:
self.materials[material] += quantity
else:
self.items[item] = quantity
self.materials[material] = quantity
def remove_item(self: "Avatar", item: "Item", quantity: int = 1) -> bool:
def remove_material(self: "Avatar", material: "Material", quantity: int = 1) -> bool:
"""
从背包移除物品
从背包移除材料
Args:
item: 要移除的物品
material: 要移除的材料
quantity: 移除数量默认为1
Returns:
bool: 是否成功移除(如果物品不足则返回False
bool: 是否成功移除(如果材料不足则返回False
"""
if quantity <= 0:
return True
if item not in self.items:
if material not in self.materials:
return False
if self.items[item] < quantity:
if self.materials[material] < quantity:
return False
self.items[item] -= quantity
self.materials[material] -= quantity
# 如果数量为0从字典中移除该物品
if self.items[item] == 0:
del self.items[item]
if self.materials[material] == 0:
del self.materials[material]
return True
def get_item_quantity(self: "Avatar", item: "Item") -> int:
def get_material_quantity(self: "Avatar", material: "Material") -> int:
"""
获取指定物品的数量
获取指定材料的数量
Args:
item: 要查询的物品
material: 要查询的材料
Returns:
int: 物品数量,如果没有该物品则返回0
int: 材料数量,如果没有该材料则返回0
"""
return self.items.get(item, 0)
return self.materials.get(material, 0)
def change_weapon(self: "Avatar", new_weapon: "Weapon") -> None:
"""
@@ -106,20 +107,20 @@ class InventoryMixin:
# ==================== 出售接口 ====================
def sell_item(self: "Avatar", item: "Item", quantity: int = 1) -> int:
def sell_material(self: "Avatar", material: "Material", quantity: int = 1) -> int:
"""
出售材料物品,返回获得的灵石数量。
应用 extra_item_sell_price_multiplier 效果。
"""
from src.classes.prices import prices
if quantity <= 0 or self.get_item_quantity(item) < quantity:
if quantity <= 0 or self.get_material_quantity(material) < quantity:
return 0
self.remove_item(item, quantity)
self.remove_material(material, quantity)
# 使用统一的卖出价格接口(包含所有加成逻辑)
unit_price = prices.get_selling_price(item, self)
unit_price = prices.get_selling_price(material, self)
total = unit_price * quantity
self.magic_stone = self.magic_stone + total
@@ -155,3 +156,132 @@ class InventoryMixin:
self.magic_stone = self.magic_stone + total
return total
def sell_elixir(self: "Avatar", elixir: "Elixir") -> int:
"""
出售丹药,返回获得的灵石数量。
"""
from src.classes.prices import prices
# 使用统一的卖出价格接口
total = prices.get_selling_price(elixir, self)
self.magic_stone = self.magic_stone + total
return total
# ==================== 购买接口 ====================
def can_buy_item(self: "Avatar", obj: Any) -> tuple[bool, str]:
"""
检查是否可以购买指定物品。
涵盖价格检查、境界限制、耐药性等。
"""
from src.classes.elixir import Elixir
from src.classes.prices import prices
from src.classes.cultivation import Realm
# 1. 检查价格
price = prices.get_buying_price(obj, self)
if self.magic_stone < price:
return False, f"灵石不足 (需要 {price})"
# 2. 丹药特殊检查
if isinstance(obj, Elixir):
# 商店业务规则:当前仅开放练气期丹药购买
if obj.realm != Realm.Qi_Refinement:
return False, "当前仅开放练气期丹药购买"
# 境界限制
if obj.realm > self.cultivation_progress.realm:
return False, f"境界不足,无法承受药力 ({obj.realm.value})"
# 耐药性/生效中检查
for consumed in self.elixirs:
if consumed.elixir.id == obj.id:
if not consumed.is_completely_expired(int(self.world.month_stamp)):
return False, "药效尚存,无法重复服用"
return True, ""
def buy_item(self: "Avatar", obj: Any) -> dict:
"""
执行购买逻辑。
包括扣款、获得物品(服用/入包/装备)、以旧换新。
返回交易报告 dict。
"""
import copy
from src.classes.elixir import Elixir
from src.classes.weapon import Weapon
from src.classes.auxiliary import Auxiliary
from src.classes.material import Material
from src.classes.prices import prices
report = {
"success": True,
"cost": 0,
"action_type": "store", # store, consume, equip
"sold_item_name": None,
"sold_item_refund": 0
}
# 1. 扣款
price = prices.get_buying_price(obj, self)
self.magic_stone -= price
report["cost"] = price
# 2. 交付
if isinstance(obj, Elixir):
# 购买即服用
self.consume_elixir(obj)
report["action_type"] = "consume"
elif isinstance(obj, Material):
# 放入背包
self.add_material(obj)
report["action_type"] = "store"
elif isinstance(obj, (Weapon, Auxiliary)):
# 装备需要深拷贝
new_equip = copy.deepcopy(obj)
# 尝试卖出旧装备并换上新装备
sold_name, refund = self._equip_and_trade_in(new_equip)
report["action_type"] = "equip"
if sold_name:
report["sold_item_name"] = sold_name
report["sold_item_refund"] = refund
return report
def _equip_and_trade_in(self: "Avatar", new_equip: Union["Weapon", "Auxiliary"]) -> tuple[str | None, int]:
"""
内部方法:装备新物品,并尝试卖出旧物品(如果有)。
返回: (旧物品名称, 卖出金额)
"""
from src.classes.weapon import Weapon
from src.classes.auxiliary import Auxiliary
sold_name = None
refund = 0
if isinstance(new_equip, Weapon):
# 检查是否有旧兵器
if self.weapon:
sold_name = self.weapon.name
# sell_weapon 会把旧兵器加到 circulation 并加钱给 avatar
# 注意sell_weapon 不会 clear self.weapon也不会 deepcopy因为是直接把引用给 circulation
# 这是正确的,旧对象给系统,新对象上身
refund = self.sell_weapon(self.weapon)
# 换上新兵器 (覆盖 self.weapon)
self.change_weapon(new_equip)
elif isinstance(new_equip, Auxiliary):
# 检查是否有旧法宝
if self.auxiliary:
sold_name = self.auxiliary.name
refund = self.sell_auxiliary(self.auxiliary)
# 换上新法宝
self.change_auxiliary(new_equip)
return sold_name, refund