diff --git a/README.md b/README.md index 3a634c6..f675850 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ - [ ] 兽潮 ### ⚔️ 战斗系统 -- [ ] 战斗方式设计 +- [ ] 战斗方式设计(灵根影响技能与战斗风格) - [ ] 优劣互克关系 - ✅ 胜率计算系统(简单) - [ ] 战斗规则引擎 diff --git a/src/classes/action.py b/src/classes/action.py index 96fd31a..5cf369e 100644 --- a/src/classes/action.py +++ b/src/classes/action.py @@ -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) diff --git a/src/classes/age.py b/src/classes/age.py index 4ab5283..dd20176 100644 --- a/src/classes/age.py +++ b/src/classes/age.py @@ -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: """返回年龄的详细字符串表示""" diff --git a/src/classes/avatar.py b/src/classes/avatar.py index eb65502..9fffe61 100644 --- a/src/classes/avatar.py +++ b/src/classes/avatar.py @@ -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) # 添加灵石信息 diff --git a/src/classes/cultivation.py b/src/classes/cultivation.py index 6bfe7f6..4637158 100644 --- a/src/classes/cultivation.py +++ b/src/classes/cultivation.py @@ -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, } \ No newline at end of file diff --git a/src/classes/root.py b/src/classes/root.py index 1e1e6b4..af282c4 100644 --- a/src/classes/root.py +++ b/src/classes/root.py @@ -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, -} \ No newline at end of file +extra_breakthrough_success_rate = { + Root.HEAVEN: 0.1, +} +extra_breakthrough_success_rate = defaultdict(lambda: 0, extra_breakthrough_success_rate) \ No newline at end of file diff --git a/src/run/run.py b/src/run/run.py index 7d816f5..532c498 100644 --- a/src/run/run.py +++ b/src/run/run.py @@ -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): diff --git a/src/sim/simulator.py b/src/sim/simulator.py index 03d7da5..2e68708 100644 --- a/src/sim/simulator.py +++ b/src/sim/simulator.py @@ -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) diff --git a/static/config.yml b/static/config.yml index 66e9579..ce878ea 100644 --- a/static/config.yml +++ b/static/config.yml @@ -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: diff --git a/static/templates/ai.txt b/static/templates/ai.txt index ca172d6..43f8ba3 100644 --- a/static/templates/ai.txt +++ b/static/templates/ai.txt @@ -19,4 +19,4 @@ 要求与约束: - 若需要先移动再修炼,请将 "MoveToRegion" 放在前面,随后接 "Cultivate"。 -- 若当前可突破,可在合适时机插入 "Breakthrough"。 \ No newline at end of file +- thought可从侧面体现出角色个性 \ No newline at end of file diff --git a/tests/test_basic.py b/tests/test_basic.py index 706a9d1..aae7be0 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -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 )