diff --git a/src/classes/action.py b/src/classes/action.py index cff3112..f43806c 100644 --- a/src/classes/action.py +++ b/src/classes/action.py @@ -6,6 +6,7 @@ import random 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.alignment import Alignment from src.classes.event import Event, NULL_EVENT from src.classes.item import Item, items_by_name from src.classes.prices import prices @@ -689,3 +690,77 @@ class Battle(DefineAction, ActualActionMixin): winner, loser = res return [Event(self.world.month_stamp, f"{winner} 战胜了 {loser}")] return [] + + +@long_action(step_month=3) +class PlunderMortals(DefineAction, ActualActionMixin): + """ + 在城镇对凡人进行搜刮,获取少量灵石。 + 仅邪阵营可执行。 + """ + COMMENT = "在城镇搜刮凡人,获取少量灵石" + DOABLES_REQUIREMENTS = "仅限城市区域,且角色阵营为‘邪’" + PARAMS = {} + GAIN = 20 + + def _execute(self) -> None: + region = self.avatar.tile.region + if not isinstance(region, CityRegion): + return + gain = self.GAIN + self.avatar.magic_stone = self.avatar.magic_stone + gain + + def can_start(self) -> bool: + region = self.avatar.tile.region + if not isinstance(region, CityRegion): + return False + return self.avatar.alignment == Alignment.EVIL + + def start(self) -> Event: + return Event(self.world.month_stamp, f"{self.avatar.name} 在城镇开始搜刮凡人") + + def step(self) -> tuple[StepStatus, list[Event]]: + self.execute() + return (StepStatus.COMPLETED if getattr(self, "is_finished")() else StepStatus.RUNNING), [] + + def finish(self) -> list[Event]: + return [] + + +@long_action(step_month=3) +class HelpMortals(DefineAction, ActualActionMixin): + """ + 在城镇帮助凡人,消耗少量灵石。 + 仅正阵营可执行。 + """ + COMMENT = "在城镇帮助凡人,消耗少量灵石" + DOABLES_REQUIREMENTS = "仅限城市区域,且角色阵营为‘正’,并且灵石足够" + PARAMS = {} + COST = 10 + + def _execute(self) -> None: + region = self.avatar.tile.region + if not isinstance(region, CityRegion): + return + cost = self.COST + if getattr(self.avatar.magic_stone, "value", 0) >= cost: + self.avatar.magic_stone = self.avatar.magic_stone - cost + + def can_start(self) -> bool: + region = self.avatar.tile.region + if not isinstance(region, CityRegion): + return False + if self.avatar.alignment != Alignment.RIGHTEOUS: + return False + cost = self.COST + return getattr(self.avatar.magic_stone, "value", 0) >= cost + + def start(self) -> Event: + return Event(self.world.month_stamp, f"{self.avatar.name} 在城镇开始帮助凡人") + + def step(self) -> tuple[StepStatus, list[Event]]: + self.execute() + return (StepStatus.COMPLETED if getattr(self, "is_finished")() else StepStatus.RUNNING), [] + + def finish(self) -> list[Event]: + return [] diff --git a/src/classes/actions.py b/src/classes/actions.py index 8e23bd0..31bbdec 100644 --- a/src/classes/actions.py +++ b/src/classes/actions.py @@ -13,6 +13,8 @@ from src.classes.action import ( Harvest, Sold, Battle, + PlunderMortals, + HelpMortals, ) from src.classes.mutual_action import ( DriveAway, @@ -33,6 +35,8 @@ ALL_ACTION_CLASSES = [ Hunt, Harvest, Sold, + PlunderMortals, + HelpMortals, # 互动相关动作(实际执行的反馈动作也纳入) DriveAway, Attack, @@ -49,6 +53,8 @@ ALL_ACTUAL_ACTION_CLASSES = [ Hunt, Harvest, Sold, + PlunderMortals, + HelpMortals, DriveAway, Attack, ] diff --git a/src/classes/alignment.py b/src/classes/alignment.py new file mode 100644 index 0000000..b4617bd --- /dev/null +++ b/src/classes/alignment.py @@ -0,0 +1,21 @@ +from enum import Enum + + +class Alignment(Enum): + """ + 阵营:正/邪。 + 值使用英文,便于与代码/保存兼容;__str__ 返回中文。 + """ + RIGHTEOUS = "righteous" # 正 + EVIL = "evil" # 邪 + + def __str__(self) -> str: + return alignment_strs.get(self, self.value) + + +alignment_strs = { + Alignment.RIGHTEOUS: "正", + Alignment.EVIL: "邪", +} + + diff --git a/src/classes/avatar.py b/src/classes/avatar.py index 966c3ad..7693aee 100644 --- a/src/classes/avatar.py +++ b/src/classes/avatar.py @@ -25,6 +25,7 @@ from src.utils.id_generator import get_avatar_id from src.utils.config import CONFIG from src.classes.relation import Relation from src.run.log import get_logger +from src.classes.alignment import Alignment persona_num = CONFIG.avatar.persona_num @@ -73,6 +74,7 @@ class Avatar: hp: HP = field(default_factory=lambda: HP(0, 0)) # 将在__post_init__中初始化 mp: MP = field(default_factory=lambda: MP(0, 0)) # 将在__post_init__中初始化 relations: dict["Avatar", Relation] = field(default_factory=dict) + alignment: Alignment = field(default_factory=lambda: random.choice(list(Alignment))) def __post_init__(self): """ @@ -101,7 +103,7 @@ class Avatar: 尽量多打一些,因为会用来给LLM进行决策 """ personas_str = ", ".join([persona.name for persona in self.personas]) - return f"Avatar(id={self.id}, 性别={self.gender}, 年龄={self.age}, name={self.name}, 区域={self.tile.region.name}, 灵根={str(self.root)}, 境界={self.cultivation_progress}, HP={self.hp}, MP={self.mp}, 个性={personas_str})" + return f"Avatar(id={self.id}, 性别={self.gender}, 年龄={self.age}, name={self.name}, 阵营={self.alignment}, 区域={self.tile.region.name}, 灵根={str(self.root)}, 境界={self.cultivation_progress}, HP={self.hp}, MP={self.mp}, 个性={personas_str})" def __str__(self) -> str: return self.get_info() @@ -437,11 +439,11 @@ class Avatar: def get_other_avatar_info(self, other_avatar: "Avatar") -> str: """ - 仅显示三个字段:名字、境界、关系。 + 仅显示4个字段:名字、境界、关系、阵营。 """ relation = self.get_relation(other_avatar) relation_str = str(relation) - return f"{other_avatar.name},境界:{other_avatar.cultivation_progress.get_simple_info()},关系:{relation_str}" + return f"{other_avatar.name},境界:{other_avatar.cultivation_progress.get_simple_info()},关系:{relation_str},阵营:{other_avatar.alignment}" @property def move_step_length(self) -> int: diff --git a/src/classes/avatar_manager.py b/src/classes/avatar_manager.py index 50f3136..3fa5eae 100644 --- a/src/classes/avatar_manager.py +++ b/src/classes/avatar_manager.py @@ -25,4 +25,31 @@ class AvatarManager: same_region.append(other) return same_region + def remove_avatar(self, avatar_id: str) -> None: + """ + 从管理器中删除一个 avatar,并清理所有与其相关的双向关系。 + """ + avatar = self.avatars.get(avatar_id) + if avatar is None: + return + # 先清理与其直接记录的关系(会保持对称) + related = list(getattr(avatar, "relations", {}).keys()) + for other in related: + avatar.clear_relation(other) + # 再次扫一遍所有 avatar,确保不存在残留引用 + for other in list(self.avatars.values()): + if other is avatar: + continue + if getattr(other, "relations", None) is not None and avatar in other.relations: + other.clear_relation(avatar) + # 最后移除自身 + self.avatars.pop(avatar_id, None) + + def remove_avatars(self, avatar_ids: List[str]) -> None: + """ + 批量删除 avatars,并清理所有关系。 + """ + for aid in list(avatar_ids): + self.remove_avatar(aid) + diff --git a/src/front/rendering.py b/src/front/rendering.py index bb61f82..8452a2f 100644 --- a/src/front/rendering.py +++ b/src/front/rendering.py @@ -141,6 +141,7 @@ def draw_tooltip_for_avatar(pygame_mod, screen, colors, font, avatar: Avatar): f"{avatar.name}", f"性别: {avatar.gender}", f"年龄: {avatar.age}", + f"阵营: {avatar.alignment}", f"境界: {str(avatar.cultivation_progress)}", f"HP: {avatar.hp}", f"MP: {avatar.mp}", diff --git a/src/run/run.py b/src/run/run.py index 532c498..38f3a30 100644 --- a/src/run/run.py +++ b/src/run/run.py @@ -24,6 +24,7 @@ from src.utils.id_generator import get_avatar_id from src.utils.config import CONFIG from src.run.log import get_logger from src.classes.relation import Relation +from src.classes.alignment import Alignment def clamp(value: int, lo: int, hi: int) -> int: @@ -85,6 +86,8 @@ def make_avatars(world: World, count: int = 12, current_month_stamp: MonthStamp root=random.choice(list(Root)), # 随机选择灵根 ) avatar.tile = world.map.get_tile(x, y) + # 随机分配阵营(正/邪) + avatar.alignment = random.choice(list(Alignment)) avatars[avatar.id] = avatar # —— 为演示添加少量示例关系 —— avatar_list = list(avatars.values()) diff --git a/src/sim/simulator.py b/src/sim/simulator.py index b2c4a15..17ed8e9 100644 --- a/src/sim/simulator.py +++ b/src/sim/simulator.py @@ -56,7 +56,7 @@ class Simulator: if new_events: events.extend(new_events) - # 结算战斗等导致的死亡(HP<=0)与寿命逻辑 + # 结算战斗等导致的死亡逻辑 for avatar_id, avatar in list(self.world.avatar_manager.avatars.items()): if avatar.hp <= 0: death_avatar_ids.append(avatar_id) @@ -66,11 +66,15 @@ class Simulator: death_avatar_ids.append(avatar_id) event = Event(self.world.month_stamp, f"{avatar.name} 老死了,时年{avatar.age.get_age()}岁") events.append(event) + # 删除死亡的角色(由 AvatarManager 清理关系并移除) + if death_avatar_ids: + self.world.avatar_manager.remove_avatars(death_avatar_ids) + + # 寿命逻辑 + for avatar_id, avatar in self.world.avatar_manager.avatars.items(): avatar.update_age(self.world.month_stamp) - # 删除死亡的角色 - for avatar_id in death_avatar_ids: - self.world.avatar_manager.avatars.pop(avatar_id) + # 新角色 if random.random() < self.birth_rate: diff --git a/static/config.yml b/static/config.yml index b631b63..585caf2 100644 --- a/static/config.yml +++ b/static/config.yml @@ -20,4 +20,4 @@ df: ids_separator: ";" avatar: - persona_num: 1 \ No newline at end of file + persona_num: 3 \ No newline at end of file diff --git a/static/game_configs/persona.csv b/static/game_configs/persona.csv index 8261746..d4e1b7c 100644 --- a/static/game_configs/persona.csv +++ b/static/game_configs/persona.csv @@ -1,3 +1,22 @@ id,name,exclusion_ids,prompt ,,和本persona互斥的persona的id,输入给LLM的prompt -12,复仇,11;14,你是一个复仇心强的人,你绝不轻易放下仇怨,为了复仇愿意付出代价与时间。你要立刻复仇。 +1,理性,2;5,你是一个理性的人,你总是会用逻辑来思考问题,做事会谋定而后动。 +2,无常,1;9;20,你是一个无常的人,目标飘忽不定,不会长期坚持一个目标。 +3,怠惰,4;20,你是一个怠惰的人,你总是会拖延,不想努力,更热衷于享受人生。 +4,冒险,3;10,你是一个冒险的人,你总是会冒险,喜欢刺激,总想放手一搏。 +5,随性,1;20,你是一个随性的人,你总是会随机应变,性子到哪里了就是哪里,没有一定之规。 +6,贪财,,你是一个贪财的人,你对灵石和财富有着强烈的渴望。 +7,采药,,你是一个热爱采集的人,喜欢在山林中寻找各种奇花异草和灵药,对植物有着敏锐的直觉和深厚的兴趣。你认为大自然的恩赐需要用心去发现和珍惜。 +8,猎者,,你是一个热爱狩猎的人,享受在野外追踪猎物的刺激感,对各种动物的习性了如指掌。你相信通过狩猎能够磨练自己的意志和技能,获得更强大的力量。 +9,沉思,2,你是一个沉思的人,你总是会深思熟虑,思考问题比较有哲理。 +10,惜命,4;20,你是一个惜命的人,你总是会珍惜自己的生命,不会轻易冒险。 +11,友爱,13;14;15;12;20,你是一个友爱的人,你重视同伴与和谐,乐于助人,倾向通过协作与沟通化解矛盾。 +12,复仇,11;14,你是一个复仇心强的人,你绝不轻易放下仇怨,为了复仇愿意付出代价与时间。 +13,孤僻,11,你是一个孤僻的人,你喜欢独处,避免与人深交,更信赖自己的判断与行动。 +14,淡漠,11;12;15;20,你是一个淡漠的人,你情感克制,对外界冷静疏离,不轻易被他人或事件影响。 +15,好斗,11;14;10;17,你是一个好斗的人,你直面冲突,偏好以力量与对抗解决问题,越挫越勇。 +16,鲁莽,1;9;10,你是一个鲁莽的人,你行事冲动、少考虑后果,常凭直觉立刻行动。 +17,胆小,4;15;12;20,你是一个胆小的人,你谨小慎微,容易畏惧风险,倾向回避正面冲突。 +18,霸道,11;17,你是一个霸道的人,你行事强势,不讲道理,习惯以自己的利益为先,倾向多吃多占、压人一步,对他人的反对不以为意。 +19,修行痴迷,2;3;5,你是一个对修行极度痴迷的人,你将绝大多数时间用于修炼,厌恶与修行无关的社交与享乐。 +20,极端,11;14;2;5;3;10;17,你是一个极端的人,你仇视对立阵营,如果你是正义阵营,那么你极度正义;如果你是邪恶阵营,那么你极度邪恶。