From fd15f9eb8fa48ba0efbd2d9eb03550fe6cca3312 Mon Sep 17 00:00:00 2001 From: shihao <3127647737@qq.com> Date: Fri, 26 Dec 2025 18:10:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 48 + __pycache__/pz_webui.cpython-311.pyc | Bin 0 -> 12962 bytes i18n/sandboxvars_zh.json | 269 +++++ pz_config/__init__.py | 2 + .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 227 bytes pz_config/__pycache__/models.cpython-311.pyc | Bin 0 -> 1986 bytes pz_config/__pycache__/parsers.cpython-311.pyc | Bin 0 -> 13975 bytes pz_config/__pycache__/writers.cpython-311.pyc | Bin 0 -> 4403 bytes pz_config/models.py | 42 + pz_config/parsers.py | 287 +++++ pz_config/writers.py | 87 ++ pz_webui.py | 205 ++++ requirements.txt | 2 + server.ini | 475 ++++++++ server_SandboxVars.lua | 1011 +++++++++++++++++ templates/index.html | 900 +++++++++++++++ tools/generate_sandboxvars_zh.py | 158 +++ 17 files changed, 3486 insertions(+) create mode 100644 README.md create mode 100644 __pycache__/pz_webui.cpython-311.pyc create mode 100644 i18n/sandboxvars_zh.json create mode 100644 pz_config/__init__.py create mode 100644 pz_config/__pycache__/__init__.cpython-311.pyc create mode 100644 pz_config/__pycache__/models.cpython-311.pyc create mode 100644 pz_config/__pycache__/parsers.cpython-311.pyc create mode 100644 pz_config/__pycache__/writers.cpython-311.pyc create mode 100644 pz_config/models.py create mode 100644 pz_config/parsers.py create mode 100644 pz_config/writers.py create mode 100644 pz_webui.py create mode 100644 requirements.txt create mode 100644 server.ini create mode 100644 server_SandboxVars.lua create mode 100644 templates/index.html create mode 100644 tools/generate_sandboxvars_zh.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4acf24 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# PZ 配置 WebUI 编辑器(server.ini + server_SandboxVars.lua) + +这个小工具用于在浏览器里逐项编辑《僵尸毁灭工程 / Project Zomboid》的两个配置文件: + +- `server.ini` +- `server_SandboxVars.lua` + +支持: + +- 精确到每一项设置的表单编辑(布尔/数字/下拉枚举/文本) +- 每项设置显示中文说明(`server.ini` 直接读取文件内的中文注释;`server_SandboxVars.lua` 使用 `i18n/sandboxvars_zh.json`) +- 一键导出修改后的配置(下载 zip),或选择“保存覆盖本地” + +## 运行 + +在本目录(包含两个配置文件)打开终端: + +```powershell +python -m pip install -r requirements.txt +python .\\pz_webui.py +``` + +然后浏览器打开: + +``` +http://127.0.0.1:5050 +``` + +## 导出 + +- 点击页面底部 `下载配置 ZIP`:不会改动本地文件,会下载一个 zip,里面包含修改后的两个文件。 +- 点击 `保存覆盖本地`:会直接覆盖写回当前路径下的两个文件(建议先下载 zip 备份)。 + +## 可选:自定义文件路径/端口 + +```powershell +python .\\pz_webui.py --ini .\\server.ini --lua .\\server_SandboxVars.lua --host 127.0.0.1 --port 5050 +``` + +## 重新生成 SandboxVars 中文说明(可选) + +`i18n/sandboxvars_zh.json` 是离线映射文件。如果你的 `server_SandboxVars.lua` 更新了(新增/变更项),可以重新生成: + +```powershell +python .\\tools\\generate_sandboxvars_zh.py --lua .\\server_SandboxVars.lua --out .\\i18n\\sandboxvars_zh.json --resume +``` + +说明:生成脚本会使用 Google Translate 的公开接口做机翻,仅用于生成本地离线文件。 diff --git a/__pycache__/pz_webui.cpython-311.pyc b/__pycache__/pz_webui.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7601c6fdadebfc34f9efd2e74b40126a785a393e GIT binary patch literal 12962 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 o`jeCEZf=_DoZ33I^=50ndZSXkG4I%mJOBUy literal 0 HcmV?d00001 diff --git a/i18n/sandboxvars_zh.json b/i18n/sandboxvars_zh.json new file mode 100644 index 0000000..a1a4caf --- /dev/null +++ b/i18n/sandboxvars_zh.json @@ -0,0 +1,269 @@ +{ + "VERSION": "SandboxVars 文件版本号(通常不用改)。", + "StartYear": "开局年份(通常与开局日期/月份一起使用)。", + "Zombies": "更改此设置还会设置“高级僵尸选项”中的“人口倍增器”。默认 = 正常\n1 = 疯狂\n2 = 非常高\n3 = 高\n4 = 正常\n5 = 低\n6 = 无", + "Distribution": "僵尸如何分布在地图上。默认=以城市为中心\n1 = 以城市为中心\n2 = 均匀", + "ZombieVoronoiNoise": "控制是否将某些随机化应用于僵尸分布。", + "ZombieRespawn": "新僵尸添加到世界的频率。默认 = 正常\n1 = 高\n2 = 正常\n3 = 低\n4 = 无", + "ZombieMigrate": "僵尸允许迁移到空的单元格。", + "DayLength": "默认 = 1 小时 30 分钟\n1 = 15 分钟\n2 = 30 分钟\n3 = 1 小时\n4 = 1 小时 30 分钟\n5 = 2 小时\n6 = 3 小时\n7 = 4 小时\n8 = 5 小时\n9 = 6 小时\n10 = 7 小时\n11 = 8 小时\n12 = 9 小时\n13 = 10 小时\n14 = 11 小时\n15 = 12 小时\n16 = 13 小时\n17 = 14 小时\n18 = 15 小时\n19 = 16 小时\n20 = 17 小时\n21 = 18 小时\n22 = 19 小时\n23 = 20 小时\n24 = 21 小时\n25 = 22 小时\n26 = 23 小时\n27 = 实时", + "StartMonth": "比赛开始的月份。默认 = 七月\n1 = 一月\n2 = 二月\n3 = 三月\n4 = 四月\n5 = 五月\n6 = 六月\n7 = 七月\n8 = 八月\n9 = 九月\n10 = 十月\n11 = 十一月\n12 = 十二月", + "StartDay": "比赛开始的月份的哪一天。", + "StartTime": "一天中比赛开始的时间。默认 = 上午 9 点\n1 = 上午 7 点\n2 = 上午 9 点\n3 = 中午 12 点\n4 = 下午 2 点\n5 = 下午 5 点\n6 = 晚上 9 点\n7 = 上午 12 点\n8 = 凌晨 2 点\n9 = 凌晨 5 点", + "DayNightCycle": "一天中的时间是否自然变化,或者始终是白天/黑夜。默认 = 正常\n1 = 正常\n2 = 无尽的一天\n3 = 无尽的夜晚", + "ClimateCycle": "天气是否变化或保持在单一状态。默认 = 正常\n1 = 正常\n2 = 无天气\n3 = 无尽的雨\n4 = 无尽风暴\n5 = 无尽的雪\n6 = 无尽的暴风雪", + "FogCycle": "雾是自然发生、从未发生还是始终存在。默认 = 正常\n1 = 正常\n2 = 无雾\n3 = 无尽的迷雾", + "WaterShut": "在默认开始日期(1993 年 7 月 9 日)之后多久,管道装置(例如水槽)不再是无限水源。默认 = 0-30 天\n1 = 即时\n2 = 0-30 天\n3 = 0-2 个月\n4 = 0-6 个月\n5 = 0-1 年\n6 = 0-5 年\n7 = 2-6 个月\n8 = 6-12 个月\n9 = 禁用", + "ElecShut": "默认开始日期(1993 年 7 月 9 日)后多久,世界电力将永久关闭。默认 = 0-30 天\n1 = 即时\n2 = 0-30 天\n3 = 0-2 个月\n4 = 0-6 个月\n5 = 0-1 年\n6 = 0-5 年\n7 = 2-6 个月\n8 = 6-12 个月\n9 = 禁用", + "AlarmDecay": "电源关闭后报警电池可以持续多长时间。默认 = 0-30 天\n1 = 即时\n2 = 0-30 天\n3 = 0-2 个月\n4 = 0-6 个月\n5 = 0-1 年\n6 = 0-5 年", + "WaterShutModifier": "在默认开始日期(1993 年 7 月 9 日)之后多久,管道装置(例如水槽)不再是无限水源。最小值:-1 最大值:2147483647 默认值:14", + "ElecShutModifier": "默认开始日期(1993 年 7 月 9 日)后多久,世界电力将永久关闭。最小值:-1 最大值:2147483647 默认值:14", + "AlarmDecayModifier": "电源关闭后报警电池可以持续多长时间。最小值:-1 最大值:2147483647 默认值:14", + "FoodLootNew": "任何可能腐烂或变质的食物。最小值:0.00 最大值:4.00 默认值:0.60", + "LiteratureLootNew": "所有可读取的项目,包括传单 最小值:0.00 最大值:4.00 默认值:0.60", + "MedicalLootNew": "药品、绷带和急救工具。最小值:0.00 最大值:4.00 默认值:0.60", + "SurvivalGearsLootNew": "钓鱼竿、帐篷、野营装备等 最小值:0.00 最大值:4.00 默认值:0.60", + "CannedFoodLootNew": "罐头和干食品、饮料。最小值:0.00 最大值:4.00 默认值:0.60", + "WeaponLootNew": "不属于其他类别工具的武器。最小值:0.00 最大值:4.00 默认值:0.60", + "RangedWeaponLootNew": "还包括武器附件。最小值:0.00 最大值:4.00 默认值:0.60", + "AmmoLootNew": "散装弹药、盒子和杂志。最小值:0.00 最大值:4.00 默认值:0.60", + "MechanicsLootNew": "车辆零件以及安装它们所需的工具。最小值:0.00 最大值:4.00 默认值:0.60", + "OtherLootNew": "其他一切。也会影响城镇/道路区域中所有物品的觅食。最小值:0.00 最大值:4.00 默认值:0.60", + "ClothingLootNew": "所有不是容器的可穿戴物品。最小值:0.00 最大值:4.00 默认值:0.60", + "ContainerLootNew": "背包和其他可穿戴/可装备的容器,例如。案例。最小值:0.00 最大值:4.00 默认值:0.60", + "KeyLootNew": "建筑物/汽车钥匙、钥匙圈和锁。最小值:0.00 最大值:4.00 默认值:0.60", + "MediaLootNew": "VHS 磁带和 CD。最小值:0.00 最大值:4.00 默认值:0.60", + "MementoLootNew": "Spiffo 物品、毛绒玩具和其他可收藏的纪念品,例如。照片。最小值:0.00 最大值:4.00 默认值:0.60", + "CookwareLootNew": "用于烹饪的物品,包括那些可以作为武器的物品(例如刀)。不包括食物。包括可用和不可用的物品。最小值:0.00 最大值:4.00 默认值:0.60", + "MaterialLootNew": "用作制作或建筑原料的物品和武器。这是一个一般类别,不包括属于其他类别(例如炊具或医疗用品)的物品。不包括工具。最小值:0.00 最大值:4.00 默认值:0.60", + "FarmingLootNew": "用于动植物农业的物品和武器,例如种子、抹刀或铲子。最小值:0.00 最大值:4.00 默认值:0.60", + "ToolLootNew": "属于工具但不属于其他类别(例如机械或农业)的物品和武器。最小值:0.00 最大值:4.00 默认值:0.60", + "RollsMultiplier": " [!] 建议您不要更改此设置。 [!] 可用于调整生成战利品时在战利品表上进行的掷骰数。不会将掷骰数减少到 1 以下。如果设置为高值,可能会对性能产生负面影响。强烈建议不要更改此设置。最小值:0.10 最大值:100.00 默认值:1.00", + "LootItemRemovalList": "以逗号分隔的物品类型列表,不会作为普通战利品生成。", + "RemoveStoryLoot": "如果启用,战利品移除列表中的物品或稀有度设置为“无”的物品将不会在随机世界故事中生成。", + "RemoveZombieLoot": "如果启用,战利品移除列表中的物品或稀有度设置为“无”的物品将不会生成被僵尸穿戴或附着的物品。", + "ZombiePopLootEffect": "如果大于 0,则战利品的生成量会相对于附近僵尸的数量而增加,效果会乘以该数字。最小值:0 最大值:20 默认值:10", + "InsaneLootFactor": "最小值:0.00 最大值:0.20 默认值:0.05", + "ExtremeLootFactor": "最小值:0.05 最大值:0.60 默认值:0.20", + "RareLootFactor": "最小值:0.20 最大值:1.00 默认值:0.60", + "NormalLootFactor": "最小值:0.60 最大值:2.00 默认值:1.00", + "CommonLootFactor": "最小值:1.00 最大值:3.00 默认值:2.00", + "AbundantLootFactor": "最小值:2.00 最大值:4.00 默认值:3.00", + "Temperature": "全球温度。默认 = 正常\n1 = 非常冷\n2 = 冷\n3 = 正常\n4 = 热\n5 = 非常热", + "Rain": "多久下一次雨。默认 = 正常\n1 = 非常干燥\n2 = 干\n3 = 正常\n4 = 下雨\n5 = 非常多雨", + "ErosionSpeed": "侵蚀系统(向世界添加藤蔓、长草、新树等)达到 100% 增长所需的天数。默认 = 正常(100 天)\n1 = 非常快(20 天)\n2 = 快速(50 天)\n3 = 正常(100 天)\n4 = 慢(200 天)\n5 = 非常慢(500 天)", + "ErosionDays": "对于自定义侵蚀速度。零表示使用“侵蚀速度”选项。最长为 36,500 天(约 100 年)。最小值:-1 最大值:36500 默认值:0", + "Farming": "植物生长的速度。默认 = 正常\n1 = 非常快\n2 = 快\n3 = 正常\n4 = 慢\n5 = 非常慢", + "CompostTime": "食物在堆肥器中分解需要多长时间。默认 = 2 周\n1 = 1 周\n2 = 2 周\n3 = 3 周\n4 = 4 周\n5 = 6 周\n6 = 8 周\n7 = 10 周\n8 = 12 周", + "StatsDecrease": "玩家的饥饿、口渴和疲劳减少的速度有多快。默认 = 正常\n1 = 非常快\n2 = 快\n3 = 正常\n4 = 慢\n5 = 非常慢", + "NatureAbundance": "觅食模式中发现的物品丰富。默认 = 正常\n1 = 非常差\n2 = 差\n3 = 正常\n4 = 丰富\n5 = 非常丰富", + "Alarm": "玩家闯入新房子时激活房屋警报的可能性有多大。默认 = 有时\n1 = 从不\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常", + "LockedHouses": "住宅和建筑物的门被发现后会被锁上的频率。默认 = 经常\n1 = 从不\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常", + "StarterKit": "生成时带有薯片、水瓶、小背包、棒球棒和锤子。", + "Nutrition": "食物的营养价值会影响球员的身体状况。关闭此功能将阻止玩家体重增加或减轻。", + "FoodRotSpeed": "食物在冰箱内部或外部变质的速度有多快。默认 = 正常\n1 = 非常快\n2 = 快\n3 = 正常\n4 = 慢\n5 = 非常慢", + "FridgeFactor": "冰箱在延长食物保鲜方面的效果如何。默认 = 正常\n1 = 非常低\n2 = 低\n3 = 正常\n4 = 高\n5 = 非常高\n6 = 无衰减", + "SeenHoursPreventLootRespawn": "当大于 0 时,战利品不会在游戏时间内访问过的区域中重生。最小值:0 最大值:2147483647 默认值:0", + "HoursForLootRespawn": "当大于 0 时,X 小时后,世界上城镇和拖车公园中的所有集装箱都将重生战利品。要生成战利品,容器必须至少被掠夺过一次。战利品重生不受可见性或后续抢劫的影响。最小值:0 最大值:2147483647 默认值:0", + "MaxItemsForLootRespawn": "物品数量大于或等于此设置的容器将不会重生。最小值:0 最大值:2147483647 默认值:5", + "ConstructionPreventsLootRespawn": "物品不会在玩家设置路障或建造的建筑物中重生。", + "WorldItemRemovalList": "将在 HoursForWorldItemRemoval 小时后删除的以逗号分隔的项目类型列表。", + "HoursForWorldItemRemoval": "从物品掉落到地面到被移除之前的小时数。 下次加载该部分地图时,项目将被删除。 零意味着项目没有被删除。最小值:0.00 最大值:2147483647.00 默认值:24.00", + "ItemRemovalListBlacklistToggle": "如果为 true,则 WorldItemRemovalList 中*不*的任何项目都将被删除。", + "TimeSinceApo": "世界末日多久之后才开始。这将影响起始世界的侵蚀和食物腐败。不影响开始日期。默认 = 0\n1 = 0\n2 = 1\n3 = 2\n4 = 3\n5 = 4\n6 = 5\n7 = 6\n8 = 7\n9 = 8\n10 = 9\n11 = 10\n12 = 11\n13 = 12", + "PlantResilience": "植物每天会损失多少水,以及它们避免疾病的能力。默认 = 正常\n1 = 非常高\n2 = 高\n3 = 正常\n4 = 低\n5 = 非常低", + "PlantAbundance": "收获时植物的产量。默认 = 正常\n1 = 非常差\n2 = 差\n3 = 正常\n4 = 丰富\n5 = 非常丰富", + "EndRegen": "执行动作后从疲劳中恢复。默认 = 正常\n1 = 非常快\n2 = 快\n3 = 正常\n4 = 慢\n5 = 非常慢", + "Helicopter": "直升机飞越活动区的频率。默认 = 一次\n1 = 从不\n2 = 一次\n3 = 有时\n4 = 经常", + "MetaEvent": "诸如远处枪声等吸引僵尸的元游戏事件发生的频率。默认 = 有时\n1 = 从不\n2 = 有时\n3 = 经常", + "SleepingEvent": "玩家睡眠期间发生的事件(例如噩梦)的频率。默认 = 从不\n1 = 从不\n2 = 有时\n3 = 经常", + "GeneratorFuelConsumption": "游戏中每个小时发电机消耗多少燃料。最小值:0.00 最大值:100.00 默认值:0.10", + "GeneratorSpawning": "发电机在地图上产生的几率。默认=稀有\n1 = 无(不推荐)\n2 = 极其罕见\n3 = 极其罕见\n4 = 稀有\n5 = 正常\n6 = 常见\n7 = 丰富", + "AnnotatedMapChance": "被掠夺的地图上多久会有由已故幸存者写的注释。默认 = 有时\n1 = 从不\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常", + "CharacterFreePoints": "在角色创建过程中添加免费积分。最小值:-100 最大值:100 默认值:0", + "ConstructionBonusPoints": "为玩家建造的建筑提供额外的生命值,使它们更能抵抗僵尸的伤害。默认 = 正常\n1 = 非常低\n2 = 低\n3 = 正常\n4 = 高\n5 = 非常高", + "NightDarkness": "夜间环境照明水平。默认 = 正常\n1 = 漆黑\n2 = 深色\n3 = 正常\n4 = 明亮", + "NightLength": "从黄昏到黎明的时间。默认 = 正常\n1 = 永远是夜晚\n2 = 长\n3 = 正常\n4 = 短\n5 = 永远是一天", + "BoneFracture": "如果幸存者可能因撞击、僵尸伤害、跌倒等而导致四肢骨折。", + "InjurySeverity": "受伤对您身体的影响及其愈合时间。默认 = 正常\n1 = 低\n2 = 正常\n3 = 高", + "HoursForCorpseRemoval": "距离僵尸尸体从世界上消失还有多长时间(以小时为单位)。 如果为 0,蛆不会在尸体上生成。最小值:-1.00 最大值:2147483647.00 默认值:216.00", + "DecayingCorpseHealthImpact": "附近腐烂的尸体对玩家的健康和情绪的影响。默认 = 正常\n1 = 无\n2 = 低\n3 = 正常\n4 = 高\n5 = 疯狂", + "ZombieHealthImpact": "附近的“活”僵尸是否对玩家的健康和情绪有同样的影响。", + "BloodLevel": "有多少血因受伤而喷洒在地板和墙壁上。默认 = 正常\n1 = 无\n2 = 低\n3 = 正常\n4 = 高\n5 = 极度血腥", + "ClothingDegradation": "衣服降解、变脏、沾血的速度有多快。默认 = 正常\n1 = 禁用\n2 = 慢\n3 = 正常\n4 = 快", + "FireSpread": "如果火灾在开始时蔓延。", + "DaysForRottenFoodRemoval": "腐烂食物从地图上移除之前的游戏内天数。 -1表示腐烂的食物永远不会被清除。最小值:-1 最大值:2147483647 默认值:-1", + "AllowExteriorGenerator": "如果启用,发电机将在外部瓷砖上运行。 例如,这将允许为气泵提供动力。", + "MaxFogIntensity": "雾的最大强度。默认 = 正常\n1 = 正常\n2 = 中等\n3 = 低\n4 = 无", + "MaxRainFxIntensity": "最大降雨强度。默认 = 正常\n1 = 正常\n2 = 中等\n3 = 低", + "EnableSnowOnGround": "如果地上会积雪。 如果禁用,雪仍会显示在植被和屋顶上。", + "AttackBlockMovements": "如果近战攻击减慢了你的速度。", + "SurvivorHouseChance": "在地图上找到随机建筑物的机会(例如,烧毁的房屋、藏有战利品或尸体的房屋)。默认=稀有\n1 = 从不\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常\n7 = 总是尝试", + "VehicleStoryChance": "道路故事(例如警察路障)产生的机会。默认=稀有\n1 = 从不\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常\n7 = 总是尝试", + "ZoneStoryChance": "特定于地图区域(例如森林中的露营地)的故事产生的机会。默认=稀有\n1 = 从不\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常\n7 = 总是尝试", + "AllClothesUnlocked": "自定义角色时允许您从游戏中的每件衣服中进行选择", + "EnableTaintedWaterText": "如果水被污染,将会显示警告标记。", + "EnableVehicles": "如果车辆会产卵。", + "CarSpawnRate": "在地图上发现车辆的频率。默认 = 低\n1 = 无\n2 = 非常低\n3 = 低\n4 = 正常\n5 = 高", + "ZombieAttractionMultiplier": "僵尸的一般发动机响度。最小值:0.00 最大值:100.00 默认值:1.00", + "VehicleEasyUse": "发现车辆是否上锁、是否需要钥匙启动等。", + "InitialGas": "已发现车辆的油箱有多满。默认 = 低\n1 = 非常低\n2 = 低\n3 = 正常\n4 = 高\n5 = 非常高\n6 = 满", + "FuelStationGasInfinite": "如果启用,加油泵将永远不会耗尽燃料", + "FuelStationGasMin": "加油泵中可以产生的最小汽油量。选中下面的“高级”框以使用自定义金额。最小值:0.00 最大值:1.00 默认值:0.00", + "FuelStationGasMax": "加油泵中可产生的最大汽油量。选中下面的“高级”框以使用自定义金额。最小值:0.00 最大值:1.00 默认值:0.70", + "FuelStationGasEmptyChance": "单个气泵最初没有燃料的可能性(以百分比表示)。最小值:0 最大值:100 默认值:20", + "LockedCar": "汽车被锁的可能性有多大 默认 = 罕见\n1 = 从不\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常", + "CarGasConsumption": "汽车是多么耗油。最小值:0.00 最大值:100.00 默认值:1.00", + "CarGeneralCondition": "发现车辆将处于一般状况。默认 = 低\n1 = 非常低\n2 = 低\n3 = 正常\n4 = 高\n5 = 非常高", + "CarDamageOnImpact": "碰撞车辆造成的损坏程度。默认 = 正常\n1 = 非常低\n2 = 低\n3 = 正常\n4 = 高\n5 = 非常高", + "DamageToPlayerFromHitByACar": "玩家因碰撞而受到的伤害。默认 = 无\n1 = 无\n2 = 低\n3 = 正常\n4 = 高\n5 = 非常高", + "TrafficJam": "如果主要道路上出现由失事汽车组成的交通堵塞。", + "CarAlarm": "发现车辆发出警报的频率。默认 = 极其罕见\n1 = 从不\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常", + "PlayerDamageFromCrash": "如果玩家可能因车祸而受伤。", + "SirenShutoffHours": "游戏进行了多少个小时后,警报声就会停止。最小值:0.00 最大值:168.00 默认值:0.00", + "ChanceHasGas": "找到一辆油箱里有汽油的车辆的机会。默认 = 低\n1 = 低\n2 = 正常\n3 = 高", + "RecentlySurvivorVehicles": "玩家是否可以发现诺克斯感染发生后得到保养的汽车。默认 = 低\n1 = 无\n2 = 低\n3 = 正常\n4 = 高", + "MultiHitZombies": "如果某些近战武器能够一击击中多个僵尸。", + "RearVulnerability": "僵尸从背后攻击时被咬伤的概率。默认=高\n1 = 低\n2 = 中\n3 = 高", + "SirenEffectsZombies": "如果僵尸会朝着车辆警报声的方向前进。", + "AnimalStatsModifier": "动物统计数据(饥饿、口渴等)降低的速度。默认 = 正常\n1 = 超快\n2 = 非常快\n3 = 快\n4 = 正常\n5 = 慢\n6 = 非常慢", + "AnimalMetaStatsModifier": "动物在元状态下统计数据(饥饿、口渴等)减少的速度。默认 = 正常\n1 = 超快\n2 = 非常快\n3 = 快\n4 = 正常\n5 = 慢\n6 = 非常慢", + "AnimalPregnancyTime": "动物在分娩前会怀孕多长时间。默认 = 非常快\n1 = 超快\n2 = 非常快\n3 = 快\n4 = 正常\n5 = 慢\n6 = 非常慢", + "AnimalAgeModifier": "动物衰老的速度。默认 = 快速\n1 = 超快\n2 = 非常快\n3 = 快\n4 = 正常\n5 = 慢\n6 = 非常慢", + "AnimalMilkIncModifier": "默认 = 快速\n1 = 超快\n2 = 非常快\n3 = 快\n4 = 正常\n5 = 慢\n6 = 非常慢", + "AnimalWoolIncModifier": "默认 = 快速\n1 = 超快\n2 = 非常快\n3 = 快\n4 = 正常\n5 = 慢\n6 = 非常慢", + "AnimalRanchChance": "在农场找到动物的机会。默认 = 始终\n1 = 从不\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常\n7 = 始终", + "AnimalGrassRegrowTime": "草被动物吃掉或被玩家割断后重新生长的小时数。最小值:1 最大值:9999 默认值:240", + "AnimalMetaPredator": "如果夜间鸡舍的门开着,元狐狸(即在游戏中实际上不可见)可能会攻击您的鸡。", + "AnimalMatingSeason": "如果有交配季节的动物会尊重它。 否则它们可以全年繁殖/产卵。", + "AnimalEggHatch": "小动物从蛋中孵化出来需要多长时间。默认 = 快速\n1 = 超快\n2 = 非常快\n3 = 快\n4 = 正常\n5 = 慢\n6 = 非常慢", + "AnimalSoundAttractZombies": "如果属实,动物的叫声会吸引附近的僵尸。", + "AnimalTrackChance": "动物留下足迹的机会。默认 = 有时\n1 = 从不\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常", + "AnimalPathChance": "为被猎杀的动物创造一条道路的机会。默认 = 有时\n1 = 从不\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常", + "MaximumRatIndex": "例如的频率和强度。老鼠出没的建筑物中。最小值:0 最大值:50 默认值:25", + "DaysUntilMaximumRatIndex": "达到最大害虫指数需要多长时间。最小值:0 最大值:365 默认值:90", + "MetaKnowledge": "如果一段媒体尚未完全查看或读取,此设置将确定它是完全显示、显示为“???”还是完全隐藏。默认=完全隐藏\n1 = 完全显露\n2 = 显示为 ???\n3 = 完全隐藏", + "SeeNotLearntRecipe": "如果属实,您将能够看到可以通过工作站完成的任何食谱,即使您尚未学习它们。", + "MaximumLootedBuildingRooms": "如果建筑物的房间数量超过此数量,则不会被抢劫。最小值:0 最大值:200 默认值:50", + "EnablePoisoning": "如果有毒可添加到食物中。默认=真\n1 = 正确\n2 = 假\n3 = 仅禁用漂白剂中毒", + "MaggotSpawn": "如果/当蛆虫可以在尸体中产卵时。默认 = 体内和周围\n1 = 体内和周围\n2 = 仅在体内\n3 = 从不", + "LightBulbLifespan": "该值越高,灯泡在损坏前的使用寿命就越长。 如果为 0,灯泡永远不会破裂。 不影响车辆前灯。最小值:0.00 最大值:1000.00 默认值:1.00", + "FishAbundance": "河流、湖泊中鱼类丰富。默认 = 正常\n1 = 非常差\n2 = 差\n3 = 正常\n4 = 丰富\n5 = 非常丰富", + "LevelForMediaXPCutoff": "当某项技能达到此级别或更高级别时,电视/VHS/其他媒体将不会为其提供 XP。最小值:0 最大值:10 默认值:3", + "LevelForDismantleXPCutoff": "当技能达到此级别或更高级别时,报废家具不会为相关技能提供经验值。不适用于电气。最小值:0 最大值:10 默认值:0", + "BloodSplatLifespanDays": "旧血迹被清除之前的天数。加载地图块时会发生删除。 0表示它们永远不会消失。最小值:0 最大值:365 默认值:0", + "LiteratureCooldown": "一个人可以从阅读以前阅读过的文献中受益之前的天数。最小值:1 最大值:365 默认值:90", + "NegativeTraitsPenalty": "如果选择多个负面特征所提供的奖励特征点的回报递减。默认 = 无\n1 = 无\n2 = 每选择 3 个负面特征扣 1 分\n3 = 每选择 2 个负面特征扣 1 分\n4 = 第一个之后选择的每个负面特征扣 1 分", + "MinutesPerPage": "阅读一页技能书所需的游戏分钟数。最小值:0.00 最大值:60.00 默认值:2.00", + "KillInsideCrops": "启用后,建筑物内种植的农作物和草药将会死亡。不影响室内植物。", + "PlantGrowingSeasons": "启用后,植物的生长会受到季节的影响。", + "PlaceDirtAboveground": " [!] 建议您不要更改此设置。更改此设置可能会导致性能问题。 [!] 启用后,可以放置泥土,并可以在地面以外的地方进行耕种。", + "FarmingSpeedNew": "植物生长的速度。最小值:0.10 最大值:100.00 默认值:1.00", + "FarmingAmountNew": "农作物丰收。最小值:0.10 最大值:10.00 默认值:1.00", + "MaximumLooted": "任何建筑物在被发现时已经被洗劫的可能性。选中下面的“高级”框以使用自定义号码。最小值:0 最大值:200 默认值:50", + "DaysUntilMaximumLooted": "达到最大被掠夺建筑几率需要多长时间。最小值:0 最大值:3650 默认值:90", + "RuralLooted": "任何农村建筑被发现时都有可能被洗劫一空。选中下面的“高级”框以使用自定义号码。最小值:0.00 最大值:2.00 默认值:0.50", + "MaximumDiminishedLoot": "当达到最大减少战利品前的天数时,不会产生最大战利品。选中下面的“高级”框以使用准确的百分比。最小值:0 最大值:100 默认值:0", + "DaysUntilMaximumDiminishedLoot": "达到最大减少战利品百分比需要多长时间。最小值:0 最大值:3650 默认值:3650", + "MuscleStrainFactor": "当因挥舞武器或搬运重物而造成肌肉拉伤时,可起到倍增器的作用。最小值:0.00 最大值:10.00 默认值:1.00", + "DiscomfortFactor": "当因磨损物品而感到不适时,可起到乘数作用。最小值:0.00 最大值:10.00 默认值:1.00", + "WoundInfectionFactor": "如果大于零,则可以从严重的伤口感染中得到损害。最小值:0.00 最大值:10.00 默认值:0.00", + "NoBlackClothes": "如果具有随机色调的真实服装不会太暗而几乎是黑色的。", + "EasyClimbing": "消除攀爬绳索或翻墙时的失败机会。", + "MaximumFireFuelHours": "可以在篝火、木火炉等中放置燃料的最大小时数。最小值:1 最大值:168 默认值:8", + "FirearmUseDamageChance": "用伤害几率计算取代命中几率机制。 此模式优先考虑玩家瞄准。", + "FirearmNoiseMultiplier": "僵尸可以听到枪声的距离的乘数。最小值:0.20 最大值:2.00 默认值:1.00", + "FirearmJamMultiplier": "枪支干扰机会的乘数。 0 禁用干扰。最小值:0.00 最大值:10.00 默认值:0.00", + "FirearmMoodleMultiplier": "Moodle 效果乘数对命中率的影响。 0 禁用 Moodle 惩罚。最小值:0.00 最大值:10.00 默认值:1.00", + "FirearmWeatherMultiplier": "天气(风、雨和雾)对命中率影响的乘数。 0 禁用天气效果。最小值:0.00 最大值:10.00 默认值:1.00", + "FirearmHeadGearEffect": "允许让焊接面罩等头盔影响命中率", + "ClayLakeChance": "有机会将泥土地板变成粘土地板。适用于湖泊。最小值:0.00 最大值:1.00 默认值:0.05", + "ClayRiverChance": "有机会将泥土地板变成粘土地板。适用于河流。最小值:0.00 最大值:1.00 默认值:0.05", + "GeneratorTileRange": "最小值:1 最大值:100 默认值:20", + "GeneratorVerticalPowerRange": "发电机可以为上方和下方多少层提供电力。最小值:1 最大值:15 默认值:3", + "Basement.SpawnFrequency": "地下室在随机位置生成的频率。默认 = 有时\n1 = 从不\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常\n7 = 始终", + "Map.AllowMiniMap": "如果启用,将出现一个迷你地图窗口。", + "Map.AllowWorldMap": "如果启用,则可以访问世界地图。", + "Map.MapAllKnown": "如果启用,世界地图将在游戏开始时被完全填满。", + "Map.MapNeedsLight": "如果启用,除非有可用光源,否则无法读取地图。", + "ZombieLore.Speed": "僵尸移动的速度有多快。默认 = 随机\n1 = 疾跑者\n2 = 快速蹒跚者\n3 = 蹒跚者\n4 = 随机", + "ZombieLore.SprinterPercentage": "如果启用随机速度,这将控制僵尸中短跑运动员的百分比。选中下面的“高级”框以使用自定义百分比。最小值:0 最大值:100 默认值:0", + "ZombieLore.Strength": "僵尸每次攻击造成的伤害。默认 = 正常\n1 = 超人\n2 = 正常\n3 = 弱\n4 = 随机", + "ZombieLore.Toughness": "杀死僵尸的难度。默认 = 随机\n1 = 艰难\n2 = 正常\n3 = 脆弱\n4 = 随机", + "ZombieLore.Transmission": "诺克斯病毒如何传播。默认 = 血液和唾液\n1 = 血液和唾液\n2 = 仅唾液\n3 = 每个人都被感染\n4 = 无", + "ZombieLore.Mortality": "感染生效的速度有多快。默认 = 2-3 天\n1 = 即时\n2 = 0-30 秒\n3 = 0-1 分钟\n4 = 0-12 小时\n5 = 2-3 天\n6 = 1-2 周\n7 = 从不", + "ZombieLore.Reanimate": "受感染的尸体变成僵尸的速度有多快。默认 = 0-1 分钟\n1 = 即时\n2 = 0-30 秒\n3 = 0-1 分钟\n4 = 0-12 小时\n5 = 2-3 天\n6 = 1-2 周", + "ZombieLore.Cognition": "僵尸智力。默认 = 基本导航\n1 = 导航和使用门\n2 = 导航\n3 = 基本导航\n4 = 随机", + "ZombieLore.CrawlUnderVehicle": "僵尸在停放的车辆下爬行的频率。默认 = 经常\n1 = 仅爬网程序\n2 = 极其罕见\n3 = 稀有\n4 = 有时\n5 = 经常\n6 = 经常\n7 = 始终", + "ZombieLore.Memory": "僵尸在看到或听到玩家后会记住他们多久。默认 = 正常\n1 = 长\n2 = 正常\n3 = 短\n4 = 无\n5 = 随机\n6 = 正常和无之间随机", + "ZombieLore.Sight": "僵尸视野半径。默认 = 正常和较差之间随机\n1 = 鹰\n2 = 正常\n3 = 差\n4 = 随机\n5 = 正常和较差之间随机", + "ZombieLore.Hearing": "僵尸的听觉半径。默认 = 正常和较差之间随机\n1 = 精确定位\n2 = 正常\n3 = 差\n4 = 随机\n5 = 正常和较差之间随机", + "ZombieLore.SpottedLogic": "激活新的先进隐形机制,让您能够躲避汽车后面的僵尸,考虑特征和天气等等。", + "ZombieLore.ThumpNoChasing": "如果没有看到/听到玩家的僵尸可以在漫游时攻击门和建筑物。", + "ZombieLore.ThumpOnConstruction": "如果僵尸可以摧毁玩家的建筑和防御。", + "ZombieLore.ActiveOnly": "僵尸在白天还是晚上更“活跃”。 “活跃”僵尸将使用“速度”设置中设置的速度。 “不活跃”的僵尸会比较慢,并且不会追赶。默认 = 两者\n1 = 两者\n2 = 夜晚\n3 = 日", + "ZombieLore.TriggerHouseAlarm": "如果僵尸冲破窗户或门时触发房屋警报。", + "ZombieLore.ZombiesDragDown": "如果多个攻击僵尸可以拖垮你并杀死你。 取决于僵尸的实力。", + "ZombieLore.ZombiesCrawlersDragDown": "如果玩家身边有爬行僵尸,就会有被一群僵尸拖下去杀死的几率。", + "ZombieLore.ZombiesFenceLunge": "如果僵尸在爬过栅栏或穿过窗户后有机会向你猛扑(如果你离得太近)。", + "ZombieLore.ZombiesArmorFactor": "在确定僵尸所穿盔甲的有效性时充当乘数。最小值:0.00 最大值:100.00 默认值:2.00", + "ZombieLore.ZombiesMaxDefense": "任何磨损的防护服可以为僵尸提供的最大防御百分比。最小值:0 最大值:100 默认值:85", + "ZombieLore.ChanceOfAttachedWeapon": "拥有随机附加武器的百分比几率。最小值:0 最大值:100 默认值:6", + "ZombieLore.ZombiesFallDamage": "僵尸从高处坠落时会受到多少伤害。最小值:0.00 最大值:100.00 默认值:1.00", + "ZombieLore.DisableFakeDead": "一些看起来已经死了的僵尸是否会复活并攻击玩家。默认=世界僵尸\n1 = 世界僵尸\n2 = 世界和战斗僵尸\n3 = 从不", + "ZombieLore.PlayerSpawnZombieRemoval": "僵尸不会在玩家生成的地方生成。默认 = 建筑物内部及其周围\n1 = 建筑物内部及其周围\n2 = 建筑物内部\n3 = 房间内\n4 = 僵尸可以在任何地方生成", + "ZombieLore.FenceThumpersRequired": "需要多少僵尸才能破坏高高的栅栏。最小值:-1 最大值:100 默认值:50", + "ZombieLore.FenceDamageMultiplier": "僵尸破坏高栅栏的速度有多快。最小值:0.01 最大值:100.00 默认值:1.00", + "ZombieConfig.PopulationMultiplier": "通过“僵尸计数”人口选项或此处​​的自定义数字进行设置。疯狂 = 2.5,非常高 = 1.6,高 = 1.2,正常 = 0.65,低 = 0.15,无 = 0.0。最小值:0.00 最大值:4.00 默认值:0.65", + "ZombieConfig.PopulationStartMultiplier": "游戏开始时所需僵尸数量的乘数。疯狂 = 3.0,非常高 = 2.0,高 = 1.5,正常 = 1.0,低 = 0.5,无 = 0.0。最小值:0.00 最大值:4.00 默认值:1.00", + "ZombieConfig.PopulationPeakMultiplier": "高峰日所需僵尸数量的乘数。疯狂 = 3.0,非常高 = 2.0,高 = 1.5,正常 = 1.0,低 = 0.5,无 = 0.0。最小值:0.00 最大值:4.00 默认值:1.50", + "ZombieConfig.PopulationPeakDay": "人口达到顶峰的那一天。最小值:1 最大值:365 默认值:28", + "ZombieConfig.RespawnHours": "僵尸在牢房中重生之前必须经过的小时数。如果为 0,则禁用生成。最小值:0.00 最大值:8760.00 默认值:72.00", + "ZombieConfig.RespawnUnseenHours": "在僵尸可以在其中重生之前,块必须不可见的小时数。最小值:0.00 最大值:8760.00 默认值:16.00", + "ZombieConfig.RespawnMultiplier": "每个 RespawnHours 可能重生的细胞所需数量的比例。最小值:0.00 最大值:1.00 默认值:0.10", + "ZombieConfig.RedistributeHours": "僵尸迁移到同一单元的空部分之前必须经过的小时数。如果为 0,则禁用迁移。最小值:0.00 最大值:8760.00 默认值:12.00", + "ZombieConfig.FollowSoundDistance": "僵尸试图走向它最后听到的声音的距离。最小值:10 最大值:1000 默认值:100", + "ZombieConfig.RallyGroupSize": "真正的僵尸闲置时形成的群体规模。 0 表示僵尸不形成群体。群体不会在建筑物或森林区域内形成。最小值:0 最大值:1000 默认值:20", + "ZombieConfig.RallyGroupSizeVariance": "僵尸组的数量(以百分比表示)的大小可以与默认值不同(更大或更小)。 例如,如果默认组大小为 20,方差为 50%,则组大小将在 10-30 之间变化。最小值:0 最大值:100 默认值:50", + "ZombieConfig.RallyTravelDistance": "真正的僵尸闲置时形成群体的行进距离。最小值:5 最大值:50 默认值:20", + "ZombieConfig.RallyGroupSeparation": "僵尸群体之间的距离。最小值:5 最大值:25 默认值:15", + "ZombieConfig.RallyGroupRadius": "僵尸群体的成员与该群体的“领导者”的关系有多密切。最小值:1 最大值:10 默认值:3", + "ZombieConfig.ZombiesCountBeforeDelete": "最小值:10 最大值:500 默认值:300", + "MultiplierConfig.Global": "所有技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.GlobalToggle": "启用后,所有技能都将使用全局乘数。", + "MultiplierConfig.Fitness": "健身技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Strength": "力量技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Sprinting": "冲刺技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Lightfoot": "轻足技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Nimble": "敏捷技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Sneak": "潜行技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Axe": "斧头技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Blunt": "长钝器技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.SmallBlunt": "短钝技能的升级速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.LongBlade": "长刃技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.SmallBlade": "短刃技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Spear": "矛技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Maintenance": "维护技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Woodwork": "木工技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Cooking": "烹饪技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Farming": "农业技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Doctor": "急救技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Electricity": "电气技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.MetalWelding": "焊接技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Mechanics": "机械技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Tailoring": "裁缝技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Aiming": "瞄准技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Reloading": "装弹技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Fishing": "钓鱼技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Trapping": "陷阱技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.PlantScavenging": "觅食技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.FlintKnapping": "敲击技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Masonry": "石工技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Pottery": "陶艺技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Carving": "雕刻技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Husbandry": "动物护理技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Tracking": "追踪技能升级的速率。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Blacksmith": "锻造技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Butchering": "屠宰技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00", + "MultiplierConfig.Glassmaking": "玻璃制造技能升级的速度。最小值:0.00 最大值:1000.00 默认值:1.00" +} diff --git a/pz_config/__init__.py b/pz_config/__init__.py new file mode 100644 index 0000000..4d30d46 --- /dev/null +++ b/pz_config/__init__.py @@ -0,0 +1,2 @@ +"""Project Zomboid config editor helpers (server.ini + SandboxVars lua).""" + diff --git a/pz_config/__pycache__/__init__.cpython-311.pyc b/pz_config/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b393e9d13e6ca22cc6076141ad5ce373935c8c0d GIT binary patch literal 227 zcmZ3^%ge<81o19@nZ7{!F^B^Lj8MjBkdo;PDGX5zDU87knoL!$0Y&*)smUb@QTe$^ z`I#vS$@zI{ndu6tDVZhtMG6_IIR&Xj#R?k5sYPX}MS7WenF`tp!HIb(N%<9Fi9ng0 z(nL)?KTXD4?D6p_`N{F|D;Yk6Z2P6`Vii*WF+3(9DyE<+9%6J%d_2&UlK6PNg34bU lHo5sJr8%i~MQlJlAZHc}0*MdIjEsyo7`!jQP!S7I6af70KY9QF literal 0 HcmV?d00001 diff --git a/pz_config/__pycache__/models.cpython-311.pyc b/pz_config/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb917aa53196e96d2880a1c4376a67f14d06276c GIT binary patch literal 1986 zcmaJ?PjA~c6sIi8wj4WYlXOnjZd3j(F_3zf4j77d!?0qFgTyJ2t_L@PqL@sgO_n@L z?h<1M8Q2Hdt=}NS(C)U=K7|bQAn+7mr`;T+r=0po*;3+57P;08>s)HL^O>3lUX$4V@;MqY8 zRLi6gRf+aGeX1bz3ZA5;&|^e0zaf%6O@SSrq@BXwtAT=Q*$<4?VRY_;qjfU2jY|3d zM7)y1$1KMKy(>TIvVbxtdB+6chCUcnAij4Ox1~ zGLmJ%t4o#xHa7I;B`bh!d}y1H>_$x^H^DyH*V1GPwCTQT5bFLeH5* zgT0#NnQa;bu*UXZIRo0ah7XEE4NC05+6Uq%egoFuFsL0IQ3f{lg0iyh+J-mK%x%jh z``+<0i}_`zWtDR>;9cW;=u)Xx?i+heD>yP9J~V!!Cwo_%8!fBv+%s(7AYKqqVyw{e zVcD3kd5wLWZg`BAx0{rb`H~!Yd8?nm=gaUG%d5sROu4)Z8S`4KPPq!1<5*0{RhW)z zhswrU8jC2$FrvCS`BKNJZG8>kF*@Y^QQT8%X0djjA;_D4(18d zymCNHlYI;;W48s|5%7tCPXYc!7l7z;?$Q07Ci8xz^z;dHAMU_R)bY#B z6IKvo1nBVi5kPq5*Tb5Y?I8i#kdew!b+zS<9xUlu65R8JRYux>z(x& z&qh+Zcs9lhk(4f8i1B1t4Xd4MjHkk_aI3R*hKrGuE-uD%OJOy_ONn&x(%IrlSc&jT zB3--^Uo#ks;bw#v6Y1i`cx5$w9O2bOx&y+N21|VZRqz$KN3R074pZ{Lslc#g8nEOV z$NY6-K?L?oPY%Zivr|YJ{N3d0K)En18_BQXEIZRWZeum!_CfSjHe?hT|6D* z+mV#+fF!X+*eWaw%JHBTv>22l6SpQG8(zlLXINO$a1!C_Vd68y(gLOh$cmD0H~Pkx z*#}91f6cyte&R#pbAX3eVTUxs=wjZ$Ob?C*<_ yQtT?lH%hUWDk^t-=sKJW+gkzWDoV`^yWYDV0V{WU=sKJW+gkzuM;PfPck~@wqY;$= literal 0 HcmV?d00001 diff --git a/pz_config/__pycache__/parsers.cpython-311.pyc b/pz_config/__pycache__/parsers.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3182419b12cda612e65478d8b52cc14e131bdeb0 GIT binary patch literal 13975 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 literal 0 HcmV?d00001 diff --git a/pz_config/models.py b/pz_config/models.py new file mode 100644 index 0000000..e201e1b --- /dev/null +++ b/pz_config/models.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Literal, Mapping + +ValueType = Literal["bool", "int", "float", "string"] + + +@dataclass(frozen=True) +class Setting: + """ + A single editable setting from either server.ini or server_SandboxVars.lua. + + - For INI: `path == key` + - For Lua: `path` is dotted (e.g. "ZombieLore.Speed") + """ + + source: Literal["ini", "lua"] + path: str + key: str + group: str + value_type: ValueType + value: bool | int | float | str + raw_value: str + line_index: int + + description_zh: str + description_en: str | None = None + + min_value: int | float | None = None + max_value: int | float | None = None + default_value: str | None = None + choices: Mapping[str, str] | None = None + + +@dataclass(frozen=True) +class ParsedConfig: + source: Literal["ini", "lua"] + filepath: str + lines: list[str] + settings: list[Setting] + diff --git a/pz_config/parsers.py b/pz_config/parsers.py new file mode 100644 index 0000000..d80f95b --- /dev/null +++ b/pz_config/parsers.py @@ -0,0 +1,287 @@ +from __future__ import annotations + +import json +import re +from dataclasses import replace +from pathlib import Path +from typing import Any + +from .models import ParsedConfig, Setting, ValueType + +_IDENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") + + +def _decode_utf8_keep_newlines(path: Path) -> str: + # Keep CRLF exactly as-is by decoding bytes directly (no newline translation). + return path.read_bytes().decode("utf-8-sig") + + +def _parse_number(text: str) -> int | float | None: + text = text.strip() + if re.fullmatch(r"-?\d+", text): + return int(text) + if re.fullmatch(r"-?\d+\.\d+", text): + return float(text) + return None + + +def _infer_value_type_and_value(raw: str) -> tuple[ValueType, bool | int | float | str]: + v = raw.strip() + if v.lower() == "true": + return "bool", True + if v.lower() == "false": + return "bool", False + if v.startswith('"') and v.endswith('"') and len(v) >= 2: + inner = v[1:-1] + inner = ( + inner.replace("\\\\", "\\") + .replace('\\"', '"') + .replace("\\n", "\n") + .replace("\\r", "\r") + .replace("\\t", "\t") + ) + return "string", inner + + num = _parse_number(v) + if isinstance(num, int): + return "int", num + if isinstance(num, float): + return "float", num + return "string", v + + +def _parse_min_max_default(comment_text: str) -> tuple[int | float | None, int | float | None, str | None]: + if not comment_text: + return None, None, None + + min_match = re.search(r"\bMin:\s*(-?\d+(?:\.\d+)?)", comment_text) + max_match = re.search(r"\bMax:\s*(-?\d+(?:\.\d+)?)", comment_text) + default_match = re.search(r"\bDefault:\s*([^\s]+)", comment_text) + + min_value = _parse_number(min_match.group(1)) if min_match else None + max_value = _parse_number(max_match.group(1)) if max_match else None + default_value = default_match.group(1) if default_match else None + return min_value, max_value, default_value + + +def _parse_choices(comment_text: str) -> dict[str, str] | None: + """ + Parse enum-like choice lines from comment blocks: + 1 = Normal + 2 = Very High + """ + if not comment_text: + return None + + choices: dict[str, str] = {} + for line in comment_text.splitlines(): + m = re.match(r"^\s*(\d+)\s*=\s*(.+?)\s*$", line) + if not m: + continue + choices[m.group(1)] = m.group(2) + + return choices or None + + +def parse_server_ini(filepath: str) -> ParsedConfig: + path = Path(filepath) + text = _decode_utf8_keep_newlines(path) + lines = text.splitlines(keepends=True) + + comment_re = re.compile(r"^\s*#\s?(.*)$") + key_re = re.compile(r"^([A-Za-z0-9_]+)=(.*)$") + + # Only used when the file itself has no comment block for a key. + fallback_zh: dict[str, str] = { + "ChatStreams": "聊天频道开关/可用频道列表(不同版本可用值可能不同)。", + "ServerImageLoginScreen": "登录界面背景图(通常填图片 URL 或留空)。", + "ServerImageLoadingScreen": "加载界面背景图(通常填图片 URL 或留空)。", + "ServerImageIcon": "服务器图标(通常填图片 URL 或留空)。", + "UsernameDisguises": "是否启用“用户名伪装/易容”相关机制(具体效果受版本影响)。", + "HideDisguisedUserName": "配合 UsernameDisguises:是否隐藏伪装后的用户名显示。", + "SwitchZombiesOwnershipEachUpdate": "僵尸网络同步/归属更新策略(高级项,非必要别改)。", + "DenyLoginOnOverloadedServer": "服务器负载过高时是否拒绝新玩家登录。", + "SafehouseDisableDisguises": "安全屋内是否禁用伪装/易容相关效果。", + "SneakModeHideFromOtherPlayers": "潜行模式下是否对其他玩家隐藏(偏玩法/平衡)。", + "UltraSpeedDoesnotAffectToAnimals": "超高速时间流逝是否不影响动物系统(B42/动物相关)。", + "LoginQueueEnabled": "是否启用登录排队系统(满服/高峰用)。", + "BanKickGlobalSound": "封禁/踢出时是否播放全服提示音。", + "BackupsOnStart": "服务器启动时是否自动备份存档。", + "BackupsOnVersionChange": "版本变更时是否自动备份存档。", + "AntiCheatMovement": "反作弊:移动相关检查等级(数值含义由游戏定义)。", + "AntiCheatPlayer": "反作弊:玩家相关检查等级(数值含义由游戏定义)。", + "AntiCheatServerCustomization": "反作弊:服务器自定义/一致性检查等级(数值含义由游戏定义)。", + "UsePhysicsHitReaction": "是否启用物理受击硬直反馈(可能影响 PVP/手感)。", + } + + settings: list[Setting] = [] + pending_comments: list[str] = [] + + for index, line in enumerate(lines): + line_no_nl = line.rstrip("\r\n") + cm = comment_re.match(line_no_nl) + if cm: + pending_comments.append(cm.group(1).rstrip()) + continue + + if not line_no_nl.strip(): + # Keep blank lines inside comment blocks so we don't merge unrelated comments. + pending_comments.append("") + continue + + km = key_re.match(line_no_nl) + if not km: + pending_comments = [] + continue + + key = km.group(1) + raw_value = km.group(2) + comment_text = "\n".join([c for c in pending_comments if c != ""]).strip() + pending_comments = [] + + if not comment_text: + comment_text = fallback_zh.get(key, "该项在当前配置文件中没有注释说明。") + + min_value, max_value, default_value = _parse_min_max_default(comment_text) + choices = _parse_choices(comment_text) + value_type, value = _infer_value_type_and_value(raw_value) + + settings.append( + Setting( + source="ini", + path=key, + key=key, + group="server.ini", + value_type=value_type, + value=value, + raw_value=raw_value, + line_index=index, + description_zh=comment_text, + description_en=None, + min_value=min_value, + max_value=max_value, + default_value=default_value, + choices=choices, + ) + ) + + return ParsedConfig(source="ini", filepath=str(path), lines=lines, settings=settings) + + +def parse_sandboxvars_lua(filepath: str, translations_json: str | None = None) -> ParsedConfig: + path = Path(filepath) + text = _decode_utf8_keep_newlines(path) + lines = text.splitlines(keepends=True) + + comment_re = re.compile(r"^\s*--\s?(.*)$") + open_table_re = re.compile(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\{\s*$") + close_table_re = re.compile(r"^\s*\},\s*$") + + translations: dict[str, str] = {} + if translations_json: + translations = json.loads(Path(translations_json).read_text(encoding="utf-8")) + + settings: list[Setting] = [] + pending_comments: list[str] = [] + table_stack: list[str] = [] + + for index, line in enumerate(lines): + line_no_nl = line.rstrip("\r\n") + cm = comment_re.match(line_no_nl) + if cm: + pending_comments.append(cm.group(1).rstrip()) + continue + + if not line_no_nl.strip(): + pending_comments = [] + continue + + om = open_table_re.match(line_no_nl) + if om: + table_stack.append(om.group(1)) + pending_comments = [] + continue + + if close_table_re.match(line_no_nl): + if table_stack: + table_stack.pop() + pending_comments = [] + continue + + if "=" not in line_no_nl or not line_no_nl.strip().endswith(","): + pending_comments = [] + continue + + left, right = line_no_nl.split("=", 1) + key = left.strip() + if not _IDENT_RE.fullmatch(key): + pending_comments = [] + continue + + right = right.strip() + if not right.endswith(","): + pending_comments = [] + continue + + raw_value = right[:-1].strip() + comment_en = "\n".join([c for c in pending_comments if c != ""]).strip() + pending_comments = [] + + # Remove the outer-most "SandboxVars" from the dotted path for UI friendliness. + effective_stack = table_stack[:] + if effective_stack[:1] == ["SandboxVars"]: + effective_stack = effective_stack[1:] + setting_path = ".".join(effective_stack + [key]) if effective_stack else key + + comment_zh = translations.get(setting_path, "").strip() + if not comment_zh: + # Fallback: translate-free, but still shows something. + comment_zh = "(暂无中文说明)" if not comment_en else f"(暂无中文说明)\n{comment_en}" + + min_value, max_value, default_value = _parse_min_max_default(comment_en) + choices_en = _parse_choices(comment_en) + choices_zh = _parse_choices(comment_zh) if comment_zh else None + + choices: dict[str, str] | None = None + if choices_en: + choices = {} + for value_key, label_en in choices_en.items(): + choices[value_key] = (choices_zh or {}).get(value_key, label_en) + + value_type, value = _infer_value_type_and_value(raw_value) + + group = effective_stack[0] if effective_stack else "基础" + settings.append( + Setting( + source="lua", + path=setting_path, + key=key, + group=group, + value_type=value_type, + value=value, + raw_value=raw_value, + line_index=index, + description_zh=comment_zh, + description_en=comment_en or None, + min_value=min_value, + max_value=max_value, + default_value=default_value, + choices=choices, + ) + ) + + # Special-case: translate the root-version and any keys that might not have comments. + # We keep them as-is, but still ensure there is some Chinese text. + fixed_settings: list[Setting] = [] + for s in settings: + if s.path in {"VERSION", "StartYear"} and (not s.description_zh or "暂无中文说明" in s.description_zh): + zh = { + "VERSION": "SandboxVars 文件版本号(通常不用改)。", + "StartYear": "开局年份(通常与开局日期/月份一起使用)。", + }.get(s.path, s.description_zh) + fixed_settings.append(replace(s, description_zh=zh)) + else: + fixed_settings.append(s) + + return ParsedConfig(source="lua", filepath=str(path), lines=lines, settings=fixed_settings) + diff --git a/pz_config/writers.py b/pz_config/writers.py new file mode 100644 index 0000000..ad2955c --- /dev/null +++ b/pz_config/writers.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import re +from typing import Mapping + +from .models import ParsedConfig, Setting + + +def _to_ini_value(setting: Setting, new_value: object) -> str: + if setting.value_type == "bool": + return "true" if bool(new_value) else "false" + return str(new_value) + + +def _lua_quote(value: str) -> str: + # Minimal Lua string escaping for double-quoted strings. + value = ( + value.replace("\\", "\\\\") + .replace('"', '\\"') + .replace("\r", "\\r") + .replace("\n", "\\n") + .replace("\t", "\\t") + ) + return f'"{value}"' + + +def _to_lua_value(setting: Setting, new_value: object) -> str: + if setting.value_type == "bool": + return "true" if bool(new_value) else "false" + if setting.value_type == "int": + return str(int(new_value)) + if setting.value_type == "float": + # Keep a plain, readable float representation. + return str(float(new_value)) + return _lua_quote(str(new_value)) + + +def write_server_ini(parsed: ParsedConfig, updates: Mapping[str, object]) -> str: + if parsed.source != "ini": + raise ValueError("write_server_ini expects ParsedConfig(source='ini')") + + lines = list(parsed.lines) + key_re = re.compile(r"^([A-Za-z0-9_]+)=(.*)$") + for setting in parsed.settings: + if setting.path not in updates: + continue + new_value = _to_ini_value(setting, updates[setting.path]) + original = lines[setting.line_index] + line_no_nl = original.rstrip("\r\n") + newline = original[len(line_no_nl) :] + m = key_re.match(line_no_nl) + if not m: + continue + key = m.group(1) + lines[setting.line_index] = f"{key}={new_value}{newline}" + return "".join(lines) + + +def write_sandboxvars_lua(parsed: ParsedConfig, updates: Mapping[str, object]) -> str: + if parsed.source != "lua": + raise ValueError("write_sandboxvars_lua expects ParsedConfig(source='lua')") + + lines = list(parsed.lines) + for setting in parsed.settings: + if setting.path not in updates: + continue + new_value = _to_lua_value(setting, updates[setting.path]) + + original = lines[setting.line_index] + line_no_nl = original.rstrip("\r\n") + newline = original[len(line_no_nl) :] + + # Replace between '=' and the final comma. + eq_index = line_no_nl.find("=") + comma_index = line_no_nl.rfind(",") + if eq_index == -1 or comma_index == -1 or comma_index <= eq_index: + continue + + prefix = line_no_nl[: eq_index + 1] + # Preserve at least one space after '=' for readability. + prefix = prefix.rstrip() + " " + # Keep original indentation + key as-is. + suffix = line_no_nl[comma_index:] + newline + lines[setting.line_index] = f"{prefix}{new_value}{suffix}" + + return "".join(lines) + diff --git a/pz_webui.py b/pz_webui.py new file mode 100644 index 0000000..5fb20f1 --- /dev/null +++ b/pz_webui.py @@ -0,0 +1,205 @@ +from __future__ import annotations + +import argparse +import io +import sys +import zipfile +from datetime import datetime +import hashlib +import re +from pathlib import Path +from typing import Any + +from flask import Flask, Response, redirect, render_template, request, url_for + +from pz_config.parsers import parse_sandboxvars_lua, parse_server_ini +from pz_config.writers import write_sandboxvars_lua, write_server_ini + + +def _parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Project Zomboid 配置 WebUI 编辑器") + parser.add_argument("--ini", default="server.ini", help="server.ini 路径(默认:./server.ini)") + parser.add_argument( + "--lua", + default="server_SandboxVars.lua", + help="server_SandboxVars.lua 路径(默认:./server_SandboxVars.lua)", + ) + parser.add_argument( + "--translations", + default=str(Path("i18n") / "sandboxvars_zh.json"), + help="SandboxVars 中文说明 JSON(默认:./i18n/sandboxvars_zh.json)", + ) + parser.add_argument("--host", default="127.0.0.1", help="监听地址(默认:127.0.0.1)") + parser.add_argument("--port", type=int, default=5050, help="监听端口(默认:5050)") + parser.add_argument("--debug", action="store_true", help="Flask debug 模式") + return parser.parse_args(argv) + + +def _coerce_value(value_type: str, raw: str) -> bool | int | float | str: + if value_type == "bool": + return raw.lower() == "true" + if value_type == "int": + return int(raw) + if value_type == "float": + return float(raw) + return raw + + +def create_app(ini_path: str, lua_path: str, translations_path: str) -> Flask: + app = Flask(__name__) + + ini_path = str(Path(ini_path)) + lua_path = str(Path(lua_path)) + translations_path = str(Path(translations_path)) + + def _stable_group_id(name: str) -> str: + ascii_slug = re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-") or "group" + digest = hashlib.md5(name.encode("utf-8")).hexdigest()[:8] + return f"{ascii_slug}-{digest}" + + long_text_keys = { + "ServerWelcomeMessage", + "PublicDescription", + "Mods", + "Map", + "SpawnItems", + "ClientCommandFilter", + "ClientActionLogs", + "BadWordReplacement", + "BadWordListFile", + "GoodWordListFile", + "LootItemRemovalList", + "WorldItemRemovalList", + "Map.MapAllKnown", + } + + @app.get("/") + def index() -> str: + translations = translations_path if Path(translations_path).exists() else None + ini_cfg = parse_server_ini(ini_path) + lua_cfg = parse_sandboxvars_lua(lua_path, translations_json=translations) + + # Group Lua settings by their top-level table. + lua_groups: dict[str, list[Any]] = {} + for s in lua_cfg.settings: + lua_groups.setdefault(s.group, []).append(s) + + for group_settings in lua_groups.values(): + group_settings.sort(key=lambda s: s.line_index) + + lua_groups_payload: list[dict[str, Any]] = [] + for group_name, items in lua_groups.items(): + first_line = min(s.line_index for s in items) if items else 0 + lua_groups_payload.append( + { + "name": group_name, + "id": _stable_group_id(group_name), + "settings": items, + "count": len(items), + "order": first_line, + } + ) + lua_groups_payload.sort(key=lambda g: g["order"]) + + lua_setting_count = sum(len(items) for items in lua_groups.values()) + return render_template( + "index.html", + ini_settings=ini_cfg.settings, + lua_groups=lua_groups_payload, + lua_setting_count=lua_setting_count, + ini_path=ini_path, + lua_path=lua_path, + has_translations=Path(translations_path).exists(), + translations_path=translations_path, + long_text_keys=long_text_keys, + saved=request.args.get("saved") == "1", + ) + + def _read_updates() -> tuple[dict[str, object], dict[str, object], list[str]]: + translations = translations_path if Path(translations_path).exists() else None + ini_cfg = parse_server_ini(ini_path) + lua_cfg = parse_sandboxvars_lua(lua_path, translations_json=translations) + + ini_updates: dict[str, object] = {} + lua_updates: dict[str, object] = {} + errors: list[str] = [] + + for s in ini_cfg.settings: + field = f"ini__{s.path}" + if s.value_type == "bool": + ini_updates[s.path] = field in request.form + continue + raw = request.form.get(field, "") + try: + ini_updates[s.path] = _coerce_value(s.value_type, raw) + except Exception: + errors.append(f"server.ini: {s.path} 值非法:{raw!r}") + + for s in lua_cfg.settings: + field = f"lua__{s.path}" + if s.value_type == "bool": + lua_updates[s.path] = field in request.form + continue + raw = request.form.get(field, "") + try: + lua_updates[s.path] = _coerce_value(s.value_type, raw) + except Exception: + errors.append(f"server_SandboxVars.lua: {s.path} 值非法:{raw!r}") + + return ini_updates, lua_updates, errors + + @app.post("/download.zip") + def download_zip() -> Response: + translations = translations_path if Path(translations_path).exists() else None + ini_cfg = parse_server_ini(ini_path) + lua_cfg = parse_sandboxvars_lua(lua_path, translations_json=translations) + + ini_updates, lua_updates, errors = _read_updates() + if errors: + return Response("\n".join(errors), status=400, mimetype="text/plain; charset=utf-8") + + ini_out = write_server_ini(ini_cfg, ini_updates) + lua_out = write_sandboxvars_lua(lua_cfg, lua_updates) + + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as zf: + zf.writestr(Path(ini_path).name, ini_out) + zf.writestr(Path(lua_path).name, lua_out) + buf.seek(0) + + filename = f"pz-config-{datetime.now().strftime('%Y%m%d-%H%M%S')}.zip" + return Response( + buf.getvalue(), + mimetype="application/zip", + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, + ) + + @app.post("/save") + def save_to_disk() -> Response: + translations = translations_path if Path(translations_path).exists() else None + ini_cfg = parse_server_ini(ini_path) + lua_cfg = parse_sandboxvars_lua(lua_path, translations_json=translations) + + ini_updates, lua_updates, errors = _read_updates() + if errors: + return Response("\n".join(errors), status=400, mimetype="text/plain; charset=utf-8") + + ini_out = write_server_ini(ini_cfg, ini_updates) + lua_out = write_sandboxvars_lua(lua_cfg, lua_updates) + + Path(ini_path).write_text(ini_out, encoding="utf-8", newline="") + Path(lua_path).write_text(lua_out, encoding="utf-8", newline="") + return redirect(url_for("index", saved="1")) + + return app + + +def main(argv: list[str]) -> int: + args = _parse_args(argv) + app = create_app(args.ini, args.lua, args.translations) + app.run(host=args.host, port=args.port, debug=args.debug) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a9901f9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +flask==3.0.3 + diff --git a/server.ini b/server.ini new file mode 100644 index 0000000..2d7f793 --- /dev/null +++ b/server.ini @@ -0,0 +1,475 @@ +# 玩家可以互相伤害、互相杀死 +PVP=true + +# PVP 行为会记录到管理员聊天频道 +PVPLogToolChat=true + +# PVP 行为会写入日志文件 +PVPLogToolFile=true + +# 服务器没人在线时,游戏时间暂停 +PauseEmpty=true + +# 开关全局聊天(true 开,false 关) +GlobalChat=true + +ChatStreams=s,r,a,w,y,sh,f,all + +# 是否允许“没在白名单里的人”直接注册/进服。 +# true:玩家进服时可以自己填账号密码(不用你提前建号) +# false:必须管理员手动创建账号/密码 +Open=true + +# 玩家登录后,聊天面板第一时间看到的欢迎语。 +# 支持 RGB 颜色:\ 这种写法会显示红色 +# 支持用 (中间不要空格)来换行/分段 +ServerWelcomeMessage=Welcome to Project Zomboid Multiplayer! To interact with the Chat panel: press Tab, T, or Enter. The Tab key will change the target stream of the message. Global Streams: /all Local Streams: /say, /yell Special Steams: /whisper, /safehouse, /faction. Press the Up arrow to cycle through your message history. Click the Gear icon to customize chat. Happy surviving! + +ServerImageLoginScreen= + +ServerImageLoadingScreen= + +ServerImageIcon= + +# (仅对 Open=true 有用)玩家首次加入时,自动把“未知用户名”加进白名单 +# 玩家加入时会自己设置用户名/密码 +AutoCreateUserInWhiteList=false + +# 游戏里在角色头顶显示用户名 +DisplayUserName=true + +# 头顶显示“名+姓”(角色名的 first/last name) +ShowFirstAndLastName=true + +UsernameDisguises=false + +HideDisguisedUserName=false + +SwitchZombiesOwnershipEachUpdate=false + +# 强制所有新玩家出生在固定坐标 x,y,z(去 map.projectzomboid.com 查坐标) +# 写 0,0,0 表示不强制(忽略) +SpawnPoint=0,0,0 + +# 安全系统:玩家可以自己开/关 PVP 模式。 +# SafetySystem=false 时,只要 PVP=true 就随时互砍 +# SafetySystem=true 时:只有至少一方开启了 PVP(左侧骷髅图标亮着)才会互相伤害 +SafetySystem=true + +# 对已开启 PVP 模式的玩家,在头顶显示骷髅图标 +ShowSafety=true + +# 切换进入/退出 PVP 模式需要的时间(分钟/秒?游戏里按这个算) +# Min: 0 Max: 1000 Default: 2 +SafetyToggleTimer=2 + +# 刚切换过 PVP 后,再次切换的冷却时间 +# Min: 0 Max: 1000 Default: 3 +SafetyCooldownTimer=3 + +# Min: 0 Max: 60 Default: 60 +SafetyDisconnectDelay=60 + +# 新玩家出生自带物品列表,用英文逗号分隔 +# 例:Base.Axe,Base.Bag_BigHikingBag +SpawnItems= + +# 玩家数据默认端口(UDP 的话,这是两个端口中的一个) +# Min: 0 Max: 65535 Default: 16261 +DefaultPort=16261 + +# Min: 0 Max: 65535 Default: 16262 +UDPPort=16262 + +# 重置 ID:用来判断服务器是否做过软重置。 +# 如果这个数字和客户端的不一样,客户端必须新建角色。 +# 会配合 PlayerServerID 一起用。强烈建议把这些 ID 备份好 +# Min: 0 Max: 2147483647 Default: 606245551 +ResetID=557835860 + +# 在这里填 Mod 的加载 ID +# 去 \Steam\steamapps\workshop\modID\mods\modName\info.txt 里找 +Mods=\B42CNTranslate;\B42Trans_CN;\B42Trans_CN_Simple + +# 填地图文件夹名(在 \Steam\steamapps\workshop\modID\mods\modName\media\maps\ 里) +Map=Riverside, KY + +# 踢掉“本地游戏文件和服务器不一致”的客户端 +DoLuaChecksum=false + +DenyLoginOnOverloadedServer=true + +# 是否在游戏内服务器列表里公开显示服务器 +# (注意:启用 Steam 的服务器总会出现在 Steam 服务器浏览器里) +Public=false + +# 服务器在列表里显示的名字(以及 Steam 浏览器显示名) +PublicName=PZ + +# 服务器简介(公服列表里看到的描述) +# 输入 可以换行(你这里原注释里那块被吞了,我按常见写法说明) +PublicDescription=几把的服务器有这么难开? + +# 最大在线人数(不含管理员) +# 警告:超过 32 人可能会导致地图加载差、不同步/卡顿,请慎重 +# Min: 1 Max: 100 Default: 32 +MaxPlayers=32 + +# 延迟(ms)超过这个值就踢人(0=不限制) +# Min: 0 Max: 2147483647 Default: 0 +PingLimit=100 + +# 玩家占领为安全屋的房子里,战利品不再刷新 +SafehousePreventsLootRespawn=true + +# 玩家死亡后,从白名单移除账号(仅 Open=false 服务器会用到) +# 这样死了就不能直接再建新角色 +DropOffWhiteListAfterDeath=false + +# 禁用所有火焰(除了营火) +NoFire=false + +# 如果启用:玩家死亡会在全服聊天里公告 +AnnounceDeath=true + +# 世界存档间隔(现实分钟) +# 地图通常只在玩家离开加载区域后才保存,这个设置能强制更频繁保存 +# Min: 0 Max: 2147483647 Default: 0 +SaveWorldEveryMinutes=0 + +# 玩家也能占领安全屋 +PlayerSafehouse=true + +# 只有管理员能占领安全屋 +AdminSafehouse=false + +# 非成员是否允许进安全屋(不用邀请) +SafehouseAllowTrepass=true + +# 火焰是否能烧坏安全屋 +SafehouseAllowFire=true + +# 非成员是否能从安全屋拿东西 +SafehouseAllowLoot=true + +# 玩家死亡后,是否允许在加入过的安全屋里复活 +SafehouseAllowRespawn=false + +# 玩家必须存活达到多少“游戏天数”才允许占领安全屋 +# Min: 0 Max: 2147483647 Default: 0 +SafehouseDaySurvivedToClaim=0 + +# 安全屋多久(现实小时)没去就自动把玩家从安全屋里移除 +# Min: 0 Max: 2147483647 Default: 144 +SafeHouseRemovalTime=144 + +# 是否允许占领“非住宅建筑”(如仓库、商店) +SafehouseAllowNonResidential=false + +SafehouseDisableDisguises=true + +# Min: 0 Max: 2147483647 Default: 20000 +MaxSafezoneSize=20000 + +# 是否允许用大锤(sledgehammer)破坏世界物件 +AllowDestructionBySledgehammer=true + +# 是否只允许在自己的安全屋里用大锤破坏(前提 AllowDestructionBySledgehammer=true) +SledgehammerOnlyInSafehouse=false + +# 开战倒计时(秒) +# Min: 60 Max: 2147483647 Default: 600 +WarStartDelay=600 + +# 战争持续时间(秒) +# Min: 60 Max: 2147483647 Default: 3600 +WarDuration=3600 + +# 安全屋在战争中可承受的“被打次数/血量” +# Min: 0 Max: 2147483647 Default: 3 +WarSafehouseHitPoints=3 + +# ServerPlayerID 用来判断角色是来自别的服务器还是单机/本服。 +# 软重置可能会改变它。如果这个数字和客户端不一样,客户端必须新建角色。 +# 会和 ResetID 一起用。强烈建议备份 +ServerPlayerID=1040979181 + +# RCON(远程控制台)端口 +# Min: 0 Max: 65535 Default: 27015 +RCONPort=27015 + +# RCON 密码(建议设置强密码) +RCONPassword= + +# 是否启用 Discord 文字聊天同步 +DiscordEnable=false + +# Discord 机器人 Token +DiscordToken= + +# Discord 频道名(不行就用频道 ID) +DiscordChannel= + +# Discord 频道 ID(频道名不好用时用这个) +DiscordChannelID= + +# Slack 的 incoming webhook URL +WebhookAddress= + +# 进服密码(用 Host 按钮开服时这个设置会被忽略) +Password=80012029 + +# 限制同一个 Steam 账号最多能在服务器创建多少个不同账户(Host 开服会忽略) +# Min: 0 Max: 2147483647 Default: 0 +MaxAccountsPerUser=0 + +# 允许同屏/远程同乐(co-op/splitscreen) +AllowCoop=true + +# 允许玩家睡觉(但不强制必须睡) +SleepAllowed=true + +# 需要睡眠(会变困,不睡不行)(SleepAllowed=false 时忽略) +SleepNeeded=false + +# WIP:开启可能造成玩家位置显示不同步 +KnockedDownAllowed=false + +SneakModeHideFromOtherPlayers=true + +UltraSpeedDoesnotAffectToAnimals=false + +# 服务器需要下载的创意工坊 Mod ID 列表,用分号分隔 +# 例:WorkshopItems=514427485;513111049 +WorkshopItems=3386702953;3556544454 + +# 玩家列表里显示 Steam 用户名和头像 +SteamScoreboard=true + +# 启用 Steam VAC 反作弊 +SteamVAC=true + +# 尝试用 UPnP 自动配置路由器端口转发 +# 如果失败就回退到默认端口 +UPnP=true + +# 启用语音(VOIP) +VoiceEnable=true + +# 语音最小可听距离(格/瓦片距离) +# Min: 0.00 Max: 100000.00 Default: 10.00 +VoiceMinDistance=10.0 + +# 语音最大可听距离 +# Min: 0.00 Max: 100000.00 Default: 100.00 +VoiceMaxDistance=100.0 + +# 语音启用 3D/方向性音频 +Voice3D=true + +# Min: 10.00 Max: 150.00 Default: 70.00 +SpeedLimit=70.0 + +LoginQueueEnabled=false + +# Min: 20 Max: 1200 Default: 60 +LoginQueueConnectTimeout=60 + +# 服务器广播用的 IP(多网卡/多 IP 环境用,比如机房服务器) +server_browser_announced_ip= + +# 玩家可在死亡地点坐标原地复活 +PlayerRespawnWithSelf=true + +# 玩家可在同屏/远程同乐玩家的位置复活 +PlayerRespawnWithOther=true + +# 睡觉时的时间流速倍率 +# Min: 1.00 Max: 100.00 Default: 40.00 +FastForwardMultiplier=40.0 + +# 如果安全屋成员有人在线,则安全屋像普通房子一样(成员在线时不再“离线保护”) +DisableSafehouseWhenPlayerConnected=false + +# 是否允许创建派系/帮派(faction) +Faction=true + +# 创建派系前必须存活多少“游戏天数” +# Min: 0 Max: 2147483647 Default: 0 +FactionDaySurvivedToCreate=0 + +# 派系拥有者创建“派系标签/前缀”所需的成员人数 +# Min: 1 Max: 2147483647 Default: 1 +FactionPlayersRequiredForTag=1 + +# 禁用有权限等级玩家的无线电发言 +DisableRadioStaff=false + +# 禁用 admin 的无线电发言 +DisableRadioAdmin=true + +# 禁用 gm 的无线电发言 +DisableRadioGM=true + +# 禁用 overseer 的无线电发言 +DisableRadioOverseer=false + +# 禁用 moderator 的无线电发言 +DisableRadioModerator=false + +# 禁用隐身玩家的无线电发言 +DisableRadioInvisible=true + +# 不写入 cmd.txt 服务器日志的命令列表(分号分隔) +# 例:-vehicle.* 表示 vehicle 相关都不记;* 表示所有 vehicle 命令都不记 +# +vehicle.installPart 表示这个命令要记录 +ClientCommandFilter=-vehicle.*;+vehicle.damageWindow;+vehicle.fixPart;+vehicle.installPart;+vehicle.uninstallPart + +# 会写入 ClientActionLogs.txt 的客户端行为列表(分号分隔) +ClientActionLogs=ISEnterVehicle;ISExitVehicle;ISTakeEngineParts; + +# 记录玩家技能等级变化到 PerkLog.txt +PerkLogs=true + +# 容器里允许放的最大“物品数量”(0=不限制) +# 注意:小物品也算,比如钉子;设 50 就表示最多放 50 个钉子 +# Min: 0 Max: 9000 Default: 0 +ItemNumbersLimitPerContainer=0 + +# 血迹保留多少天后清理(清理发生在地图区块被加载时) +# 0=永不消失 +# Min: 0 Max: 365 Default: 0 +BloodSplatLifespanDays=0 + +# 允许用户名使用非 ASCII 字符(如俄文等) +AllowNonAsciiUsername=true + +BanKickGlobalSound=true + +# 如果开启:到达尸体清理时间(HoursForCorpseRemoval)时,也会把“玩家尸体”一起清掉 +RemovePlayerCorpsesOnCorpseRemoval=false + +# 如果开启:玩家可以对垃圾桶使用“全部删除”按钮 +TrashDeleteAll=false + +# 如果开启:玩家被其他玩家打中进入硬直时,仍然可以继续挥击 +PVPMeleeWhileHitReaction=false + +# 如果开启:必须把鼠标移到别人身上才会显示对方名字 +MouseOverToSeeDisplayName=true + +# 如果开启:自动隐藏你看不见的玩家(类似僵尸的遮挡效果) +HidePlayersBehindYou=true + +# PVP 近战伤害倍率 +# Min: 0.00 Max: 500.00 Default: 30.00 +PVPMeleeDamageModifier=30.0 + +# PVP 枪械伤害倍率 +# Min: 0.00 Max: 500.00 Default: 50.00 +PVPFirearmDamageModifier=50.0 + +# 调整车辆吸引僵尸的范围倍率(值越低越不容易引怪,也可能更省性能) +# Min: 0.00 Max: 10.00 Default: 0.50 +CarEngineAttractionModifier=0.5 + +# 玩家跑动穿过别人时,是否会撞开/撞倒对方 +PlayerBumpPlayer=false + +# 控制“远程玩家”在游戏地图上的显示 +# 1=隐藏 2=仅好友 3=所有人 +# Min: 1 Max: 3 Default: 1 +MapRemotePlayerVisibility=1 + +# Min: 1 Max: 300 Default: 5 +BackupsCount=5 + +BackupsOnStart=true + +BackupsOnVersionChange=true + +# 备份周期(分钟?按服务器实现走) +# Min: 0 Max: 1500 Default: 0 +BackupsPeriod=60 + +# 禁用车辆牵引/拖车 +DisableVehicleTowing=false + +# 禁用拖车(挂斗)牵引 +DisableTrailerTowing=false + +# 禁用烧毁车辆的牵引 +DisableBurntTowing=false + +# 被禁词列表文件路径(一行一个词) +BadWordListFile= + +# 白名单词列表文件路径:即使包含禁词也允许(一行一个词) +GoodWordListFile= + +# 聊天里说禁词怎么处理: +# 1=封禁 2=踢出 3=记录到数据库 4=禁言 +BadWordPolicy=3 + +# 禁词替换成什么文本 +BadWordReplacement=[HIDDEN] + +# 禁用安全系统相关反作弊保护(数值含义由游戏决定) +AntiCheatSafety=4 + +AntiCheatMovement=4 + +# 禁用角色受击相关反作弊保护 +AntiCheatHit=4 + +# 禁用数据包检查相关反作弊保护 +AntiCheatPacket=4 + +# 禁用玩家权限相关反作弊保护 +AntiCheatPermission=4 + +# 禁用玩家经验相关反作弊保护 +AntiCheatXP=4 + +# 禁用火焰检查相关反作弊保护 +AntiCheatFire=4 + +# 禁用安全屋相关反作弊保护 +AntiCheatSafeHouse=4 + +# 禁用配方检查相关反作弊保护 +AntiCheatRecipe=4 + +AntiCheatPlayer=4 + +# 禁用校验和(checksum)相关反作弊保护 +AntiCheatChecksum=4 + +# 禁用物品检查相关反作弊保护 +AntiCheatItem=4 + +AntiCheatServerCustomization=4 + +# 多人同步统计更新周期(秒);设为 0 则禁用统计 +# Min: 0 Max: 10 Default: 1 +MultiplayerStatisticsPeriod=1 + +# 禁用计分板/玩家列表 +DisableScoreboard=false + +# 在玩家列表里隐藏管理员 +HideAdminsInPlayerList=false + +# 世界生成用的种子 +# 想换种子:填新值,并删除存档目录里的 map_worldgen.bin +Seed=NQfVrpzPXocQYSwZ + +UsePhysicsHitReaction=false + +# 聊天消息最大长度 +# Min: 64 Max: 1024 Default: 200 +ChatMessageCharacterLimit=200 + +# 聊天慢速模式间隔(秒):发完一条要等多久才能发下一条 +# Min: 1 Max: 30 Default: 3 +ChatMessageSlowModeTime=3 diff --git a/server_SandboxVars.lua b/server_SandboxVars.lua new file mode 100644 index 0000000..e00bc57 --- /dev/null +++ b/server_SandboxVars.lua @@ -0,0 +1,1011 @@ +SandboxVars = { + VERSION = 6, + -- Changing this also sets the "Population Multiplier" in Advanced Zombie Options. Default = Normal + -- 1 = Insane + -- 2 = Very High + -- 3 = High + -- 4 = Normal + -- 5 = Low + -- 6 = None + Zombies = 3, + -- How zombies are distributed across the map. Default = Urban Focused + -- 1 = Urban Focused + -- 2 = Uniform + Distribution = 1, + -- Controls whether some randomization is applied to zombie distribution. + ZombieVoronoiNoise = true, + -- How frequently new zombies are added to the world. Default = Normal + -- 1 = High + -- 2 = Normal + -- 3 = Low + -- 4 = None + ZombieRespawn = 2, + -- Zombie allowed to migrate to empty cells. + ZombieMigrate = true, + -- Default = 1 Hour, 30 Minutes + -- 1 = 15 Minutes + -- 2 = 30 Minutes + -- 3 = 1 Hour + -- 4 = 1 Hour, 30 Minutes + -- 5 = 2 Hours + -- 6 = 3 Hours + -- 7 = 4 Hours + -- 8 = 5 Hours + -- 9 = 6 Hours + -- 10 = 7 Hours + -- 11 = 8 Hours + -- 12 = 9 Hours + -- 13 = 10 Hours + -- 14 = 11 Hours + -- 15 = 12 Hours + -- 16 = 13 Hours + -- 17 = 14 Hours + -- 18 = 15 Hours + -- 19 = 16 Hours + -- 20 = 17 Hours + -- 21 = 18 Hours + -- 22 = 19 Hours + -- 23 = 20 Hours + -- 24 = 21 Hours + -- 25 = 22 Hours + -- 26 = 23 Hours + -- 27 = Real-time + DayLength = 3, + StartYear = 1, + -- Month in which the game starts. Default = July + -- 1 = January + -- 2 = February + -- 3 = March + -- 4 = April + -- 5 = May + -- 6 = June + -- 7 = July + -- 8 = August + -- 9 = September + -- 10 = October + -- 11 = November + -- 12 = December + StartMonth = 7, + -- Day of the month in which the games starts. + StartDay = 9, + -- Hour of the day in which the game starts. Default = 9 AM + -- 1 = 7 AM + -- 2 = 9 AM + -- 3 = 12 PM + -- 4 = 2 PM + -- 5 = 5 PM + -- 6 = 9 PM + -- 7 = 12 AM + -- 8 = 2 AM + -- 9 = 5 AM + StartTime = 2, + -- Whether the time of day changes naturally, or it's always day/night. Default = Normal + -- 1 = Normal + -- 2 = Endless Day + -- 3 = Endless Night + DayNightCycle = 1, + -- Whether weather changes or remains at a single state. Default = Normal + -- 1 = Normal + -- 2 = No Weather + -- 3 = Endless Rain + -- 4 = Endless Storm + -- 5 = Endless Snow + -- 6 = Endless Blizzard + ClimateCycle = 1, + -- Whether fog occurs naturally, never occurs, or is always present. Default = Normal + -- 1 = Normal + -- 2 = No Fog + -- 3 = Endless Fog + FogCycle = 1, + -- How long after the default start date (July 9, 1993) that plumbing fixtures (eg. sinks) stop being infinite sources of water. Default = 0-30 Days + -- 1 = Instant + -- 2 = 0-30 Days + -- 3 = 0-2 Months + -- 4 = 0-6 Months + -- 5 = 0-1 Year + -- 6 = 0-5 Years + -- 7 = 2-6 Months + -- 8 = 6-12 Months + -- 9 = Disabled + WaterShut = 2, + -- How long after the default start date (July 9, 1993) that the world's electricity turns off for good. Default = 0-30 Days + -- 1 = Instant + -- 2 = 0-30 Days + -- 3 = 0-2 Months + -- 4 = 0-6 Months + -- 5 = 0-1 Year + -- 6 = 0-5 Years + -- 7 = 2-6 Months + -- 8 = 6-12 Months + -- 9 = Disabled + ElecShut = 2, + -- How long alarm batteries can last for after the power shuts off. Default = 0-30 Days + -- 1 = Instant + -- 2 = 0-30 Days + -- 3 = 0-2 Months + -- 4 = 0-6 Months + -- 5 = 0-1 Year + -- 6 = 0-5 Years + AlarmDecay = 2, + -- How long after the default start date (July 9, 1993) that plumbing fixtures (eg. sinks) stop being infinite sources of water. Min: -1 Max: 2147483647 Default: 14 + WaterShutModifier = 14, + -- How long after the default start date (July 9, 1993) that the world's electricity turns off for good. Min: -1 Max: 2147483647 Default: 14 + ElecShutModifier = 14, + -- How long alarm batteries can last for after the power shuts off. Min: -1 Max: 2147483647 Default: 14 + AlarmDecayModifier = 14, + -- Any food that can rot or spoil. Min: 0.00 Max: 4.00 Default: 0.60 + FoodLootNew = 0.6, + -- All items that can be read, includes fliers Min: 0.00 Max: 4.00 Default: 0.60 + LiteratureLootNew = 0.6, + -- Medicine, bandages and first aid tools. Min: 0.00 Max: 4.00 Default: 0.60 + MedicalLootNew = 0.6, + -- Fishing Rods, Tents, camping gear etc. Min: 0.00 Max: 4.00 Default: 0.60 + SurvivalGearsLootNew = 0.6, + -- Canned and dried food, beverages. Min: 0.00 Max: 4.00 Default: 0.60 + CannedFoodLootNew = 0.6, + -- Weapons that are not tools in other categories. Min: 0.00 Max: 4.00 Default: 0.60 + WeaponLootNew = 0.6, + -- Also includes weapon attachments. Min: 0.00 Max: 4.00 Default: 0.60 + RangedWeaponLootNew = 0.6, + -- Loose ammo, boxes and magazines. Min: 0.00 Max: 4.00 Default: 0.60 + AmmoLootNew = 0.6, + -- Vehicle parts and the tools needed to install them. Min: 0.00 Max: 4.00 Default: 0.60 + MechanicsLootNew = 0.6, + -- Everything else. Also affects foraging for all items in Town/Road zones. Min: 0.00 Max: 4.00 Default: 0.60 + OtherLootNew = 0.6, + -- All wearable items that are not containers. Min: 0.00 Max: 4.00 Default: 0.60 + ClothingLootNew = 0.6, + -- Backpacks and other wearable/equippable containers, eg. cases. Min: 0.00 Max: 4.00 Default: 0.60 + ContainerLootNew = 0.6, + -- Keys for buildings/cars, key rings, and locks. Min: 0.00 Max: 4.00 Default: 0.60 + KeyLootNew = 0.6, + -- VHS tapes and CDs. Min: 0.00 Max: 4.00 Default: 0.60 + MediaLootNew = 0.6, + -- Spiffo items, plushies, and other collectible keepsake items eg. Photos. Min: 0.00 Max: 4.00 Default: 0.60 + MementoLootNew = 0.6, + -- Items that are used in cooking, including those (eg. knives) which can be weapons. Does not include food. Includes both usable and unusable items. Min: 0.00 Max: 4.00 Default: 0.60 + CookwareLootNew = 0.6, + -- Items and weapons that are used as ingredients for crafting or building. This is a general category that does not include items belonging to other categories such as Cookware or Medical. Does not include Tools. Min: 0.00 Max: 4.00 Default: 0.60 + MaterialLootNew = 0.6, + -- Items and weapons which are used in both animal and plant agriculture, such as Seeds, Trowels, or Shovels. Min: 0.00 Max: 4.00 Default: 0.60 + FarmingLootNew = 0.6, + -- Items and weapons which are Tools but don't fit in other categories such as Mechanics or Farming. Min: 0.00 Max: 4.00 Default: 0.60 + ToolLootNew = 0.6, + -- [!] It is recommended that you DO NOT change this. [!] Can be used to adjust the number of rolls made on loot tables when spawning loot. Will not reduce the number of rolls below 1. Can negatively affect performance if set to high values. It is highly recommended that this not be changed. Min: 0.10 Max: 100.00 Default: 1.00 + RollsMultiplier = 1.0, + -- A comma-separated list of item types that won't spawn as ordinary loot. + LootItemRemovalList = "", + -- If enabled, items on the Loot Item Removal List, or that have their rarity set to 'None', will not spawn in randomised world stories. + RemoveStoryLoot = false, + -- If enabled, items on the Loot Item Removal List, or that have their rarity set to 'None', will not spawn worn by, or attached to, zombies. + RemoveZombieLoot = false, + -- If greater than 0, the spawn of loot is increased relative to the number of nearby zombies, with the effect multiplied by this number. Min: 0 Max: 20 Default: 10 + ZombiePopLootEffect = 10, + -- Min: 0.00 Max: 0.20 Default: 0.05 + InsaneLootFactor = 0.05, + -- Min: 0.05 Max: 0.60 Default: 0.20 + ExtremeLootFactor = 0.2, + -- Min: 0.20 Max: 1.00 Default: 0.60 + RareLootFactor = 0.6, + -- Min: 0.60 Max: 2.00 Default: 1.00 + NormalLootFactor = 1.0, + -- Min: 1.00 Max: 3.00 Default: 2.00 + CommonLootFactor = 2.0, + -- Min: 2.00 Max: 4.00 Default: 3.00 + AbundantLootFactor = 3.0, + -- The global temperature. Default = Normal + -- 1 = Very Cold + -- 2 = Cold + -- 3 = Normal + -- 4 = Hot + -- 5 = Very Hot + Temperature = 3, + -- How often it rains. Default = Normal + -- 1 = Very Dry + -- 2 = Dry + -- 3 = Normal + -- 4 = Rainy + -- 5 = Very Rainy + Rain = 3, + -- Number of days until the erosion system (which adds vines, long grass, new trees etc. to the world) will reach 100% growth. Default = Normal (100 Days) + -- 1 = Very Fast (20 Days) + -- 2 = Fast (50 Days) + -- 3 = Normal (100 Days) + -- 4 = Slow (200 Days) + -- 5 = Very Slow (500 Days) + ErosionSpeed = 3, + -- For a custom Erosion Speed. Zero means use the Erosion Speed option. Maximum is 36,500 days (approximately 100 years). Min: -1 Max: 36500 Default: 0 + ErosionDays = 0, + -- The speed of plant growth. Default = Normal + -- 1 = Very Fast + -- 2 = Fast + -- 3 = Normal + -- 4 = Slow + -- 5 = Very Slow + Farming = 3, + -- How long it takes for food to break down in a composter. Default = 2 Weeks + -- 1 = 1 Week + -- 2 = 2 Weeks + -- 3 = 3 Weeks + -- 4 = 4 Weeks + -- 5 = 6 Weeks + -- 6 = 8 Weeks + -- 7 = 10 Weeks + -- 8 = 12 Weeks + CompostTime = 2, + -- How fast the player's hunger, thirst, and fatigue will decrease. Default = Normal + -- 1 = Very Fast + -- 2 = Fast + -- 3 = Normal + -- 4 = Slow + -- 5 = Very Slow + StatsDecrease = 3, + -- The abundance of items found in Foraging mode. Default = Normal + -- 1 = Very Poor + -- 2 = Poor + -- 3 = Normal + -- 4 = Abundant + -- 5 = Very Abundant + NatureAbundance = 3, + -- How likely the player is to activate a house alarm when breaking into a new house. Default = Sometimes + -- 1 = Never + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + Alarm = 4, + -- How frequently the doors of homes and buildings will be locked when discovered. Default = Very Often + -- 1 = Never + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + LockedHouses = 6, + -- Spawn with Chips, a Water Bottle, a Small Backpack, a Baseball Bat, and a Hammer. + StarterKit = false, + -- Nutritional value of food affects the player's condition. Turning this off will stop the player gaining or losing weight. + Nutrition = true, + -- How fast that food will spoil, inside or outside of a fridge. Default = Normal + -- 1 = Very Fast + -- 2 = Fast + -- 3 = Normal + -- 4 = Slow + -- 5 = Very Slow + FoodRotSpeed = 3, + -- How effective a fridge will be at keeping food fresh for longer. Default = Normal + -- 1 = Very Low + -- 2 = Low + -- 3 = Normal + -- 4 = High + -- 5 = Very High + -- 6 = No decay + FridgeFactor = 3, + -- When greater than 0, loot will not respawn in zones that have been visited within this number of in-game hours. Min: 0 Max: 2147483647 Default: 0 + SeenHoursPreventLootRespawn = 0, + -- When greater than 0, after X hours, all containers in towns and trailer parks in the world will respawn loot. To spawn loot a container must have been looted at least once. Loot respawn is not impacted by visibility or subsequent looting. Min: 0 Max: 2147483647 Default: 0 + HoursForLootRespawn = 0, + -- Containers with a number of items greater, or equal to, this setting will not respawn. Min: 0 Max: 2147483647 Default: 5 + MaxItemsForLootRespawn = 5, + -- Items will not respawn in buildings that players have barricaded or built in. + ConstructionPreventsLootRespawn = true, + -- A comma-separated list of item types that will be removed after HoursForWorldItemRemoval hours. + WorldItemRemovalList = "Base.Hat,Base.Glasses,Base.Maggots,Base.Slug,Base.Slug2,Base.Snail,Base.Worm,Base.Dung_Mouse,Base.Dung_Rat", + -- Number of hours since an item was dropped on the ground before it is removed. Items are removed the next time that part of the map is loaded. Zero means items are not removed. Min: 0.00 Max: 2147483647.00 Default: 24.00 + HoursForWorldItemRemoval = 24.0, + -- If true, any items *not* in WorldItemRemovalList will be removed. + ItemRemovalListBlacklistToggle = false, + -- How long after the end of the world to begin. This will affect starting world erosion and food spoilage. Does not affect the starting date. Default = 0 + -- 1 = 0 + -- 2 = 1 + -- 3 = 2 + -- 4 = 3 + -- 5 = 4 + -- 6 = 5 + -- 7 = 6 + -- 8 = 7 + -- 9 = 8 + -- 10 = 9 + -- 11 = 10 + -- 12 = 11 + -- 13 = 12 + TimeSinceApo = 1, + -- How much water plants will lose per day, and their ability to avoid disease. Default = Normal + -- 1 = Very High + -- 2 = High + -- 3 = Normal + -- 4 = Low + -- 5 = Very Low + PlantResilience = 3, + -- The yield of plants when harvested. Default = Normal + -- 1 = Very Poor + -- 2 = Poor + -- 3 = Normal + -- 4 = Abundant + -- 5 = Very Abundant + PlantAbundance = 3, + -- Recovery from being tired after performing actions. Default = Normal + -- 1 = Very Fast + -- 2 = Fast + -- 3 = Normal + -- 4 = Slow + -- 5 = Very Slow + EndRegen = 3, + -- How regularly a helicopter passes over the Event Zone. Default = Once + -- 1 = Never + -- 2 = Once + -- 3 = Sometimes + -- 4 = Often + Helicopter = 2, + -- How often zombie-attracting metagame events like distant gunshots will occur. Default = Sometimes + -- 1 = Never + -- 2 = Sometimes + -- 3 = Often + MetaEvent = 2, + -- How often events during the player's sleep, like nightmares, occur. Default = Never + -- 1 = Never + -- 2 = Sometimes + -- 3 = Often + SleepingEvent = 1, + -- How much fuel is consumed by generators per in-game hour. Min: 0.00 Max: 100.00 Default: 0.10 + GeneratorFuelConsumption = 0.1, + -- The chance of electrical generators spawning on the map. Default = Rare + -- 1 = None (not recommended) + -- 2 = Insanely Rare + -- 3 = Extremely Rare + -- 4 = Rare + -- 5 = Normal + -- 6 = Common + -- 7 = Abundant + GeneratorSpawning = 4, + -- How often a looted map will have notes on it, written by a deceased survivor. Default = Sometimes + -- 1 = Never + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + AnnotatedMapChance = 4, + -- Adds free points during character creation. Min: -100 Max: 100 Default: 0 + CharacterFreePoints = 0, + -- Gives player-built constructions extra hit points so they are more resistant to zombie damage. Default = Normal + -- 1 = Very Low + -- 2 = Low + -- 3 = Normal + -- 4 = High + -- 5 = Very High + ConstructionBonusPoints = 3, + -- The level of ambient lighting at night. Default = Normal + -- 1 = Pitch Black + -- 2 = Dark + -- 3 = Normal + -- 4 = Bright + NightDarkness = 3, + -- The time from dusk to dawn. Default = Normal + -- 1 = Always Night + -- 2 = Long + -- 3 = Normal + -- 4 = Short + -- 5 = Always Day + NightLength = 3, + -- If survivors can get broken limbs from impacts, zombie damage, falls etc. + BoneFracture = true, + -- The impact that injuries have on your body, and their healing time. Default = Normal + -- 1 = Low + -- 2 = Normal + -- 3 = High + InjurySeverity = 2, + -- How long, in hours, before dead zombie bodies disappear from the world. If 0, maggots will not spawn on corpses. Min: -1.00 Max: 2147483647.00 Default: 216.00 + HoursForCorpseRemoval = 216.0, + -- The impact that nearby decaying bodies has on the player's health and emotions. Default = Normal + -- 1 = None + -- 2 = Low + -- 3 = Normal + -- 4 = High + -- 5 = Insane + DecayingCorpseHealthImpact = 3, + -- Whether nearby "living" zombies have the same impact on the player's health and emotions. + ZombieHealthImpact = false, + -- How much blood is sprayed on floors and walls by injuries. Default = Normal + -- 1 = None + -- 2 = Low + -- 3 = Normal + -- 4 = High + -- 5 = Ultra Gore + BloodLevel = 3, + -- How quickly clothing degrades, becomes dirty, and bloodied. Default = Normal + -- 1 = Disabled + -- 2 = Slow + -- 3 = Normal + -- 4 = Fast + ClothingDegradation = 3, + -- If fires spread when started. + FireSpread = true, + -- Number of in-game days before rotten food is removed from the map. -1 means rotten food is never removed. Min: -1 Max: 2147483647 Default: -1 + DaysForRottenFoodRemoval = -1, + -- If enabled, generators will work on exterior tiles. This will allow, for example, the powering of gas pumps. + AllowExteriorGenerator = true, + -- Maximum intensity of fog. Default = Normal + -- 1 = Normal + -- 2 = Moderate + -- 3 = Low + -- 4 = None + MaxFogIntensity = 1, + -- Maximum intensity of rain. Default = Normal + -- 1 = Normal + -- 2 = Moderate + -- 3 = Low + MaxRainFxIntensity = 1, + -- If snow will accumulate on the ground. If disabled, snow will still show on vegetation and rooftops. + EnableSnowOnGround = true, + -- If melee attacking slows you down. + AttackBlockMovements = true, + -- The chance of finding randomized buildings on the map (eg. burnt out houses, ones containing loot stashes or dead bodies). Default = Rare + -- 1 = Never + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + -- 7 = Always Tries + SurvivorHouseChance = 3, + -- The chance of road stories (eg. police roadblocks) spawning. Default = Rare + -- 1 = Never + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + -- 7 = Always Tries + VehicleStoryChance = 3, + -- The chance of stories specific to map zones (eg. a campsite in a forest) spawning. Default = Rare + -- 1 = Never + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + -- 7 = Always Tries + ZoneStoryChance = 3, + -- Allows you to select from every piece of clothing in the game when customizing your character + AllClothesUnlocked = false, + -- If tainted water will show a warning marking it as such. + EnableTaintedWaterText = true, + -- If vehicles will spawn. + EnableVehicles = true, + -- How frequently vehicles can be discovered on the map. Default = Low + -- 1 = None + -- 2 = Very Low + -- 3 = Low + -- 4 = Normal + -- 5 = High + CarSpawnRate = 3, + -- General engine loudness to zombies. Min: 0.00 Max: 100.00 Default: 1.00 + ZombieAttractionMultiplier = 1.0, + -- Whether found vehicles are locked, need keys to start etc. + VehicleEasyUse = false, + -- How full the gas tank of discovered vehicles will be. Default = Low + -- 1 = Very Low + -- 2 = Low + -- 3 = Normal + -- 4 = High + -- 5 = Very High + -- 6 = Full + InitialGas = 2, + -- If enabled, gas pumps will never run out of fuel + FuelStationGasInfinite = false, + -- The minimum amount of gasoline that can spawn in gas pumps. Check the "Advanced" box below to use a custom amount. Min: 0.00 Max: 1.00 Default: 0.00 + FuelStationGasMin = 0.0, + -- The maximum amount of gasoline that can spawn in gas pumps. Check the "Advanced" box below to use a custom amount. Min: 0.00 Max: 1.00 Default: 0.70 + FuelStationGasMax = 0.7, + -- The chance, as a percentage, that individual gas pumps will initially have no fuel. Min: 0 Max: 100 Default: 20 + FuelStationGasEmptyChance = 20, + -- How likely cars will be locked Default = Rare + -- 1 = Never + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + LockedCar = 3, + -- How gas-hungry vehicles are. Min: 0.00 Max: 100.00 Default: 1.00 + CarGasConsumption = 1.0, + -- General condition discovered vehicles will be in. Default = Low + -- 1 = Very Low + -- 2 = Low + -- 3 = Normal + -- 4 = High + -- 5 = Very High + CarGeneralCondition = 2, + -- The amount of damage dealt to vehicles that crash. Default = Normal + -- 1 = Very Low + -- 2 = Low + -- 3 = Normal + -- 4 = High + -- 5 = Very High + CarDamageOnImpact = 3, + -- Damage received by the player from being crashed into. Default = None + -- 1 = None + -- 2 = Low + -- 3 = Normal + -- 4 = High + -- 5 = Very High + DamageToPlayerFromHitByACar = 1, + -- If traffic jams consisting of wrecked cars will appear on main roads. + TrafficJam = true, + -- How frequently discovered vehicles have active alarms. Default = Extremely Rare + -- 1 = Never + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + CarAlarm = 2, + -- If the player can get injured from being in a car accident. + PlayerDamageFromCrash = true, + -- How many in-game hours before a wailing siren shuts off. Min: 0.00 Max: 168.00 Default: 0.00 + SirenShutoffHours = 0.0, + -- The chance of finding a vehicle with gas in its tank. Default = Low + -- 1 = Low + -- 2 = Normal + -- 3 = High + ChanceHasGas = 1, + -- Whether a player can discover a car that has been cared for after the Knox infection struck. Default = Low + -- 1 = None + -- 2 = Low + -- 3 = Normal + -- 4 = High + RecentlySurvivorVehicles = 2, + -- If certain melee weapons will be able to strike multiple zombies in one hit. + MultiHitZombies = true, + -- Chance of being bitten when a zombie attacks from behind. Default = High + -- 1 = Low + -- 2 = Medium + -- 3 = High + RearVulnerability = 3, + -- If zombies will head towards the sound of vehicle sirens. + SirenEffectsZombies = true, + -- Speed at which animals stats (hunger, thirst etc.) reduce. Default = Normal + -- 1 = Ultra Fast + -- 2 = Very Fast + -- 3 = Fast + -- 4 = Normal + -- 5 = Slow + -- 6 = Very Slow + AnimalStatsModifier = 4, + -- Speed at which animals stats (hunger, thirst etc.) reduce while in meta. Default = Normal + -- 1 = Ultra Fast + -- 2 = Very Fast + -- 3 = Fast + -- 4 = Normal + -- 5 = Slow + -- 6 = Very Slow + AnimalMetaStatsModifier = 4, + -- How long animals will be pregnant for before giving birth. Default = Very Fast + -- 1 = Ultra Fast + -- 2 = Very Fast + -- 3 = Fast + -- 4 = Normal + -- 5 = Slow + -- 6 = Very Slow + AnimalPregnancyTime = 2, + -- Speed at which animals age. Default = Fast + -- 1 = Ultra Fast + -- 2 = Very Fast + -- 3 = Fast + -- 4 = Normal + -- 5 = Slow + -- 6 = Very Slow + AnimalAgeModifier = 3, + -- Default = Fast + -- 1 = Ultra Fast + -- 2 = Very Fast + -- 3 = Fast + -- 4 = Normal + -- 5 = Slow + -- 6 = Very Slow + AnimalMilkIncModifier = 3, + -- Default = Fast + -- 1 = Ultra Fast + -- 2 = Very Fast + -- 3 = Fast + -- 4 = Normal + -- 5 = Slow + -- 6 = Very Slow + AnimalWoolIncModifier = 3, + -- The chance of finding animals in farm. Default = Always + -- 1 = Never + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + -- 7 = Always + AnimalRanchChance = 7, + -- The number of hours grass will regrow after being eaten by an animal or cut by the player. Min: 1 Max: 9999 Default: 240 + AnimalGrassRegrowTime = 240, + -- If a meta (ie. not actually visible in-game) fox may attack your chickens if the hutch's door is left open at night. + AnimalMetaPredator = false, + -- If animals with a mating season will respect it. Otherwise they can reproduce/lay eggs all year round. + AnimalMatingSeason = true, + -- How long before baby animals will hatch from eggs. Default = Fast + -- 1 = Ultra Fast + -- 2 = Very Fast + -- 3 = Fast + -- 4 = Normal + -- 5 = Slow + -- 6 = Very Slow + AnimalEggHatch = 3, + -- If true, animal calls will attract nearby zombies. + AnimalSoundAttractZombies = false, + -- The chance of animals leaving tracks. Default = Sometimes + -- 1 = Never + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + AnimalTrackChance = 4, + -- The chance of creating a path for animals to be hunted. Default = Sometimes + -- 1 = Never + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + AnimalPathChance = 4, + -- The frequency and intensity of eg. rats in infested buildings. Min: 0 Max: 50 Default: 25 + MaximumRatIndex = 25, + -- How long it takes for the Maximum Vermin Index to be reached. Min: 0 Max: 365 Default: 90 + DaysUntilMaximumRatIndex = 90, + -- If a piece of media hasn't been fully seen or read, this setting determines whether it's displayed fully, displayed as "???", or hidden completely. Default = Completely hidden + -- 1 = Fully revealed + -- 2 = Shown as ??? + -- 3 = Completely hidden + MetaKnowledge = 3, + -- If true, you will be able to see any recipes that can be done with a station, even if you haven't learnt them yet. + SeeNotLearntRecipe = true, + -- If a building has more than this amount of rooms it will not be looted. Min: 0 Max: 200 Default: 50 + MaximumLootedBuildingRooms = 50, + -- If poison can be added to food. Default = True + -- 1 = True + -- 2 = False + -- 3 = Only bleach poisoning is disabled + EnablePoisoning = 1, + -- If/when maggots can spawn in corpses. Default = In and Around Bodies + -- 1 = In and Around Bodies + -- 2 = In Bodies Only + -- 3 = Never + MaggotSpawn = 1, + -- The higher the value, the longer lightbulbs last before breaking. If 0, lightbulbs will never break. Does not affect vehicle headlights. Min: 0.00 Max: 1000.00 Default: 1.00 + LightBulbLifespan = 1.0, + -- The abundance of fish in rivers and lakes. Default = Normal + -- 1 = Very Poor + -- 2 = Poor + -- 3 = Normal + -- 4 = Abundant + -- 5 = Very Abundant + FishAbundance = 3, + -- When a skill is at this level or above, television/VHS/other media will not provide XP for it. Min: 0 Max: 10 Default: 3 + LevelForMediaXPCutoff = 3, + -- When a skill is at this level or above, scrapping furniture does not provide XP for the relevant skill. Does not apply to Electrical. Min: 0 Max: 10 Default: 0 + LevelForDismantleXPCutoff = 0, + -- Number of days before old blood splats are removed. Removal happens when map chunks are loaded. 0 means they will never disappear. Min: 0 Max: 365 Default: 0 + BloodSplatLifespanDays = 0, + -- Number of days before one can benefit from reading previously read literature items. Min: 1 Max: 365 Default: 90 + LiteratureCooldown = 90, + -- If there are diminishing returns on bonus trait points provided from selecting multiple negative traits. Default = None + -- 1 = None + -- 2 = 1 point penalty for every 3 negative traits selected + -- 3 = 1 point penalty for every 2 negative traits selected + -- 4 = 1 point penalty for every negative trait selected after the first + NegativeTraitsPenalty = 1, + -- The number of in-game minutes it takes to read one page of a skill book. Min: 0.00 Max: 60.00 Default: 2.00 + MinutesPerPage = 0.3, + -- When enabled, crops and herbs grown inside buildings will die. Does not affect houseplants. + KillInsideCrops = true, + -- When enabled, the growth of plants is affected by seasons. + PlantGrowingSeasons = true, + -- [!] It is recommended that you DO NOT change this. Changing this can result in performance issues. [!] When enabled, dirt can be placed, and farming performed on other than the ground level. + PlaceDirtAboveground = false, + -- The speed of plant growth. Min: 0.10 Max: 100.00 Default: 1.00 + FarmingSpeedNew = 1.0, + -- The abundance of harvested crops. Min: 0.10 Max: 10.00 Default: 1.00 + FarmingAmountNew = 1.0, + -- The chance that any building will already be looted when found. Check the "Advanced" box below to use a custom number. Min: 0 Max: 200 Default: 50 + MaximumLooted = 50, + -- How long it takes for Maximum Looted Building Chance to be reached. Min: 0 Max: 3650 Default: 90 + DaysUntilMaximumLooted = 90, + -- The chance that any rural building will already be looted when found. Check the "Advanced" box below to use a custom number. Min: 0.00 Max: 2.00 Default: 0.50 + RuralLooted = 0.5, + -- The maximum loot that won't spawn when Days Until Maximum Diminished Loot is reached. Check the "Advanced" box below to use an exact percentage. Min: 0 Max: 100 Default: 0 + MaximumDiminishedLoot = 0, + -- How long it takes for Maximum Diminished Loot Percentage to be reached. Min: 0 Max: 3650 Default: 3650 + DaysUntilMaximumDiminishedLoot = 3650, + -- Functions as a multiplier when applying muscle strain from swinging weapons or carrying heavy loads. Min: 0.00 Max: 10.00 Default: 1.00 + MuscleStrainFactor = 1.0, + -- Functions as a multiplier when applying discomfort from worn items. Min: 0.00 Max: 10.00 Default: 1.00 + DiscomfortFactor = 1.0, + -- If greater than zero damage can be taken from serious wound infections. Min: 0.00 Max: 10.00 Default: 0.00 + WoundInfectionFactor = 0.0, + -- If true clothing with randomized tints will not be so dark to be virtually black. + NoBlackClothes = true, + -- Disables the failure chances when climbing sheet ropes or over walls. + EasyClimbing = false, + -- The maximum hours of fuel that can be placed in a campfire, wood stove etc. Min: 1 Max: 168 Default: 8 + MaximumFireFuelHours = 8, + -- Replaces Chance-To-Hit mechanics with Chance-To-Damage calculations. This mode prioritizes player aiming. + FirearmUseDamageChance = true, + -- A multiplier for the distance at which zombies can hear gunshots. Min: 0.20 Max: 2.00 Default: 1.00 + FirearmNoiseMultiplier = 1.0, + -- Multiplier for firearm jamming chance. 0 disables jamming. Min: 0.00 Max: 10.00 Default: 0.00 + FirearmJamMultiplier = 0.0, + -- Multiplier for Moodle effects on hit chance. 0 disables Moodle penalty. Min: 0.00 Max: 10.00 Default: 1.00 + FirearmMoodleMultiplier = 1.0, + -- Multiplier for the effects of weather (wind, rain and fog) on hit chance. 0 disables weather effect. Min: 0.00 Max: 10.00 Default: 1.00 + FirearmWeatherMultiplier = 1.0, + -- Enable to have headgear like welding masks affect hit chance + FirearmHeadGearEffect = true, + -- Chance to turn a dirt floor into a clay floor. Applies to lakes. Min: 0.00 Max: 1.00 Default: 0.05 + ClayLakeChance = 0.05, + -- Chance to turn a dirt floor into a clay floor. Applies to rivers. Min: 0.00 Max: 1.00 Default: 0.05 + ClayRiverChance = 0.05, + -- Min: 1 Max: 100 Default: 20 + GeneratorTileRange = 20, + -- How many levels both above and below a generator it can provide with electricity. Min: 1 Max: 15 Default: 3 + GeneratorVerticalPowerRange = 3, + Basement = { + -- How frequently basements spawn at random locations. Default = Sometimes + -- 1 = Never + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + -- 7 = Always + SpawnFrequency = 4, + }, + Map = { + -- If enabled, a mini-map window will be available. + AllowMiniMap = true, + -- If enabled, the world map can be accessed. + AllowWorldMap = true, + -- If enabled, the world map will be completely filled in on starting the game. + MapAllKnown = true, + -- If enabled, maps can't be read unless there's a source of light available. + MapNeedsLight = true, + }, + ZombieLore = { + -- How fast zombies move. Default = Random + -- 1 = Sprinters + -- 2 = Fast Shamblers + -- 3 = Shamblers + -- 4 = Random + Speed = 2, + -- If Random Speed is enabled, this controls what percentage of zombies are Sprinters. Check the "Advanced" box below to use a custom percentage. Min: 0 Max: 100 Default: 0 + SprinterPercentage = 0, + -- The damage zombies inflict per attack. Default = Normal + -- 1 = Superhuman + -- 2 = Normal + -- 3 = Weak + -- 4 = Random + Strength = 2, + -- The difficulty of killing a zombie. Default = Random + -- 1 = Tough + -- 2 = Normal + -- 3 = Fragile + -- 4 = Random + Toughness = 2, + -- How the Knox Virus spreads. Default = Blood and Saliva + -- 1 = Blood and Saliva + -- 2 = Saliva Only + -- 3 = Everyone's Infected + -- 4 = None + Transmission = 1, + -- How quickly the infection takes effect. Default = 2-3 Days + -- 1 = Instant + -- 2 = 0-30 Seconds + -- 3 = 0-1 Minutes + -- 4 = 0-12 Hours + -- 5 = 2-3 Days + -- 6 = 1-2 Weeks + -- 7 = Never + Mortality = 5, + -- How quickly infected corpses rise as zombies. Default = 0-1 Minutes + -- 1 = Instant + -- 2 = 0-30 Seconds + -- 3 = 0-1 Minutes + -- 4 = 0-12 Hours + -- 5 = 2-3 Days + -- 6 = 1-2 Weeks + Reanimate = 3, + -- Zombie intelligence. Default = Basic Navigation + -- 1 = Navigate and Use Doors + -- 2 = Navigate + -- 3 = Basic Navigation + -- 4 = Random + Cognition = 3, + -- How often zombies can crawl under parked vehicles. Default = Often + -- 1 = Crawlers Only + -- 2 = Extremely Rare + -- 3 = Rare + -- 4 = Sometimes + -- 5 = Often + -- 6 = Very Often + -- 7 = Always + CrawlUnderVehicle = 5, + -- How long zombies remember a player after seeing or hearing them. Default = Normal + -- 1 = Long + -- 2 = Normal + -- 3 = Short + -- 4 = None + -- 5 = Random + -- 6 = Random between Normal and None + Memory = 2, + -- Zombie vision radius. Default = Random between Normal and Poor + -- 1 = Eagle + -- 2 = Normal + -- 3 = Poor + -- 4 = Random + -- 5 = Random between Normal and Poor + Sight = 2, + -- Zombie hearing radius. Default = Random between Normal and Poor + -- 1 = Pinpoint + -- 2 = Normal + -- 3 = Poor + -- 4 = Random + -- 5 = Random between Normal and Poor + Hearing = 2, + -- Activates the new advanced stealth mechanics, which allows you to hide from zombies behind cars, takes traits and weather into account, and much more. + SpottedLogic = true, + -- If zombies that have not seen/heard player can attack doors and constructions while roaming. + ThumpNoChasing = false, + -- If zombies can destroy player constructions and defenses. + ThumpOnConstruction = true, + -- Whether zombies are more "active" during the day or night. "Active" zombies will use the speed set in the "Speed" setting. "Inactive" zombies will be slower, and tend not to give chase. Default = Both + -- 1 = Both + -- 2 = Night + -- 3 = Day + ActiveOnly = 1, + -- If zombies trigger house alarms when breaking through windows or doors. + TriggerHouseAlarm = false, + -- If multiple attacking zombies can drag you down and kill you. Dependent on zombie strength. + ZombiesDragDown = true, + -- If crawler zombies beside a player contribute to the chance of being dragged down and killed by a group of zombies. + ZombiesCrawlersDragDown = false, + -- If zombies have a chance to lunge at you after climbing over a fence or through a window if you're too close. + ZombiesFenceLunge = true, + -- Serves as a multiplier when determining the effectiveness of armor worn by zombies. Min: 0.00 Max: 100.00 Default: 2.00 + ZombiesArmorFactor = 2.0, + -- The maximum defense percentage that any worn protective garments can provide to a zombie. Min: 0 Max: 100 Default: 85 + ZombiesMaxDefense = 85, + -- Percentage chance of having a random attached weapon. Min: 0 Max: 100 Default: 6 + ChanceOfAttachedWeapon = 6, + -- How much damage zombies take when falling from height. Min: 0.00 Max: 100.00 Default: 1.00 + ZombiesFallDamage = 1.0, + -- Whether some dead-looking zombies will reanimate and attack the player. Default = World Zombies + -- 1 = World Zombies + -- 2 = World and Combat Zombies + -- 3 = Never + DisableFakeDead = 1, + -- Zombies will not spawn where players spawn. Default = Inside the building and around it + -- 1 = Inside the building and around it + -- 2 = Inside the building + -- 3 = Inside the room + -- 4 = Zombies can spawn anywhere + PlayerSpawnZombieRemoval = 1, + -- How many zombies it takes to damage a tall fence. Min: -1 Max: 100 Default: 50 + FenceThumpersRequired = 50, + -- How quickly zombies damage tall fences. Min: 0.01 Max: 100.00 Default: 1.00 + FenceDamageMultiplier = 1.0, + }, + ZombieConfig = { + -- Set by the "Zombie Count" population option, or by a custom number here. Insane = 2.5, Very High = 1.6, High = 1.2, Normal = 0.65, Low = 0.15, None = 0.0. Min: 0.00 Max: 4.00 Default: 0.65 + PopulationMultiplier = 0.65, + -- A multiplier for the desired zombie population at the start of the game. Insane = 3.0, Very High = 2.0, High = 1.5, Normal = 1.0, Low = 0.5, None = 0.0. Min: 0.00 Max: 4.00 Default: 1.00 + PopulationStartMultiplier = 1.0, + -- A multiplier for the desired zombie population on the peak day. Insane = 3.0, Very High = 2.0, High = 1.5, Normal = 1.0, Low = 0.5, None = 0.0. Min: 0.00 Max: 4.00 Default: 1.50 + PopulationPeakMultiplier = 1.5, + -- The day when the population reaches its peak. Min: 1 Max: 365 Default: 28 + PopulationPeakDay = 28, + -- The number of hours that must pass before zombies may respawn in a cell. If 0, spawning is disabled. Min: 0.00 Max: 8760.00 Default: 72.00 + RespawnHours = 72.0, + -- The number of hours that a chunk must be unseen before zombies may respawn in it. Min: 0.00 Max: 8760.00 Default: 16.00 + RespawnUnseenHours = 16.0, + -- The fraction of a cell's desired population that may respawn every RespawnHours. Min: 0.00 Max: 1.00 Default: 0.10 + RespawnMultiplier = 0.1, + -- The number of hours that must pass before zombies migrate to empty parts of the same cell. If 0, migration is disabled. Min: 0.00 Max: 8760.00 Default: 12.00 + RedistributeHours = 12.0, + -- The distance a zombie will try to walk towards the last sound it heard. Min: 10 Max: 1000 Default: 100 + FollowSoundDistance = 100, + -- The size of groups real zombies form when idle. 0 means zombies don't form groups. Groups don't form inside buildings or forest zones. Min: 0 Max: 1000 Default: 20 + RallyGroupSize = 20, + -- The amount, as a percentage, that zombie groups can vary in size from the default (both larger and smaller). For example, at 50% variance with a default group size of 20, groups will vary in size from 10-30. Min: 0 Max: 100 Default: 50 + RallyGroupSizeVariance = 50, + -- The distance real zombies travel to form groups when idle. Min: 5 Max: 50 Default: 20 + RallyTravelDistance = 20, + -- The distance between zombie groups. Min: 5 Max: 25 Default: 15 + RallyGroupSeparation = 15, + -- How close members of a zombie group stay to the group's "leader". Min: 1 Max: 10 Default: 3 + RallyGroupRadius = 3, + -- Min: 10 Max: 500 Default: 300 + ZombiesCountBeforeDelete = 300, + }, + MultiplierConfig = { + -- The rate at which all skills level up. Min: 0.00 Max: 1000.00 Default: 1.00 + Global = 3.0, + -- When enabled, all skills will use the Global Multiplier. + GlobalToggle = true, + -- Rate at which Fitness skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Fitness = 1.0, + -- Rate at which Strength skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Strength = 1.0, + -- Rate at which Sprinting skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Sprinting = 1.0, + -- Rate at which Lightfooted skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Lightfoot = 1.0, + -- Rate at which Nimble skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Nimble = 1.0, + -- Rate at which Sneaking skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Sneak = 1.0, + -- Rate at which Axe skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Axe = 1.0, + -- Rate at which Long Blunt skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Blunt = 1.0, + -- Rate at which Short Blunt skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + SmallBlunt = 1.0, + -- Rate at which Long Blade skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + LongBlade = 1.0, + -- Rate at which Short Blade skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + SmallBlade = 1.0, + -- Rate at which Spear skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Spear = 1.0, + -- Rate at which Maintenance skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Maintenance = 1.0, + -- Rate at which Carpentry skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Woodwork = 1.0, + -- Rate at which Cooking skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Cooking = 1.0, + -- Rate at which Agriculture skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Farming = 1.0, + -- Rate at which First Aid skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Doctor = 1.0, + -- Rate at which Electrical skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Electricity = 1.0, + -- Rate at which Welding skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + MetalWelding = 1.0, + -- Rate at which Mechanics skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Mechanics = 1.0, + -- Rate at which Tailoring skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Tailoring = 1.0, + -- Rate at which Aiming skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Aiming = 1.0, + -- Rate at which Reloading skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Reloading = 1.0, + -- Rate at which Fishing skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Fishing = 1.0, + -- Rate at which Trapping skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Trapping = 1.0, + -- Rate at which Foraging skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + PlantScavenging = 1.0, + -- Rate at which Knapping skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + FlintKnapping = 1.0, + -- Rate at which Masonry skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Masonry = 1.0, + -- Rate at which Pottery skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Pottery = 1.0, + -- Rate at which Carving skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Carving = 1.0, + -- Rate at which Animal Care skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Husbandry = 1.0, + -- Rate at which Tracking skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Tracking = 1.0, + -- Rate at which Blacksmithing skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Blacksmith = 1.0, + -- Rate at which Butchering skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Butchering = 1.0, + -- Rate at which Glassmaking skill levels up. Min: 0.00 Max: 1000.00 Default: 1.00 + Glassmaking = 1.0, + }, +} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..b61e76b --- /dev/null +++ b/templates/index.html @@ -0,0 +1,900 @@ + + + + + + PZ Config Studio + + + + + + + + + + + + {% macro sidebar(dismiss_offcanvas=false) -%} + + {%- endmacro %} + +
+ + +
+
+
配置导航
+ +
+
+ {{ sidebar(true) }} +
+
+ +
+
+
+ {% if saved %} +
+ 已保存覆盖到本地文件。 +
+ {% endif %} + +
+
+ +
+ +
+
+
+ 没有匹配项(尝试清空搜索/取消“只看已修改”)。 +
+ +
+
+
+
server.ini
+
按项编辑 · {{ ini_settings | length }} 项
+
+
点击每项右侧“详情”查看完整中文说明
+
+ +
+
+
+ {% for s in ini_settings %} +
+
+
+
+
+
+ {{ s.path }} + {{ s.value_type }} + {% if s.default_value %}默认 {{ s.default_value }}{% endif %} + {% if s.min_value is not none %}Min {{ s.min_value }}{% endif %} + {% if s.max_value is not none %}Max {{ s.max_value }}{% endif %} +
+
+ {{ s.description_zh.split('\n', 1)[0] }} +
+
+ +
+ +
{{ s.description_zh }}
+
+
+ +
+ {% set name = 'ini__' ~ s.path %} + {% if s.choices %} + + {% elif s.value_type == 'bool' %} + + {% elif s.value_type in ['int','float'] %} + + {% else %} + {% set v = s.value | string %} + {% if v | length > 80 or s.path in long_text_keys %} + + {% else %} + + {% endif %} + {% endif %} +
+
+
+ {% endfor %} +
+
+
+
+ + {% for g in lua_groups %} +
+
+
+
SandboxVars · {{ g.name }}
+
按项编辑 · {{ g.count }} 项
+
+
点击每项右侧“详情”查看完整中文/英文说明
+
+ +
+
+
+ {% for s in g.settings %} +
+
+
+
+
+
+ {{ s.path }} + {{ s.value_type }} + {% if s.default_value %}默认 {{ s.default_value }}{% endif %} + {% if s.min_value is not none %}Min {{ s.min_value }}{% endif %} + {% if s.max_value is not none %}Max {{ s.max_value }}{% endif %} + {% if s.choices %}枚举{% endif %} +
+
+ {{ s.description_zh.split('\n', 1)[0] }} +
+
+ +
+ +
{{ s.description_zh }}
+
{{ s.description_en or '' }}
+
+ +
+ {% set name = 'lua__' ~ s.path %} + {% if s.choices %} + + {% elif s.value_type == 'bool' %} + + {% elif s.value_type in ['int','float'] %} + + {% else %} + {% set v = s.value | string %} + {% if v | length > 80 or s.path in long_text_keys %} + + {% else %} + + {% endif %} + {% endif %} +
+
+
+ {% endfor %} +
+
+
+
+ {% endfor %} +
+
+
+
+
+
+
+ + + + +
+ +
+ + + + + + + diff --git a/tools/generate_sandboxvars_zh.py b/tools/generate_sandboxvars_zh.py new file mode 100644 index 0000000..e5e07fc --- /dev/null +++ b/tools/generate_sandboxvars_zh.py @@ -0,0 +1,158 @@ +from __future__ import annotations + +import argparse +import json +import time +import urllib.parse +import urllib.request +from urllib.error import URLError +from pathlib import Path + + +def _decode_utf8_keep_newlines(path: Path) -> str: + return path.read_bytes().decode("utf-8-sig") + + +def translate_en_to_zh( + text: str, + *, + timeout_s: int = 45, + retries: int = 6, + backoff_s: float = 1.0, +) -> str: + """ + Uses Google Translate's public endpoint (no API key) to translate English -> zh-CN. + + Note: This is a best-effort helper for generating a local offline mapping file. + """ + if not text.strip(): + return "" + + q = urllib.parse.quote(text) + url = ( + "https://translate.googleapis.com/translate_a/single" + "?client=gtx&sl=en&tl=zh-CN&dt=t&q=" + + q + ) + headers = {"User-Agent": "pz-config-editor/1.0 (+https://translate.googleapis.com)"} + req = urllib.request.Request(url, headers=headers) + + last_err: Exception | None = None + for attempt in range(retries): + try: + with urllib.request.urlopen(req, timeout=timeout_s) as resp: + data = resp.read().decode("utf-8") + payload = json.loads(data) + parts = payload[0] or [] + return "".join((p[0] or "") for p in parts) + except (TimeoutError, URLError, json.JSONDecodeError) as e: + last_err = e + time.sleep(backoff_s * (attempt + 1)) + + raise RuntimeError(f"Translate failed after {retries} retries: {last_err}") from last_err + + +def extract_sandbox_comment_blocks(lua_path: Path) -> dict[str, str]: + """ + Returns: { "Zombies": "comment lines...", "ZombieLore.Speed": "comment lines..." } + """ + lines = _decode_utf8_keep_newlines(lua_path).splitlines() + + def is_ident(s: str) -> bool: + return s.isidentifier() + + comments: list[str] = [] + table_stack: list[str] = [] + out: dict[str, str] = {} + + for line in lines: + stripped = line.strip() + if stripped.startswith("--"): + comments.append(stripped[2:].lstrip()) + continue + + if not stripped: + comments = [] + continue + + if stripped.endswith("= {"): + name = stripped.split("=", 1)[0].strip() + if is_ident(name): + table_stack.append(name) + comments = [] + continue + + if stripped == "},": + if table_stack: + table_stack.pop() + comments = [] + continue + + if "=" in stripped and stripped.endswith(","): + key = stripped.split("=", 1)[0].strip() + if not is_ident(key): + comments = [] + continue + + effective_stack = table_stack[:] + if effective_stack[:1] == ["SandboxVars"]: + effective_stack = effective_stack[1:] + setting_path = ".".join(effective_stack + [key]) if effective_stack else key + out[setting_path] = "\n".join(comments).strip() + comments = [] + continue + + comments = [] + + return out + + +def main() -> int: + parser = argparse.ArgumentParser(description="Generate SandboxVars English->Chinese comment mapping JSON.") + parser.add_argument("--lua", default="server_SandboxVars.lua", help="Path to server_SandboxVars.lua") + parser.add_argument("--out", default=str(Path("i18n") / "sandboxvars_zh.json"), help="Output JSON path") + parser.add_argument("--sleep", type=float, default=0.12, help="Sleep between requests (seconds)") + parser.add_argument("--resume", action="store_true", help="If output exists, load and continue") + args = parser.parse_args() + + lua_path = Path(args.lua) + out_path = Path(args.out) + out_path.parent.mkdir(parents=True, exist_ok=True) + + blocks = extract_sandbox_comment_blocks(lua_path) + + mapping: dict[str, str] = { + "VERSION": "SandboxVars 文件版本号(通常不用改)。", + "StartYear": "开局年份(通常与开局日期/月份一起使用)。", + } + if args.resume and out_path.exists(): + try: + mapping.update(json.loads(out_path.read_text(encoding="utf-8"))) + except Exception: + pass + + cache: dict[str, str] = {} + total = 0 + for key, en in blocks.items(): + if key in mapping: + continue + total += 1 + if en in cache: + mapping[key] = cache[en] + continue + try: + zh = translate_en_to_zh(en) + except Exception as e: + print(f"[WARN] {key}: {e}") + zh = "" + cache[en] = zh + mapping[key] = zh + time.sleep(args.sleep) + + out_path.write_text(json.dumps(mapping, ensure_ascii=False, indent=2), encoding="utf-8") + print(f"Wrote {len(mapping)} entries to {out_path} (translated {total} blocks).") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())