diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..6c1ed4a76d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +@CONTRIBUTING.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..6c1ed4a76d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6fbbcbeb8c..4b437bca09 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,119 @@ -> W.I.P +欢迎为[《无名杀》](https://github.com/libnoname/noname)贡献代码!本文档帮助你了解项目结构、开发规范和提交流程。 -请参考[此处](https://github.com/libnoname/noname/wiki/%E3%80%8A%E6%97%A0%E5%90%8D%E6%9D%80%E3%80%8B%E9%A1%B9%E7%9B%AE-Pull-Request-%E6%8F%90%E4%BA%A4%E8%A7%84%E8%8C%83)了解无名杀的PR规范。 +## 快速开始 + +- 环境配置: [本地文档](docs/how-to-start.md) / [GitHub 文档](https://github.com/libnoname/noname/wiki/%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E6%97%A0%E5%90%8D%E6%9D%80%EF%BC%88%E7%A8%8B%E5%BA%8F%E5%91%98%E7%89%88%EF%BC%89) +- 本地文档: [docs/](docs/) + +**技术栈**: + +- **包管理**: pnpm workspace (>=9) +- **运行时**: Node.js ^20.19.0 || >=22.12.0 +- **构建**: Vite (preserveModules, 不压缩, 不 treeshake) +- **类型**: TypeScript (迁移中, strict 未开启) +- **UI**: 原生JS (正在迁移到Vue 3) +- **代码风格**: ESLint + Prettier, tab缩进, 小驼峰 + +```bash +pnpm install # 安装依赖 +pnpm dev # 启动开发服务器 +``` + +## 项目概览 + +### 项目结构 + +```text +apps/core/ 主项目 — 游戏核心、UI、武将/卡牌/模式数据 +apps/electron/ Electron 桌面端 +apps/mobile/ 移动端 (Capacitor) +packages/ 工程化包 + fs/ HTTP静态资源服务器 + 文件操作接口 + jit/ JIT 编译 + server/ 联机服务器 + extension/ 工程化扩展开发 → 产物放到 apps/core/extension/ +docs/ 本地文档 +scripts/ 顶层构建/工具脚本 +``` + +### 命名空间 + +`apps/core/noname.js` 是框架入口,导出六个全局命名空间。所有内容直接挂载在上面,无隔离、无对外接口: + +| 导出 | 别名 | 职责 | +| ---- | ---- | ---- | +| `Library` | `lib` | 元素/技能/配置管理 | +| `Game` | `game` | 游戏流程/事件循环 | +| `UI` | `ui` | 界面渲染 | +| `Get` | `get` | 查询/检索 | +| `Status` | `_status` | 运行时状态 | +| `AI` | `ai` | AI 逻辑 | + +命名空间非模块,是类 C++ `std::` 的全局寄存器。任何内容注册后全局可访问,修改时需注意兼容性(尤其在社区扩展生态中)。 + +### 目录自动导入 + +`apps/core/noname/init/import.ts` 按目录结构自动解析导入路径: + +- **武将包**: `/character/{包名}/index.js` → 先试 `.js`,安全上下文下回退到 `.ts` +- **卡牌包**: `/card/{包名}.js` +- **模式**: `/mode/{模式名}/index.js` +- **扩展**: `/extension/{扩展名}/extension.js` + +所有内容必须导出 `type` 字段标明类型 (`"character" | "card" | "mode" | "extension"`)。 + +旧版是单文件入口(`character/name.js`),新版是目录入口(`character/name/index.ts`),已迁移的武将通过 `game/config.json` 中的 `moderned_characters` 列表追踪。 + +## 开发规范 + +- **提交信息**: 中文 Conventional Commits (`feat:`, `fix:`, `perf:`, `docs:`, `build:`, `refactor:` 等) +- **命名**: JS 生态小驼峰,无特殊中文/拼音要求 +- **分支**: 主分支 `main` +- **PR 流程**: 参考 [PR 模板](.github/PULL_REQUEST_TEMPLATE/)(功能提交 / 更新报告) +- **测试**: 项目目前没有自动化测试。修改核心逻辑(命名空间挂载、ContentCompiler、导入系统)需格外谨慎 +- **"代提" PR**: 表示替不会用 git/上不了 GitHub 的贡献者提交代码 + +新手入门: + +- [Git 下载安装指南](https://github.com/libnoname/noname/wiki/Git%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85%E6%8C%87%E5%8D%97) +- [Github 桌面版客户端使用入门](https://docs.github.com/zh/desktop/overview/getting-started-with-github-desktop) +- [如何提交代码到《无名杀》Github 仓库](https://github.com/libnoname/noname/wiki/%E5%A6%82%E4%BD%95%E6%8F%90%E4%BA%A4%E4%BB%A3%E7%A0%81%E5%88%B0%E3%80%8A%E6%97%A0%E5%90%8D%E6%9D%80%E3%80%8BGithub%E4%BB%93%E5%BA%93) + +## 核心架构 + +### Content 系统 + +游戏逻辑的执行引擎,使用责任链模式统一编译为 `(event: GameEvent) => Promise`: + +| 写法 | 状态 | 说明 | +| ---- | ---- | ---- | +| 单 async 函数 | **推荐** | `setContent(async (event, trigger, player) => { ... })` | +| 函数数组 | 待废弃 | `setContent([fn1, fn2, fn3])` | +| step 语法 | 待废弃 | `setContent(function() { "step 0"; ... })` | + +**所有新代码使用单 async 函数写法。** 项目正从旧的 step DSL 逐步迁移到 async,迁移路线详见 `docs/game-event/content.md`。 + +### 构建系统 + +`apps/core/scripts/build.ts` 是核心构建脚本: + +- 本体构建使用 `preserveModules: true` 保留文件结构,`treeshake: false`,`minify: false` +- Vue 作为 external,通过 import map 运行时加载 +- 武将包/卡牌包/模式包单独构建到 `dist/` 对应子目录 +- 构建后通过 static copy 和 import map 组装为与旧项目兼容的物理结构 + +### 扩展系统 + +双层结构以兼容旧生态的相对路径导入: + +- `packages/extension/` — 工程化扩展的**源码** +- `apps/core/extension/` — 扩展的**产物**(工程化扩展打包结果 + 未工程化扩展直接存放) + +扩展生态与主仓库开发差异较大。 + +## 迁移状态 + +项目正从 **ES5 巨石应用** 迁移到 **pnpm monorepo + TypeScript**。所有迁移都是新旧并存、渐进式的: + +- **JS → TS**: 类型文件集中在 `typings/`,严格度暂时放宽 (`noImplicitAny: false` 等) +- **step → async**: 旧的 step DSL(字符串标记步骤边界)正在逐步替换为单 async 函数。`docs/game-event/content.md` 详细描述了 ContentCompiler 的三种格式及迁移路线 diff --git a/README.md b/README.md index ab04f49dca..5fcc1be0fd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# 无名杀 +# 无名杀 (noname) + +开源卡牌游戏。 ## 项目使用约定 @@ -8,60 +10,47 @@ 1. 打包、二次分发 **请保留代码出处**: 2. 请不要用于商业用途。 +> 关于《无名杀十周年》破坏开源协议的违法行为的通告: +> + ## 快速启动 -### 环境要求 +> **提示:** 新手请参考 [本地文档](./docs/how-to-start.md) 或 [GitHub 文档](https://github.com/libnoname/noname/wiki/%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E6%97%A0%E5%90%8D%E6%9D%80%EF%BC%88%E7%A8%8B%E5%BA%8F%E5%91%98%E7%89%88%EF%BC%89) 配置环境。 -> **提示:** 请参考 [本地文档](./docs/how-to-start.md) 或 [github文档](https://github.com/libnoname/noname/wiki/%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E6%97%A0%E5%90%8D%E6%9D%80%EF%BC%88%E7%A8%8B%E5%BA%8F%E5%91%98%E7%89%88%EF%BC%89) 配置环境。 +### 环境要求 - [Node.js](https://nodejs.org/) ^20.19.0 || >=22.12.0 - [pnpm](https://pnpm.io/) >= 9 -- Webview: Chromium >= 91 || Safari >=16.4.0 (暂不支持Firefox) - -### 安装依赖 +- Webview: Chromium >= 91 || Safari >= 16.4 (暂不支持 Firefox) -```bash -pnpm install -``` - -### 启动 +### 安装与启动 ```bash -pnpm dev +pnpm install # 安装依赖 +pnpm dev # 启动开发服务器 ``` ---- - -贡献代码可阅读相关文档: - -[Git 下载安装指南](https://github.com/libnoname/noname/wiki/Git%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85%E6%8C%87%E5%8D%97) +## 外部资源 -[Github 桌面版客户端使用入门](https://docs.github.com/zh/desktop/overview/getting-started-with-github-desktop) +- 仓库: +- 贡献指南: [CONTRIBUTING.md](CONTRIBUTING.md) -[如何提交代码到《无名杀》Github 仓库](https://github.com/libnoname/noname/wiki/%E5%A6%82%E4%BD%95%E6%8F%90%E4%BA%A4%E4%BB%A3%E7%A0%81%E5%88%B0%E3%80%8A%E6%97%A0%E5%90%8D%E6%9D%80%E3%80%8BGithub%E4%BB%93%E5%BA%93) +客户端下载: -[《无名杀》项目 Pull Request 提交规范](https://github.com/libnoname/noname/wiki/%E3%80%8A%E6%97%A0%E5%90%8D%E6%9D%80%E3%80%8B%E9%A1%B9%E7%9B%AE-Pull-Request-%E6%8F%90%E4%BA%A4%E8%A7%84%E8%8C%83) +- 安卓: +- PC: -客户端下载戳这里: +## 项目结构 -安卓: - -PC: - -网页端推荐使用 Chrome 系内核浏览器游玩,暂不支持 Firefox 浏览器 - -请尽量保证游玩的 Chrome 系浏览器或手机 Webview 的`内核版本大于等于91` - ---- - -最近有一伙人号称是《无名杀十周年》的开发团队,宣称《无名杀十周年》“全新无名杀,比旧版拥有更多武将,兼容更多扩展”,实际上: - -- 《无名杀十周年》(原《无名杀清瑶版》)由无名杀 v1.9.124 修改而来,属于无名杀的一种**Fork**,并非“全新无名杀”;且《无名杀十周年》开发团队**公然违反 GPL-3.0 协议**,详情请看[这里](https://github.com/github/dmca/blob/master/2023/09/2023-09-20-noname.md)、[这里](https://tieba.baidu.com/p/8623890806)以及[这里](https://tieba.baidu.com/p/8624582238)。 -- 《无名杀十周年》至今没有更新神典韦等新机制武将,且删除了部分无名杀的原创武将,导致《无名杀十周年》的武将数量远远不及无名杀前几个版本的武将数量;不仅如此,《无名杀十周年》自分裂后的部分武将源码依然来自无名杀和其他开发者开源的代码。 -- 《无名杀十周年》兼容扩展的方式是不更新本体数据,从而导致《无名杀十周年》仍然在用 1.9.124 版本的代码,无法兼容使用 1.10 以后功能的扩展;而且《无名杀十周年》开发团队在使用**大量**GPL-3.0 开源的代码后对生成产物进行了**混淆加密**,在**违反开源精神**的同时,也导致扩展稳定性极具下降,更容易出问题。 - -《无名杀十周年》就是彻头彻尾的骗局,《无名杀十周年》的开发团队更是一群拿无名杀吸血的骗子,虽然号称“不忘初心”,却公然对最有资格论述无名杀创作初心的无名杀创始人进行侮辱谩骂,直接违背其制定的规则和开源精神,恶劣程度远超当初在多个无名杀社群“自立”的水叶之流。 - -先秦介子推曾言:“窃人之财,犹谓之盗,况贪天之功以为己力乎。”无名杀社区发展至今,正是因为有大量的开源代码进行参考,才能不断推陈出新。试想每个扩展开发者在成为一个扩展开发者之前,谁敢说没有大量参考社区内的源码?每个作品凝聚的都是大家的心血,而不是仅仅归属于个别人。我们相信:开放、共享、多元才是无名杀的初心,绝不是封闭、私藏与趋同。 - -我们在此呼吁无名杀社区正确认识《无名杀十周年》开发团队的一些行为与做法,并希望《无名杀十周年》开发团队能反省迄今以来的所作所为。**自由开源**是无名杀社区的灵魂,希望各方都能够遵循这一精神。 +```text +apps/core/ 主项目 — 游戏核心、UI、武将/卡牌/模式数据 +apps/electron/ Electron 桌面端 +apps/mobile/ 移动端 (Capacitor) +packages/ 工程化包 + fs/ HTTP静态资源服务器 + 文件操作接口 + jit/ JIT 编译 + server/ 联机服务器 + extension/ 工程化扩展开发 → 产物放到 apps/core/extension/ +docs/ 本地文档 +scripts/ 顶层构建/工具脚本 +``` diff --git a/apps/core/character/bingshi/character.js b/apps/core/character/bingshi/character.js index a2e3c91280..56aebb4923 100644 --- a/apps/core/character/bingshi/character.js +++ b/apps/core/character/bingshi/character.js @@ -1,4 +1,10 @@ const characters = { + pot_lvyi: { + sex: "male", + group: "wu", + hp: 4, + skills: ["pothuilv", "potsongyan", "potshishi"], + }, pot_caoshuang: { sex: "male", group: "wei", diff --git a/apps/core/character/bingshi/characterTitle.js b/apps/core/character/bingshi/characterTitle.js index 1ef48b41dc..4ff3459f40 100644 --- a/apps/core/character/bingshi/characterTitle.js +++ b/apps/core/character/bingshi/characterTitle.js @@ -1,4 +1,5 @@ export default { + //pot_lvyi: "", pot_caoshuang: "擅政专权", //pot_zhangren: "", pot_sunchen: "凶竖盈溢", @@ -7,7 +8,7 @@ export default { //pot_chenjiao: "", //pot_zanghong: "", sp_zhonghui: "荡蜚缴志", - mb_chenzhi: "承担接贤", + mb_chenzhi: "承担接贤", mb_sunjun: "横逆自固", guoyuan: "清介有守", mb_huangzu: "守殁枭寒", diff --git a/apps/core/character/bingshi/intro.js b/apps/core/character/bingshi/intro.js index 92ecd35687..066dd3341f 100644 --- a/apps/core/character/bingshi/intro.js +++ b/apps/core/character/bingshi/intro.js @@ -1,4 +1,5 @@ const characterIntro = { + lvyi: "吕壹(?—约238年),三国时期孙吴官员。吕壹出身不详,因受孙权宠信而官至中书典校郎(亦称“校事”),负责监察中央和地方州郡的文书事务。吕壹性苛惨,用法深刻,善于罗织罪名、诬陷大臣。他先后诬陷丞相顾雍、左将军朱据(孙权女婿)、江夏太守刁嘉等人,朝野震恐,太常潘濬甚至计划在宴会上亲手刺杀他以除国害。大将军陆逊等人亦“忧壹乱国,每言之辄流涕”。后朱据一案真相大白,孙权幡然醒悟,下令处死吕壹,并“引咎责躬”,向群臣致歉。吕壹事件是孙权晚年借校事制衡江东大族、巩固皇权之举,但亦反映其晚年猜忌群臣、治国严苛的一面。", zanghong: "臧洪(?—约196年 [21]),字子源,广陵射阳(今江苏宝应县东)人,东汉文学家。臧洪十五岁时,以父功拜童子郎,知名太学。后举孝廉为郎,任即丘长。中平末,弃官还家,太守张超任为功曹。后董卓专权,臧洪劝说张超联合兖州刺史刘岱等起兵讨卓,于酸枣设坛。臧洪升坛盟誓,辞气慷慨,涕泣横下,闻者莫不激扬。后归袁绍,任青州刺史、东郡太守。后张超被曹操围困,求救于臧洪,袁绍不允发兵,臧洪遂与袁绍决裂。袁绍兴兵围东郡,历年攻城不下,便令陈琳写信给臧洪,劝其归降。但臧洪回信拒绝,后城破被杀。臧洪在“缔谋连衡”西伐董卓的过程中,起了重大的积极作用的。臧洪著有《酸枣盟辞》《答陈琳书》。", huanjie: "桓阶(?—221年),字伯绪(《孙夫人碑》作伯序),长沙临湘(今湖南长沙)人。三国时期曹魏大臣,先为郡功曹,太守孙坚举为孝廉,朝廷任命他做尚书郎。孙坚战死,桓阶冒险求见刘表,索回孙坚尸体。曹操平定荆州,感念桓阶曾游说长沙太守张羡投曹,任命他当丞相主薄、赵郡太守。曹操封公建国,桓阶任虎贲中郎将、侍中。曹丕继位,桓阶任尚书令、侍中,封高乡亭侯,被曹丕视为寄命之臣。黄初二年(221年),桓阶得病,进爵安乐乡侯,改任太常,同年去世,谥号贞侯。", chenzhi: "陈祗(?―258年/259年9月23日),字奉宗,汝南(今河南平舆)人,三国时期蜀汉大臣,大司徒许靖兄长的外孙。陈祗早年受费祎的赏拔,董允死后担任侍中,逐渐成为蜀汉后主刘禅的宠臣,官至尚书令、镇军将军。陈祗上承主指,下接阉竖,权力甚至超过大将军姜维,也导致宦官黄皓开始干预政事。景耀元年(《华阳国志》作景耀二年八月丙子),陈祗去世,被追谥为“忠侯”。", diff --git a/apps/core/character/bingshi/skill.js b/apps/core/character/bingshi/skill.js index 84e773d7e5..a77a098b4c 100644 --- a/apps/core/character/bingshi/skill.js +++ b/apps/core/character/bingshi/skill.js @@ -3,6 +3,270 @@ import { lib, game, ui, get, ai, _status } from "noname"; /** @type { importCharacterConfig["skill"] } */ const skills = { //potential--潜在, 潜力, 可能, 电位, 潜能, 势 + //势吕壹------by 清风 + pothuilv: { + audio: 2, + trigger: { global: "phaseUseBegin" }, + filter(event, player) { + if (player == event.player) { + return false; + } + return ["red", "black"].some(color => !player.getStorage("pothuilv_round").includes(color)); + }, + async cost(event, trigger, player) { + const list = ["red", "black", "cancel2"].removeArray(player.getStorage(event.skill + "_round")), + target = trigger.player; + const result = await player + .chooseControl(list) + .set("prompt", get.prompt(event.skill, target)) + .set("prompt2", "令其从牌堆中获得一张此颜色的牌") + .set("ai", () => { + const { player, controls, target } = get.event(); + if (get.attitude(player, target) > 0) { + return "cancel2"; + } + if (controls.includes("red")) { + return "red"; + } + return controls.slice().remove("cancel2").randomGet(); + }) + .set("target", target) + .forResult(); + if (typeof result.control == "string" && result.control != "cancel2") { + event.result = { + bool: true, + cost_data: result.control, + }; + } + }, + logTarget: "player", + async content(event, trigger, player) { + const { + cost_data: color, + targets: [target], + } = event; + player.addTempSkill(event.name + "_round", "roundStart"); + player.markAuto(event.name + "_round", [color]); + const card = get.cardPile2(card => get.color(card) == color); + if (card) { + player.addTempSkill(event.name + "_effect", "phaseUseAfter"); + player.markAuto(event.name + "_effect", [card]); + player + .when({ global: "phaseUseEnd" }) + .filter(evt => evt == trigger) + .step(async (event, trigger, player) => { + trigger.pothuilv_check = true; + }); + target.addTempSkill(event.name + "_mark", "phaseUseAfter"); + const next = target.gain(card, "gain2"); + next.gaintag.add(event.name + "_mark"); + await next; + await target.showCards(card, `${get.translation(player)}发动了【${get.translation(event.name)}】`); + } else { + player.chat("没牌喽"); + } + }, + subSkill: { + round: { charlotte: true, onremove: true }, + mark: { + charlotte: true, + onremove(player, skill) { + player.removeGaintag(skill); + }, + }, + effect: { + audio: "pothuilv", + charlotte: true, + onremove: true, + trigger: { global: "phaseUseEnd" }, + filter(event, player) { + if (!event.pothuilv_check) { + return false; + } + const bool1 = event.player.hasHistory("useCard", evt => evt.getParent("phaseUse") == event && evt.cards?.some(card => player.getStorage("pothuilv_effect").includes(card))); + const bool2 = player.getStorage("pothuilv_effect").some(card => get.position(card) !== "d"); + return bool1 || bool2; + }, + forced: true, + logTarget: "player", + async content(event, trigger, player) { + const target = trigger.player; + const bool1 = target.hasHistory("useCard", evt => evt.getParent("phaseUse") == trigger && evt.cards?.some(card => player.getStorage(event.name).includes(card))); + const bool2 = player.getStorage(event.name).some(card => get.position(card) !== "d"); + if (bool1) { + await target.damage(); + } + if (bool2) { + await player.damage(target, "unreal"); + const cards = player.getStorage(event.name).filter(card => get.position(card) !== "d"); + if (cards.length && game.hasPlayer(current => current != target)) { + const result = await player + .chooseTarget({ + prompt: `隳律:你可以将${get.translation(cards)}交给另一名角色`, + filterTarget(card, player, target) { + return target != get.event().target; + }, + ai(target) { + const player = get.player(); + if (get.event().cards[0].name == "du") { + return -get.attitude(player, target); + } + return get.attitude(player, target); + }, + }) + .set("cards", cards) + .set("target", target) + .forResult(); + if (result?.bool && result.targets?.length) { + const target = result.targets[0]; + player.line(target); + const gainEvent = target.gain(cards, "give"); + gainEvent.giver = player; + await gainEvent; + } + } + } + }, + }, + }, + }, + potsongyan: { + audio: 2, + trigger: { player: ["phaseZhunbeiBegin", "damageEnd"] }, + filter(event, player) { + return player.hasUseTarget(get.autoViewAs({ name: "wuzhong", isCard: true })); + }, + prompt2: "视为使用一张【无中生有】", + frequent: true, + async content(event, trigger, player) { + game.filterPlayer().forEach(current => current.addTempSkill(event.name + "_wuxie")); + await player.chooseUseTarget(get.autoViewAs({ name: "wuzhong", isCard: true }), true); + game.filterPlayer().forEach(current => current.removeSkill(event.name + "_wuxie")); + }, + getColors(player) { + const colors = player + .getRoundHistory("lose", evt => evt.type == "discard") + .flatMap(evt => evt.cards) + .map(card => get.color(card)) + .unique(); + return colors; + }, + subSkill: { + wuxie: { + charlotte: true, + onChooseToUse(event) { + if (!game.online && !event.potsongyan) { + const player = event.player; + event.set("potsongyan", get.info("potsongyan").getColors(player)); + } + }, + enable: "chooseToUse", + filter(event, player) { + if (event.type != "wuxie") { + return false; + } + return player.hasCards("hes", card => (event.potsongyan || []).includes(get.color(card))); + }, + position: "hes", + filterCard(card, player) { + return get.event().potsongyan?.includes(get.color(card)); + }, + viewAs: { name: "wuxie" }, + prompt: "将一张牌当做【无懈可击】使用", + check(card) { + return 8 - get.value(card); + }, + }, + }, + }, + potshishi: { + getRespondEvts(event, player) { + let respondEvts = []; + for (const current of game.filterPlayer2(current => current != player, null, true)) { + respondEvts.addArray(current.getAllHistory("useCard", evt => evt.respondTo?.[0] === player, event)); + respondEvts.addArray(current.getAllHistory("respond", evt => evt.respondTo?.[0] === player, event)); + } + return respondEvts; + }, + init(player, skill) { + player.addSkill(skill + "_mark"); + }, + onremove(player, skill) { + delete player.storage[skill]; + player.removeSkill(skill + "_mark"); + }, + intro: { content: "$不能响应你使用的牌,直到你使用牌被其他角色响应" }, + audio: 2, + trigger: { player: "useCard" }, + filter(event, player) { + const respondEvts = get.info("potshishi").getRespondEvts(null, player); + if ( + player.hasAllHistory("useCard", evt => { + return respondEvts.some(evtx => evtx.respondTo[1] == evt.card); + }) + ) { + return false; + } + return get.info("potshishi").logTarget(event, player).length > 0; + }, + forced: true, + logTarget(event, player) { + return player + .getAllHistory("damage", evt => evt.source?.isIn()) + .map(evt => evt.source) + .toUniqued() + .sortBySeat(); + }, + async content(event, trigger, player) { + trigger.directHit.addArray(event.targets); + game.log(event.targets, "不可响应", trigger.card); + }, + ai: { + directHit_ai: true, + skillTagFilter(player, tag, arg) { + if (player.getStorage("potshishi").includes(arg?.target)) return true; + }, + }, + subSkill: { + mark: { + charlotte: true, + trigger: { + player: "damage", + global: ["useCard", "respond"], + }, + filter(event, player) { + const respondEvts = get.info("potshishi").getRespondEvts(event.name == "damage" ? null : event, player); + if (event.name == "damage") { + if (get.itemtype(event.source) !== "player" || player.getStorage("potshishi").includes(event.source)) { + return false; + } + return !player.hasAllHistory("useCard", evt => { + return respondEvts.some(evtx => evtx.respondTo[1] == evt.card); + }); + } + if (!player.getStorage("potshishi").length) { + return false; + } + if (!Array.isArray(event.respondTo)) { + return false; + } + if (player == event.player || event.respondTo[0] !== player) { + return false; + } + return respondEvts.indexOf(event) == 0; + }, + silent: true, + firstDo: true, + async content(event, trigger, player) { + if (trigger.name == "damage") { + player.markAuto("potshishi", [trigger.source]); + } else { + player.setStorage("potshishi", [], true); + } + }, + }, + }, + }, //势曹爽 potdianyi: { audio: 2, diff --git a/apps/core/character/bingshi/translate.js b/apps/core/character/bingshi/translate.js index 681edc05d6..732d651f40 100644 --- a/apps/core/character/bingshi/translate.js +++ b/apps/core/character/bingshi/translate.js @@ -1,6 +1,14 @@ import { lib, game, ui, get, ai, _status } from "noname"; const translates = { + pot_lvyi: "势吕壹", + pot_lvyi_prefix: "势", + pothuilv: "隳律", + pothuilv_info: "每轮每种颜色限一次,其他角色的出牌阶段开始时,你可以选择一种颜色,令其从牌堆中随机获得一张该颜色的牌并展示。此阶段结束时,若该角色于此阶段内使用过此牌,你对其造成1点伤害;若此牌不在弃牌堆,其对你造成1点虚拟伤害,然后你可将该牌交给另一名角色。", + potsongyan: "耸言", + potsongyan_info: "准备阶段或当你受到伤害后,你可以视为使用一张【无中生有】,此【无中生有】结算过程中,本轮因弃置失去过牌的角色可以将一张与其本轮弃置的牌颜色相同的牌当做【无懈可击】使用。", + potshishi: "恃势", + potshishi_info: "锁定技,对你造成过伤害的角色不能响应你使用的牌,直到你使用的牌被其他角色响应。", pot_caoshuang: "势曹爽", pot_caoshuang_prefix: "势", potdianyi: "典易", diff --git a/apps/core/character/rank.js b/apps/core/character/rank.js index 9f94f18d68..f4b296a824 100644 --- a/apps/core/character/rank.js +++ b/apps/core/character/rank.js @@ -553,6 +553,7 @@ window.noname_character_rank = { "yj_zhenji", ], bp: [ + "pot_lvyi", "dc_shen_liubei", "pot_zhangren", "pot_sunchen", @@ -2668,6 +2669,7 @@ window.noname_character_rank = { "v_sunce", ], epic: [ + "pot_lvyi", "dc_shen_liubei", "dc_shen_zhangliao", "dc_muludawang",