本模组的细节代码除参考了他人开源的旧仓库外,均由我与多个大模型(DeepSeek、Mimo、GPT 等)协作讨论完成;「大王」「小王」两张牌面则由我使用 Photoshop 亲手绘制。
开发过程历经多轮迭代:从最初适配 1.21.1 NeoForge,到重构客户端与服务端的交互规范、设置页规范与音乐播放规范,最终完成了 1.21 ~ 1.21.11 的全版本适配,并配套进行了自动化测试与文档编写。文档内容涵盖斗地主的玩法规则、各版本迭代的适配改动、音乐包的制作方法等。
尽管已进行了较为全面的测试,但在不同版本环境下仍可能出现报错。如果你在使用中遇到问题,欢迎提出 Issue,我会尽力修复。另外,后续将不再做全量更新,只会针对模组生态较为活跃的大版本进行玩法拓展与维护。
简体中文(默认) | English
Crafty Cards 在 Minecraft 世界里提供三人联机斗地主:合成并放下一张牌桌,三人入局、叫地主、出牌、结算。 当前斗地主不用全屏 GUI:手牌、桌面出牌、别人的牌都渲染在世界或 HUD 里,你能看到牌桌周围的一切。
本项目基于 OmbreMoon 的 PlayingCards 改造;该项目又移植自 Calemi 的 Playing Cards。本项目不是两者的官方续作。继承的代码与美术、致谢及许可核对情况见 CREDITS.md。
**模组安装包请从 CurseForge 的 Crafty Cards 页面 下载。**请选择与你的 Minecraft 版本及 NeoForge、Fabric 或 Forge 加载器相匹配的 JAR,并将其安装到客户端和服务器的 mods 目录。音乐包和制作工具可从 GitHub Release 获取。
| 项目 | 版本 |
|---|---|
| Minecraft | 1.21–1.21.11(默认构建目标 1.21.1,不含 26.x) |
| NeoForge | 随 Minecraft 目标选择;版本表见 common/gradle/minecraft-target.gradle |
| Fabric | Loader 0.19.5;Fabric API 随 Minecraft 目标选择 |
| Forge | 除 1.21.2(无目标发行版)外按 Minecraft 目标选择 |
| Java | 21 |
斗地主只支持 3 人联机(服务端权威),没有单人/AI 对手。
Fabric 构建、三客户端自动验收与当前范围见 Fabric POC 验收说明。 Forge 构建与验收见 Forge POC 说明。三种加载器使用各自的 jar,客户端连接相同加载器的服务器;尚未验证跨加载器混服。
版本选择、构建命令与后续移植边界见 版本兼容性说明。
1.21.2—1.21.11 测试适配见 现代版本说明:覆盖 NeoForge、Fabric,以及有目标发行版的 Forge(Forge 无 1.21.2),不包含 26.x。安装包按版本和加载器分开;构建与单测通过不等于 HUD 观感和音乐听音均已人工确认。
逐版本三客户端自动验收的运行命令、结果位置和人工复核边界见 多版本自动化验收。
源码按 <加载器>-mc<版本> 命名,例如 neoforge-mc1.21.1、fabric-mc1.21.11;完整列表见 Branches。每个版本分支都是可独立构建的完整快照,只保留该目标需要的代码。
- 只改某个版本:Fork 后切到对应版本分支,PR 的 base 也选它,不要选
main。 - 改公共逻辑:PR 到
main,合并后再cherry-pick到需要的版本分支。 - 版本分支之间只 cherry-pick、不 merge;不要把版本分支 merge 回
main——那会删掉fabric/、forge/和现代版本适配层。
规则与构建方式见 CONTRIBUTING.md,发布流程见 版本分支与发布流程。
| Minecraft | NeoForge | Fabric | Forge |
|---|---|---|---|
| 1.21.11 | neoforge-mc1.21.11 | fabric-mc1.21.11 | forge-mc1.21.11 |
| 1.21.10 | neoforge-mc1.21.10 | fabric-mc1.21.10 | forge-mc1.21.10 |
| 1.21.9 | neoforge-mc1.21.9 | fabric-mc1.21.9 | forge-mc1.21.9 |
| 1.21.8 | neoforge-mc1.21.8 | fabric-mc1.21.8 | forge-mc1.21.8 |
| 1.21.7 | neoforge-mc1.21.7 | fabric-mc1.21.7 | forge-mc1.21.7 |
| 1.21.6 | neoforge-mc1.21.6 | fabric-mc1.21.6 | forge-mc1.21.6 |
| 1.21.5 | neoforge-mc1.21.5 | fabric-mc1.21.5 | forge-mc1.21.5 |
| 1.21.4 | neoforge-mc1.21.4 | fabric-mc1.21.4 | forge-mc1.21.4 |
| 1.21.3 | neoforge-mc1.21.3 | fabric-mc1.21.3 | forge-mc1.21.3 |
| 1.21.2 | neoforge-mc1.21.2 | fabric-mc1.21.2 | — |
| 1.21.1 | neoforge-mc1.21.1 | fabric-mc1.21.1 | forge-mc1.21.1 |
| 1.21 | neoforge-mc1.21 | fabric-mc1.21 | forge-mc1.21 |
main 只负责公共代码、文档和 .github 配置(Issue/PR 模板、可复用 CI),不直接发布 jar;每个版本分支负责该目标的源码、构建与发布。文件归属、新增分支步骤和 GitHub 设置清单见 仓库结构与维护职责。
-
合成牌桌:绿地毯 ×1 + 木板 ×3 + 木棍 ×2
C C = 绿地毯 PPP P = 木板(任意种类) S S S = 木棍 -
先定筹码:一人 Shift+右键牌桌成为房主,在本桌配置界面选择筹码、底注与门槛。 启用筹码但未配置类型时无法入局;也可以关闭本桌筹码,玩纯娱乐局。
-
三人各自右键牌桌入局(坐满自动发牌)。入局要求背包里备够入局门槛个本局筹码 默认 = 底注 × 6,房主也可以在「玩法设置」里直接指定门槛(档位到 100 万);不足会明确提示还差多少。
-
入局后背包会得到一张**「斗地主扑克」**——握着它才会展开你自己的手牌。
| 操作 | 效果 |
|---|---|
| 右键牌桌 | 入局;叫分轮到你时确认叫分;出牌轮到你时出牌 / 过牌 |
| Shift + 右键牌桌 | 在局内 = 离开;局外房主 = 本桌配置;已有其他在线房主 = 开始/停止观战 |
| 滚轮(握着扑克) | 叫分阶段切换【不叫/1分/2分/3分】;出牌阶段移动手牌焦点,方向与滚轮相反 |
| 左键(握着扑克) | 把焦点牌加入/移出选中(非回合也能预选) |
| 右键空气(握着扑克) | 清除全部选中 |
切武器不影响牌局:主手换成别的物品只是收起牌面,牌局照旧继续;切回扑克即恢复。
非参局玩家靠近牌桌后 Shift+右键即可观战(不占座位、不必等坐满);能否看到各家的牌面由服务端配置决定, 可自由走动、牌面持续更新。走近但不操作是看不到牌的——这是刻意的安全默认值。
配置文件按归属分三份,游戏内也按这个结构呈现:
| 文件 | 归属 | 内容 | 游戏内入口 |
|---|---|---|---|
config/crafty_cards/visual.json |
客户端 | 渲染参数(HUD 手牌、世界内持牌、桌面出牌、筹码图标) | 手改 / /craftycards reload 热更新 |
config/crafty_cards/sounds.json |
客户端 | 总开关、音量、选用的音乐包(声道、是否压低原版音乐只能手改) | Mods → Crafty Cards → Config 或 /craftycards sounds |
config/crafty_cards/server.json |
服务端 | 筹码总开关、新桌默认值、距离、旁观可见性、限时与空闲超时 | 在服务端编辑并重启;本地环境可用玩法设置入口 |
玩法设置改的是当前运行环境的本地文件;连专用服务器时,玩家电脑上的
server.json不会控制服务器。NeoForge 可从模组配置入口打开音乐包页;Fabric/Forge POC 的 Mods 配置按钮尚未接入,可编辑客户端配置后重启。
server.json:
| 项 | 默认 | 说明 |
|---|---|---|
spectatorsSeeCards |
true |
观战者能否看到牌面(false 则只能看到牌背与张数) |
participantsSeeFaces |
false |
参局者能否互相看牌(默认关,防作弊) |
spectateRadius |
0 |
走近就自动共享手牌的半径,0 = 关闭(推荐保持 0) |
idleTimeoutTicks |
12000 |
无操作多久销毁牌局(10 分钟),0 = 不超时 |
joinRadius |
8 |
入局/观战的最大距离(格) |
chipItems |
钻石/金锭/铁锭/绿宝石/下界合金锭 | 可作本局筹码的物品白名单(可加模组物品) |
chipStake |
1 |
新桌子的底注默认值;每张桌子初始化后由房主独立配置 |
chipEntryCount |
0 |
新桌子的入局门槛默认值;0 = 自动(底注 × 6),每桌可另行指定 |
chipsRequired |
true |
是否需要筹码。false = 纯娱乐局:不必声明筹码、入局不收押注、结算不转移任何物品 |
spectateRefreshTicks |
20 |
观战快照的补发间隔(tick) |
turnTimeoutTicks |
600 |
每轮限时 30 秒;0 = 不限时 |
| 项 | 规则 |
|---|---|
| 底分 | 叫分(1 / 2 / 3) |
| 倍数 | 2^(炸弹 + 火箭数),春天或反春天再 ×2 |
| 春天 | 地主赢且两农民一张牌都没出过 |
| 反春天 | 农民赢且地主只出了开局那一手 |
| 每人得失 | 底分 × 倍数 × 底注(地主为两倍) |
底注由每桌房主在牌桌配置界面设置;server.json 中的 chipStake(默认 1)只为新桌提供初始值。入局门槛默认取
底注 × 6 = 叫满 3 分(底分上限)× 2(地主一人对两农民、最多赔两份)× 底注,
即"无炸弹时最坏的一局";房主也可以用 chipEntryCount 直接指定门槛(0 = 自动,
界面档位到 100 万)想开高额桌就调高,想宽松点就调低(低到赔不出时结算会少赔并提示)。
筹码物品原样转移(附魔、自定义名等属性不丢)。中途解散会全额退还。倍数会让单局输赢 远超底注(例如叫 3 分 + 两个炸弹 = 12 倍),超出池子的部分从输家背包补取;实在取不到会 少赔并给出提示——这是"用实物结算倍数赌注"的固有限制。
模组本身不含任何音频文件:音效与 BGM 全部来自音乐包,包由玩家自己安装。
仓库的 三个 1.21.1 音乐包学习样本包含实际音频,可用来查看包结构;来源和授权风险见音频权利说明。将所选包目录复制到 config/crafty_cards/soundpacks/ 即可启用声音。
音乐包若未配置「残局」或「王炸之后」BGM,会自动退回「对局进行中」。
config/crafty_cards/soundpacks/<包 id>/
pack.json ← 清单:哪些音频键用哪些文件、各自权重
*.ogg
放进目录后,游戏内 Mods → Crafty Cards → Config → 音乐包 里点「扫描」→ 选用 →「保存并应用」。
(也可以直接改 config/crafty_cards/sounds.json 的 activePack。)
音频以"音频键"为单位(pass 过牌 / bomb 炸弹 / bgm_playing 对局中 / bgm_clutch 有人只剩三张 /
bgm_rocket 王炸之后 / dan3 单张 3 / shunzi 顺子…共 61 个),每个键可以配多个文件,各带整数权重;
播放时由游戏按权重随机挑一个 —— 所以"不要"可以准备三五种说法,听起来不重复。
权重写在包的清单里({ "file": "...", "weight": N }),在制作程序里逐条调。
牌型语音优先于通用出牌音效:想让某种牌型闭嘴,就把它的条目从包里去掉(没有候选自然落到下一档)。
用 tools/soundpack_maker.py(可视化单体 GUI):
python tools/soundpack_maker.py # 打开图形界面
python tools/soundpack_maker.py --cli <音频目录> --name <显示名> --dry-run # 批量预览,不写盘
python tools/soundpack_maker.py --cli <音频目录> --name <显示名> # 批量生成它会自动把 MP3/WAV/FLAC 转成 Ogg Vorbis、把文件按你的选择归到各音频键、写上权重,
最后生成 pack.json。界面上每个文件都有「试听」,会用系统默认播放器打开它
(音质与设备跟你平时听的一致;本程序不内嵌播放器)。
音频很多时,推荐先按音频键命名,运行 --dry-run 批量核对映射、权重与未识别文件,
再去掉它生成;图形界面适合手动调整少量条目。需要助手协助整理素材,可复制
快速制作音乐包提示词。
包的增删改只能在这个程序里做(游戏内只读取与选用),这样不会出现"游戏把包改坏"的情况。
格式细节与键的完整清单见 docs/音乐包格式.md。
包 id(就是包目录名)由程序随机生成,你只填显示名与作者——游戏里的音乐包列表显示的 就是这两项,按显示名认自己的包。id 不让填是有意的:一个目录只放得下一份清单,两个包共用 id 时 后构建的会把前一个顶掉,而随机 id 让两个包撞不到一起。
只有三样:总开关(启用/关闭模组音频)、音量、选用哪个音乐包 (Mods → Crafty Cards → Config → 音乐包,改完点「保存并应用」)。 音频的内容(哪个键用哪些文件、各占多少权重)只能由音乐包决定——游戏侧不再提供 "逐键指派文件 / 逐条调权重 / 试听"这类直接改音频的操作,要改音频就用上面的制作程序改包。 这样音频只有一条来源,出问题时不用先问"你改的是界面还是包"。
「保存并应用」是必要的:音频必须经资源重载才会被游戏加载,这是游戏机制的限制。
bgmCategory决定 BGM 跟随哪个原版音量滑块(默认"玩家",因此播放 BGM 时能把原版背景音乐压到 10%)—— 它和"压低原版音乐"没有界面入口,需要的话改config/crafty_cards/sounds.json(改完重启生效)。
实现说明:Minecraft 无法在运行时直接播放磁盘上的任意音频,必须经资源包加载。 模组会把当前生效的文件生成为一个高优先级资源包(
config/crafty_cards/sounds_pack/, 自动生成、不用手改),游戏日志里能看到它被加载。
./gradlew build # 编译 → build/libs/
./gradlew test # 单元测试
./gradlew runGameTestServer # 游戏内测试(无头服务端)
./gradlew runClient # 开发客户端(联机再开 runClient2/3/4)架构与实现细节见 AGENTS.md,功能说明见 docs/斗地主玩法与实现介绍.md, 测试范围与证据见 验证与已知边界。
部分卡牌和筹码资源源自 PlayingCards;项目来源与致谢见 CREDITS.md。
模组 jar 不包含 .ogg;音频接口是为玩家/服务器的音乐包预留的(见上),没有音乐包时默认播放 Minecraft 原版音效。
GPL-3.0-only 是本项目当前声明的许可。继承的 PlayingCards 代码与美术仍需按 来源与致谢 逐项核实;本项目的许可声明不能替代上游权利人的授权。