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 import inspect
from src.classes.essence import Essence, EssenceType 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.region import Region, CultivateRegion, NormalRegion, CityRegion
from src.classes.event import Event, NULL_EVENT from src.classes.event import Event, NULL_EVENT
from src.classes.item import Item, items_by_name from src.classes.item import Item, items_by_name
@@ -335,16 +335,21 @@ class Cultivate(DefineAction, ActualActionMixin):
@long_action(step_month=1) @long_action(step_month=1)
class Breakthrough(DefineAction, ActualActionMixin): class Breakthrough(DefineAction, ActualActionMixin):
""" """
突破境界 突破境界
成功率由 `CultivationProgress.get_breakthrough_success_rate()` 决定;
失败时按 `CultivationProgress.get_breakthrough_fail_reduce_lifespan()` 减少寿元(年)。
""" """
COMMENT = "尝试突破境界" COMMENT = "尝试突破境界(成功增加寿元上限,失败折损寿元上限;境界越高,成功率越低。)"
DOABLES_REQUIREMENTS = "角色处于瓶颈时" DOABLES_REQUIREMENTS = "角色处于瓶颈时"
PARAMS = {} PARAMS = {}
def calc_success_rate(self) -> float: 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: def _execute(self) -> None:
""" """
@@ -360,6 +365,12 @@ class Breakthrough(DefineAction, ActualActionMixin):
# 突破成功时更新HP和MP的最大值 # 突破成功时更新HP和MP的最大值
if new_realm != old_realm: if new_realm != old_realm:
self._update_hp_mp_on_breakthrough(new_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): def _update_hp_mp_on_breakthrough(self, new_realm):
""" """
@@ -487,18 +498,24 @@ class Harvest(DefineAction, ActualActionMixin):
COMMENT = "在当前区域采集植物,获取植物材料" COMMENT = "在当前区域采集植物,获取植物材料"
DOABLES_REQUIREMENTS = "在有植物的普通区域且avatar的境界必须大于等于植物的境界" DOABLES_REQUIREMENTS = "在有植物的普通区域且avatar的境界必须大于等于植物的境界"
PARAMS = {} 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: def _execute(self) -> None:
""" """
执行采集动作 执行采集动作
""" """
region = self.avatar.tile.region
success_rate = self.get_success_rate() success_rate = self.get_success_rate()
available_plants = self.get_available_plants()
if random.random() < success_rate: if random.random() < success_rate:
# 成功采集从avatar境界足够的植物中随机选择一种 # 成功采集从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) target_plant = random.choice(available_plants)
# 随机选择该植物的一种物品 # 随机选择该植物的一种物品
item = random.choice(target_plant.items) item = random.choice(target_plant.items)
@@ -523,15 +540,12 @@ class Harvest(DefineAction, ActualActionMixin):
判断是否可以采集必须在有植物的普通区域且avatar的境界必须大于等于植物的境界 判断是否可以采集必须在有植物的普通区域且avatar的境界必须大于等于植物的境界
""" """
region = self.avatar.tile.region region = self.avatar.tile.region
if not isinstance(region, NormalRegion) or len(region.plants) == 0: if not isinstance(region, NormalRegion):
return False return False
avaliable_plants = self.get_available_plants()
# 检查avatar的境界是否足够采集区域内的植物 if len(avaliable_plants) == 0:
avatar_realm = self.avatar.cultivation_progress.realm return False
for plant in region.plants: return True
if avatar_realm >= plant.realm:
return True
return False
@long_action(step_month=1) @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_LIFESPAN = {
Realm.Qi_Refinement: 50, # 练气期100年 Realm.Qi_Refinement: 80, # 练气期100年
Realm.Foundation_Establishment: 60, # 筑基期200年 Realm.Foundation_Establishment: 120, # 筑基期200年
Realm.Core_Formation: 70, # 金丹期500年 Realm.Core_Formation: 200, # 金丹期500年
Realm.Nascent_Soul: 80, # 元婴期1000年 Realm.Nascent_Soul: 500, # 元婴期1000年
} }
def __init__(self, age: int): def __init__(self, age: int, realm: Realm):
self.age = age self.age = age
# 最大理论寿元(年),初始化为 max(境界基线, 当前年龄+1)
self.max_lifespan: int = max(self.get_base_expected_lifespan(realm), self.age + 1)
def get_age(self) -> int: def get_age(self) -> int:
"""获取当前年龄""" """获取当前年龄"""
return self.age return self.age
def get_expected_lifespan(self, realm: Realm) -> int: 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) 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 老死概率范围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 return 0.0
# 超过期望寿命的年数 # 超过期望寿命的年数
years_over_lifespan = self.age - self.get_expected_lifespan(realm) years_over_lifespan = self.age - expected
# 基础概率每超过1年增加0.01的概率 # 基础概率每超过1年增加0.01的概率
death_probability = min(years_over_lifespan * 0.01, 0.1) 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) 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: def __str__(self) -> str:
"""返回年龄的字符串表示""" max_str = str(self.max_lifespan)
return str(self.age) return f"{self.age}/{max_str}"
def __repr__(self) -> 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) max_mp = MP_MAX_BY_REALM.get(self.cultivation_progress.realm, 100)
self.hp = HP(max_hp, max_hp) self.hp = HP(max_hp, max_hp)
self.mp = MP(max_mp, max_mp) self.mp = MP(max_mp, max_mp)
# 最大寿元已在 Age 构造时基于境界初始化
# 如果personas列表为空则随机分配两个不互斥的persona # 如果personas列表为空则随机分配两个不互斥的persona
if not self.personas: if not self.personas:
@@ -182,6 +184,7 @@ class Avatar:
action_name, _ = pair action_name, _ = pair
action = self.create_action(action_name) action = self.create_action(action_name)
doable = action.is_doable doable = action.is_doable
assert isinstance(doable, bool)
return doable return doable
async def act(self) -> List[Event]: async def act(self) -> List[Event]:
@@ -356,7 +359,7 @@ class Avatar:
# 构建personas的提示词信息 # 构建personas的提示词信息
personas_prompts = [] personas_prompts = []
for i, persona in enumerate(self.personas, 1): 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) personas_info = "\n".join(personas_prompts)
# 添加灵石信息 # 添加灵石信息

