using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("RankedPeakZh")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.5")]
[assembly: AssemblyInformationalVersion("1.1.5")]
[assembly: AssemblyProduct("RankedPeakZh")]
[assembly: AssemblyTitle("RankedPeakZh")]
[assembly: AssemblyVersion("1.1.5.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace RankedPeakZh
{
[BepInPlugin("com.tfjsn.rankedpeak.zh", "PEAK RANKED 中文显示", "1.1.5")]
public class Plugin : BaseUnityPlugin
{
public const string Guid = "com.tfjsn.rankedpeak.zh";
internal static ManualLogSource Log;
private Harmony _harmony;
private bool _patched;
private float _next;
private static Func<string, string> _head;
private static Func<string, string> _muted;
private static Func<object, string> _keyHelper;
private static Func<int, string> _modList;
private static FieldInfo _hudMetaField;
private static PropertyInfo _hudMetaText;
private void Awake()
{
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
_harmony = new Harmony("com.tfjsn.rankedpeak.zh");
CreateWatermark();
TryPatch();
}
private void Update()
{
if (!_patched && !(Time.unscaledTime < _next))
{
_next = Time.unscaledTime + 5f;
TryPatch();
}
}
private void CreateWatermark()
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Expected O, but got Unknown
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
try
{
GameObject val = new GameObject("TFJSN_Watermark");
Object.DontDestroyOnLoad((Object)val);
((Object)val).hideFlags = (HideFlags)61;
val.AddComponent<WatermarkBehaviour>();
Log.LogInfo((object)"署名水印已显示:左上角「由 TFJSN 汉化」(常驻,不可关闭)。");
}
catch (Exception ex)
{
Log.LogWarning((object)("水印没建起来:" + ex.Message));
}
}
private void TryPatch()
{
//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
//IL_00c2: Expected O, but got Unknown
//IL_0405: Unknown result type (might be due to invalid IL or missing references)
//IL_0412: Expected O, but got Unknown
//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
//IL_01b5: Expected O, but got Unknown
try
{
Type type = null;
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
type = assemblies[i].GetType("RankedPeak.Localization.Loc", throwOnError: false);
if (type != null)
{
break;
}
}
if (type == null)
{
Log.LogInfo((object)"等待 PEAK RANKED 载入……");
return;
}
MethodInfo method = type.GetMethod("T", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(string) }, null);
if (method == null)
{
Log.LogWarning((object)"没有找到 Loc.T(string),模组版本可能变过,中文未生效。");
return;
}
MethodInfo method2 = typeof(Plugin).GetMethod("PostfixT", BindingFlags.Static | BindingFlags.NonPublic);
_harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
MethodInfo method3 = typeof(Plugin).GetMethod("PostfixHelp", BindingFlags.Static | BindingFlags.NonPublic);
Type type2 = null;
Type type3 = null;
int num = 0;
Type[] array = SafeTypes(type.Assembly);
foreach (Type type4 in array)
{
if (type4 == null || type4.Namespace == null || !type4.Namespace.StartsWith("RankedPeak") || !type4.Name.StartsWith("HelpText"))
{
continue;
}
MethodInfo method4 = type4.GetMethod("Section", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(int) }, null);
if (!(method4 == null))
{
if (type2 == null)
{
type2 = type4;
}
if (type4.Name == "HelpText")
{
type3 = type4;
}
try
{
_harmony.Patch((MethodBase)method4, (HarmonyMethod)null, new HarmonyMethod(method3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
num++;
Log.LogInfo((object)("帮助正文已挂钩:" + type4.FullName + ".Section(int)"));
}
catch (Exception ex)
{
Log.LogWarning((object)("挂 " + type4.FullName + ".Section 失败:" + ex.Message));
}
}
}
if (type2 != null)
{
_head = BuildHelper(type2, "Head");
_muted = BuildHelper(type2, "Muted");
_keyHelper = BuildKeyHelper(type2);
_modList = BuildListHelper(type2, "AllowedModsList", "BlockedItemsList");
Log.LogInfo((object)("帮助窗口正文已接上中文(" + num + " 处,排版助手取自 " + type2.Name + ")。"));
}
else
{
Log.LogWarning((object)"没找到 HelpText*.Section(int),帮助窗口保持原样。");
}
if (type3 != null)
{
try
{
string text = (string)type3.GetMethod("Section", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(int) }, null).Invoke(null, new object[1] { 0 });
text = ((text == null) ? "(空)" : text.Replace("\n", " / "));
Log.LogInfo((object)("帮助自检 第 0 篇开头:" + ((text.Length > 30) ? text.Substring(0, 30) : text)));
}
catch (Exception ex2)
{
Log.LogWarning((object)("帮助自检失败:" + ex2.Message));
}
}
int num2 = 0;
array = SafeTypes(type.Assembly);
foreach (Type type5 in array)
{
if (type5 == null)
{
continue;
}
MethodInfo method5 = type5.GetMethod("RefreshContent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
if (method5 == null)
{
continue;
}
FieldInfo field = type5.GetField("_metaLabel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (!(field == null))
{
MethodInfo method6 = typeof(Plugin).GetMethod("PostfixHud", BindingFlags.Static | BindingFlags.NonPublic);
try
{
_harmony.Patch((MethodBase)method5, (HarmonyMethod)null, new HarmonyMethod(method6), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
_hudMetaField = field;
num2++;
Log.LogInfo((object)("计时条已挂钩:" + type5.FullName + ".RefreshContent"));
}
catch (Exception ex3)
{
Log.LogWarning((object)("挂 " + type5.FullName + ".RefreshContent 失败:" + ex3.Message));
}
}
}
if (num2 == 0)
{
Log.LogWarning((object)"没找到计时条 HUD,RTA 那行保持原样。");
}
_patched = true;
Log.LogInfo((object)("PEAK RANKED 中文补丁已挂上(Loc.T,可译条目 " + ZhMap.Table.Count + " 条)。"));
}
catch (Exception ex4)
{
Log.LogError((object)("挂补丁失败:" + ex4));
}
}
private static void PostfixT(string __0, ref string __result)
{
__result = ZhMap.Apply(__0, __result);
}
private static void PostfixHud(object __instance)
{
try
{
if (_hudMetaField == null || __instance == null)
{
return;
}
object value = _hudMetaField.GetValue(__instance);
if (value == null)
{
return;
}
if (_hudMetaText == null)
{
_hudMetaText = value.GetType().GetProperty("text");
if (_hudMetaText == null)
{
return;
}
}
string text = (string)_hudMetaText.GetValue(value, null);
if (!string.IsNullOrEmpty(text) && text.IndexOf("RTA · PEAK RANKED", StringComparison.Ordinal) >= 0)
{
_hudMetaText.SetValue(value, text.Replace("RTA · PEAK RANKED", "实时计时 · PEAK RANKED"), null);
}
}
catch
{
}
}
private static void PostfixHelp(int __0, ref string __result)
{
try
{
string text = ZhHelp.Render(__0, _head, _muted, KeyFor, _modList);
if (!string.IsNullOrEmpty(text))
{
__result = text;
}
}
catch (Exception ex)
{
Log.LogWarning((object)("帮助正文第 " + __0 + " 篇没换成中文:" + ex.Message));
}
}
private static Type[] SafeTypes(Assembly asm)
{
try
{
return asm.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
List<Type> list = new List<Type>();
if (ex.Types != null)
{
Type[] types = ex.Types;
foreach (Type type in types)
{
if (type != null)
{
list.Add(type);
}
}
}
return list.ToArray();
}
catch
{
return new Type[0];
}
}
private static Func<string, string> BuildHelper(Type help, string method)
{
MethodInfo m = help.GetMethod(method, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null);
if (m == null)
{
return null;
}
return (string text) => (string)m.Invoke(null, new object[1] { text });
}
private static Func<object, string> BuildKeyHelper(Type help)
{
MethodInfo m = help.GetMethod("Key", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (m == null)
{
return null;
}
return (object key) => (string)m.Invoke(null, new object[1] { key });
}
private static Func<int, string> BuildListHelper(Type help, string allowed, string blocked)
{
MethodInfo a = help.GetMethod(allowed, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
MethodInfo b = help.GetMethod(blocked, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
return delegate(int index)
{
MethodInfo methodInfo = ((index == 11) ? b : a);
return (!(methodInfo != null)) ? "" : ((string)methodInfo.Invoke(null, null));
};
}
private static string KeyFor(string name)
{
try
{
if (_keyHelper == null)
{
return name;
}
string name2 = name switch
{
"clock" => "ClockToggleKey",
"reset" => "RunResetKey",
"race" => "RaceMenuKey",
"settings" => "SettingsMenuKey",
_ => "OverlayToggleKey",
};
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
Type type = assemblies[i].GetType("RankedPeak.Plugin", throwOnError: false);
if (type == null)
{
continue;
}
FieldInfo field = type.GetField(name2, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
object obj = ((field != null) ? field.GetValue(null) : null);
if (obj == null)
{
PropertyInfo property = type.GetProperty(name2, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null)
{
obj = property.GetValue(null, null);
}
}
if (obj != null)
{
PropertyInfo property2 = obj.GetType().GetProperty("Value");
if (!(property2 == null))
{
return _keyHelper(property2.GetValue(obj, null));
}
}
}
}
catch
{
}
return name;
}
}
public sealed class WatermarkBehaviour : MonoBehaviour
{
private GUIStyle _style;
private int _builtFor = -1;
private void OnGUI()
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_0030: Expected O, but got Unknown
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_006e: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_009c: Unknown result type (might be due to invalid IL or missing references)
//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
try
{
if (_style == null || _builtFor != Screen.height)
{
Build();
}
Vector2 val = _style.CalcSize(new GUIContent("由 TFJSN 汉化"));
float num = 12f;
Color color = GUI.color;
GUI.color = new Color(0f, 0f, 0f, 0.42f);
GUI.DrawTexture(new Rect(12f - 4f, num - 2f, val.x + 8f, val.y + 4f), (Texture)(object)Texture2D.whiteTexture);
GUI.color = color;
GUI.Label(new Rect(12f, num, val.x, val.y), "由 TFJSN 汉化", _style);
}
catch
{
}
}
private void Build()
{
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_0103: Unknown result type (might be due to invalid IL or missing references)
//IL_0044: Expected O, but got Unknown
int num = Mathf.Clamp(Mathf.RoundToInt((float)Screen.height / 64f), 14, 26);
_style = (((Object)(object)GUI.skin != (Object)null) ? new GUIStyle(GUI.skin.label) : new GUIStyle());
try
{
Font val = Font.CreateDynamicFontFromOSFont(new string[9] { "Microsoft YaHei", "微软雅黑", "SimHei", "SimSun", "Noto Sans CJK SC", "Source Han Sans SC", "PingFang SC", "Microsoft JhengHei", "Arial Unicode MS" }, num);
if ((Object)(object)val != (Object)null)
{
_style.font = val;
}
}
catch
{
}
_style.fontSize = num;
_style.alignment = (TextAnchor)0;
_style.wordWrap = false;
_style.richText = false;
_style.normal.textColor = new Color(1f, 1f, 1f, 0.82f);
_builtFor = Screen.height;
}
}
internal static class ZhMap
{
internal static readonly Dictionary<string, string> Table = new Dictionary<string, string>(627)
{
{ "common.sections", "栏目" },
{ "common.you", "(你)" },
{ "common.anonymous", "匿名" },
{ "common.opponent", "对手" },
{ "common.host", "房主" },
{ "common.notAvailable", "不可用" },
{ "common.unknown", "未知" },
{ "common.notBound", "未绑定" },
{ "common.notBoundLower", "未绑定" },
{ "common.understood", "知道了" },
{ "common.date.format", "MM/dd HH:mm" },
{ "gate.mapAltered.title", "地图未通过校验" },
{ "gate.mapAltered", "在 PEAK RANKED 看到这张地图之前,已经有别的模组把它换掉了:今天的位置上应该是 {0},但游戏实际烘焙出来的是 {1}。\n\n排位成绩只有在所有人爬的是同一座山时才有意义,所以这一局不能按排位局开始。\n\n请把改动地图池的那个模组关掉再启动 PEAK——最常见的是 MorePeak:它会把其中一个位置换成一个随机抽到的关卡,所以大多数日子不受影响,今天偏偏被换了。或者走左边那根路牌,跑一局普通的、不计分的。" },
{ "mode.staticDaily", "每日杯" },
{ "mode.rankedDuel", "1V1 幻影决斗" },
{ "mode.speedrun", "速通" },
{ "mode.ghostTraining", "幻影训练" },
{ "mode.liveRace", "实时竞速" },
{ "mode.custom", "自定义跑图" },
{ "mode.vanilla", "原版 – 不计分" },
{ "mode.short.staticDaily", "每日杯" },
{ "mode.short.rankedDuel", "幻影决斗" },
{ "mode.short.speedrunSet", "速通(指定种子)" },
{ "mode.short.speedrunRandom", "速通(随机)" },
{ "mode.short.liveRace", "实时竞速" },
{ "sign.ranked", "排位" },
{ "sign.placement", "定级({0}/3)" },
{ "sign.settings", "设置" },
{ "sign.mode.staticDaily", "每日杯" },
{ "sign.mode.rankedDuel", "1V1 幻影决斗" },
{ "sign.mode.speedrun", "速通" },
{ "sign.mode.liveRace", "实时竞速" },
{ "menu.subtitle", "全球排行榜、最好成绩,以及你的生涯档案" },
{ "menu.box.profile", "你的生涯档案" },
{ "menu.box.view", "查看" },
{ "menu.box.table", "排行榜" },
{ "menu.box.myRuns", "我的记录" },
{ "menu.box.hallOfFame", "名人堂" },
{ "menu.btn.refresh", "刷新" },
{ "menu.tab.elo", "ELO 排名" },
{ "menu.tab.myRuns", "我的记录" },
{ "menu.myRuns.hint", "点一行把它钉成对比对象(再点一次取消)——点 REPLAY 可以回放。" },
{ "menu.myRuns.empty", "还没有记录。" },
{ "menu.myRuns.pinned", "对比中" },
{ "menu.tab.hallOfFame", "名人堂" },
{ "menu.period.today", "今天" },
{ "menu.period.cup", "杯赛成绩" },
{ "menu.version.only", "仅 {0}" },
{ "menu.version.all", "全部版本" },
{ "menu.route.whole", "全程" },
{ "menu.route.only", "仅 {0}" },
{ "menu.col.player", "玩家" },
{ "menu.col.tier", "攀登段位" },
{ "menu.col.duels", "决斗(胜/负)" },
{ "menu.col.time", "用时" },
{ "menu.col.map", "地图" },
{ "menu.col.seed", "种子" },
{ "menu.col.game", "游戏版本" },
{ "menu.col.date", "日期" },
{ "menu.col.route", "路线" },
{ "menu.col.mode", "模式" },
{ "menu.col.cups", "杯赛(冠/亚/季)" },
{ "menu.col.eloChange", "ELO" },
{ "menu.col.eloAfter", "新总分" },
{ "menu.card.loading", "正在读取生涯档案……" },
{ "menu.card.duels", "决斗:{0}(胜率 {1}%)" },
{ "menu.card.pb", "每日杯(最佳用时):{0}" },
{ "menu.card.placement", "定级({0}/3 局)" },
{ "menu.card.placementHint", "再跑 {0} 局就有第一个攀登段位" },
{ "menu.page", "第 {0} / {1} 页" },
{ "menu.btn.prev", "< 上一页" },
{ "menu.btn.next", "下一页 >" },
{ "menu.btn.trainTop", "对战榜首幻影" },
{ "menu.btn.trainVs", "对战 {0}" },
{ "menu.loading", "正在从服务器读取数据……" },
{ "menu.error.unreachable", "连不上服务器。" },
{ "menu.error.elo", "读取排名失败。" },
{ "menu.error.speedrunBoard", "读取速通榜失败。" },
{ "menu.error.leaderboard", "读取排行榜失败。" },
{ "menu.empty.elo", "还没有排位玩家。先跑完 3 局!" },
{ "menu.empty.speedrun", "还没有记录。" },
{ "menu.empty.speedrunVersion", "游戏版本 {0} 上还没有记录——切到「全部版本」能看到更早的。" },
{ "menu.empty.dailyToday", "今天还没人跑——来做第一个!" },
{ "menu.cup.empty", "还没有结算过杯赛。每天 17:00 UTC 结算。" },
{ "menu.cup.meta", "{0} 的每日杯 — {1} 名攀登者" },
{ "menu.cal.dow.1", "一" },
{ "menu.cal.dow.2", "二" },
{ "menu.cal.dow.3", "三" },
{ "menu.cal.dow.4", "四" },
{ "menu.cal.dow.5", "五" },
{ "menu.cal.dow.6", "六" },
{ "menu.cal.dow.7", "日" },
{ "menu.cal.mon.1", "1月" },
{ "menu.cal.mon.2", "2月" },
{ "menu.cal.mon.3", "3月" },
{ "menu.cal.mon.4", "4月" },
{ "menu.cal.mon.5", "5月" },
{ "menu.cal.mon.6", "6月" },
{ "menu.cal.mon.7", "7月" },
{ "menu.cal.mon.8", "8月" },
{ "menu.cal.mon.9", "9月" },
{ "menu.cal.mon.10", "10月" },
{ "menu.cal.mon.11", "11月" },
{ "menu.cal.mon.12", "12月" },
{ "menu.hof.champion", "冠军" },
{ "menu.hof.cupsOf", "共 {0} 次" },
{ "menu.hof.season", "第 {0} 赛季" },
{ "menu.hof.empty", "还没有赛季结束。当前赛季关闭后,最终排名会存档到这里。" },
{ "menu.hof.meta", "第 {0} 赛季最终排名 — {1} 名攀登者" },
{ "menu.error.cup", "读取杯赛成绩失败。" },
{ "menu.error.hof", "读取名人堂失败。" },
{ "menu.cell.wholeMountain", "全程" },
{ "menu.cell.segment", "第 {0} 段" },
{ "menu.meta.blocked", "{0} 条记录 · 其中 {1} 条已淡出:地图集不同或没有录像,不能作为幻影播放" },
{ "menu.meta.speedrun", "记录:{0} · 你的游戏:{1}(第 {2} 套,{3} 张图)" },
{ "menu.meta.total", "记录总数:{0}" },
{ "menu.btn.replay", "回放" },
{ "settings.subtitle", "设置——改动立刻生效" },
{ "settings.cat.keys", "按键" },
{ "settings.cat.timer", "计时与 HUD" },
{ "settings.cat.chat", "实时聊天" },
{ "settings.cat.ghost", "幻影与回放" },
{ "settings.cat.run", "对局与计分" },
{ "settings.cat.replay", "回放与训练" },
{ "settings.cat.diagnostics", "诊断" },
{ "settings.btn.help", "帮助" },
{ "settings.toggle.on", "开" },
{ "settings.toggle.off", "关" },
{ "settings.key.overlay.title", "计时器显示" },
{ "settings.key.overlay.desc", "显示或隐藏计时面板与竞速信息。不在对局中时计时器保持隐藏——按这个键可以提前叫出来。不管怎样时钟都在走。" },
{ "settings.key.window.title", "这个窗口" },
{ "settings.key.window.desc", "打开设置——在菜单里和山上都能用。" },
{ "settings.key.race.title", "竞速窗口" },
{ "settings.key.race.desc", "房间码、参加者,以及退出正在进行中的竞速。在「实时竞速」路牌那里创建。" },
{ "settings.key.clock.title", "时钟:自己 / 观看对象" },
{ "settings.key.clock.desc", "自己完赛后观看别人时,把大时钟在你已定格的成绩和所观看侦察员的实时用时之间切换。" },
{ "settings.key.reset.title", "放弃本局" },
{ "settings.key.reset.desc", "放弃当前尝试并把计时归零。不会重载地图——关卡原样保留。默认未绑定。" },
{ "settings.key.chatFocus.title", "聊天 – 输入" },
{ "settings.key.chatFocus.desc", "打开输入框并锁定键盘,直到回车(发送)或 Esc(放弃)。" },
{ "settings.key.chatToggle.title", "聊天 – 显示/隐藏" },
{ "settings.key.chatToggle.desc", "整体开关聊天窗口,消息一起隐藏。" },
{ "settings.key.hint", "读取按键时,Esc 取消、Del 清除绑定。" },
{ "settings.key.capture", "请按一个键……" },
{ "settings.race.active", "正在竞速——按这个键打开窗口。" },
{ "settings.race.inactive", "只在竞速中可用——当前没有进行中的竞速。" },
{ "settings.display.title", "显示方式" },
{ "settings.display.desc", "原生面板属于游戏画面的一部分,会跟着画面缩放。经典显示是面板出问题时的备用方案。" },
{ "settings.display.panel", "面板" },
{ "settings.display.classic", "经典" },
{ "settings.position.title", "显示位置" },
{ "settings.position.desc", "计时面板跟着鼠标,左键放下。" },
{ "settings.position.move", "移动" },
{ "settings.position.reset", "左上角" },
{ "settings.position.hint", "左键放下显示 · Esc 取消" },
{ "settings.summary.title", "跑完后的结算" },
{ "settings.summary.desc", "把成绩、分段、对比、上传状态和当日名次盖在游戏的侦察报告上。报告本身不动。" },
{ "settings.raceHud.title", "竞速信息显示" },
{ "settings.raceHud.desc", "竞速中的排名:名次、进度、体力条和每名参加者的物品。" },
{ "settings.raceHudPos.title", "竞速信息位置" },
{ "settings.raceHudPos.desc", "让显示跟着鼠标,点一下放下。八名参加者时会出现。" },
{ "settings.summaryState.title", "结算出现方式" },
{ "settings.summaryState.desc", "折叠后是画面底边的一条窄条,把报告让出来。窗口标题栏上的按钮也是设这个。" },
{ "settings.summaryState.open", "展开" },
{ "settings.summaryState.collapsed", "折叠" },
{ "settings.chat.title", "竞速实时聊天" },
{ "settings.chat.desc", "参加者之间的文字聊天,在机场和岛上、竞速进行时显示。" },
{ "settings.chat.hint", "聊天走的是竞速连接——连不上就没有可联系的人,所以会自动隐藏。" },
{ "settings.chatScope.title", "何时显示" },
{ "settings.chatScope.desc", "只在竞速时,或者一直显示(留给以后有竞速外的聊天室时用)。" },
{ "settings.chatScope.raceOnly", "只竞速时" },
{ "settings.chatScope.always", "一直显示" },
{ "settings.voiceReverb.title", "语音混响" },
{ "settings.voiceReverb.desc", "参加者的声音像游戏里那样带混响:洞穴里有回响,60 米外有回声。关掉:只剩方向和距离。" },
{ "settings.chatPos.title", "聊天位置" },
{ "settings.chatPos.desc", "让聊天框跟着鼠标,左键放下。" },
{ "settings.ghostPb.title", "最佳成绩幻影" },
{ "settings.ghostPb.desc", "在同一张地图、同一模式下,把你自己的最快一次跑在你旁边——半透明,不碰撞。" },
{ "settings.vanilla.title", "PEAK RANKED 之外的对局" },
{ "settings.vanilla.desc", "默认「不干预」时,不从我们的路牌开始的对局没有计时、没有结算卡、也不记录。那样的成绩永远不会计分。从下一局开始生效。" },
{ "settings.vanilla.off", "不干预" },
{ "settings.vanilla.on", "计时 + 结算卡" },
{ "settings.comparison.title", "对比对象" },
{ "settings.comparison.desc", "自动 = 你在这张地图、这条路线上的最好成绩。手动 = 你在「我的记录」里钉住的那一局。" },
{ "settings.comparison.auto", "自动" },
{ "settings.comparison.manual", "手动指定" },
{ "settings.ghostOpacity.title", "幻影不透明度" },
{ "settings.ghostOpacity.desc", "半透明人形有多清楚。立刻生效,对局中也一样。" },
{ "settings.ghostItemOpacity.title", "幻影物品不透明度" },
{ "settings.ghostItemOpacity.desc", "对手拿的、放的东西:手里的物品、气球、绳子、锁链、锚、岩钉、豆藤。" },
{ "settings.liveGhostCount.title", "显示的对手数量" },
{ "settings.liveGhostCount.none", "不显示" },
{ "settings.liveGhostCount.desc", "同时以人形出现多少名实时对手,由近到远。有没有人形由房主决定——房主关掉了对手,这里改什么都没用。" },
{ "settings.ghostItems.title", "幻影手里的物品" },
{ "settings.ghostItems.desc", "在幻影模型上显示那名跑者当时拿着什么。" },
{ "settings.ghostPanel.title", "对手面板" },
{ "settings.ghostPanel.desc", "一个小框,显示幻影的时钟、体力、状态数值和背包。" },
{ "settings.ghostPanelPos.title", "对手面板位置" },
{ "settings.ghostPanelPos.desc", "让面板跟着鼠标,点一下放下。" },
{ "settings.ghost.hint", "在排行榜里挑别人的幻影:点一行,然后点「对战这个幻影」。" },
{ "settings.key.trainingPause.title", "暂停幻影训练" },
{ "settings.key.trainingPause.desc", "冻结时间、你和整个世界——在时间轴上观察幻影。再按一次继续。" },
{ "settings.key.replayPlay.title", "播放 / 暂停" },
{ "settings.key.replayPlay.desc", "开始和停止回放。" },
{ "settings.key.replayBack.title", "后退" },
{ "settings.key.replayBack.desc", "按下面的步长往回跳。" },
{ "settings.key.replayForward.title", "快进" },
{ "settings.key.replayForward.desc", "按下面的步长往前跳。" },
{ "settings.key.replaySlower.title", "减速" },
{ "settings.key.replaySlower.desc", "0.25x / 0.5x / 1x / 2x / 4x。" },
{ "settings.key.replayFaster.title", "加速" },
{ "settings.key.replayFaster.desc", "0.25x / 0.5x / 1x / 2x / 4x。" },
{ "settings.key.replayDock.title", "跟随幻影" },
{ "settings.key.replayDock.desc", "镜头黏在幻影身上,或者自由飞行(WASD,空格/Ctrl 升降,Shift 加速)。" },
{ "settings.replayStep.title", "跳跃步长" },
{ "settings.replayStep.desc", "后退和快进一次跳多远。" },
{ "settings.replayKeepDnf.title", "保留未完成回放" },
{ "settings.replayKeepDnf.desc", "有多少局没跑完的(死了、退出)留在本机,供「我的记录」里回放。最旧的先删。" },
{ "settings.replay.hint", "按住鼠标右键可以环视。" },
{ "settings.logLevel.title", "日志详细程度" },
{ "settings.logLevel.desc", "模组往 BepInEx 日志里写多少。错误和警告始终显示;报问题时用「详细」有帮助,会稍微影响性能。" },
{ "settings.logLevel.error", "仅错误" },
{ "settings.logLevel.warning", "警告" },
{ "settings.logLevel.info", "普通" },
{ "settings.logLevel.debug", "详细" },
{ "settings.spawnLog.title", "生成日志" },
{ "settings.spawnLog.desc", "把每一次生成写进 BepInEx 日志。用来证明同种子两局拿到同样的物品。影响性能。" },
{ "settings.btn.logFolder", "打开日志文件夹" },
{ "settings.btn.upload", "立刻上传" },
{ "settings.info.modVersion", "模组版本" },
{ "settings.info.server", "服务器" },
{ "settings.info.gameVersion", "游戏版本" },
{ "settings.info.mapSet", "地图集" },
{ "settings.info.mapSetValue", "{0} 张图,id {1}" },
{ "settings.info.steam", "Steam 账号" },
{ "settings.info.steamPending", "还没就绪" },
{ "settings.info.rank", "段位" },
{ "settings.info.pendingUploads", "待上传" },
{ "settings.rules.hint", "时钟永远从你第一次操作开始,物品永远由本局的种子决定。这两样都不是设置项:排行榜只能比较在同样规则下跑出来的成绩。" },
{ "help.title", "帮助" },
{ "help.subtitle", "PEAK RANKED 怎么运作,各模式有什么区别" },
{ "help.cat.overview", "总览" },
{ "help.cat.dailyCup", "每日杯" },
{ "help.cat.ghostDuel", "1V1 幻影决斗" },
{ "help.cat.speedrun", "速通" },
{ "help.cat.liveRace", "实时竞速" },
{ "help.cat.timing", "计时" },
{ "help.cat.elo", "ELO 与段位" },
{ "help.cat.ghosts", "幻影与训练" },
{ "help.cat.leaderboards", "排行榜" },
{ "help.cat.fairness", "公平竞技" },
{ "help.cat.allowlist", "允许的模组" },
{ "help.cat.blockedItems", "禁用物品" },
{ "help.cat.builtWithAi", "用 AI 做的" },
{ "help.cat.trouble", "卡住了怎么办" },
{ "run.speedrun.subtitle", "一个人,对表" },
{ "run.race.subtitle", "和别人在同一路线上比" },
{ "run.mode.static", "指定" },
{ "run.mode.random", "随机" },
{ "run.mode.hint", "指定:种子和地图自己挑。随机:开跑之前两者都保密。" },
{ "run.box.mountain", "物品与天气种子 · 地图" },
{ "run.box.random", "随机山体" },
{ "run.box.ascent", "天阶等级" },
{ "run.box.route", "路线" },
{ "run.box.race", "竞速" },
{ "run.btn.daily", "每日" },
{ "run.btn.copySeed", "复制" },
{ "run.btn.rollSeed", "重掷" },
{ "run.msg.seedRolled", "新种子 {0}。" },
{ "run.btn.today", "今天" },
{ "run.seed.hint", "同种子 = 同样的物品,以及同一用时点的同样天气。Ctrl+V 可以粘贴种子。" },
{ "run.random.hint.race", "种子和地图只在创建竞速时掷出——连房主事前也不知道会出哪座山。" },
{ "run.random.hint.solo", "种子和地图只在开始那一刻掷出——你第一次见到这座山就是站在它面前的时候。" },
{ "run.route.limitHint", "PEAK 只提供前 {0} 段作为迷你跑。" },
{ "run.btn.autoStart", "自动开始" },
{ "run.btn.rivals", "显示对手" },
{ "run.hint.rivalsOn", "对手会随攀登出现在你的山里。" },
{ "run.hint.rivalsOff", "只共享排名——你的山里没有对手。" },
{ "run.btn.join", "加入" },
{ "run.btn.spectate", "观战" },
{ "run.btn.spectators", "观战者" },
{ "run.btn.voice", "语音聊天" },
{ "run.btn.create", "创建竞速" },
{ "run.btn.copyCode", "复制房间码" },
{ "run.btn.startRace", "开始竞速" },
{ "run.btn.leave", "离开" },
{ "run.btn.cancel", "取消" },
{ "run.btn.start", "开始攀登" },
{ "run.players", "{0} 名玩家" },
{ "run.msg.noDailySeed", "服务器没有可用的每日种子。" },
{ "run.msg.seedCopied", "已复制种子 {0}。" },
{ "run.msg.creating", "正在创建竞速……" },
{ "run.msg.created", "房间码 {0}——发出去,然后去机场。" },
{ "run.msg.createFailed", "创建失败:{0}" },
{ "run.msg.codeTooShort", "请输入那个六位房间码。" },
{ "run.msg.joining", "正在加入 {0}……" },
{ "run.msg.joined", "已加入({0}/{1})——去机场吧。" },
{ "run.msg.joinFailed", "加入失败:{0}" },
{ "run.msg.spectating", "正在以观战身份加入 {0}……" },
{ "run.msg.spectateOk", "你在观战。目前 {0} 名观战者。" },
{ "run.msg.spectateFailed", "观战失败:{0}" },
{ "run.msg.startFailed", "开始失败:{0}" },
{ "run.msg.left", "已离开竞速。" },
{ "run.msg.codeCopied", "已复制房间码 {0}。" },
{ "run.btn.back", "返回" },
{ "run.msg.configFailed", "无法更新竞速:{0}" },
{ "run.err.race_not_found", "没有这个房间码的竞速" },
{ "run.err.race_started", "竞速已经开始了" },
{ "run.err.version_mismatch", "游戏版本 {0}——你的是 {1}" },
{ "run.err.race_full", "竞速人数已满" },
{ "run.err.race_over", "竞速已经结束" },
{ "run.err.spectators_off", "房主关闭了观战" },
{ "run.err.too_many_spectators", "观战者已经太多" },
{ "run.err.already_racing", "你已经在里面参赛了——只能事后观战" },
{ "run.err.not_host", "只有房主能做这个" },
{ "run.err.below_participants", "场上的侦察员比这个数还多" },
{ "run.btn.autoEnd.off", "自动结束 · 关" },
{ "run.btn.autoEnd", "自动结束 · {0}" },
{ "run.btn.rules", "物品与危险" },
{ "run.btn.rules.custom", "物品与危险 · {0}" },
{ "run.summary.customRules", "自定义规则 – 不上榜" },
{ "run.level.slot", "位置 {0}" },
{ "run.level.noCatalog", "地图目录还没读到。" },
{ "run.level.map", "地图 {0}/{1}" },
{ "run.level.biomes", "群系 {0}" },
{ "run.level.version", "游戏版本 {0}(第 {1} 套)" },
{ "run.level.today", "今天的地图" },
{ "run.level.swapped", "被模组替换过(应为 {0})– 无法计入排位" },
{ "rules.title", "物品与危险" },
{ "rules.subtitle", "你的竞速,你定规则" },
{ "rules.cat.rules", "规则" },
{ "rules.cat.hazards", "危险" },
{ "rules.cat.items", "物品" },
{ "rules.cat.mystical", "神秘物品" },
{ "rules.btn.vanilla", "恢复原版" },
{ "rules.hint.editable", "这里改过任何东西,这局就变成私人局:成绩不上任何排行榜。服务器禁用的物品仍然禁用。" },
{ "rules.hint.readonly", "由房主设置——只有房主能改。" },
{ "rules.hint.vanilla", "全部都是原版——这局和别的局一样会登上榜单。" },
{ "rules.hint.custom", "有 {0} 处与原版不同——这局不会上榜。" },
{ "rules.hint.ascent", "显示的数值来自 {0};改天阶时,被覆盖过的项会保留自己的值。" },
{ "rules.group.other", "其他" },
{ "rules.unavailable", "游戏还没准备好对局设置。先随便跑一局再回来。" },
{ "rules.on", "开" },
{ "rules.section.race", "本局" },
{ "rules.section.game", "游戏规则" },
{ "rules.reviveDead", "复活死亡的玩家" },
{ "rules.hint.revive", "复活:死亡不会结束这局——等一会儿后你在自己的尸体旁站起来,代价是一层诅咒。带死亡的对局不上榜。" },
{ "rules.off", "关" },
{ "rules.cat.presets", "预设" },
{ "rules.btn.presets", "预设" },
{ "rules.presets.save", "保存" },
{ "rules.presets.load", "载入" },
{ "rules.presets.delete", "删除" },
{ "rules.presets.confirm", "确定?" },
{ "rules.presets.hint", "输入名字并保存当前规则。预设存在本机;同名会覆盖。" },
{ "rules.presets.readonly", "你可以保存房主的规则——只有房主能把预设载入这局。" },
{ "rules.presets.empty", "还没有保存过预设。" },
{ "rules.presets.entry", "有 {0} 处与原版不同 · 保存于 {1}" },
{ "rules.presets.saved", "已保存为「{0}」。" },
{ "rules.presets.replaced", "「{0}」已覆盖。" },
{ "rules.presets.loaded", "已载入「{0}」。" },
{ "rules.presets.deleted", "「{0}」已删除。" },
{ "rules.presets.needName", "先输入一个名字。" },
{ "rules.presets.nothing", "没有可保存的——全都是原版。" },
{ "rules.presets.failed", "预设文件写入失败——看日志。" },
{ "race.role.box", "当房主还是加入?" },
{ "race.role.host", "开一局竞速" },
{ "race.role.join", "加入竞速" },
{ "race.role.hint", "房主挑种子、地图、天阶和路线,然后拿到一个房间码。其他人只需要这个码——设置会一起带过去。" },
{ "race.join.hint", "输入房主的房间码——进去之后就能看到这局的设置。" },
{ "config.ascent.tenderfoot", "新手" },
{ "config.ascent.peak", "PEAK" },
{ "config.ascent.numbered", "天阶 {0}" },
{ "config.ascent.desc.tenderfoot", "攀爬消耗的体力更少。饥饿涨得更慢。没有时间限制。" },
{ "config.ascent.desc.0", "岛上标准的难度。" },
{ "config.ascent.desc.1", "坠落伤害更高。" },
{ "config.ascent.desc.2", "饥饿涨得更快。" },
{ "config.ascent.desc.3", "物品更重。" },
{ "config.ascent.desc.4", "峰顶上没有信号弹。" },
{ "config.ascent.desc.5", "夜晚寒冷。" },
{ "config.ascent.desc.6", "攀爬消耗的体力更多。" },
{ "config.ascent.desc.more", "开局就带诅咒。每复活一次,诅咒加深一层。" },
{ "config.ascent.desc.8", "必须解放侦察长领队的灵魂才能胜利——路线越过熔炉,一直通向深渊。" },
{ "config.route.whole", "全程" },
{ "config.route.biome", "群系 {0}" },
{ "config.route.biomeTest", "群系 {0}(测试)" },
{ "config.route.desc.whole", "从海岸到峰顶——完整路线。" },
{ "config.route.desc.experimental", "实验性:PEAK 不提供第 5 段(城塞)作为迷你跑。结果不确定——开始后日志会显示该值是否被接受。" },
{ "config.route.desc.mini", "迷你跑:只跑这座山的第 {0} 段。那一段是哪个生物群系由种子决定。" },
{ "config.route.desc.miniKnown", "迷你跑:只跑这座山的第 {0} 段——在这张地图上是{1}。" },
{ "summary.title", "结算" },
{ "summary.subtitle", "折叠起来把侦察报告让出来" },
{ "summary.btn.collapse", "折叠" },
{ "summary.btn.expand", "展开" },
{ "summary.box.result", "成绩" },
{ "summary.box.run", "对局" },
{ "summary.box.splits", "分段" },
{ "summary.box.race", "竞速" },
{ "summary.col.segment", "分段" },
{ "summary.col.total", "合计" },
{ "summary.col.time", "用时" },
{ "summary.col.player", "玩家" },
{ "summary.info.map", "地图:{0} {1}" },
{ "summary.info.ascent", "天阶:{0} 游戏:{1}" },
{ "summary.info.route", "路线:{0}" },
{ "summary.info.seed", "种子:{0}" },
{ "summary.info.mode", "模式:{0}" },
{ "summary.info.race", "竞速:{0}({1} 名参加者)" },
{ "summary.info.opponent", "对手:{0}({1} ELO)" },
{ "summary.info.ghost", "幻影:{0}" },
{ "summary.cmp.opponent", "对手" },
{ "summary.cmp.ghost", "幻影" },
{ "summary.cmp.winner", "胜者 {0}" },
{ "summary.cmp.pb", "最好成绩 {0}" },
{ "summary.newPb", "新最好成绩" },
{ "summary.pbDelta", "较最佳:{0}" },
{ "summary.duel.won", "胜" },
{ "summary.duel.lost", "负" },
{ "summary.duel.result", "决斗 {0}:{1} -> {2} ELO({3})" },
{ "summary.upload.sending", "上传:发送中……" },
{ "summary.upload.practice", "练习局 – 不进入排行榜" },
{ "summary.upload.vanilla", "原版局 – 不在排位范围内" },
{ "summary.upload.pb", "上传:成功(个人最佳!)" },
{ "summary.upload.ok", "上传:成功" },
{ "summary.upload.duplicate", "上传:成功(已记录过)" },
{ "summary.upload.review", "上传:审核中 – 通过后计入" },
{ "summary.upload.flagged", "上传:被标记(反作弊)– 不上榜" },
{ "summary.upload.rejected", "上传:被服务器拒绝" },
{ "summary.end.open", "还有 {0} 人在爬" },
{ "summary.end.done", "所有人都跑完了。" },
{ "summary.end.raceOver", "竞速结束——排名已定。" },
{ "verdict.died", "死亡" },
{ "verdict.aborted", "放弃" },
{ "verdict.won", "获胜" },
{ "verdict.lost", "落败" },
{ "verdict.finished", "完成" },
{ "verdict.place2", "第 2 名" },
{ "verdict.place3", "第 3 名" },
{ "verdict.placeN", "第 {0} 名" },
{ "verdict.raceWon", "赢下竞速" },
{ "verdict.done", "登顶" },
{ "rank.loading", "每日榜:读取中……" },
{ "rank.unreachable", "每日榜:连不上" },
{ "rank.position", "每日榜:第 {0} 名 / 共 {1}" },
{ "rank.none", "每日榜:–" },
{ "state.finished", "已完成" },
{ "state.dnf", "未完赛" },
{ "state.ready", "已就绪" },
{ "state.loading", "载入中……" },
{ "state.down", "倒地" },
{ "state.deaths", "复活 {0} 次" },
{ "state.deathsShort", "{0} 次" },
{ "state.running", "攀登中" },
{ "state.joined", "在大厅" },
{ "state.disconnected", "连接断开" },
{ "state.dnfProgress", "在 {0} % 处未完赛" },
{ "state.hiddenProgress", "?? %" },
{ "board.status.lobby", "等待中 {0}/{1}" },
{ "board.status.loading", "所有人都在载入" },
{ "board.status.countdown", "{0} 后开始" },
{ "board.status.running", "竞速进行中" },
{ "board.status.finished", "竞速结束" },
{ "board.status.final", "最终排名" },
{ "board.status.ownDnf", "你放弃了——竞速继续" },
{ "board.status.ownDone", "你已完成——竞速继续" },
{ "board.status.endsIn", "竞速将在 {0} 后结束" },
{ "board.col.rank", "#" },
{ "board.col.name", "侦察员" },
{ "board.col.progress", "进度" },
{ "board.col.segments", "篝火" },
{ "board.col.state", "用时" },
{ "board.info.seed", "种子 {0}" },
{ "board.info.map", "地图 {0}" },
{ "board.info.secret", "保密" },
{ "board.info.rivalsOn", "对手可见" },
{ "board.info.rivalsOff", "对手隐藏" },
{ "board.info.spectators", "{0} 人观战" },
{ "board.info.autoEnd", "第一人完赛后 {0} 结束" },
{ "board.info.voice", "语音聊天" },
{ "board.info.revive", "复活已开" },
{ "board.info.customRules", "自定义规则 · 不上榜" },
{ "board.btn.settings", "设置" },
{ "board.btn.forfeit", "放弃" },
{ "board.btn.forfeitConfirm", "真要放弃?" },
{ "board.msg.forfeited", "你放弃了——这局对你记为未完赛。" },
{ "hud.revive.countdown", "{0} 后复活" },
{ "hud.revive.detail", "第 {0} 次死亡 · 还剩 {1} 次复活 · 诅咒 +{2}" },
{ "hud.revive.last", "最后一次复活——再死一次这局就结束了" },
{ "hud.revive.done", "已复活" },
{ "hud.revive.giveUp", "按住互动放弃并观战" },
{ "hud.revive.givingUp", "正在放弃…… {0} %" },
{ "hud.ghostOffset", "幻影偏移 {0} · 暂停后转为实时" },
{ "hud.compare", "对比(最佳)" },
{ "hud.compare.pinned", "对比(指定)" },
{ "hud.sob", "理论最快用时" },
{ "hud.watching", "观战中 · {0}" },
{ "hud.watching.best", "最佳" },
{ "hud.watching.place", "名次" },
{ "hud.watching.progress", "进度" },
{ "hud.replay.of", "回放 · {0}" },
{ "hud.replay.final", "最终用时" },
{ "hud.training.ghost", "已暂停 · 幻影 {0}" },
{ "hud.clock.own", "你的用时 · 已定格" },
{ "hud.clock.live", "实时" },
{ "hud.clock.switchLive", "{0} = {1} 的实时用时" },
{ "hud.clock.switchOwn", "{0} = 你的用时" },
{ "hud.ready", "就绪——第一次操作开始计时" },
{ "hud.preview.segment", "第 {0} 段" },
{ "hud.ghost", "幻影" },
{ "hud.vs", "对" },
{ "hud.finish", "终点 {0}" },
{ "hud.newPb", "新最好成绩" },
{ "hud.died", "已死亡——本局无效" },
{ "hud.aborted", "本局已放弃" },
{ "hud.meta.seed", "种子 {0}" },
{ "hud.meta.ascent", "天阶 {0}" },
{ "hud.vanilla.note", "仅计时 – 无种子、无幻影、不上传" },
{ "overlay.mode", "模式:{0}" },
{ "overlay.seed", "种子:{0}" },
{ "overlay.map", "地图:{0} 天阶:{1}" },
{ "overlay.waitingInput", "等待第一次操作……" },
{ "overlay.pb", "最佳:{0}" },
{ "overlay.splits", "--- 分段 ---" },
{ "overlay.completed", "跑完了" },
{ "overlay.newPb", "新的个人最佳!" },
{ "overlay.pbDelta", "较最佳:{0}" },
{ "overlay.died", "已死亡 - 本局无效" },
{ "overlay.aborted", "本局已放弃" },
{ "overlay.result", "成绩:{0}" },
{ "race.hud.aboutToStart", "即将开始" },
{ "race.hud.waiting", "等待玩家" },
{ "race.hud.go", "出发!" },
{ "race.hud.finished", "竞速结束" },
{ "race.hud.endsIn", "{0} 后结束" },
{ "race.hud.previewName", "参赛者" },
{ "race.hud.hintHost", "在柜台用登机牌 → 开始 · {0} = 菜单" },
{ "race.hud.hintGuest", "等待房主 · {0} = 菜单" },
{ "race.countdown.caption", "竞速将在" },
{ "race.countdown.go", "出发!" },
{ "race.countdown.endCaption", "竞速剩余" },
{ "race.brief.revive", "复活已开——死亡不会结束这局({0} 次复活,之后记未完赛)" },
{ "race.brief.rules", "自定义规则——这局不上榜" },
{ "race.pass.participants", "{0}/{1} 名参加者" },
{ "race.pass.hostHint", "开始会让所有人一起开跑。" },
{ "race.pass.guestHint", "等待房主。" },
{ "race.loadFailed.title", "已离开竞速" },
{ "race.loadFailed", "竞速世界没能及时载入。为了不让别人等,你已被移出这局。" },
{ "chat.hint", "回车发送 · Esc 关闭" },
{ "chat.idleHint", "{0} · 聊天" },
{ "chat.notConnected", "未连接——消息没有发出去。" },
{ "chat.sys.join", "{0} 加入了。" },
{ "chat.sys.leave", "{0} 离开了竞速。" },
{ "duel.err.noSteam", "读不到 Steam ID。请先启动 Steam。" },
{ "duel.err.noOpponent", "没有找到决斗对手({0})。" },
{ "duel.err.matchmaking", "匹配处理出错。" },
{ "duel.err.noGhostData", "{0} 的幻影没有录像数据。请换一个对手。" },
{ "ghostPanel.title", "对手" },
{ "ghostPanel.nameSuffix", "{0}(幻影)" },
{ "ghostPanel.remaining", "还剩 {0}" },
{ "status.injury", "受伤" },
{ "status.hunger", "饥饿" },
{ "status.cold", "寒冷" },
{ "status.poison", "中毒" },
{ "status.crab", "螃蟹" },
{ "status.curse", "诅咒" },
{ "status.drowsy", "困倦" },
{ "status.weight", "负重" },
{ "status.hot", "炎热" },
{ "status.thorns", "荆棘" },
{ "status.spores", "孢子" },
{ "status.web", "蛛网" },
{ "status.arrow", "箭伤" },
{ "status.petrify", "石化" },
{ "status.flytrap", "捕蝇草" },
{ "season.sign.title", "第 {0} 赛季结束" },
{ "season.sign.unknown", "赛季" },
{ "spectate.hint.offer", "{0} 以幻影身份观看其他人" },
{ "spectate.btn.toMenu", "回到主菜单" },
{ "spectate.btn.watch", "观看" },
{ "spectate.splits.best", "最佳" },
{ "spectate.blocked.rivalsOff", "无法观看——房主关闭了对手。" },
{ "spectate.blocked.offline", "无法观看——连不上这局竞速。" },
{ "spectate.board.title", "观战中 选一名侦察员" },
{ "spectate.board.titleN", "观战中 还有 {0} 人在爬" },
{ "spectate.hint.armed", "关掉报告——然后你就留在岛上。" },
{ "spectate.hint.free", "WASD 飞行 · Shift 加速 · 空格/Ctrl 升降 · Tab 跳到下一个 · F 附着 · Esc 排名" },
{ "spectate.hint.jumped", "<size=140%>{0}</size>" },
{ "spectate.hint.attached", "附着在 {0} 上 · F 松开 · Tab 下一个 · Esc 排名" },
{ "voice.mixer.title", "语音" },
{ "voice.mixer.mute", "静音" },
{ "voice.mixer.mutedShort", "已静音" },
{ "voice.mixer.hint", "这些音量只有你自己听得到。" },
{ "voice.mixer.alone", "竞速里还没有别人。" },
{ "voice.mixer.offline", "未连接——现在谁都听不到。" },
{ "voice.mixer.spectator", "{0}(观战中)" },
{ "replay.title", "回放 · {0}" },
{ "replay.you", "你" },
{ "replay.btn.play", "播放" },
{ "replay.btn.pause", "暂停" },
{ "replay.btn.dock", "跟随中" },
{ "replay.btn.free", "自由镜头" },
{ "replay.btn.exit", "退出" },
{ "replay.btn.resume", "继续" },
{ "replay.btn.live", "实时" },
{ "replay.btn.linked", "已联动" },
{ "replay.btn.unlinked", "已分离" },
{ "replay.label.ghost", "幻影 {0}" },
{ "replay.label.you", "你 {0}" },
{ "replay.btn.restartAt", "从 {0} 重开" },
{ "replay.btn.restartLate", "在暂停之后" },
{ "replay.btn.restartEarly", "在最近篝火之前" },
{ "replay.btn.restartNone", "这里没有存档点" },
{ "replay.titlePaused", "训练已暂停 · {0} · {1} 继续" },
{ "replay.hint", "{0} 播放/暂停 · {1}/{2} 速度 · {3} 跟随 · 鼠标右键:环视 · WASD、空格/Ctrl:飞行" },
{ "replay.err.noData", "这一局在本机没有录像。" },
{ "common.seconds", "{0} 秒" },
{ "common.minutes", "{0} 分" },
{ "run.summary.seed", "种子" },
{ "summary.revived", "复活 {0} 次 – 不上榜" }
};
internal static string Apply(string key, string current)
{
if (key == null)
{
return current;
}
if (!Table.TryGetValue(key, out var value))
{
return current;
}
return value;
}
}
internal static class ZhHelp
{
internal static readonly string[] Sections = new string[14]
{
"PEAK RANKED 把这座山变成了一条赛道。模组按速通规则给你计时,保证所有人爬的是同一座山、拿的是同一批物品,再把成绩送上全球排行榜。\n\n«右侧路牌上的四个模式»\n• «每日杯» – 主模式。每天一座山,全世界都一样。计入你的段位。\n• «1V1 幻影决斗» – 和别人录下来的那一局正面对决。计入你的段位。\n• «速通» – 自由练习:种子、地图、路线都自己挑。不计段位。\n• «实时竞速» – 几个人同时跑同一条路线。不计段位。\n\n最上面那块路牌显示你的段位,点开就是排行榜。最下面那块打开这个窗口。\n\n«你当前的按键»\n┈计时器显示 {K:overlay}\n┈设置 {K:settings}\n┈竞速窗口 {K:race} (仅竞速进行中)\n┈放弃本局 {K:reset}", "每天一座山——同一张地图、同一个种子、同一批物品,全世界都一样。正是这一点,才让每日榜有可比性。\n\n«怎么开始»\n点«每日杯»路牌。模组从服务器取来当天的种子,直接把你送进关卡——跳过机场。\n\n«什么计入成绩»\n• 计分的是你登顶的用时。\n• 这局会计入你的 ELO 段位。\n• 一天到 17:00 UTC 结束,之后岛就换了。主菜单右上角有换岛的倒计时。\n• 跑完之后在结算里能看到你的名次,排行榜的每日杯下面也随时查得到。\n\n┈如果你死了或者中途退出关卡,这次尝试算作放弃——没有成绩。", "和一个不必在线的真人对手比赛:你跑的是他录下来的那一局。\n\n«怎么开始»\n点«1V1 幻影决斗»路牌。模组会找一个 ELO 相近的对手,载入他的录像,把你俩放在同一种子上开跑。跑的过程中你能看见他就在旁边,是一个半透明的人形。\n\n«什么计入成绩»\n• 先登顶的人赢。\n• 跑完立刻结算 ELO。\n• 要是没找到合适的对手,照样开跑——只是没有幻影。\n\n┈幻影碰不到你,也捡不了东西。它是个显示,不是玩家。", "练习模式——同时也是你想往 speedrun.com 提交成绩时用的那个模式。\n\n«怎么开始»\n点«速通»路牌。窗口里可以设:\n• «指定 / 随机» – 自己挑种子和地图,或者开跑那一刻现掷。\n• «种子» – 同一个数字给出同一座山,物品也一样。\n• «地图» – 轮换池里的某一座岛。\n• «天阶等级» – 游戏难度。\n• «路线» – 整座山,或者只跑某一段来练。\n\n«什么计入成绩»\n• 不计 ELO——这个模式不影响你的段位。\n• 成绩仍然会上速通榜,按地图、路线、游戏版本分开列。\n\n┈单独一段自成分类,永远不会和整趟攀登混在同一张榜里。", "几个人、同一条路线、同一秒出发。\n\n«怎么开始»\n• 点«实时竞速»路牌,把路线设好,按«创建»。\n• 你会拿到一个房间码。想参加的人在同一个窗口里输入它。\n• 人到齐后倒计时开始——自动开始,或者由你作为创建者下令开始。\n\n«公平的起跑»\n倒计时结束前,所有人的操作都是锁住的。没有这把锁,决定胜负的就成了加载速度:谁先读完谁就能先站好位。\n\n«竞速途中»\n所有参加者的名次在右上角。按 {K:race} 可以把竞速窗口叫回来——房间码、参加者名单,以及退出。\n┈这个键只在竞速进行中有效。每日杯或速通里,那个窗口没什么可做的。\n\n«房主选项»\n• «自动结束»:第一个人登顶之后开始的等待时间。时间一到,还留在山上的所有人一起结束——记为未完赛。\n• «物品与危险»:给整场竞速开关物品和危险,就像游戏里的自定义冒险。改过任何东西,这场就变成私人局:成绩不上任何排行榜。服务器禁用的物品依旧禁用。\n• «复活死亡的玩家»(在«物品与危险»里,最上面):死亡不会结束这局。你会在地上躺 10、20、然后 30 秒,然后在自己尸体处站起来——如果是从悬崖或岩浆里摔下去,则在最后一个安全点——物品都在,每次死亡 +0.10 诅咒,状态值上限被压住。第五次死亡才结束这局。竞速仍然公开,但带死亡的那一局不上榜。你躺着的时候别人看得见,长按互动键就是放弃:记未完赛,转入观战。存档旗和游戏里一样管用;变成骷髅复活回来也还是骷髅。\n\n«跑完之后»\n没跑完的人按进度排在完赛者后面。观战某个人时,{K:clock} 可以在你自己已定格的成绩和他的实时用时之间切换大时钟;每个幻影都带着自己的名字。\n\n┈不计 ELO——实时竞速是为了好玩,也是为了练手。", "«计时什么时候开始»\n你第一次操作的时候——也就是你能真正操控角色的那一刻。这也是 speedrun.com 计时的规则。游戏自己的起点大约早四秒,在飞入动画中间;从那里算,每个人的成绩都会莫名地长。\n┈不是设置项——所有人同一个规则,因为排行榜只能比较用同一种量法量出来的成绩。\n\n«什么时候停»\n登顶时。死了就是死亡收场,离开关卡算放弃。两种都没有计分成绩。\n\n«它显示在哪»\n计时面板跟着本局出现、跟着本局消失——在主菜单和机场,它只会显示你上一次尝试的状态。按 {K:overlay} 可以随时提前叫出来,也可以在跑的过程中藏起来。\n\n«PEAK RANKED 之外的对局»\n用平时的方式开游戏——离线游玩、开房、进朋友的房间——模组不插手:不计时、不弹结算卡、什么也不记录。原版就是原版。\n你要是还是想要那个时钟,可以在«设置 – 对局与计分»里,把«PEAK RANKED 之外的对局»那一行打开。这种成绩只留在你本机——它上不了榜,因为种子、地图和天阶不是我们设的,我们没法为它担保。\n\n«分段»\n每到一个篝火都会记一次分段。计时面板把它们和对比局并排放,<color=#1B8449>绿色</color>是更快,<color=#BA322D>红色</color>是更慢。\n\n«对比的是什么»\n对比对象永远是你在«同一张地图、同一条路线»上的某一次成绩。别的山的时间没有意义:PEAK 每一座山都由不同的一组生物群系拼成。\n那个分类里还没有跑完的记录时,会显示一条短横线而不是时间——宁可什么都不给,也不给一个毫无意义的数字。\n\n默认是你在这个分类里的最好成绩。你也可以改成钉住某一局:打开排行榜,进«我的记录»,点一行。计时面板上就会写«对比(指定)»而不是«对比(最佳)»,不会被误认成你的最好成绩。\n┈在«设置 – 对局与计分»里,那一行叫「对比对象」。\n\n«放弃一局»\n{K:reset} 键把计时归零,等下一次尝试。它«不会»重载地图——山还是你离开时的样子。", "ELO 是衡量你有多强的一个数字。赢下强的对手就涨,输给弱的对手就掉。\n\n«六个攀登段位»\n• 侦察员 ┈最高 799\n• 徒步者 ┈800 – 1,199\n• 攀登者 ┈1,200 – 1,599\n• 登山家 ┈1,600 – 1,999\n• 夏尔巴 ┈2,000 – 2,399\n• 巅峰传奇 ┈2,400 起\n每个段位还有三个小级(I、II、III)。\n\n«定级»\n前三次计分对局是定级局。还没跑完之前,段位牌上写的是「定级」而不是段位。\n\n«什么会影响段位»\n• «每日杯» – 一天一次,ELO 给得多。\n• «幻影决斗» – 每场给得少,但随时能打。\n• «速通和实时竞速» – 一点都不给。", "幻影是一局攀登的录像:同一条路线、同一道走法,和你的计时同步播放。\n\n«你自己的最好成绩幻影»\n只要你在同一模式再跑同一张地图,它就自动陪你跑。它来自你本机的录像,不从服务器来。\n\n«拿别人的幻影练»\n在排行榜里点一行,然后按«对战这个幻影»。模组会接管那一局的种子和地图,让你对着它开跑。\n┈淡出的行点不了:那一局来自别的地图集,它的走法在当前游戏版本里会穿进石头里。\n\n«幻影会显示什么(幻影 V2)»\n幻影穿着那位玩家自己的服装和徽章,动作是真实的身体——手抓着岩壁、坠落、做表情。它用两只手拿着当时拿的东西,位置就在他拿的地方;他放置的东西——绳子、锁链、锚、岩钉、豆藤、气球——会以带名字的淡色全息形式出现。1.3 之前的录像还是按老样子播放。\n\n«回放»\n在排行榜或«我的记录»里选一行,按«回放»。你会落在那一座山上,自己不带任何一局:底下一条时间轴(P 播放/暂停,方向键跳跃和改速度,F 跟随幻影),自由镜头(WASD、空格/Ctrl、鼠标右键环视),左上角是他的计时,右上角是他的背包。你没跑完的局(死了、退出)也留着,标为未完赛——默认留最近 10 局,见「设置 – 回放与训练」。\n\n«训练里暂停和倒回»\n在幻影训练里,«P» 会冻住你、你的时钟和整个世界;幻影可以在时间轴上拖动。下面那条紫色滑条是你:把它拖回去,再按«从……重开»,就会带着当时的体力、状态效果和整个背包回到那个位置——最远回到你上一个篝火。两条滑条解除联动可以把幻影挪到你旁边;«实时»把它放回你的时钟上。\n\n«同种子,同天气»\n下雨、暴雪、大风、龙卷风,以及昼夜时刻,都跟着种子和你的用时走。同一个种子上的人,会在同一秒遇到同一场暴风雪。\n\n┈显示人数和不透明度在「设置 – 幻影与回放」里。同一页还能设同时以人形出现多少名实时对手——不过有没有人形,由那场竞速的房主决定。", "排行榜从右边架子最上面那块路牌打开。里面有三种视图。\n\n• «ELO 排名» – 谁是最强的攀登者。\n• «每日杯» – 今天这座山上的用时,也可以看历史全部。\n• «速通» – 所有跑得快的局,也就是 speedrun.com 的视角。\n\n«为什么分开分类»\n只有路线一样,时间才有意义。所以速通视图按这些分开:\n• «地图» – 每一座岛都是不同的山。\n• «路线» – 整座山,或者单独一段。一段只花掉一小部分时间;混在一起,这张榜就不再是排名了。\n• «游戏版本» – 一次更新可能改动这座山。默认只显示你自己的版本;需要时可以把全部版本混在一起。\n\n┈分段是按生物群系的名字筛的,不是按编号:第 2 段在一座岛上是雨林,在下一座岛上就是森林。", "排行榜的价值,恰好等于大家愿意给它的信任。所以这里把话说明白:计分对局里模组做了什么,以及没做什么。\n\n«会记录什么»\n计分对局进行中,模组每秒两次记录你上山走的路径、你身上带的东西,以及体力和状态条的走势。这也就是你幻影的素材来源。除此之外,它还会记下一些游戏状态信号用于公平性检查:有没有数值以游戏本身不可能产生的方式变动。\n\n«不会记录什么»\n• 游戏进程之外的一切都不碰。不读文件、不截图、不列你运行的程序、不记录按键。\n• 原版对局里什么都不做。从左边那块路牌开始,模组只给你计时。\n\n«哪些模组可以运行»\n在计分对局里——凡是从«右侧»路牌开始的——只有白名单上的模组保持启用。其他模组在对局期间一律关闭,你回到主菜单的那一刻就恢复。这样榜上每个人都是在同样条件下爬的,也没人需要事后证明自己的模组无害。«允许的模组»那一章列出了当前名单。\n\n计分对局之外什么都不动:从左边路牌开始,你的配置怎么装的就怎么跑。\n\n«哪些物品可以刷出来»\n有少数几件物品是让你跳过一段路,而不是爬过去。凡是从右侧路牌开始的对局,这些东西都会从掉落里拿掉——不是从你游戏里删掉,只是不会抽到。是哪些、为什么,在«禁用物品»那一章;名单来自服务器,不用更新模组就能变。\n\n«你的模组不在名单上?» 说一声——名单是手工维护的,外观类和便利类的模组本来就该在里面。\n┈1. 进 Discord: discord.gg/nKUHvzdmVp\n┈2. 在 PEAK RANKED 帖子里留言(进服之后帖子才会对你可见):\n┈ discord.com/channels/1363179626435707082/1539751504746127451\n\n«惹眼的成绩会怎么样»\n它从来不会被直接判掉——而是被单独留出来,由人来看。误杀一次诚实成绩,比晚一次审核才抓到作弊更糟。凡是阈值靠标定、而非绝对的地方,结算卡上会写┈审核中┈,让你知道你的成绩在检查中,而不是丢了。",
"在计分对局里——凡是从«右侧»路牌开始的——只有这份名单上的模组保持启用。其他模组一律关闭,直到你回到主菜单。理由写在«公平竞技»那一章。\n\n«当前名单»\n名单在服务器上手工维护,你坐在主菜单里时会自动刷新,最多每 30 分钟一次。\n\n{LIST}\n\n┈名字前面有标记,说明你装了那个模组;灰字是它的标识符。有些条目是 Harmony ID,对应不上任何插件名。少了你的模组?«公平竞技»里写了怎么加。", "有少数几件物品是越过一段路,而不是爬过那段路——它们直接把你带过本来该靠本事挣来的一段。这些物品会从 PEAK RANKED 开始的对局掉落里拿掉,好让一个快成绩说明的是攀登,而不是正好有东西躺在箱子里。\n\n«在哪些情况下生效»\n凡是从«右侧»路牌开始的对局——每日杯、幻影决斗、速通、实时竞速都一样。从«左侧»路牌开始就什么都不动:你的游戏刷的还是它一直刷的那些。\n\n«怎么实现的»\n被禁的物品在填充山体之前就从掉落表里移除了。它不是事后删除,跑的过程中也不会有东西从你手里消失——那个位置只是放着别的东西。关卡固定放在某处的物品,而不是从掉落表里抽的,不受影响。\n\n«当前禁用»\n名单在服务器上,你坐在主菜单里时会自动刷新,最多每 30 分钟一次——所以某件物品被禁或者被放开,都不需要你装任何东西。\n\n{LIST}\n\n┈灰字是物品的内部名——禁用的依据就是它。想讨论某一条,引用它。\n\n«觉得某件物品被禁得不对?» 说一声——名单是手工维护的,而且本来就该很短。\n┈1. 进 Discord: discord.gg/nKUHvzdmVp\n┈2. 在 PEAK RANKED 帖子里留言(进服之后帖子才会对你可见):\n┈ discord.com/channels/1363179626435707082/1539751504746127451", "这件事我选择摊开说,因为这是个合理的疑问,也因为这里没有任何东西是藏着的。\n\n«代码是谁写的»\n这个模组的代码是 AI 写的。每一处改动都经过人阅读、在游戏里测试,然后决定留下还是扔掉——但动手打字的是 AI。\n\n«模组从哪儿来»\n模式、规则、修正和分类都不是 AI 的主意。它们来自开发者,也来自社区:大家在 Discord 里提的要求、报的 bug,争论不休的路线和榜上分段。AI 把这些变成能跑的代码——它不决定这个模组该是什么样。\n\n┈如果这让你不舒服:这里没有任何东西是藏着的,也没有人逼你装这个模组。", "«我的成绩不在榜上»\n服务器连不上时,成绩会先存在本机、之后再补交。在「设置 – 诊断」里能看到还有多少条待上传;也可以在那里手动触发上传。\n\n«幻影不见了»\n它需要你自己在同一张地图、同一模式下有一次跑完的记录。死亡或放弃之后是没有东西可录的。\n\n«队友刷到的物品不一样»\n那说明你们游戏版本不同,或者有人是从左边那块路牌开始的——那边模组不干预物品分配。\n\n«显示不见了»\n在主菜单和机场,计时器是关着的——它跟着本局出现。其他情况下按 {K:overlay} 可以把它打开。如果它滑出屏幕了,「设置 – 计时与 HUD,左上角」能帮上忙。\n\n«确实坏掉了»\n在「设置 – 诊断」里,版本、服务器和地图集都在一起,旁边的按钮会打开放着日志的那个文件夹。"
};
internal static string Render(int index, Func<string, string> head, Func<string, string> muted, Func<string, string> key, Func<int, string> list)
{
if (index < 0 || index >= Sections.Length)
{
return null;
}
string text = Sections[index];
if (string.IsNullOrEmpty(text))
{
return null;
}
text = text.Replace("{LIST}", (list == null) ? "" : (list(index) ?? ""));
text = ReplaceKeys(text, key);
return Style(text, head, muted);
}
private static string ReplaceKeys(string s, Func<string, string> key)
{
if (key == null)
{
return s;
}
int num;
while ((num = s.IndexOf("{K:", StringComparison.Ordinal)) >= 0)
{
int num2 = s.IndexOf('}', num);
if (num2 < 0)
{
break;
}
string arg = s.Substring(num + 3, num2 - num - 3);
s = s.Substring(0, num) + key(arg) + s.Substring(num2 + 1);
}
return s;
}
private static string Style(string s, Func<string, string> head, Func<string, string> muted)
{
string[] array = s.Split('\n');
for (int i = 0; i < array.Length; i++)
{
string text = array[i];
bool num = text.StartsWith("┈", StringComparison.Ordinal);
if (num)
{
text = text.Substring(1);
}
text = ReplacePaired(text, '«', '»', head, toEndIfUnclosed: false);
text = ReplacePaired(text, '┈', '┈', muted, toEndIfUnclosed: true);
if (num && muted != null)
{
text = muted(text);
}
array[i] = text;
}
return string.Join("\n", array);
}
private static string ReplacePaired(string s, char open, char close, Func<string, string> wrap, bool toEndIfUnclosed)
{
int num;
while ((num = s.IndexOf(open)) >= 0)
{
int num2 = s.IndexOf(close, num + 1);
string text;
string text2;
if (num2 < 0)
{
if (!toEndIfUnclosed)
{
break;
}
text = s.Substring(num + 1);
text2 = "";
}
else
{
text = s.Substring(num + 1, num2 - num - 1);
text2 = s.Substring(num2 + 1);
}
string text3 = ((wrap == null) ? text : (wrap(text) ?? text));
s = s.Substring(0, num) + text3 + text2;
}
return s;
}
}
}