From 0315dca6e65ac808fa0fc40f21e90129f2d497ba Mon Sep 17 00:00:00 2001 From: 4thfever Date: Sat, 31 Jan 2026 20:43:42 +0800 Subject: [PATCH] Feat/hidden domain (#113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary 新增秘境探索,属于多人活动,每N年触发一次 Closes #105 --- docs/I18N_MAINTENANCE_GUIDE.md | 83 +++++ src/classes/animal.py | 6 +- src/classes/death_reason.py | 3 + src/classes/effect/consts.py | 28 ++ src/classes/effect/desc.py | 4 +- src/classes/gathering/__init__.py | 1 + src/classes/gathering/hidden_domain.py | 328 ++++++++++++++++++ src/classes/lode.py | 6 +- src/classes/material.py | 4 +- src/classes/plant.py | 6 +- src/classes/sect.py | 4 +- src/classes/sect_ranks.py | 2 +- src/classes/technique.py | 8 +- src/classes/weapon.py | 4 +- src/classes/world.py | 5 + .../locales/en_US/LC_MESSAGES/messages.mo | Bin 55480 -> 57007 bytes .../locales/en_US/LC_MESSAGES/messages.po | 48 ++- .../locales/zh_CN/LC_MESSAGES/messages.mo | Bin 51900 -> 53319 bytes .../locales/zh_CN/LC_MESSAGES/messages.po | 43 ++- src/server/main.py | 4 +- src/sim/load/load_game.py | 2 + src/sim/save/save_game.py | 1 + src/sim/simulator.py | 4 + static/config.yml | 1 + .../en-US/game_configs/hidden_domain.csv | 5 + static/locales/en-US/game_configs/persona.csv | 2 +- .../locales/en-US/game_configs/world_info.csv | 1 + .../zh-CN/game_configs/hidden_domain.csv | 5 + static/locales/zh-CN/game_configs/persona.csv | 2 +- .../locales/zh-CN/game_configs/world_info.csv | 1 + .../zh-CN/templates/story_gathering.txt | 1 - tests/conftest.py | 5 +- tests/test_hidden_domain.py | 246 +++++++++++++ tests/test_history.py | 9 +- 34 files changed, 843 insertions(+), 29 deletions(-) create mode 100644 docs/I18N_MAINTENANCE_GUIDE.md create mode 100644 src/classes/gathering/hidden_domain.py create mode 100644 static/locales/en-US/game_configs/hidden_domain.csv create mode 100644 static/locales/zh-CN/game_configs/hidden_domain.csv create mode 100644 tests/test_hidden_domain.py diff --git a/docs/I18N_MAINTENANCE_GUIDE.md b/docs/I18N_MAINTENANCE_GUIDE.md new file mode 100644 index 0000000..d3b6c0e --- /dev/null +++ b/docs/I18N_MAINTENANCE_GUIDE.md @@ -0,0 +1,83 @@ +# 国际化资源文件 (messages.po) 维护与增补指南 + +## 1. 核心问题警示 (CRITICAL WARNING) + +**严禁在 Windows PowerShell 环境下直接使用重定向符号 (`>>`) 追加内容到 PO 文件!** + +### 现象 +`messages.po` 文件末尾出现大量 `NULL` (`\x00`) 字符或乱码,导致文件损坏,IDE 打开后显示为空白或包含红色方块。 + +### 原因 +* 项目中的 PO 文件使用标准的 **UTF-8** 编码。 +* Windows PowerShell 的默认输出编码(尤其是在使用 `>>` 时)通常是 **UTF-16LE** (Windows Unicode)。 +* 当 UTF-16LE 的内容被追加到 UTF-8 文件末尾时,文件会包含两种不兼容的编码,且 UTF-16 中的 `\x00` 字节会被视为乱码。 + +--- + +## 2. 正确的增补方式 + +### 方法 A:直接使用 IDE 编辑 (推荐) +最安全、最简单的方法。直接在 Cursor / VSCode 中打开 `src/i18n/locales/xx_XX/LC_MESSAGES/messages.po`,在文件末尾手动粘贴或输入新的翻译条目。 + +### 方法 B:使用 Python 脚本追加 +如果必须通过脚本自动化,请务必使用 Python 并显式指定 UTF-8 编码。 + +```python +# correct_append.py +content = """ +msgid "new_key" +msgstr "Translation" +""" + +with open("path/to/messages.po", "a", encoding="utf-8") as f: + f.write(content) +``` + +### 方法 C:Linux/Bash 环境 +在 Git Bash 或 WSL 中使用 `cat >>` 是安全的,因为它们默认处理 UTF-8 流。 + +```bash +# Git Bash / WSL only +cat temp.po >> messages.po +``` + +--- + +## 3. 文件格式规范 + +在增补 PO 文件时,请遵守以下格式规则,否则可能导致解析错误: + +1. **不要重复 Header**: + * PO 文件的开头已经包含了元数据(`Project-Id-Version`, `Content-Type` 等)。 + * **不要**在追加的内容中再次包含这些 Header 信息。追加内容应仅包含 `msgid` 和 `msgstr`。 + +2. **保持空行分隔**: + * 每个 `msgid`/`msgstr` 对之间应保留一个空行,以提高可读性。 + +3. **UTF-8 无 BOM**: + * 始终确保文件保存为 UTF-8 编码且不带 BOM 头。 + +## 4. 紧急修复指南 + +如果不慎损坏了文件(出现了 NULL 字节),请按以下步骤修复: + +1. **立即停止写入**。 +2. 使用支持二进制查看的编辑器(或 Python)读取文件。 +3. 去除所有的 `\x00` 字节。 +4. 检查并删除文件中段出现的重复 Header。 +5. 重新保存为 UTF-8。 + +**Python 修复脚本示例:** + +```python +def fix_po_file(path): + with open(path, 'rb') as f: + content = f.read() + + # 替换掉 null 字节 + content = content.replace(b'\x00', b'') + + # 重新写入为 UTF-8 + with open(path, 'w', encoding='utf-8') as f: + f.write(content.decode('utf-8')) # 假设剩余内容是合法的 utf-8 +``` diff --git a/src/classes/animal.py b/src/classes/animal.py index 25f968d..31bb2ca 100644 --- a/src/classes/animal.py +++ b/src/classes/animal.py @@ -38,11 +38,11 @@ class Animal: """ from src.i18n import t # 使用格式化字符串 msgid - base_info = t("[{name}] ({realm})", name=t(self.name), realm=str(self.realm)) - info_parts = [base_info, t(self.desc)] + base_info = t("[{name}] ({realm})", name=self.name, realm=str(self.realm)) + info_parts = [base_info, self.desc] if self.materials: - material_names = [t(material.name) for material in self.materials] + material_names = [material.name for material in self.materials] materials_str = t("comma_separator").join(material_names) info_parts.append(t("Drops: {materials}", materials=materials_str)) diff --git a/src/classes/death_reason.py b/src/classes/death_reason.py index bf17037..a84bcf2 100644 --- a/src/classes/death_reason.py +++ b/src/classes/death_reason.py @@ -7,6 +7,7 @@ class DeathType(Enum): OLD_AGE = "old_age" BATTLE = "battle" SERIOUS_INJURY = "serious_injury" + HIDDEN_DOMAIN = "hidden_domain" @dataclass class DeathReason: @@ -20,6 +21,8 @@ class DeathReason: return t("Killed by {killer}", killer=killer) elif self.death_type == DeathType.SERIOUS_INJURY: return t("Died from severe injuries") + elif self.death_type == DeathType.HIDDEN_DOMAIN: + return t("Perished in a Hidden Domain") elif self.death_type == DeathType.OLD_AGE: return t("Died of old age") return t(self.death_type.value) diff --git a/src/classes/effect/consts.py b/src/classes/effect/consts.py index 443599a..9c07e19 100644 --- a/src/classes/effect/consts.py +++ b/src/classes/effect/consts.py @@ -364,6 +364,30 @@ EXTRA_PLUNDER_MULTIPLIER = "extra_plunder_multiplier" - 大量: 2 """ +# 秘境相关 +EXTRA_HIDDEN_DOMAIN_DROP_PROB = "extra_hidden_domain_drop_prob" +""" +额外秘境掉落概率 +类型: float +结算: src/classes/gathering/hidden_domain.py +说明: 增加在秘境中获得宝物的概率。 +数值参考: + - 微量: 0.05 + - 中量: 0.1 + - 大量: 0.2 +""" + +EXTRA_HIDDEN_DOMAIN_DANGER_PROB = "extra_hidden_domain_danger_prob" +""" +额外秘境危险概率 +类型: float +结算: src/classes/gathering/hidden_domain.py +说明: 增加(或减少,负值)在秘境中遇到危险的概率。 +数值参考: + - 降低危险: -0.1 (降低10%危险率) + - 增加危险: 0.1 +""" + # --- 特殊权限 --- LEGAL_ACTIONS = "legal_actions" """ @@ -479,6 +503,10 @@ ALL_EFFECTS = [ "shop_buy_price_reduction", # float - 商铺购买价格倍率减免 "extra_plunder_multiplier", # float - 额外搜刮收益倍率 + # 秘境相关 + "extra_hidden_domain_drop_prob", # float - 额外秘境掉落概率 + "extra_hidden_domain_danger_prob", # float - 额外秘境危险概率 + # 特殊权限 "legal_actions", # list[str] - 合法动作列表 ] diff --git a/src/classes/effect/desc.py b/src/classes/effect/desc.py index 663457c..f62ac30 100644 --- a/src/classes/effect/desc.py +++ b/src/classes/effect/desc.py @@ -37,6 +37,8 @@ def get_effect_desc(effect_key: str) -> str: "cultivate_duration_reduction": "effect_cultivate_duration_reduction", "extra_cast_success_rate": "effect_extra_cast_success_rate", "extra_refine_success_rate": "effect_extra_refine_success_rate", + "extra_hidden_domain_drop_prob": "effect_extra_hidden_domain_drop_prob", + "extra_hidden_domain_danger_prob": "effect_extra_hidden_domain_danger_prob", } msgid = msgid_map.get(effect_key, effect_key) @@ -189,7 +191,7 @@ def format_effects_to_text(effects: dict[str, Any] | list[dict[str, Any]]) -> st # 如果有条件,添加条件描述 if effects.get("when"): cond = translate_condition(str(effects["when"])) - return t("[{condition}] {effects}", condition=cond, effects=text) + return f"[{cond}] {text}" return text diff --git a/src/classes/gathering/__init__.py b/src/classes/gathering/__init__.py index 3decb47..05ce5e3 100644 --- a/src/classes/gathering/__init__.py +++ b/src/classes/gathering/__init__.py @@ -1,2 +1,3 @@ from .gathering import Gathering, GatheringManager from .auction import Auction +from .hidden_domain import HiddenDomain diff --git a/src/classes/gathering/hidden_domain.py b/src/classes/gathering/hidden_domain.py new file mode 100644 index 0000000..d6daa37 --- /dev/null +++ b/src/classes/gathering/hidden_domain.py @@ -0,0 +1,328 @@ +from typing import List, Dict, Optional, Any, TYPE_CHECKING +import random +import asyncio +from dataclasses import dataclass + +from src.classes.gathering.gathering import Gathering, register_gathering +from src.classes.event import Event +if TYPE_CHECKING: + from src.classes.world import World + from src.classes.avatar import Avatar + +from src.classes.item import Item +from src.utils.df import game_configs, get_str, get_float, get_int +from src.classes.cultivation import Realm, REALM_ORDER, REALM_RANK +from src.classes.death_reason import DeathReason, DeathType +from src.classes.death import handle_death +from src.classes.weapon import get_random_weapon_by_realm +from src.classes.auxiliary import get_random_auxiliary_by_realm +from src.classes.technique import get_random_technique_for_avatar +from src.i18n import t +from src.run.log import get_logger + +logger = get_logger().logger + +@dataclass +class DomainConfig: + id: str + name: str + desc: str + max_realm: Realm + danger_prob: float + hp_loss_percent: float + drop_prob: float + cd_years: int + open_prob: float + +@register_gathering +class HiddenDomain(Gathering): + """ + 秘境系统 (Hidden Domain) + 定期开启,符合境界条件的修士可进入探索,面临凶险或获得机缘。 + """ + + # 记录每个秘境上次开启的年份 {domain_id: last_open_year} + _domain_states: Dict[str, int] = {} + + # 临时存储本轮开启的秘境 + _active_domains: List[DomainConfig] = [] + + # LLM Prompt ID + STORY_PROMPT_ID = "hidden_domain_story_prompt" + + @classmethod + def get_story_prompt(cls) -> str: + return t(cls.STORY_PROMPT_ID) + + def _load_configs(self) -> List[DomainConfig]: + """从配置表加载秘境配置""" + configs = [] + df = game_configs.get("hidden_domain") + if df is None: + return [] + + for row in df: + try: + # 必须字段 + conf = DomainConfig( + id=get_str(row, "id"), + name=get_str(row, "name"), + desc=get_str(row, "desc"), + max_realm=Realm.from_str(get_str(row, "max_realm")), + danger_prob=get_float(row, "danger_prob"), + hp_loss_percent=get_float(row, "hp_loss_percent"), + drop_prob=get_float(row, "drop_prob"), + cd_years=get_int(row, "cd_years"), + open_prob=get_float(row, "open_prob"), + ) + configs.append(conf) + except Exception as e: + logger.error(f"Failed to load hidden domain config: {e}") + continue + return configs + + def is_start(self, world: "World") -> bool: + """ + 判断是否有秘境开启 + """ + self._active_domains = [] + current_year = world.month_stamp.get_year() + configs = self._load_configs() + + for conf in configs: + last_open = self._domain_states.get(conf.id, -999) + + # 只有 CD 转好才进行概率判定 + if current_year - last_open >= conf.cd_years: + if random.random() < conf.open_prob: + self._active_domains.append(conf) + # 立即更新状态,防止同一step多次调用导致状态不一致(虽然后续execute才算正式执行) + # 但GatheringManager是 is_start -> execute 顺序执行,所以这里更新没问题 + # 如果需要execute失败回滚,可以把更新移到execute里。这里简单起见放在这里。 + self._domain_states[conf.id] = current_year + + return len(self._active_domains) > 0 + + def get_related_avatars(self, world: "World") -> List[int]: + """ + 获取所有可能参与的角色(即所有存活角色,具体筛选在 execute 中按秘境条件进行) + """ + return [av.id for av in world.avatar_manager.get_living_avatars()] + + def get_info(self, world: "World") -> str: + details = [] + for conf in self._active_domains: + detail = t("Hidden Domain {name} opened! Entry restricted to {realm} and below.", + name=conf.name, + realm=str(conf.max_realm)) + details.append(detail) + return t("Hidden Domains opened: {names}", names="\n".join(details)) + + def _get_next_realm(self, realm: Realm) -> Optional[Realm]: + """获取下一个大境界""" + current_idx = REALM_RANK.get(realm) + if current_idx is not None and current_idx + 1 < len(REALM_ORDER): + return REALM_ORDER[current_idx + 1] + return None + + def _generate_loot(self, avatar: "Avatar", next_realm: Realm) -> Optional[Item]: + """生成掉落物:优先给予高一阶的物品""" + # 掉落类型权重:兵器(40%), 防具(40%), 功法(20%) + roll = random.random() + + loot = None + if roll < 0.4: + # 兵器 + loot = get_random_weapon_by_realm(next_realm) + elif roll < 0.8: + # 防具 + loot = get_random_auxiliary_by_realm(next_realm) + else: + # 功法:尝试获取更高级的功法 + # get_random_technique_for_avatar 根据灵根匹配,但这里我们希望给一点“机缘” + # 复用该函数,但可能获取到同阶的。为了体现“机缘”,我们允许多试几次取最好的,或者直接给 + # 这里简单调用现有接口 + loot = get_random_technique_for_avatar(avatar) + + return loot + + async def execute(self, world: "World") -> List[Event]: + events = [] + + for domain in self._active_domains: + domain_events = await self._process_single_domain(world, domain) + events.extend(domain_events) + + return events + + async def _process_single_domain(self, world: "World", domain: DomainConfig) -> List[Event]: + """处理单个秘境的逻辑""" + events = [] + month_stamp = world.month_stamp + + # 1. 筛选进入秘境的角色 + entrants: List["Avatar"] = [] + for av in world.avatar_manager.get_living_avatars(): + # 境界判定:realm <= max (取消最低限制,允许越阶挑战) + if av.cultivation_progress.realm <= domain.max_realm: + entrants.append(av) + + # 添加开启事件 + entrants_names = [av.name for av in entrants] + if entrants_names: + entrants_str = ", ".join(entrants_names) + open_event_content = t("Hidden Domain {name} opened! Entry restricted to {realm} and below. Entrants: {entrants}", + name=domain.name, + realm=str(domain.max_realm), + entrants=entrants_str) + else: + open_event_content = t("Hidden Domain {name} opened! Entry restricted to {realm} and below. No one entered.", + name=domain.name, + realm=str(domain.max_realm)) + events.append(Event(month_stamp, open_event_content)) + + if not entrants: + return events + + # 记录本次秘境的事件文本和相关角色 + event_texts: List[str] = [open_event_content] + related_avatars_set: set["Avatar"] = set() + + # 2. 遍历角色执行逻辑 + for av in entrants: + # --- 效果结算 --- + extra_drop = float(av.effects.get("extra_hidden_domain_drop_prob", 0.0)) + extra_danger = float(av.effects.get("extra_hidden_domain_danger_prob", 0.0)) + + drop_prob = domain.drop_prob + extra_drop + danger_prob = domain.danger_prob + extra_danger + + # 确保概率合理 + danger_prob = max(0.0, danger_prob) + + # --- 凶险判定 --- + if random.random() < danger_prob: + loss_percent = domain.hp_loss_percent + damage = int(av.hp.max * loss_percent) + av.hp.cur -= damage + + if av.hp.cur <= 0: + # 死亡结算 + reason = DeathReason(DeathType.HIDDEN_DOMAIN) + handle_death(world, av, reason) + + event_content = t("{name} perished in the hidden domain {domain}.", name=av.name, domain=domain.name) + event = Event( + month_stamp, + event_content, + related_avatars=[av.id] + ) + events.append(event) + + event_texts.append(event_content) + related_avatars_set.add(av) + continue # 死了就不能拿奖励了 + + # --- 机缘判定 --- + if random.random() < drop_prob: + # 获取奖励阶位(高一阶) + target_realm = self._get_next_realm(av.cultivation_progress.realm) + # 如果已经是最高阶,则维持当前阶位 + if not target_realm: + target_realm = av.cultivation_progress.realm + + loot = self._generate_loot(av, target_realm) + + if loot: + # 发放奖励 + from src.classes.weapon import Weapon + from src.classes.auxiliary import Auxiliary + from src.classes.technique import Technique + from src.classes.prices import prices + + loot_name = loot.name + + if isinstance(loot, Weapon): + old = av.weapon + av.change_weapon(loot) + if old: # 回收旧物 + av.magic_stone += prices.get_selling_price(old, av) + + elif isinstance(loot, Auxiliary): + old = av.auxiliary + av.change_auxiliary(loot) + if old: + av.magic_stone += prices.get_selling_price(old, av) + + elif isinstance(loot, Technique): + # 只有当比当前功法好,或者还没功法时才更换? + # 或者直接放入背包(如果有)?目前 Avatar 没有通用背包,通常直接修习 + # 简化逻辑:直接修习 + av.technique = loot + + # 记录事件 + event_content = t("{name} found a treasure {loot} in {domain}!", name=av.name, loot=loot_name, domain=domain.name) + event = Event( + month_stamp, + event_content, + related_avatars=[av.id] + ) + events.append(event) + + event_texts.append(event_content) + related_avatars_set.add(av) + + # 3. 生成故事 (StoryTeller) + # 只有当发生了一些值得记录的事情(死人、或者有人获得重宝)才生成故事,避免刷屏 + if event_texts: + story_event = await self._generate_story(world, domain, event_texts, list(related_avatars_set)) + if story_event: + events.append(story_event) + + return events + + async def _generate_story( + self, + world: "World", + domain: DomainConfig, + event_texts: List[str], + related_avatars: List["Avatar"] + ) -> Optional[Event]: + """调用 LLM 生成秘境探索故事""" + + if not related_avatars: + return None + + # 1. 场景描述 + gathering_info = t( + "Event: Hidden Domain Opening\nName: {name}\nDescription: {desc}", + name=domain.name, desc=domain.desc + ) + + # 2. 事件列表 + events_str = "\n".join(event_texts) + + # 3. 角色信息 (可选,增加故事细节) + details_list = [] + details_list.append(t("【Related Avatars Information】")) + for av in related_avatars: + info = av.get_info(detailed=True) + details_list.append(f"- {av.name}: {info}") + details_text = "\n".join(details_list) + + # 4. 调用 StoryTeller + from src.classes.story_teller import StoryTeller + story = await StoryTeller.tell_gathering_story( + gathering_info=gathering_info, + events_text=events_str, + details_text=details_text, + related_avatars=related_avatars, + prompt=self.get_story_prompt() + ) + + return Event( + month_stamp=world.month_stamp, + content=story, + related_avatars=[av.id for av in related_avatars], + is_major=True + ) diff --git a/src/classes/lode.py b/src/classes/lode.py index 54554ed..8166446 100644 --- a/src/classes/lode.py +++ b/src/classes/lode.py @@ -37,11 +37,11 @@ class Lode: """ from src.i18n import t # 使用格式化字符串 msgid - base_info = t("[{name}] ({realm})", name=t(self.name), realm=str(self.realm)) - info_parts = [base_info, t(self.desc)] + base_info = t("[{name}] ({realm})", name=self.name, realm=str(self.realm)) + info_parts = [base_info, self.desc] if self.materials: - material_names = [t(material.name) for material in self.materials] + material_names = [material.name for material in self.materials] materials_str = t("comma_separator").join(material_names) info_parts.append(t("Drops: {materials}", materials=materials_str)) diff --git a/src/classes/material.py b/src/classes/material.py index c7ef71f..ded09c4 100644 --- a/src/classes/material.py +++ b/src/classes/material.py @@ -25,11 +25,11 @@ class Material(Item): def get_info(self) -> str: from src.i18n import t - return t("{name} ({realm})", name=t(self.name), realm=str(self.realm)) + return t("{name} ({realm})", name=self.name, realm=str(self.realm)) def get_detailed_info(self) -> str: from src.i18n import t - return t("{name}: {desc} ({realm})", name=t(self.name), desc=t(self.desc), realm=str(self.realm)) + return t("{name}: {desc} ({realm})", name=self.name, desc=self.desc, realm=str(self.realm)) def get_structured_info(self) -> dict: return { diff --git a/src/classes/plant.py b/src/classes/plant.py index 9372f08..c8fad8b 100644 --- a/src/classes/plant.py +++ b/src/classes/plant.py @@ -38,11 +38,11 @@ class Plant: """ from src.i18n import t # 使用格式化字符串 msgid - base_info = t("[{name}] ({realm})", name=t(self.name), realm=str(self.realm)) - info_parts = [base_info, t(self.desc)] + base_info = t("[{name}] ({realm})", name=self.name, realm=str(self.realm)) + info_parts = [base_info, self.desc] if self.materials: - material_names = [t(material.name) for material in self.materials] + material_names = [material.name for material in self.materials] materials_str = t("comma_separator").join(material_names) info_parts.append(t("Drops: {materials}", materials=materials_str)) diff --git a/src/classes/sect.py b/src/classes/sect.py index 002f662..2f22e3f 100644 --- a/src/classes/sect.py +++ b/src/classes/sect.py @@ -327,8 +327,8 @@ def get_sect_info_with_rank(avatar: "Avatar", detailed: bool = False) -> str: # 构造详细信息,使用标准空格和括号 detail_content = t("(Alignment: {alignment}, Style: {style}, Headquarters: {hq_name}){effect}", alignment=avatar.sect.alignment, - style=t(avatar.sect.member_act_style), - hq_name=t(hq.name), + style=avatar.sect.member_act_style, + hq_name=hq.name, effect=effect_part) return f"{sect_rank_str} {detail_content}" diff --git a/src/classes/sect_ranks.py b/src/classes/sect_ranks.py index f9edd19..8c08338 100644 --- a/src/classes/sect_ranks.py +++ b/src/classes/sect_ranks.py @@ -116,7 +116,7 @@ def get_rank_display_name(rank: SectRank, sect: Optional["Sect"] = None) -> str: if sect is not None: custom_name = sect.get_rank_name(rank) if custom_name: - return t(custom_name) + return custom_name val = DEFAULT_RANK_NAMES.get(rank, "弟子") return t(val) diff --git a/src/classes/technique.py b/src/classes/technique.py index 7ef3f11..15765d6 100644 --- a/src/classes/technique.py +++ b/src/classes/technique.py @@ -106,21 +106,21 @@ class Technique: if detailed: return self.get_detailed_info() from src.i18n import t - return t("{name} ({attribute}) {grade}", name=t(self.name), attribute=str(self.attribute), grade=str(self.grade)) + return t("{name} ({attribute}) {grade}", name=self.name, attribute=str(self.attribute), grade=str(self.grade)) def get_detailed_info(self) -> str: from src.i18n import t effect_part = t(" Effect: {effect_desc}", effect_desc=self.effect_desc) if self.effect_desc else "" return t("{name} ({attribute}) {grade} {desc}{effect}", - name=t(self.name), attribute=str(self.attribute), grade=str(self.grade), - desc=t(self.desc), effect=effect_part) + name=self.name, attribute=str(self.attribute), grade=str(self.grade), + desc=self.desc, effect=effect_part) def get_colored_info(self) -> str: """获取带颜色标记的信息,供前端渲染使用""" from src.i18n import t r, g, b = self.grade.color_rgb # 使用与 get_info 相同的格式,但带有颜色标签 - info = t("{name} ({attribute}·{grade})", name=t(self.name), attribute=str(self.attribute), grade=str(self.grade)) + info = t("{name} ({attribute}·{grade})", name=self.name, attribute=str(self.attribute), grade=str(self.grade)) return f"{info}" def get_structured_info(self) -> dict: diff --git a/src/classes/weapon.py b/src/classes/weapon.py index 55e8b2c..cb05998 100644 --- a/src/classes/weapon.py +++ b/src/classes/weapon.py @@ -44,8 +44,8 @@ class Weapon(Item): from src.i18n import t effect_part = t(" Effect: {effect_desc}", effect_desc=self.effect_desc) if self.effect_desc else "" return t("{name} ({type}·{realm}, {desc}){effect}", - name=t(self.name), type=str(self.weapon_type), realm=str(self.realm), - desc=t(self.desc), effect=effect_part) + name=self.name, type=str(self.weapon_type), realm=str(self.realm), + desc=self.desc, effect=effect_part) def get_colored_info(self) -> str: """获取带颜色标记的信息,供前端渲染使用""" diff --git a/src/classes/world.py b/src/classes/world.py index 780a3e7..c4025b4 100644 --- a/src/classes/world.py +++ b/src/classes/world.py @@ -35,6 +35,8 @@ class World(): gathering_manager: GatheringManager = field(default_factory=GatheringManager) # 世界历史 history: "History" = field(default_factory=lambda: History()) + # 世界开始年份 + start_year: int = 0 def get_info(self, detailed: bool = False, avatar: Optional["Avatar"] = None) -> dict: """ @@ -106,6 +108,7 @@ class World(): map: "Map", month_stamp: MonthStamp, events_db_path: Path, + start_year: int = 0, ) -> "World": """ 工厂方法:创建使用 SQLite 持久化事件的 World 实例。 @@ -114,6 +117,7 @@ class World(): map: 地图对象。 month_stamp: 时间戳。 events_db_path: 事件数据库文件路径。 + start_year: 世界开始年份。 Returns: 配置好的 World 实例。 @@ -123,4 +127,5 @@ class World(): map=map, month_stamp=month_stamp, event_manager=event_manager, + start_year=start_year, ) diff --git a/src/i18n/locales/en_US/LC_MESSAGES/messages.mo b/src/i18n/locales/en_US/LC_MESSAGES/messages.mo index 956b99c04cf0385ea2ca9f995bdf1876d299d9c5..ff641097a3cd0818fad5ae48854063ffbbbf3780 100644 GIT binary patch delta 12684 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 Fe*@LVVDp_*4 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 diff --git a/src/i18n/locales/en_US/LC_MESSAGES/messages.po b/src/i18n/locales/en_US/LC_MESSAGES/messages.po index bac80e6..6b58937 100644 --- a/src/i18n/locales/en_US/LC_MESSAGES/messages.po +++ b/src/i18n/locales/en_US/LC_MESSAGES/messages.po @@ -1626,6 +1626,18 @@ msgstr "Refine Success Rate" +msgid "effect_extra_hidden_domain_drop_prob" + +msgstr "Hidden Domain Drop Rate" + + + +msgid "effect_extra_hidden_domain_danger_prob" + +msgstr "Hidden Domain Danger Rate" + + + # Action Short Names (for legal_actions) msgid "action_dualcultivation_short_name" @@ -2086,7 +2098,7 @@ msgstr "\n【Related Avatars Information】" msgid "auction_story_prompt" -msgstr "Select the most interesting aspect or bidding moment to describe, no need to cover everything." +msgstr "Select the most interesting aspect or a single bid to describe, without needing to cover everything. Focus on describing the tense atmosphere during the bidding process." @@ -3376,3 +3388,37 @@ msgstr "Two people are holding an elegant tea party." msgid "action_chess_story_prompt" msgstr "Two people are playing chess." + +# ============================================================================ +# Hidden Domain +# ============================================================================ + +msgid "hidden_domain_story_prompt" +msgstr "Describe the exploration of the hidden domain, focusing on the dangers encountered or the opportunities seized, creating a mysterious and tense atmosphere." + +msgid "Hidden Domains opened: {names}" +msgstr "{names}" + +msgid "Hidden Domain {name} opened! Entry restricted to {realm} and below." +msgstr "Hidden Domain {name} opened! Entry restricted to {realm} and below." + +msgid "{name} perished in the hidden domain {domain}." +msgstr "{name} perished in the hidden domain {domain}." + +msgid "{name} found a treasure {loot} in {domain}!" +msgstr "{name} found a treasure {loot} in {domain}!" + +msgid "Perished in a Hidden Domain" +msgstr "Perished in a Hidden Domain" + +msgid "Event: Hidden Domain Opening\nName: {name}\nDescription: {desc}" +msgstr "Event: Hidden Domain Opening\nName: {name}\nDescription: {desc}" + +msgid "Hidden Domain {name} opened! Entry restricted to {realm} and below. Entrants: {entrants}" +msgstr "Hidden Domain {name} opened! Entry restricted to {realm} and below. Entrants: {entrants}" + +msgid "Hidden Domain {name} opened! Entry restricted to {realm} and below. No one entered." +msgstr "Hidden Domain {name} opened! Entry restricted to {realm} and below. No one entered." + +msgid "【Related Avatars Information】" +msgstr "【Related Avatars Information】" diff --git a/src/i18n/locales/zh_CN/LC_MESSAGES/messages.mo b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.mo index 48ddf748007a13e62b0b0c38f04cf021974a7e7b..a6d15b0cef184d2efbc48c9577030a9353ab85ed 100644 GIT binary patch delta 12553 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 POQ*gSk|$&TH1B@_6B)ZU delta 11207 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