View File

@@ -185,10 +185,13 @@ class CultivationProgress:
def is_in_bottleneck(self) -> bool: 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: def can_break_through(self) -> bool:
""" """
@@ -213,6 +216,12 @@ class CultivationProgress:
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.realm.value}{self.stage.value}({self.level}级)。在瓶颈期:{self.is_in_bottleneck()}" 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 = { breakthrough_success_rate_by_realm = {
@@ -223,8 +232,8 @@ breakthrough_success_rate_by_realm = {
} }
breakthrough_fail_reduce_lifespan_by_realm = { breakthrough_fail_reduce_lifespan_by_realm = {
Realm.Qi_Refinement: 10, Realm.Qi_Refinement: 5,
Realm.Foundation_Establishment: 20, Realm.Foundation_Establishment: 10,
Realm.Core_Formation: 30, Realm.Core_Formation: 15,
Realm.Nascent_Soul: 40, Realm.Nascent_Soul: 20,
} }

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import random
from src.classes.calendar import Month, Year, MonthStamp from src.classes.calendar import Month, Year, MonthStamp
from src.classes.avatar import Avatar, get_new_avatar_from_ordinary, Gender from src.classes.avatar import Avatar, get_new_avatar_from_ordinary, Gender
from src.classes.age import Age from src.classes.age import Age
from src.classes.cultivation import Realm
from src.classes.world import World from src.classes.world import World
from src.classes.event import Event, is_null_event from src.classes.event import Event, is_null_event
from src.classes.ai import llm_ai, rule_ai 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(): 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: if new_events:
events.extend(new_events) events.extend(new_events)
if avatar.death_by_old_age(): if avatar.death_by_old_age():
@@ -72,7 +75,7 @@ class Simulator:
age = random.randint(16, 60) age = random.randint(16, 60)
gender = random.choice(list(Gender)) gender = random.choice(list(Gender))
name = get_random_name(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 self.world.avatar_manager.avatars[new_avatar.id] = new_avatar
event = Event(self.world.month_stamp, f"{new_avatar.name}晋升为修士了。") event = Event(self.world.month_stamp, f"{new_avatar.name}晋升为修士了。")
events.append(event) events.append(event)

View File

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

View File

@@ -19,4 +19,4 @@
要求与约束: 要求与约束:
- 若需要先移动再修炼,请将 "MoveToRegion" 放在前面,随后接 "Cultivate"。 - 若需要先移动再修炼,请将 "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.world import World
from src.classes.tile import Map, TileType from src.classes.tile import Map, TileType
from src.classes.age import Age from src.classes.age import Age
from src.classes.cultivation import Realm
from src.utils.names import get_random_name from src.utils.names import get_random_name
def test_basic(): def test_basic():
@@ -22,7 +23,7 @@ def test_basic():
name=get_random_name(Gender.MALE), name=get_random_name(Gender.MALE),
id=get_avatar_id(), id=get_avatar_id(),
birth_month_stamp=create_month_stamp(Year(2000), Month.JANUARY), birth_month_stamp=create_month_stamp(Year(2000), Month.JANUARY),
age=Age(20), age=Age(20, Realm.Qi_Refinement),
gender=Gender.MALE gender=Gender.MALE
) )