This commit is contained in:
bridge
2025-09-24 00:23:17 +08:00
parent 49df94a1a1
commit d07b6ebb87
11 changed files with 121 additions and 62 deletions

View File

@@ -86,7 +86,7 @@
- [ ] 兽潮
### ⚔️ 战斗系统
- [ ] 战斗方式设计
- [ ] 战斗方式设计(灵根影响技能与战斗风格)
- [ ] 优劣互克关系
- ✅ 胜率计算系统(简单)
- [ ] 战斗规则引擎

View File

@@ -6,7 +6,7 @@ import json
import inspect
from src.classes.essence import Essence, EssenceType
from src.classes.root import Root, get_essence_types_for_root
from src.classes.root import Root, get_essence_types_for_root, extra_breakthrough_success_rate
from src.classes.region import Region, CultivateRegion, NormalRegion, CityRegion
from src.classes.event import Event, NULL_EVENT
from src.classes.item import Item, items_by_name
@@ -335,16 +335,21 @@ class Cultivate(DefineAction, ActualActionMixin):
@long_action(step_month=1)
class Breakthrough(DefineAction, ActualActionMixin):
"""
突破境界
突破境界
成功率由 `CultivationProgress.get_breakthrough_success_rate()` 决定;
失败时按 `CultivationProgress.get_breakthrough_fail_reduce_lifespan()` 减少寿元(年)。
"""
COMMENT = "尝试突破境界"
COMMENT = "尝试突破境界(成功增加寿元上限,失败折损寿元上限;境界越高,成功率越低。)"
DOABLES_REQUIREMENTS = "角色处于瓶颈时"
PARAMS = {}
def calc_success_rate(self) -> float:
"""
计算突破境界的成功率
计算突破境界的成功率(由修为进度给出)
"""
return 0.5
base = self.avatar.cultivation_progress.get_breakthrough_success_rate()
bonus = extra_breakthrough_success_rate[self.avatar.root]
# 夹紧到 [0, 1]
return max(0.0, min(1.0, base + bonus))
def _execute(self) -> None:
"""
@@ -360,6 +365,12 @@ class Breakthrough(DefineAction, ActualActionMixin):
# 突破成功时更新HP和MP的最大值
if new_realm != old_realm:
self._update_hp_mp_on_breakthrough(new_realm)
# 成功:确保最大寿元至少达到新境界的基线
self.avatar.age.ensure_max_lifespan_at_least_realm_base(new_realm)
else:
# 突破失败:减少最大寿元上限
reduce_years = self.avatar.cultivation_progress.get_breakthrough_fail_reduce_lifespan()
self.avatar.age.decrease_max_lifespan(reduce_years)
def _update_hp_mp_on_breakthrough(self, new_realm):
"""
@@ -487,18 +498,24 @@ class Harvest(DefineAction, ActualActionMixin):
COMMENT = "在当前区域采集植物,获取植物材料"
DOABLES_REQUIREMENTS = "在有植物的普通区域且avatar的境界必须大于等于植物的境界"
PARAMS = {}
def get_available_plants(self) -> list[Plant]:
"""
获取avatar境界足够的植物
"""
region = self.avatar.tile.region
avatar_realm = self.avatar.cultivation_progress.realm
return [plant for plant in region.plants if avatar_realm >= plant.realm]
def _execute(self) -> None:
"""
执行采集动作
"""
region = self.avatar.tile.region
success_rate = self.get_success_rate()
available_plants = self.get_available_plants()
if random.random() < success_rate:
# 成功采集从avatar境界足够的植物中随机选择一种
avatar_realm = self.avatar.cultivation_progress.realm
available_plants = [plant for plant in region.plants if avatar_realm >= plant.realm]
target_plant = random.choice(available_plants)
# 随机选择该植物的一种物品
item = random.choice(target_plant.items)
@@ -523,15 +540,12 @@ class Harvest(DefineAction, ActualActionMixin):
判断是否可以采集必须在有植物的普通区域且avatar的境界必须大于等于植物的境界
"""
region = self.avatar.tile.region
if not isinstance(region, NormalRegion) or len(region.plants) == 0:
if not isinstance(region, NormalRegion):
return False
# 检查avatar的境界是否足够采集区域内的植物
avatar_realm = self.avatar.cultivation_progress.realm
for plant in region.plants:
if avatar_realm >= plant.realm:
return True
return False
avaliable_plants = self.get_available_plants()
if len(avaliable_plants) == 0:
return False
return True
@long_action(step_month=1)

View File

@@ -9,42 +9,68 @@ class Age:
"""
# 各境界的基础期望寿命(年)
# REALM_LIFESPAN = {
# Realm.Qi_Refinement: 100, # 练气期100年
# Realm.Foundation_Establishment: 200, # 筑基期200年
# Realm.Core_Formation: 500, # 金丹期500年
# Realm.Nascent_Soul: 1000, # 元婴期1000年
# }
REALM_LIFESPAN = {
Realm.Qi_Refinement: 50, # 练气期100年
Realm.Foundation_Establishment: 60, # 筑基期200年
Realm.Core_Formation: 70, # 金丹期500年
Realm.Nascent_Soul: 80, # 元婴期1000年
Realm.Qi_Refinement: 80, # 练气期100年
Realm.Foundation_Establishment: 120, # 筑基期200年
Realm.Core_Formation: 200, # 金丹期500年
Realm.Nascent_Soul: 500, # 元婴期1000年
}
def __init__(self, age: int):
def __init__(self, age: int, realm: Realm):
self.age = age
# 最大理论寿元(年),初始化为 max(境界基线, 当前年龄+1)
self.max_lifespan: int = max(self.get_base_expected_lifespan(realm), self.age + 1)
def get_age(self) -> int:
"""获取当前年龄"""
return self.age
def get_expected_lifespan(self, realm: Realm) -> int:
"""获取期望寿命"""
"""获取期望寿命(即当前最大寿元上限)。"""
return self.max_lifespan
def get_base_expected_lifespan(self, realm: Realm) -> int:
"""获取境界对应的基线期望寿命不受max_lifespan影响"""
return self.REALM_LIFESPAN.get(realm, 100)
def set_initial_max_lifespan(self, realm: Realm) -> None:
"""构造时已设置最大寿元,此处保持与构造策略一致。"""
base = self.get_base_expected_lifespan(realm)
self.max_lifespan = max(base, self.age + 1)
def ensure_max_lifespan_at_least_realm_base(self, realm: Realm) -> None:
"""确保最大寿元至少达到 max(该境界基线, 当前年龄+1)。"""
base = self.get_base_expected_lifespan(realm)
floor_value = max(base, self.age + 1)
if self.max_lifespan < floor_value:
self.max_lifespan = floor_value
def increase_max_lifespan(self, years: int) -> None:
"""提升最大寿元上限。"""
if years <= 0:
return
self.max_lifespan = (self.max_lifespan or 0) + years
def decrease_max_lifespan(self, years: int) -> None:
"""降低最大寿元上限(可以低于当前年龄)。"""
if years <= 0:
return
self.max_lifespan = self.max_lifespan - years
def get_death_probability(self, realm: Realm) -> float:
def get_death_probability(self, realm: Realm | None = None) -> float:
"""
计算当月老死的概率
返回:
老死概率范围0.0-0.1
"""
if self.age < self.get_expected_lifespan(realm):
expected = self.max_lifespan if realm is None else self.get_expected_lifespan(realm)
if self.age < expected:
return 0.0
# 超过期望寿命的年数
years_over_lifespan = self.age - self.get_expected_lifespan(realm)
years_over_lifespan = self.age - expected
# 基础概率每超过1年增加0.01的概率
death_probability = min(years_over_lifespan * 0.01, 0.1)
@@ -78,9 +104,19 @@ class Age:
"""
self.age = self.calculate_age(current_month_stamp, birth_month_stamp)
def get_lifespan_progress(self, realm: Realm | None = None) -> tuple[int, int]:
"""返回 (当前年龄, 期望寿命)。realm为空时使用当前最大寿元。"""
expected = self.max_lifespan if realm is None else self.get_expected_lifespan(realm)
return self.age, expected
def is_elderly(self, realm: Realm | None = None) -> bool:
"""是否超过期望寿命。realm为空时使用当前最大寿元。"""
expected = self.max_lifespan if realm is None else self.get_expected_lifespan(realm)
return self.age >= expected
def __str__(self) -> str:
"""返回年龄的字符串表示"""
return str(self.age)
max_str = str(self.max_lifespan)
return f"{self.age}/{max_str}"
def __repr__(self) -> str:
"""返回年龄的详细字符串表示"""

View File

@@ -83,6 +83,8 @@ class Avatar:
max_mp = MP_MAX_BY_REALM.get(self.cultivation_progress.realm, 100)
self.hp = HP(max_hp, max_hp)
self.mp = MP(max_mp, max_mp)
# 最大寿元已在 Age 构造时基于境界初始化
# 如果personas列表为空则随机分配两个不互斥的persona
if not self.personas:
@@ -182,6 +184,7 @@ class Avatar:
action_name, _ = pair
action = self.create_action(action_name)
doable = action.is_doable
assert isinstance(doable, bool)
return doable
async def act(self) -> List[Event]:
@@ -356,7 +359,7 @@ class Avatar:
# 构建personas的提示词信息
personas_prompts = []
for i, persona in enumerate(self.personas, 1):
personas_prompts.append(f"个性{i}{persona.prompt}")
personas_prompts.append(f"个性{i}{persona.prompt}")
personas_info = "\n".join(personas_prompts)
# 添加灵石信息

View File

@@ -185,10 +185,13 @@ class CultivationProgress:
def is_in_bottleneck(self) -> bool:
"""
检查是否可以突破
其实就是再瓶颈期
是否处于瓶颈期。
如果级别在LEVEL_TO_BREAK_THROUGH中同时realm不是该级别对应的realm则处于瓶颈期。
"""
return self.level in LEVEL_TO_BREAK_THROUGH.keys()
for level_threshold, realm in LEVEL_TO_BREAK_THROUGH.items():
if self.level == level_threshold and self.realm != realm:
return True
return False
def can_break_through(self) -> bool:
"""
@@ -213,6 +216,12 @@ class CultivationProgress:
def __str__(self) -> str:
return f"{self.realm.value}{self.stage.value}({self.level}级)。在瓶颈期:{self.is_in_bottleneck()}"
def get_breakthrough_success_rate(self) -> float:
return breakthrough_success_rate_by_realm[self.realm]
def get_breakthrough_fail_reduce_lifespan(self) -> int:
return breakthrough_fail_reduce_lifespan_by_realm[self.realm]
breakthrough_success_rate_by_realm = {
@@ -223,8 +232,8 @@ breakthrough_success_rate_by_realm = {
}
breakthrough_fail_reduce_lifespan_by_realm = {
Realm.Qi_Refinement: 10,
Realm.Foundation_Establishment: 20,
Realm.Core_Formation: 30,
Realm.Nascent_Soul: 40,
Realm.Qi_Refinement: 5,
Realm.Foundation_Establishment: 10,
Realm.Core_Formation: 15,
Realm.Nascent_Soul: 20,
}

View File

@@ -6,6 +6,7 @@
"""
from enum import Enum
from typing import List, Tuple
from collections import defaultdict
from src.classes.essence import EssenceType
@@ -76,15 +77,7 @@ def get_essence_types_for_root(root: Root) -> List[EssenceType]:
"""
return [_essence_by_element[e] for e in root.elements]
roots = {
"": Root.GOLD,
"": Root.WOOD,
"": Root.WATER,
"": Root.FIRE,
"": Root.EARTH,
"": Root.THUNDER,
"": Root.ICE,
"": Root.WIND,
"": Root.DARK,
"": Root.HEAVEN,
}
extra_breakthrough_success_rate = {
Root.HEAVEN: 0.1,
}
extra_breakthrough_success_rate = defaultdict(lambda: 0, extra_breakthrough_success_rate)

View File

@@ -59,8 +59,8 @@ def make_avatars(world: World, count: int = 12, current_month_stamp: MonthStamp
level = random.randint(0, 120)
cultivation_progress = CultivationProgress(level)
# 创建Age实例传入年龄
age = Age(age_years)
# 创建Age实例传入年龄与当前境界
age = Age(age_years, cultivation_progress.realm)
# 找一个非海域的出生点
for _ in range(200):

View File

@@ -3,6 +3,7 @@ import random
from src.classes.calendar import Month, Year, MonthStamp
from src.classes.avatar import Avatar, get_new_avatar_from_ordinary, Gender
from src.classes.age import Age
from src.classes.cultivation import Realm
from src.classes.world import World
from src.classes.event import Event, is_null_event
from src.classes.ai import llm_ai, rule_ai
@@ -54,7 +55,9 @@ class Simulator:
# 结算角色行为
for avatar_id, avatar in self.world.avatar_manager.avatars.items():
new_events = await avatar.act()
new_events = []
if avatar.is_next_action_doable():
new_events = await avatar.act()
if new_events:
events.extend(new_events)
if avatar.death_by_old_age():
@@ -72,7 +75,7 @@ class Simulator:
age = random.randint(16, 60)
gender = random.choice(list(Gender))
name = get_random_name(gender)
new_avatar = get_new_avatar_from_ordinary(self.world, self.world.month_stamp, name, Age(age))
new_avatar = get_new_avatar_from_ordinary(self.world, self.world.month_stamp, name, Age(age, Realm.Qi_Refinement))
self.world.avatar_manager.avatars[new_avatar.id] = new_avatar
event = Event(self.world.month_stamp, f"{new_avatar.name}晋升为修士了。")
events.append(event)

View File

@@ -12,7 +12,7 @@ ai:
max_decide_num: 3
game:
init_npc_num: 2
init_npc_num: 3
npc_birth_rate_per_month: 0.001
df:

View File

@@ -19,4 +19,4 @@
要求与约束:
- 若需要先移动再修炼,请将 "MoveToRegion" 放在前面,随后接 "Cultivate"。
- 若当前可突破,可在合适时机插入 "Breakthrough"。
- thought可从侧面体现出角色个性

View File

@@ -4,6 +4,7 @@ from src.classes.calendar import Month, Year, MonthStamp, create_month_stamp
from src.classes.world import World
from src.classes.tile import Map, TileType
from src.classes.age import Age
from src.classes.cultivation import Realm
from src.utils.names import get_random_name
def test_basic():
@@ -22,7 +23,7 @@ def test_basic():
name=get_random_name(Gender.MALE),
id=get_avatar_id(),
birth_month_stamp=create_month_stamp(Year(2000), Month.JANUARY),
age=Age(20),
age=Age(20, Realm.Qi_Refinement),
gender=Gender.MALE
)