From f7a2e377a1a2ade08ba00cc9280a0521f0b061ec Mon Sep 17 00:00:00 2001 From: bridge Date: Tue, 21 Oct 2025 01:06:18 +0800 Subject: [PATCH] update events --- src/classes/action/battle.py | 19 +++++- src/classes/action/breakthrough.py | 15 ++-- src/classes/action/cultivate.py | 2 +- src/classes/action/devour_mortals.py | 2 +- src/classes/action/escape.py | 7 +- src/classes/action/harvest.py | 2 +- src/classes/action/move_away_from_avatar.py | 9 ++- src/classes/action/move_away_from_region.py | 2 +- src/classes/action/move_to_avatar.py | 5 +- src/classes/action/move_to_region.py | 2 +- src/classes/action/plunder_mortals.py | 2 +- src/classes/action/self_heal.py | 4 +- src/classes/action/talk.py | 2 +- src/classes/event.py | 3 + src/classes/fortune.py | 4 +- src/classes/mutual_action/conversation.py | 11 +-- src/classes/mutual_action/dual_cultivation.py | 11 +-- src/classes/mutual_action/mutual_action.py | 7 +- src/front/app.py | 50 +++++++++++++- src/front/events_panel.py | 68 +++++++++++++++++-- src/sim/simulator.py | 6 +- static/game_configs/sect.csv | 4 +- 22 files changed, 193 insertions(+), 44 deletions(-) diff --git a/src/classes/action/battle.py b/src/classes/action/battle.py index 256efd0..67486f3 100644 --- a/src/classes/action/battle.py +++ b/src/classes/action/battle.py @@ -41,7 +41,13 @@ class Battle(InstantAction): target_name = target.name if target is not None else avatar_name # 展示双方折算战斗力(基于对手、含克制) s_att, s_def = get_effective_strength_pair(self.avatar, target) - event = Event(self.world.month_stamp, f"{self.avatar.name} 对 {target_name} 发起战斗(战斗力:{self.avatar.name} {int(s_att)} vs {target_name} {int(s_def)})") + rel_ids = [self.avatar.id] + if target is not None: + try: + rel_ids.append(target.id) + except Exception: + pass + event = Event(self.world.month_stamp, f"{self.avatar.name} 对 {target_name} 发起战斗(战斗力:{self.avatar.name} {int(s_att)} vs {target_name} {int(s_def)})", related_avatars=rel_ids) # 记录开始事件内容,供故事生成使用 self._start_event_content = event.content return event @@ -55,13 +61,20 @@ class Battle(InstantAction): winner, loser = res[0], res[1] loser_damage, winner_damage = res[2], res[3] result_text = f"{winner} 战胜了 {loser},{loser} 受伤{loser_damage}点,{winner} 也受伤{winner_damage}点" - result_event = Event(self.world.month_stamp, result_text) + rel_ids = [self.avatar.id] + try: + t = self._get_target(avatar_name) + if t is not None: + rel_ids.append(t.id) + except Exception: + pass + result_event = Event(self.world.month_stamp, result_text, related_avatars=rel_ids) # 生成战斗小故事:使用便捷方法从参与者直接生成 target = self._get_target(avatar_name) start_text = getattr(self, "_start_event_content", "") or result_event.content story = StoryTeller.tell_from_actors(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT) - story_event = Event(self.world.month_stamp, story) + story_event = Event(self.world.month_stamp, story, related_avatars=rel_ids) return [result_event, story_event] diff --git a/src/classes/action/breakthrough.py b/src/classes/action/breakthrough.py index 832ede7..9d8811e 100644 --- a/src/classes/action/breakthrough.py +++ b/src/classes/action/breakthrough.py @@ -120,7 +120,7 @@ class Breakthrough(TimedAction): else: self._calamity = None self._calamity_other = None - return Event(self.world.month_stamp, f"{self.avatar.name} 开始尝试突破境界") + return Event(self.world.month_stamp, f"{self.avatar.name} 开始尝试突破境界", related_avatars=[self.avatar.id]) # TimedAction 已统一 step 逻辑 @@ -132,18 +132,25 @@ class Breakthrough(TimedAction): if not getattr(self, "_gen_story", False): # 不生成故事:不出现劫难,仅简单结果 core_text = f"{self.avatar.name} 突破{'成功' if result_ok else '失败'}" - return [Event(self.world.month_stamp, core_text)] + return [Event(self.world.month_stamp, core_text, related_avatars=[self.avatar.id])] calamity = getattr(self, "_calamity", "劫难") core_text = f"{self.avatar.name} 遭遇了{calamity}劫难,突破{'成功' if result_ok else '失败'}" - events: list[Event] = [Event(self.world.month_stamp, core_text)] + rel_ids = [self.avatar.id] + other = getattr(self, "_calamity_other", None) + if other is not None: + try: + rel_ids.append(other.id) + except Exception: + pass + events: list[Event] = [Event(self.world.month_stamp, core_text, related_avatars=rel_ids)] if True: # 故事参与者:本体 +(可选)相关角色 desc = CALAMITY_DESCRIPTIONS.get(str(calamity), "") prompt = (STORY_PROMPT_BASE.format(calamity=str(calamity)) + (" " + desc if desc else "")).strip() story = StoryTeller.tell_from_actors(core_text, ("突破成功" if result_ok else "突破失败"), self.avatar, getattr(self, "_calamity_other", None), prompt=prompt) - events.append(Event(self.world.month_stamp, story)) + events.append(Event(self.world.month_stamp, story, related_avatars=rel_ids)) return events # ——— 内部:劫难选择与关联角色 ——— diff --git a/src/classes/action/cultivate.py b/src/classes/action/cultivate.py index f635948..c8dfe59 100644 --- a/src/classes/action/cultivate.py +++ b/src/classes/action/cultivate.py @@ -58,7 +58,7 @@ class Cultivate(TimedAction): return True def start(self) -> Event: - return Event(self.world.month_stamp, f"{self.avatar.name} 在 {self.avatar.tile.region.name} 开始修炼") + return Event(self.world.month_stamp, f"{self.avatar.name} 在 {self.avatar.tile.region.name} 开始修炼", related_avatars=[self.avatar.id]) # TimedAction 已统一 step 逻辑 diff --git a/src/classes/action/devour_mortals.py b/src/classes/action/devour_mortals.py index 1264d52..1805eda 100644 --- a/src/classes/action/devour_mortals.py +++ b/src/classes/action/devour_mortals.py @@ -28,7 +28,7 @@ class DevourMortals(TimedAction): return "DevourMortals" in legal def start(self) -> Event: - return Event(self.world.month_stamp, f"{self.avatar.name} 在城镇开始吞噬凡人") + return Event(self.world.month_stamp, f"{self.avatar.name} 在城镇开始吞噬凡人", related_avatars=[self.avatar.id]) def finish(self) -> list[Event]: return [] diff --git a/src/classes/action/escape.py b/src/classes/action/escape.py index c481ead..c2a1178 100644 --- a/src/classes/action/escape.py +++ b/src/classes/action/escape.py @@ -41,7 +41,7 @@ class Escape(InstantAction): success = _r.random() < escape_rate result_text = "成功" if success else "失败" - result_event = Event(self.world.month_stamp, f"{self.avatar.name} 试图从 {target.name} 逃离:{result_text}") + result_event = Event(self.world.month_stamp, f"{self.avatar.name} 试图从 {target.name} 逃离:{result_text}", related_avatars=[self.avatar.id, target.id]) EventHelper.push_pair(result_event, initiator=self.avatar, target=target, to_sidebar_once=True) if success: self._preempt_avatar(self.avatar) @@ -62,7 +62,10 @@ class Escape(InstantAction): def start(self, avatar_name: str) -> Event: target = self._find_avatar_by_name(avatar_name) target_name = target.name if target is not None else avatar_name - return Event(self.world.month_stamp, f"{self.avatar.name} 尝试从 {target_name} 逃离") + rel_ids = [self.avatar.id] + if target is not None: + rel_ids.append(target.id) + return Event(self.world.month_stamp, f"{self.avatar.name} 尝试从 {target_name} 逃离", related_avatars=rel_ids) # InstantAction 已实现 step 完成 diff --git a/src/classes/action/harvest.py b/src/classes/action/harvest.py index 40c9182..6e71a53 100644 --- a/src/classes/action/harvest.py +++ b/src/classes/action/harvest.py @@ -63,7 +63,7 @@ class Harvest(TimedAction): def start(self) -> Event: region = self.avatar.tile.region - return Event(self.world.month_stamp, f"{self.avatar.name} 在 {region.name} 开始采集") + return Event(self.world.month_stamp, f"{self.avatar.name} 在 {region.name} 开始采集", related_avatars=[self.avatar.id]) # TimedAction 已统一 step 逻辑 diff --git a/src/classes/action/move_away_from_avatar.py b/src/classes/action/move_away_from_avatar.py index 5f834cb..9838cd2 100644 --- a/src/classes/action/move_away_from_avatar.py +++ b/src/classes/action/move_away_from_avatar.py @@ -50,7 +50,14 @@ class MoveAwayFromAvatar(TimedAction): target_name = t.name except Exception: pass - return Event(self.world.month_stamp, f"{self.avatar.name} 开始远离 {target_name}") + rel_ids = [self.avatar.id] + try: + t = self._find_avatar_by_name(avatar_name) + if t is not None: + rel_ids.append(t.id) + except Exception: + pass + return Event(self.world.month_stamp, f"{self.avatar.name} 开始远离 {target_name}", related_avatars=rel_ids) # TimedAction 已统一 step 逻辑 diff --git a/src/classes/action/move_away_from_region.py b/src/classes/action/move_away_from_region.py index a8e5754..b850560 100644 --- a/src/classes/action/move_away_from_region.py +++ b/src/classes/action/move_away_from_region.py @@ -36,7 +36,7 @@ class MoveAwayFromRegion(InstantAction): return True def start(self, region: str) -> Event: - return Event(self.world.month_stamp, f"{self.avatar.name} 开始离开 {region}") + return Event(self.world.month_stamp, f"{self.avatar.name} 开始离开 {region}", related_avatars=[self.avatar.id]) # InstantAction 已实现 step 完成 diff --git a/src/classes/action/move_to_avatar.py b/src/classes/action/move_to_avatar.py index f8824e7..b8a08ed 100644 --- a/src/classes/action/move_to_avatar.py +++ b/src/classes/action/move_to_avatar.py @@ -43,7 +43,10 @@ class MoveToAvatar(DefineAction, ActualActionMixin): def start(self, avatar_name: str) -> Event: target = self._get_target(avatar_name) target_name = target.name if target is not None else avatar_name - return Event(self.world.month_stamp, f"{self.avatar.name} 开始移动向 {target_name}") + rel_ids = [self.avatar.id] + if target is not None: + rel_ids.append(target.id) + return Event(self.world.month_stamp, f"{self.avatar.name} 开始移动向 {target_name}", related_avatars=rel_ids) def step(self, avatar_name: str) -> ActionResult: self.execute(avatar_name=avatar_name) diff --git a/src/classes/action/move_to_region.py b/src/classes/action/move_to_region.py index b181dbb..a5e93a2 100644 --- a/src/classes/action/move_to_region.py +++ b/src/classes/action/move_to_region.py @@ -109,7 +109,7 @@ class MoveToRegion(DefineAction, ActualActionMixin): def start(self, region: Region | str) -> Event: r = self._resolve_region(region) region_name = r.name # 仅使用规范化后的区域名 - return Event(self.world.month_stamp, f"{self.avatar.name} 开始移动向 {region_name}") + return Event(self.world.month_stamp, f"{self.avatar.name} 开始移动向 {region_name}", related_avatars=[self.avatar.id]) def step(self, region: Region | str) -> ActionResult: self.execute(region=region) diff --git a/src/classes/action/plunder_mortals.py b/src/classes/action/plunder_mortals.py index a8c8caa..d2355cd 100644 --- a/src/classes/action/plunder_mortals.py +++ b/src/classes/action/plunder_mortals.py @@ -33,7 +33,7 @@ class PlunderMortals(TimedAction): return self.avatar.alignment == Alignment.EVIL def start(self) -> Event: - return Event(self.world.month_stamp, f"{self.avatar.name} 在城镇开始搜刮凡人") + return Event(self.world.month_stamp, f"{self.avatar.name} 在城镇开始搜刮凡人", related_avatars=[self.avatar.id]) # TimedAction 已统一 step 逻辑 diff --git a/src/classes/action/self_heal.py b/src/classes/action/self_heal.py index b0f0d75..3d6b781 100644 --- a/src/classes/action/self_heal.py +++ b/src/classes/action/self_heal.py @@ -53,13 +53,13 @@ class SelfHeal(TimedAction): region_name = getattr(region, "name", "宗门总部") # 重置累计量 self._healed_total = 0 - return Event(self.world.month_stamp, f"{self.avatar.name} 在 {region_name} 开始静养疗伤") + return Event(self.world.month_stamp, f"{self.avatar.name} 在 {region_name} 开始静养疗伤", related_avatars=[self.avatar.id]) # TimedAction 已统一 step 逻辑 def finish(self) -> list[Event]: healed_total = int(getattr(self, "_healed_total", 0)) # 统一用一次事件简要反馈 - return [Event(self.world.month_stamp, f"{self.avatar.name} 疗伤完成,HP已回满(本次恢复{healed_total}点,当前HP {self.avatar.hp})")] + return [Event(self.world.month_stamp, f"{self.avatar.name} 疗伤完成,HP已回满(本次恢复{healed_total}点,当前HP {self.avatar.hp})", related_avatars=[self.avatar.id])] diff --git a/src/classes/action/talk.py b/src/classes/action/talk.py index 06c8bec..2a1cfe4 100644 --- a/src/classes/action/talk.py +++ b/src/classes/action/talk.py @@ -33,7 +33,7 @@ class Talk(InstantAction): def start(self) -> Event: self.observed_others = self._get_observed_others() # 记录开始事件 - return Event(self.world.month_stamp, f"{self.avatar.name} 尝试与感知范围内的他人攀谈") + return Event(self.world.month_stamp, f"{self.avatar.name} 尝试与感知范围内的他人攀谈", related_avatars=[self.avatar.id]) def step(self) -> ActionResult: import random diff --git a/src/classes/event.py b/src/classes/event.py index a9030b5..5be455f 100644 --- a/src/classes/event.py +++ b/src/classes/event.py @@ -2,6 +2,7 @@ event class """ from dataclasses import dataclass +from typing import List, Optional from src.classes.calendar import Month, Year, MonthStamp @@ -9,6 +10,8 @@ from src.classes.calendar import Month, Year, MonthStamp class Event: month_stamp: MonthStamp content: str + # 相关角色ID列表;若与任何角色无关则为 None + related_avatars: Optional[List[str]] = None def __str__(self) -> str: year = self.month_stamp.get_year() diff --git a/src/classes/fortune.py b/src/classes/fortune.py index 2bde044..869d13d 100644 --- a/src/classes/fortune.py +++ b/src/classes/fortune.py @@ -115,8 +115,8 @@ def try_trigger_fortune(avatar: Avatar) -> list[Event]: story = StoryTeller.tell_from_actors(event_text, res_text, avatar, prompt=story_prompt) events: list[Event] = [ - Event(avatar.world.month_stamp, event_text), - Event(avatar.world.month_stamp, story), + Event(avatar.world.month_stamp, event_text, related_avatars=[avatar.id]), + Event(avatar.world.month_stamp, story, related_avatars=[avatar.id]), ] return events diff --git a/src/classes/mutual_action/conversation.py b/src/classes/mutual_action/conversation.py index abfac27..b4406e6 100644 --- a/src/classes/mutual_action/conversation.py +++ b/src/classes/mutual_action/conversation.py @@ -63,7 +63,10 @@ class Conversation(MutualAction): def start(self, target_avatar: "Avatar|str", **kwargs) -> Event: target = self._get_target_avatar(target_avatar) target_name = target.name if target is not None else str(target_avatar) - event = Event(self.world.month_stamp, f"{self.avatar.name} 与 {target_name} 开始交谈") + rel_ids = [self.avatar.id] + if target is not None: + rel_ids.append(target.id) + event = Event(self.world.month_stamp, f"{self.avatar.name} 与 {target_name} 开始交谈", related_avatars=rel_ids) # 写入历史即可,内容事件稍后生成 self.avatar.add_event(event, to_sidebar=False) if target is not None: @@ -89,7 +92,7 @@ class Conversation(MutualAction): # 仅当明确接受时才记录对话与关系;其余一律视为拒绝 if fb == "Talk": if talk_content: - content_event = Event(self.world.month_stamp, f"{self.avatar.name} 与 {target.name} 的交谈:{talk_content}") + content_event = Event(self.world.month_stamp, f"{self.avatar.name} 与 {target.name} 的交谈:{talk_content}", related_avatars=[self.avatar.id, target.id]) # 进入侧栏一次,并写入双方历史 EventHelper.push_pair(content_event, initiator=self.avatar, target=target, to_sidebar_once=True) @@ -97,12 +100,12 @@ class Conversation(MutualAction): rel = Relation.from_chinese(into_relation_str) if rel is not None: self.avatar.set_relation(target, rel) - set_event = Event(self.world.month_stamp, f"{self.avatar.name} 与 {target.name} 的关系变为:{relation_display_names.get(rel, str(rel))}") + set_event = Event(self.world.month_stamp, f"{self.avatar.name} 与 {target.name} 的关系变为:{relation_display_names.get(rel, str(rel))}", related_avatars=[self.avatar.id, target.id]) EventHelper.push_pair(set_event, initiator=self.avatar, target=target, to_sidebar_once=True) return ActionResult(status=ActionStatus.COMPLETED, events=[]) else: - feedback_event = Event(self.world.month_stamp, f"{target.name} 拒绝与 {self.avatar.name} 交谈") + feedback_event = Event(self.world.month_stamp, f"{target.name} 拒绝与 {self.avatar.name} 交谈", related_avatars=[self.avatar.id, target.id]) EventHelper.push_pair(feedback_event, initiator=self.avatar, target=target, to_sidebar_once=True) return ActionResult(status=ActionStatus.COMPLETED, events=[]) diff --git a/src/classes/mutual_action/dual_cultivation.py b/src/classes/mutual_action/dual_cultivation.py index f1239e0..7fce363 100644 --- a/src/classes/mutual_action/dual_cultivation.py +++ b/src/classes/mutual_action/dual_cultivation.py @@ -51,7 +51,10 @@ class DualCultivation(MutualAction): def start(self, target_avatar: "Avatar|str") -> Event: target = self._get_target_avatar(target_avatar) target_name = target.name if target is not None else str(target_avatar) - event = Event(self.world.month_stamp, f"{self.avatar.name} 邀请 {target_name} 进行双修") + rel_ids = [self.avatar.id] + if target is not None: + rel_ids.append(target.id) + event = Event(self.world.month_stamp, f"{self.avatar.name} 邀请 {target_name} 进行双修", related_avatars=rel_ids) # 仅写入历史 self.avatar.add_event(event, to_sidebar=False) if target is not None: @@ -102,17 +105,17 @@ class DualCultivation(MutualAction): if success: gain = int(self._dual_exp_gain) result_text = f"{self.avatar.name} 与 {target.name} 成功双修,{self.avatar.name} 获得修为经验 +{gain} 点" - result_event = Event(self.world.month_stamp, result_text) + result_event = Event(self.world.month_stamp, result_text, related_avatars=[self.avatar.id, target.id]) events.append(result_event) # 生成恋爱/双修小故事:使用 StoryTeller 便捷方法 start_text = self._start_event_content or result_event.content story = StoryTeller.tell_from_actors(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT) - story_event = Event(self.world.month_stamp, story) + story_event = Event(self.world.month_stamp, story, related_avatars=[self.avatar.id, target.id]) events.append(story_event) else: result_text = f"{target.name} 拒绝了与 {self.avatar.name} 的双修" - result_event = Event(self.world.month_stamp, result_text) + result_event = Event(self.world.month_stamp, result_text, related_avatars=[self.avatar.id, target.id]) events.append(result_event) return events diff --git a/src/classes/mutual_action/mutual_action.py b/src/classes/mutual_action/mutual_action.py index 01384b1..af218a9 100644 --- a/src/classes/mutual_action/mutual_action.py +++ b/src/classes/mutual_action/mutual_action.py @@ -134,7 +134,7 @@ class MutualAction(DefineAction, LLMAction, TargetingMixin): self._settle_feedback(target_avatar, feedback) # 3) 反馈事件(进入侧边栏与双方历史,中文化文案) fb_label = self.FEEDBACK_LABELS.get(str(feedback).strip(), str(feedback)) - feedback_event = Event(self.world.month_stamp, f"{target_avatar.name} 对 {self.avatar.name} 的反馈:{fb_label}") + feedback_event = Event(self.world.month_stamp, f"{target_avatar.name} 对 {self.avatar.name} 的反馈:{fb_label}", related_avatars=[self.avatar.id, target_avatar.id]) # 侧边栏仅推送一次,另一侧仅写入历史,避免重复 EventHelper.push_pair(feedback_event, initiator=self.avatar, target=target_avatar, to_sidebar_once=True) # 4) 记录历史(文本记录) @@ -160,7 +160,10 @@ class MutualAction(DefineAction, LLMAction, TargetingMixin): target = self._get_target_avatar(target_avatar) target_name = target.name if target is not None else str(target_avatar) action_name = getattr(self, 'ACTION_NAME', self.name) - event = Event(self.world.month_stamp, f"{self.avatar.name} 对 {target_name} 发起 {action_name}") + rel_ids = [self.avatar.id] + if target is not None: + rel_ids.append(target.id) + event = Event(self.world.month_stamp, f"{self.avatar.name} 对 {target_name} 发起 {action_name}", related_avatars=rel_ids) # 仅写入历史,避免与提交阶段重复推送到侧边栏 self.avatar.add_event(event, to_sidebar=False) if target is not None: diff --git a/src/front/app.py b/src/front/app.py index af60990..4624ec0 100644 --- a/src/front/app.py +++ b/src/front/app.py @@ -77,6 +77,10 @@ class Front: self._avatar_display_states: Dict[str, Dict[str, float]] = {} self._init_avatar_display_states() + # 侧栏筛选状态:None 表示所有人;否则为 avatar_id + self._sidebar_filter_avatar_id: Optional[str] = None + self._sidebar_filter_open: bool = False + def add_events(self, new_events: List[Event]): self.events.extend(new_events) if len(self.events) > 1000: @@ -108,6 +112,8 @@ class Front: elif event.key == pygame.K_SPACE: if current_step_task is None or current_step_task.done(): current_step_task = asyncio.create_task(self._step_once_async()) + elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: + self._handle_mouse_click() if self._auto_step and self._last_step_ms >= self.step_interval_ms: if current_step_task is None or current_step_task.done(): current_step_task = asyncio.create_task(self._step_once_async()) @@ -162,10 +168,33 @@ class Front: ) # 先绘制状态栏和侧边栏,再绘制 tooltip 保证 tooltip 在最上层 draw_status_bar(pygame, self.screen, self.colors, self.status_font, self.margin, self.world, self._auto_step) - draw_sidebar( - pygame, self.screen, self.colors, self.sidebar_font, self.events, + + # 计算筛选后的事件 + if self._sidebar_filter_avatar_id is None: + events_to_draw: List[Event] = self.events + else: + aid = self._sidebar_filter_avatar_id + events_to_draw = [e for e in self.events if getattr(e, "related_avatars", None) and (aid in e.related_avatars)] + + # 构造下拉选项(第一个是所有人;其余为当前世界中的角色) + options: List[tuple[str, Optional[str]]] = [("所有人", None)] + for avatar_id, avatar in self.world.avatar_manager.avatars.items(): + options.append((avatar.name, avatar_id)) + sel_label = "所有人" + if self._sidebar_filter_avatar_id is not None: + sel_avatar = self.world.avatar_manager.avatars.get(self._sidebar_filter_avatar_id) + if sel_avatar is not None: + sel_label = sel_avatar.name + + sidebar_ui = draw_sidebar( + pygame, self.screen, self.colors, self.sidebar_font, events_to_draw, self.world.map, self.tile_size, self.margin, self.sidebar_width, + filter_selected_label=sel_label, + filter_is_open=self._sidebar_filter_open, + filter_options=options, ) + # 保存供点击检测 + self._sidebar_ui = sidebar_ui if hovered_avatar is not None: draw_tooltip_for_avatar(pygame, self.screen, self.colors, self.tooltip_font, hovered_avatar) elif hovered_region is not None: @@ -173,6 +202,23 @@ class Front: draw_tooltip_for_region(pygame, self.screen, self.colors, self.tooltip_font, hovered_region, mouse_x, mouse_y) pygame.display.flip() + def _handle_mouse_click(self) -> None: + # 仅处理侧栏筛选点击 + pygame = self.pygame + mouse_pos = pygame.mouse.get_pos() + ui = getattr(self, "_sidebar_ui", {}) or {} + toggle_rect = ui.get("filter_toggle_rect") + option_rects = ui.get("filter_option_rects") or [] + if toggle_rect and toggle_rect.collidepoint(mouse_pos): + self._sidebar_filter_open = not self._sidebar_filter_open + return + if self._sidebar_filter_open: + for oid, rect in option_rects: + if rect.collidepoint(mouse_pos): + self._sidebar_filter_avatar_id = oid if oid is not None else None + self._sidebar_filter_open = False + return + def _get_region_font(self, size: int): return _get_region_font_cached(self.pygame, self._region_font_cache, size, self.font_path) diff --git a/src/front/events_panel.py b/src/front/events_panel.py index 58eb35f..28d0ec7 100644 --- a/src/front/events_panel.py +++ b/src/front/events_panel.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional, Tuple, Dict from .rendering import STATUS_BAR_HEIGHT @@ -25,8 +25,21 @@ def _wrap_text_by_pixels(font, text: str, max_width_px: int) -> List[str]: return lines -def draw_sidebar(pygame_mod, screen, colors, font, events: List[object], - world_map, tile_size: int, margin: int, sidebar_width: int): +def draw_sidebar( + pygame_mod, + screen, + colors, + font, + events: List[object], + world_map, + tile_size: int, + margin: int, + sidebar_width: int, + *, + filter_selected_label: str, + filter_is_open: bool, + filter_options: List[Tuple[str, Optional[str]]], +) -> Dict[str, object]: sidebar_x = world_map.width * tile_size + margin * 2 sidebar_y = margin + STATUS_BAR_HEIGHT @@ -39,13 +52,54 @@ def draw_sidebar(pygame_mod, screen, colors, font, events: List[object], pygame_mod.draw.rect(screen, colors["sidebar_bg"], sidebar_rect) pygame_mod.draw.rect(screen, colors["sidebar_border"], sidebar_rect, 2) + # 下拉选择器:显示“所有人/某人”,放在最上方 + dropdown_margin_x = 10 + dropdown_width = sidebar_width - 20 + # 先用一个基准高度,确保点击区域更易操作 + dropdown_height = 24 + dropdown_x = sidebar_x + dropdown_margin_x + dropdown_y = sidebar_y + 10 + dropdown_rect = pygame_mod.Rect(dropdown_x, dropdown_y, dropdown_width, dropdown_height) + # 填充底色并描边 + pygame_mod.draw.rect(screen, colors["sidebar_bg"], dropdown_rect) + pygame_mod.draw.rect(screen, colors["sidebar_border"], dropdown_rect, 1) + # 选中项文本 + sel_text = filter_selected_label or "所有人" + sel_surf = font.render(f"筛选:{sel_text}", True, colors["event_text"]) + screen.blit(sel_surf, (dropdown_x + 6, dropdown_y + (dropdown_height - sel_surf.get_height()) // 2)) + # 右侧箭头 + arrow_char = "▲" if filter_is_open else "▼" + arrow_surf = font.render(arrow_char, True, colors["event_text"]) + screen.blit(arrow_surf, (dropdown_x + dropdown_width - arrow_surf.get_width() - 6, dropdown_y + (dropdown_height - arrow_surf.get_height()) // 2)) + + option_rects: List[Tuple[Optional[str], object]] = [] + options_total_h = 0 + if filter_is_open and filter_options: + # 整体下拉区域背景,避免与事件文字混在一起 + options_total_h = dropdown_height * len(filter_options) + options_area_rect = pygame_mod.Rect(dropdown_x, dropdown_y + dropdown_height, dropdown_width, options_total_h) + pygame_mod.draw.rect(screen, colors["sidebar_bg"], options_area_rect) + pygame_mod.draw.rect(screen, colors["sidebar_border"], options_area_rect, 1) + # 逐项绘制 + opt_y = dropdown_y + dropdown_height + for label, oid in filter_options: + opt_rect = pygame_mod.Rect(dropdown_x, opt_y, dropdown_width, dropdown_height) + pygame_mod.draw.rect(screen, colors["sidebar_bg"], opt_rect) + pygame_mod.draw.rect(screen, colors["sidebar_border"], opt_rect, 1) + opt_surf = font.render(label, True, colors["event_text"]) + screen.blit(opt_surf, (dropdown_x + 6, opt_y + (dropdown_height - opt_surf.get_height()) // 2)) + option_rects.append((oid, opt_rect)) + opt_y += dropdown_height + + # 标题“事件历史”位于筛选下拉之下 title_text = "事件历史" title_surf = font.render(title_text, True, colors["text"]) title_x = sidebar_x + 10 - title_y = sidebar_y + 10 + title_y = dropdown_y + dropdown_height + (options_total_h if filter_is_open else 0) + 10 screen.blit(title_surf, (title_x, title_y)) - line_y = title_y + title_surf.get_height() + 10 + # 事件列表起始位置位于标题之后 + line_y = title_y + title_surf.get_height() + 6 pygame_mod.draw.line(screen, colors["sidebar_border"], (sidebar_x + 10, line_y), (sidebar_x + sidebar_width - 10, line_y), 1) @@ -70,6 +124,10 @@ def draw_sidebar(pygame_mod, screen, colors, font, events: List[object], no_event_text = "暂无事件" no_event_surf = font.render(no_event_text, True, colors["event_text"]) screen.blit(no_event_surf, (title_x, event_y)) + return { + "filter_toggle_rect": dropdown_rect, + "filter_option_rects": option_rects, + } __all__ = ["draw_sidebar"] diff --git a/src/sim/simulator.py b/src/sim/simulator.py index 64ae4fc..6980839 100644 --- a/src/sim/simulator.py +++ b/src/sim/simulator.py @@ -83,11 +83,11 @@ class Simulator: for avatar_id, avatar in list(self.world.avatar_manager.avatars.items()): if avatar.hp <= 0: death_avatar_ids.append(avatar_id) - event = Event(self.world.month_stamp, f"{avatar.name} 因重伤身亡") + event = Event(self.world.month_stamp, f"{avatar.name} 因重伤身亡", related_avatars=[avatar.id]) events.append(event) if avatar.death_by_old_age(): death_avatar_ids.append(avatar_id) - event = Event(self.world.month_stamp, f"{avatar.name} 老死了,时年{avatar.age.get_age()}岁") + event = Event(self.world.month_stamp, f"{avatar.name} 老死了,时年{avatar.age.get_age()}岁", related_avatars=[avatar.id]) events.append(event) if death_avatar_ids: self.world.avatar_manager.remove_avatars(death_avatar_ids) @@ -106,7 +106,7 @@ class Simulator: name = get_random_name(gender) 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}晋升为修士了。") + event = Event(self.world.month_stamp, f"{new_avatar.name}晋升为修士了。", related_avatars=[new_avatar.id]) events.append(event) return events diff --git a/static/game_configs/sect.csv b/static/game_configs/sect.csv index 40110d5..3bc3ea9 100644 --- a/static/game_configs/sect.csv +++ b/static/game_configs/sect.csv @@ -3,9 +3,9 @@ id,name,desc,member_act_style,alignment,sect_surnames,male_sect_given_names,fema 1,明心剑宗,通玄界东方第一宗,以无上剑道称雄于世。云纹禁制为不传心法。,清明克己,行止如一。重剑与心法并重,讲究明心见性。,正,明;心;剑;霄;玄;霁;衡;孤;徽;肃,澄川;宏石;磐岳;霆岱;寂岚;久安;宸秋;烁离;沧岳;砺锋;炎洲;远歌,采微;霏岚;韶华;绮澜;珠影;远岫;若水;凝香;雪瑶;南絮;轻萝;宛竹,1, 2,百兽宗,以驯养灵兽闻名,豢养各种妖兽灵怪为战力。,你言语直接,重视力量与血性,崇尚狩猎与搏斗。,邪,,驼王;飞熊;虎魄;狼行;熊罡;白猿;石坚;山岚;青鬃;玄爪;金瞳;裂爪;破角;狂鬃;赤鬣;苍隼;啸风;裂岩,狐绮;白貂;青翎;雪牙;赤羽;玄狸;灵爪;月狐;银鳞;霜蹄;云貉;绒尾;锦狐;轻蹄,1, 3,水镜宗,正道十宗之一,实则严守中立。拥有仙界异宝"彻天水镜"可预知未来。,你处事冷静圆融,喜以柔克刚,擅借力与反制。,中,水;镜;寒;霜;冰;清;沐;澜;渊;泉,涟光;沧浪;泽远;浩川;泊舟;涓石;溪原;涵舟;泠曜;漪岑;淞岳;涔雨,漫霖;洛漪;潋月;涵烟;沁波;翠波;漫葭;汀兰;潭歌;涓玥;澧宁;潇然,1, -4,冥王宗,行走幽冥之道,术法阴冷狠厉。,你言辞冷厉少情,敬畏因果而不惧杀伐,偏向效率与结果。,邪,冥;王;玄;幽;夜;白;冷;狱;魇;阴,血燎;焚魄;灰灭;殁川;绝尘;厌离;朔寒;邪风;归墟;朽骨;朔月;止戈,寒绫;霜瑶;凄歌;素鸢;祭宁;黛魂;夙梦;绫雪;凛珑;霁月;旷音;凝岚,1000, +4,冥王宗,行走幽冥之道,术法阴冷狠厉。,你言辞冷厉少情,敬畏因果而不惧杀伐,偏向效率与结果。,邪,冥;王;玄;幽;夜;白;冷;狱;魇;阴,血燎;焚魄;灰灭;殁川;绝尘;厌离;朔寒;邪风;归墟;朽骨;朔月;止戈,寒绫;霜瑶;凄歌;素鸢;祭宁;黛魂;夙梦;绫雪;凛珑;霁月;旷音;凝岚,1, 5,朱勾宗,邪宗大派。以炼器、机关、暗杀闻名于世,素来阴毒冷僻。,你直面欲望与代价,不惧黑暗,以攻伐见长。,邪,朱;绯;刃;戮;蚀;渊;钧;鸦;墨;殷,暗阑;机括;鬼匣;夜禁;幻锁;残锋;暗弦;影栅;幽钩;断线;潜匿;迷踪,玄簪;霜绡;纤罗;碎玉;影裳;轻弦;凝黛;凝烟;冷珥;素纱;凛钗;寒袖,1, -6,合欢宗,以情入道,靠双修增进修为,善驭人心,长于权变。,你辞令婉转,善于拿捏人欲与局势,以柔制刚。,中,合;欢;苏;陆;柳;花;月;楚;顾;白,流烟;迟夜;长陌;归舟;暮成;远辞;行止;轻寒;沉香;野鹤;乘风,婉心;轻柔;疏影;如梦;绮念;惜香;慕雪;倾城;绯烟;晚晴;素袖;霁眉;绸缪;静妍,1000,"{""legal_actions"": [""DualCultivation""]}" +6,合欢宗,以情入道,靠双修增进修为,善驭人心,长于权变。,你辞令婉转,善于拿捏人欲与局势,以柔制刚。,中,合;欢;苏;陆;柳;花;月;楚;顾;白,流烟;迟夜;长陌;归舟;暮成;远辞;行止;轻寒;沉香;野鹤;乘风,婉心;轻柔;疏影;如梦;绮念;惜香;慕雪;倾城;绯烟;晚晴;素袖;霁眉;绸缪;静妍,1,"{""legal_actions"": [""DualCultivation""]}" 7,镇魂宗,铁血风格,擅安魂、封邪、渡厄,兼有刚烈镇压之术。,你肃穆沉稳,重安魂镇邪,少言而果决。,正,厉;卢;镇;魂;钟;青;凌;白;楚;顾,安魄;靖川;霁阳;季衡;砺甲;烁锋;昊戈;祁光;漠石;启封;殷策;定魄,清宁;素铃;靖霜;澄心;霁雪;安祈;净月;宁枝;祷绫;明槐;采霁;定寒,1, 8,幽魂噬影宗,镇宗典籍《幽冥录》。幽明气为根基。,你行事隐秘果断,重结果轻虚名,擅潜行与出其不意。,邪,冥;阎;鬼;幽;归;应;阴;碧;夜;魅,噬影;无藏;绝响;断痕;影渊;暗行;潜踪;魄隐;迷雾;空蝉;断念;裂隙,影绫;暗萝;霜绡;素鹭;玄绮;凝灯;绫岚;凛铃;朔华;漠音;宵岑;泠歌,1, 9,千帆城,炼器大宗,巧匠云集。著名法宝有灵灭丝、定魂蓝星、天罗网、万里极光壁、飞翼等。商旅云集,自成体系。,你务实精明,重交易与信誉,崇尚规则与秩序。,中,商;楚;顾;白;苏;林;叶;秦;赵;魏,持衡;清评;问价;立契;通衡;问道;理市;衡准;守约;筑匠;铸衡;估算,素蓝;明衡;巧心;青帆;绫舟;观星;衡绫;星槎;织霓;采绫;芸巧;霁行;砚秋;镶玑;绘蓝;银梭;珩心,1,