266 lines
12 KiB
JavaScript

async function main() {
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
await figma.loadFontAsync({ family: "Inter", style: "Medium" });
await figma.loadFontAsync({ family: "Inter", style: "Bold" });
const page = figma.currentPage;
page.name = "TH1 Main Menu Reverse Map";
const frames = page.children.filter((node) =>
node.name === "TH1 Main Menu - Current Unity Structure" ||
node.name === "TH1 Main Menu - Logic Flow"
);
for (const frame of frames) frame.remove();
const layout = createFrame(page, "TH1 Main Menu - Current Unity Structure", 0, 0, 1920, 1080, "#101319");
const flow = createFrame(page, "TH1 Main Menu - Logic Flow", 2040, 0, 1920, 1080, "#0f1419");
drawMenuLayout(layout);
drawLogicFlow(flow);
figma.viewport.scrollAndZoomIntoView([layout, flow]);
figma.closePlugin("Created TH1 main menu structure and logic flow frames.");
}
function drawMenuLayout(parent) {
addRect(parent, "Background image: Background / MenuPreview", 0, 0, 1920, 1080, "#171d24", 0);
addRect(parent, "dim overlay", 0, 0, 1920, 1080, "#000000", 0, 0.18);
addText(parent, "title", "TH1 Main Menu - current Unity structure", 32, 28, 820, 36, 26, "#f5f0d7", "Bold");
addText(parent, "source note",
"Sources: UIOutsideMenuView.cs, UIOutsideMenuController.cs, UIEventManagerBinder.cs, UIOutsideManager.cs, UIOutsideMenu.prefab",
32, 66, 1220, 28, 14, "#aab3bd", "Regular");
addRect(parent, "GameLogo placeholder", 883, -21, 100, 100, "#d9c07a", 10, 0.95);
addText(parent, "GameLogo text", "GameLogo", 871, 84, 140, 24, 13, "#d9c07a", "Medium", "CENTER");
addRect(parent, "Logo image placeholder", 195, 467, 1651, 390, "#2b3542", 12, 0.68, "#d9c07a");
addText(parent, "Logo label", "Logo image node\nanchored center: x=60, y=-122, size=1651x390", 275, 586, 600, 76, 30, "#f4e8be", "Bold");
addRect(parent, "ButtonRow container", 620, 250, 761, 341, "#1b2430", 8, 0.82, "#6e8bb8");
addText(parent, "ButtonRow label", "ButtonRow - six MenuButton prefab instances", 648, 264, 520, 22, 16, "#b9c8df", "Medium");
const buttons = [
["StartButton", "Start / 开始", "ShowUIOutsideSelect", 650, 310],
["MultiplayButton", "Multiplay / 多人", "ShowUIOutsideMultiplay", 1030, 310],
["ResumeButton", "Resume / 继续", "visible only when quick resume exists", 650, 420],
["LibraryButton", "Library / 图鉴", "ShowUIOutsideLibrary", 1030, 420],
["HistoryButton", "History / 历史", "ShowUIOutsideHistory", 650, 530],
["TutorButton", "Tutor / 教程", "ShowUIOutsideTutor", 1030, 530]
];
for (const [name, label, event, x, y] of buttons) {
addButton(parent, name, label, event, x, y, 300, 82);
}
addButton(parent, "StoryButton", "Story / 序章", "ShowUIOutsideStory", 26, 747, 322, 105, "#4d3844");
addButton(parent, "TransReport", "Translation / bug feedback", "ShowUIGlobalBugReport", 786, 564, 338, 54, "#314658");
addButton(parent, "Questionnaire", "Questionnaire / 问卷", "Created or found at runtime, then ShowUIOutsideQuestionnaire", 786, 628, 338, 54, "#314658");
addIconButton(parent, "AnnouncementButton / BoardingButton", "updates", 372, 862);
addIconButton(parent, "AboutButton", "about", 1825, 789);
addIconButton(parent, "SettingButton", "settings", 1825, 887);
addIconButton(parent, "ShutdownButton", "quit", 1825, 985);
addPanel(parent, "SteamHint", "Early access / DEMO copy\nPrefab text: 当前是抢先体验版本", 26, 566, 391, 171);
addPanel(parent, "QQHint / DiscordHint", "Community copy\nZH: QQ group\nTDZH: Discord URL becomes clickable", 26, 886, 392, 179);
const runtime = createGroupLabel(parent, "Runtime behavior", 1368, 144, 500, 384);
const notes = [
"SetContent() binds all button listeners each open.",
"ButtonList icons are randomized from GiantType sprites.",
"ResumeButton is hidden unless GameArchiveManager.HasQuickResumeArchive(NetMode.Single).",
"AudioManager.PlayMusic(\"Main\", 1f, 2f, true) starts menu BGM.",
"Traditional Chinese Discord hint opens external URL on click.",
"Startup fallback image is hidden once the menu opens."
];
let noteY = 194;
for (const note of notes) {
addText(parent, "runtime note", "- " + note, 1392, noteY, 450, 34, 14, "#d8dfeb", "Regular");
noteY += 48;
}
addModal(parent, "AnnouncementPanel", "VersionButtonList + VersionContent\nOpens locally from AnnouncementButton", 1260, 650, "#293647");
addModal(parent, "AboutPanel", "VersionLabel + external links + StaffArea\nBilibili / Website / X / YouTube / Discord", 1480, 650, "#293647");
addModal(parent, "SettingPanel", "LanguageOptionGroup\nMoreLanguageModule + ManageButton -> UIOutsideMod", 1260, 840, "#293647");
addModal(parent, "ShutdownPanel", "CancelButton / CheckButton / BlockButton\nConfirm calls Application.Quit()", 1480, 840, "#293647");
addText(parent, "implementation note",
"This frame is a structural restoration. It intentionally uses placeholders instead of copying Unity texture assets into Figma.",
32, 1030, 1180, 24, 14, "#9aa6b2", "Regular");
}
function drawLogicFlow(parent) {
addText(parent, "title", "TH1 Main Menu - logic flow", 32, 28, 760, 36, 26, "#f5f0d7", "Bold");
addText(parent, "source note",
"Main menu is an OutsideUI task. One current Outside task is replaced by StartNewTask(), while loading and some overlays are independent.",
32, 66, 1220, 28, 14, "#aab3bd", "Regular");
const init = [
["UIManager.OnGameStart()", "publishes ShowUIOutsideMenu"],
["UIEventManagerBinder", "HandleShowUIOutsideMenu"],
["UIOutsideManager", "StartNewTask(UIOutsideMenuController)"],
["UIOutsideMenuController.OnOpen()", "WindowScript.SetContent(evt)"],
["UIOutsideMenuView.SetContent()", "bind buttons, version text, Discord hint, BGM"]
];
let y = 132;
for (let i = 0; i < init.length; i++) {
addFlowCard(parent, init[i][0], init[i][1], 72 + i * 350, y, 290, 100, "#223044");
if (i < init.length - 1) addArrowText(parent, "->", 372 + i * 350, y + 36);
}
addSectionLabel(parent, "Button event map", 72, 300);
const events = [
["StartButton", "ShowUIOutsideSelect", "Outside task replaced by select screen."],
["ResumeButton", "ShowUIOutsideLoading -> Main.ResumeMatch -> HideUIOutsideAll", "Only visible when quick single-player archive exists."],
["MultiplayButton", "ShowUIOutsideMultiplay", "Opens Steam lobby / room flow."],
["LibraryButton", "ShowUIOutsideLibrary", "Library screen can later open Select."],
["HistoryButton", "ShowUIOutsideHistory", "History screen can later open Select."],
["TutorButton", "ShowUIOutsideTutor", "Tutorial selection screen."],
["StoryButton", "ShowUIOutsideStory", "Uses UIOutsideManager.OpenStory(), independent of current task."],
["TransReport", "ShowUIGlobalBugReport", "GlobalTop overlay, does not replace menu task."],
["Questionnaire", "ShowUIOutsideQuestionnaire", "Runtime-created if prefab child is absent."],
["AnnouncementButton", "AnnouncementPanel.Open()", "Local child panel, version list from VersionCfg."],
["AboutButton", "AboutPanel.Open()", "Local child panel, external links and staff list."],
["SettingButton", "SettingPanel.Open()", "Language options; ManageButton opens UIOutsideMod."],
["ShutdownButton", "ShutdownPanel.Open()", "Confirm exits game or stops editor play mode."]
];
let col = 0;
let row = 0;
for (const [button, event, note] of events) {
const x = 72 + col * 455;
const yy = 340 + row * 112;
addEventCard(parent, button, event, note, x, yy);
row++;
if (row === 6) {
row = 0;
col++;
}
}
addSectionLabel(parent, "OutsideUI replacement rules", 72, 1030);
addText(parent, "outside rules",
"StartNewTask closes the previous Outside task before opening the new one. Loading uses a separate task. Story, Invited, and Wiki are independent controllers in UIOutsideManager.",
350, 1028, 1300, 32, 15, "#d8dfeb", "Regular");
}
function addButton(parent, name, label, event, x, y, w, h, fill) {
const color = fill || "#34415a";
addRect(parent, name, x, y, w, h, color, 8, 0.94, "#e2c576");
addRect(parent, name + " icon mask", x + 14, y + 10, 64, h - 20, "#202733", 6, 1, "#6f7e92");
addText(parent, name + " label", label, x + 92, y + 13, w - 110, 27, 18, "#fff2c3", "Bold");
addText(parent, name + " event", event, x + 92, y + 43, w - 110, h - 47, 12, "#c2ccd9", "Regular");
}
function addIconButton(parent, name, label, x, y) {
addRect(parent, name, x, y, 64, 64, "#222c37", 32, 1, "#d2bd72");
addText(parent, name + " glyph", label, x - 12, y + 22, 88, 18, 11, "#f5e6b6", "Medium", "CENTER");
}
function addPanel(parent, name, text, x, y, w, h) {
addRect(parent, name, x, y, w, h, "#1c2630", 8, 0.92, "#52687d");
addText(parent, name + " title", name, x + 18, y + 16, w - 36, 24, 17, "#f4e8be", "Bold");
addText(parent, name + " text", text, x + 18, y + 52, w - 36, h - 64, 14, "#c7d0dc", "Regular");
}
function addModal(parent, name, text, x, y, fill) {
addRect(parent, name, x, y, 180, 140, fill, 8, 0.96, "#e2c576");
addText(parent, name + " title", name, x + 14, y + 14, 152, 22, 15, "#fff2c3", "Bold");
addText(parent, name + " body", text, x + 14, y + 42, 152, 86, 11, "#d8dfeb", "Regular");
}
function addFlowCard(parent, title, body, x, y, w, h, fill) {
addRect(parent, title, x, y, w, h, fill, 8, 0.97, "#637a94");
addText(parent, title + " title", title, x + 16, y + 16, w - 32, 24, 16, "#fff2c3", "Bold");
addText(parent, title + " body", body, x + 16, y + 48, w - 32, 38, 13, "#d8dfeb", "Regular");
}
function addEventCard(parent, button, event, note, x, y) {
addRect(parent, button, x, y, 410, 92, "#1d2835", 8, 0.96, "#40576d");
addText(parent, button + " title", button, x + 14, y + 12, 150, 22, 14, "#f5e6b6", "Bold");
addText(parent, button + " event", event, x + 168, y + 12, 228, 26, 12, "#9fd1ff", "Medium");
addText(parent, button + " note", note, x + 14, y + 44, 382, 34, 11, "#c7d0dc", "Regular");
}
function addSectionLabel(parent, text, x, y) {
addText(parent, text + " label", text, x, y, 260, 26, 18, "#f5e6b6", "Bold");
addRect(parent, text + " underline", x, y + 28, 220, 2, "#d2bd72", 0, 0.9);
}
function addArrowText(parent, text, x, y) {
addText(parent, "arrow", text, x, y, 48, 24, 20, "#d2bd72", "Bold", "CENTER");
}
function createGroupLabel(parent, title, x, y, w, h) {
addRect(parent, title, x, y, w, h, "#111821", 8, 0.78, "#40576d");
addText(parent, title + " title", title, x + 24, y + 22, w - 48, 28, 20, "#f5e6b6", "Bold");
}
function createFrame(parent, name, x, y, w, h, fill) {
const frame = figma.createFrame();
frame.name = name;
frame.x = x;
frame.y = y;
frame.resize(w, h);
frame.fills = [paint(fill)];
parent.appendChild(frame);
return frame;
}
function addRect(parent, name, x, y, w, h, fill, radius, opacity, stroke) {
const rect = figma.createRectangle();
rect.name = name;
rect.x = x;
rect.y = y;
rect.resize(w, h);
rect.cornerRadius = radius || 0;
rect.fills = [paint(fill, opacity == null ? 1 : opacity)];
if (stroke) {
rect.strokes = [paint(stroke)];
rect.strokeWeight = 1;
}
parent.appendChild(rect);
return rect;
}
function addText(parent, name, value, x, y, w, h, size, fill, style, align) {
const text = figma.createText();
text.name = name;
text.x = x;
text.y = y;
text.resize(w, h);
text.fontName = { family: "Inter", style: style || "Regular" };
text.fontSize = size;
text.lineHeight = { unit: "AUTO" };
text.textAlignHorizontal = align || "LEFT";
text.fills = [paint(fill)];
text.characters = value;
parent.appendChild(text);
return text;
}
function paint(hex, opacity) {
const rgb = hexToRgb(hex);
return {
type: "SOLID",
color: {
r: rgb.r / 255,
g: rgb.g / 255,
b: rgb.b / 255
},
opacity: opacity == null ? 1 : opacity
};
}
function hexToRgb(hex) {
const cleaned = hex.replace("#", "");
return {
r: parseInt(cleaned.slice(0, 2), 16),
g: parseInt(cleaned.slice(2, 4), 16),
b: parseInt(cleaned.slice(4, 6), 16)
};
}
main();