Card.js:修订间差异
来自卡厄思梦境WIKI
无编辑摘要 |
无编辑摘要 标签:已被回退 |
||
| 第203行: | 第203行: | ||
} | } | ||
// 辅助函数:提取数值 | // 辅助函数:提取数值 | ||
| 第230行: | 第224行: | ||
} | } | ||
// 解析灵光一闪/神光一闪列表(修复版) | |||
function parseInspirationList(content) { | function parseInspirationList(content) { | ||
const inspirations = []; | const inspirations = []; | ||
// | // 使用更精确的解析方法 | ||
let depth = 0; | let depth = 0; | ||
let currentBlock = ''; | let currentBlock = ''; | ||
let inString = false; | let inString = false; | ||
let escapeNext = false; | |||
let startNewBlock = false; | |||
for (let i = 0; i < content.length; i++) { | for (let i = 0; i < content.length; i++) { | ||
const char = content[i]; | const char = content[i]; | ||
const prevChar = i > 0 ? content[i - 1] : ''; | const prevChar = i > 0 ? content[i - 1] : ''; | ||
// 处理转义字符 | |||
if (escapeNext) { | |||
if (depth > 0) currentBlock += char; | |||
escapeNext = false; | |||
continue; | |||
} | |||
if (char === '\\') { | |||
escapeNext = true; | |||
if (depth > 0) currentBlock += char; | |||
continue; | |||
} | |||
// 处理字符串 | // 处理字符串 | ||
if (char === '"' && | if (char === '"' && !escapeNext) { | ||
inString = !inString; | inString = !inString; | ||
if (depth > 0) currentBlock += char; | |||
continue; | |||
} | |||
if (inString) { | |||
if (depth > 0) currentBlock += char; | |||
continue; | |||
} | } | ||
// 处理括号 | |||
if (char === '{') { | |||
if (depth === 0) { | |||
startNewBlock = true; | |||
} else { | |||
currentBlock += char; | |||
} | |||
depth++; | |||
} else if (char === '}') { | |||
depth--; | |||
if (depth === 0 && startNewBlock) { | |||
// 解析完整的块 | |||
if (currentBlock.trim()) { | |||
const inspiration = parseInspirationBlock(currentBlock); | |||
const | |||
if (Object.keys(inspiration).length > 0) { | if (Object.keys(inspiration).length > 0) { | ||
inspirations.push(inspiration); | inspirations.push(inspiration); | ||
} | } | ||
} | } | ||
currentBlock = ''; | |||
startNewBlock = false; | |||
} else if (depth > 0) { | } else if (depth > 0) { | ||
currentBlock += char; | currentBlock += char; | ||
| 第291行: | 第295行: | ||
} | } | ||
// | // 解析单个灵光块 | ||
function parseInspirationBlock(blockContent) { | |||
const inspiration = {}; | |||
// 解析 ap | |||
const apPattern = /ap\s*=\s*(?:"([^"]*)"|'([^']*)'|(\d+)|([^,\n}\s]+))/; | |||
const apMatch = blockContent.match(apPattern); | |||
if (apMatch) { | |||
const apValue = apMatch[1] || apMatch[2] || apMatch[3] || apMatch[4]; | |||
if (apValue !== undefined && apValue !== '') { | |||
inspiration.ap = apValue.trim(); | |||
} | |||
} | |||
// 解析 type | |||
const typePattern = /type\s*=\s*"([^"]*)"/; | |||
const typeMatch = blockContent.match(typePattern); | |||
if (typeMatch && typeMatch[1]) { | |||
inspiration.type = typeMatch[1]; | |||
} | |||
// 解析 desc_global | |||
const descPattern = /desc_global\s*=\s*"((?:[^"\\]|\\.)*)"/; | |||
const descMatch = blockContent.match(descPattern); | |||
if (descMatch && descMatch[1]) { | |||
inspiration.desc_global = descMatch[1].replace(/\\"/g, '"').replace(/\\n/g, '\n'); | |||
} | |||
// 解析 dict | |||
const dictPattern = /dict\s*=\s*"((?:[^"\\]|\\.)*)"/; | |||
const dictMatch = blockContent.match(dictPattern); | |||
if (dictMatch && dictMatch[1]) { | |||
inspiration.dict = dictMatch[1].replace(/\\"/g, '"'); | |||
} | |||
return inspiration; | |||
} | |||
// 解析Lua代码(修复版) | |||
function parseLuaCode(luaText) { | function parseLuaCode(luaText) { | ||
const cards = []; | const cards = []; | ||
| 第312行: | 第354行: | ||
} | } | ||
// | // 使用更精确的方法解析每张卡牌 | ||
const | const lines = luaText.split('\n'); | ||
let | let currentCard = null; | ||
let inCard = false; | |||
let inBase = false; | |||
let inVar = false; | |||
let inInspiration = false; | |||
let inGodInspiration = false; | |||
let currentGod = null; | |||
let braceDepth = 0; | |||
let baseContent = ''; | |||
let inspirationContent = ''; | |||
let godInspirationContent = ''; | |||
let godContent = ''; | |||
for (let i = 0; i < lines.length; i++) { | |||
const | const line = lines[i]; | ||
const | const trimmed = line.trim(); | ||
const card = createEmptyCard(); | // 检测新卡牌开始 | ||
const cardStartMatch = line.match(/^card\["([^"]+)"\]\s*=\s*\{/); | |||
if (cardStartMatch) { | |||
inCard = true; | |||
currentCard = createEmptyCard(); | |||
currentCard.name = cardStartMatch[1]; | |||
braceDepth = 1; | |||
continue; | |||
} | |||
if (!inCard) continue; | |||
// 计算括号深度 | |||
for (let char of line) { | |||
if (char === '{') braceDepth++; | |||
if (char === '}') braceDepth--; | |||
} | } | ||
// | // 检测 base 开始 | ||
if (trimmed.startsWith('base = {')) { | |||
inBase = true; | |||
baseContent = ''; | |||
continue; | |||
// | } | ||
if ( | // 收集 base 内容 | ||
if (inBase) { | |||
if (trimmed === '},') { | |||
inBase = false; | |||
// 解析 base 内容 | |||
parseBaseContent(currentCard, baseContent); | |||
} else { | |||
baseContent += line + '\n'; | |||
} | } | ||
continue; | |||
} | |||
if ( | // 检测 var 开始 | ||
if (trimmed.startsWith('var = {')) { | |||
inVar = true; | |||
continue; | |||
} | |||
if ( | // 在 var 内部检测 inspiration | ||
if (inVar && trimmed.startsWith('inspiration = {')) { | |||
inInspiration = true; | |||
inspirationContent = ''; | |||
continue; | |||
} | |||
// 收集 inspiration 内容 | |||
if (inInspiration) { | |||
if (trimmed === '},') { | |||
inInspiration = false; | |||
currentCard.var.inspiration = parseInspirationList(inspirationContent); | |||
} else { | |||
inspirationContent += line + '\n'; | |||
} | |||
continue; | |||
} | |||
// 检测 god_inspiration 开始 | |||
if (inVar && trimmed.startsWith('god_inspiration = {')) { | |||
inGodInspiration = true; | |||
godInspirationContent = ''; | |||
continue; | |||
} | |||
// 在 god_inspiration 内部 | |||
if (inGodInspiration) { | |||
if (trimmed === '},') { | |||
inGodInspiration = false; | |||
inVar = false; | |||
} else { | |||
// 检测单个神明 | |||
const godMatch = trimmed.match(/^(circen|diallos|nihilum|secred|vitor)\s*=\s*\{/); | |||
if (godMatch) { | |||
if (currentGod && godContent) { | |||
// 保存前一个神明的数据 | |||
currentCard.var.god_inspiration[currentGod] = parseInspirationList(godContent); | |||
} | } | ||
}); | currentGod = godMatch[1]; | ||
godContent = ''; | |||
} else if (trimmed === '},') { | |||
// 当前神明结束 | |||
if (currentGod && godContent) { | |||
currentCard.var.god_inspiration[currentGod] = parseInspirationList(godContent); | |||
currentGod = null; | |||
godContent = ''; | |||
} | |||
} else if (currentGod) { | |||
godContent += line + '\n'; | |||
} | |||
} | } | ||
continue; | |||
} | } | ||
cards.push( | // 检测卡牌结束 | ||
if (braceDepth === 0 && inCard) { | |||
cards.push(currentCard); | |||
currentCard = null; | |||
inCard = false; | |||
inBase = false; | |||
inVar = false; | |||
inInspiration = false; | |||
inGodInspiration = false; | |||
baseContent = ''; | |||
inspirationContent = ''; | |||
godContent = ''; | |||
currentGod = null; | |||
} | |||
} | |||
// 处理最后一张卡牌 | |||
if (currentCard) { | |||
cards.push(currentCard); | |||
} | } | ||
} catch (error) { | } catch (error) { | ||
console.error('解析Lua代码失败:', error); | console.error('解析Lua代码失败:', error); | ||
console.error('错误堆栈:', error.stack); | |||
} | } | ||
return { cards, defaultInfo }; | return { cards, defaultInfo }; | ||
} | } | ||
// 解析 base 内容 | |||
function parseBaseContent(card, content) { | |||
card.base.displayname = extractStringValue(content, 'displayname') || ''; | |||
card.base.art = extractStringValue(content, 'art') || ''; | |||
card.base.group = extractStringValue(content, 'group') || ''; | |||
card.base.rarity = extractStringValue(content, 'rarity') || ''; | |||
card.base.god = extractStringValue(content, 'god') || ''; | |||
card.base.type = extractStringValue(content, 'type') || ''; | |||
card.base.dict = extractStringValue(content, 'dict') || ''; | |||
card.base.desc_global = extractStringValue(content, 'desc_global') || ''; | |||
card.base.sub = extractStringValue(content, 'sub') || ''; | |||
// 解析 ap(可能是数字、字符串或特殊值) | |||
const apMatch = content.match(/ap\s*=\s*(?:"([^"]*)"|(\d+)|([^,\n]+))/); | |||
if (apMatch) { | |||
card.base.ap = apMatch[1] || apMatch[2] || (apMatch[3] ? apMatch[3].trim() : ''); | |||
} | |||
card.base.isinspiration = extractNumberValue(content, 'isinspiration') || 0; | |||
card.base.isgod_inspiration = extractNumberValue(content, 'isgod_inspiration') || 0; | |||
} | |||
// 辅助函数:提取字符串值(支持转义) | |||
function extractStringValue(text, key) { | |||
const pattern = new RegExp(key + '\\s*=\\s*"((?:[^"\\\\]|\\\\.)*)"', 'm'); | |||
const match = text.match(pattern); | |||
if (match && match[1]) { | |||
// 处理转义字符 | |||
return match[1] | |||
.replace(/\\"/g, '"') | |||
.replace(/\\n/g, '\n') | |||
.replace(/\\t/g, '\t') | |||
.replace(/\\\\/g, '\\'); | |||
} | |||
return ''; | |||
} | |||
// Lua字符串转义 | // Lua字符串转义 | ||
2025年10月23日 (四) 17:31的版本
(function() {
'use strict';
// 加载CSS
mw.loader.load('/index.php?title=Mediawiki:Card.css&action=raw&ctype=text/css', 'text/css');
// 状态管理
const state = {
currentFighter: '',
fighters: [],
cards: [],
currentCard: null,
defaultInfo: {
order: '',
ego: ''
},
editMode: 'base',
currentGod: 'circen'
};
// 卡牌数据结构
function createEmptyCard() {
return {
name: '',
base: {
displayname: '',
art: '',
group: '',
rarity: '',
god: '',
ap: '',
type: '',
dict: '',
desc_global: '',
sub: '',
isinspiration: 0,
isgod_inspiration: 0
},
var: {
inspiration: [],
god_inspiration: {
circen: [],
diallos: [],
nihilum: [],
secred: [],
vitor: []
}
}
};
}
// 创建元素辅助函数
function createElement(tag, className, attributes = {}) {
const el = document.createElement(tag);
if (className) el.className = className;
Object.entries(attributes).forEach(([key, value]) => {
if (key === 'textContent') {
el.textContent = value;
} else if (key.startsWith('on')) {
el.addEventListener(key.substring(2).toLowerCase(), value);
} else {
el.setAttribute(key, value);
}
});
return el;
}
// 创建自定义下拉选择器
function createSelect(options, selectedValue, onChange) {
const select = createElement('div', 'form-select');
select.textContent = selectedValue || options[0] || '请选择';
select.setAttribute('tabindex', '0');
const dropdown = createElement('div', 'dropdown-menu');
options.forEach(option => {
const item = createElement('div', 'dropdown-item');
item.textContent = option;
item.addEventListener('click', () => {
select.textContent = option;
dropdown.style.display = 'none';
if (onChange) onChange(option);
});
dropdown.appendChild(item);
});
select.addEventListener('click', (e) => {
e.stopPropagation();
dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
const rect = select.getBoundingClientRect();
dropdown.style.top = rect.bottom + 'px';
dropdown.style.left = rect.left + 'px';
dropdown.style.width = rect.width + 'px';
});
document.addEventListener('click', () => {
dropdown.style.display = 'none';
});
const wrapper = createElement('div');
wrapper.style.position = 'relative';
wrapper.appendChild(select);
document.body.appendChild(dropdown);
return { wrapper, select, dropdown };
}
// 创建自定义输入框
function createInput(type, value, onChange, placeholder = '') {
const input = createElement('div', 'form-input');
input.setAttribute('contenteditable', 'true');
input.textContent = value || '';
input.setAttribute('data-placeholder', placeholder);
input.style.minHeight = type === 'textarea' ? '100px' : 'auto';
if (!value) {
input.style.color = '#999';
input.textContent = placeholder;
}
input.addEventListener('focus', () => {
if (input.textContent === placeholder) {
input.textContent = '';
input.style.color = '#333';
}
});
input.addEventListener('blur', () => {
if (!input.textContent.trim()) {
input.textContent = placeholder;
input.style.color = '#999';
}
if (onChange) onChange(input.textContent === placeholder ? '' : input.textContent);
});
input.addEventListener('input', () => {
if (onChange && input.textContent !== placeholder) {
onChange(input.textContent);
}
});
return input;
}
// 创建复选框
function createCheckbox(checked, onChange) {
const wrapper = createElement('div', 'checkbox-wrapper');
const checkbox = createElement('div', checked ? 'checkbox checked' : 'checkbox');
const label = createElement('div', '');
label.textContent = '是';
wrapper.addEventListener('click', () => {
const newChecked = !checkbox.classList.contains('checked');
if (newChecked) {
checkbox.classList.add('checked');
} else {
checkbox.classList.remove('checked');
}
if (onChange) onChange(newChecked ? 1 : 0);
});
wrapper.appendChild(checkbox);
wrapper.appendChild(label);
return wrapper;
}
// 创建表单组
function createFormGroup(label, control) {
const group = createElement('div', 'form-group');
const labelEl = createElement('div', 'form-label');
labelEl.textContent = label;
group.appendChild(labelEl);
group.appendChild(control);
return group;
}
// 创建按钮
function createButton(text, className, onClick) {
const btn = createElement('div', 'btn ' + className);
btn.textContent = text;
btn.addEventListener('click', onClick);
return btn;
}
// 文本插入辅助函数
function insertTextAtCursor(element, text, wrap = false) {
element.focus();
const selection = window.getSelection();
if (wrap && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const selectedText = range.toString();
if (selectedText) {
range.deleteContents();
const textNode = document.createTextNode(text.replace('选择文字', selectedText));
range.insertNode(textNode);
return;
}
}
document.execCommand('insertText', false, text);
}
// 辅助函数:提取数值
function extractNumberValue(text, key) {
const pattern = new RegExp(key + '\\s*=\\s*(\\d+)', 'm');
const match = text.match(pattern);
return match ? parseInt(match[1]) : 0;
}
// 辅助函数:提取任意值(包括X、Ø等)
function extractValue(text, key) {
const pattern = new RegExp(key + '\\s*=\\s*([^,\\n]+)', 'm');
const match = text.match(pattern);
if (match) {
let value = match[1].trim();
// 移除引号
value = value.replace(/^["']|["']$/g, '');
return value;
}
return '';
}
// 解析灵光一闪/神光一闪列表(修复版)
function parseInspirationList(content) {
const inspirations = [];
// 使用更精确的解析方法
let depth = 0;
let currentBlock = '';
let inString = false;
let escapeNext = false;
let startNewBlock = false;
for (let i = 0; i < content.length; i++) {
const char = content[i];
const prevChar = i > 0 ? content[i - 1] : '';
// 处理转义字符
if (escapeNext) {
if (depth > 0) currentBlock += char;
escapeNext = false;
continue;
}
if (char === '\\') {
escapeNext = true;
if (depth > 0) currentBlock += char;
continue;
}
// 处理字符串
if (char === '"' && !escapeNext) {
inString = !inString;
if (depth > 0) currentBlock += char;
continue;
}
if (inString) {
if (depth > 0) currentBlock += char;
continue;
}
// 处理括号
if (char === '{') {
if (depth === 0) {
startNewBlock = true;
} else {
currentBlock += char;
}
depth++;
} else if (char === '}') {
depth--;
if (depth === 0 && startNewBlock) {
// 解析完整的块
if (currentBlock.trim()) {
const inspiration = parseInspirationBlock(currentBlock);
if (Object.keys(inspiration).length > 0) {
inspirations.push(inspiration);
}
}
currentBlock = '';
startNewBlock = false;
} else if (depth > 0) {
currentBlock += char;
}
} else if (depth > 0) {
currentBlock += char;
}
}
return inspirations;
}
// 解析单个灵光块
function parseInspirationBlock(blockContent) {
const inspiration = {};
// 解析 ap
const apPattern = /ap\s*=\s*(?:"([^"]*)"|'([^']*)'|(\d+)|([^,\n}\s]+))/;
const apMatch = blockContent.match(apPattern);
if (apMatch) {
const apValue = apMatch[1] || apMatch[2] || apMatch[3] || apMatch[4];
if (apValue !== undefined && apValue !== '') {
inspiration.ap = apValue.trim();
}
}
// 解析 type
const typePattern = /type\s*=\s*"([^"]*)"/;
const typeMatch = blockContent.match(typePattern);
if (typeMatch && typeMatch[1]) {
inspiration.type = typeMatch[1];
}
// 解析 desc_global
const descPattern = /desc_global\s*=\s*"((?:[^"\\]|\\.)*)"/;
const descMatch = blockContent.match(descPattern);
if (descMatch && descMatch[1]) {
inspiration.desc_global = descMatch[1].replace(/\\"/g, '"').replace(/\\n/g, '\n');
}
// 解析 dict
const dictPattern = /dict\s*=\s*"((?:[^"\\]|\\.)*)"/;
const dictMatch = blockContent.match(dictPattern);
if (dictMatch && dictMatch[1]) {
inspiration.dict = dictMatch[1].replace(/\\"/g, '"');
}
return inspiration;
}
// 解析Lua代码(修复版)
function parseLuaCode(luaText) {
const cards = [];
const defaultInfo = {
order: '',
ego: ''
};
try {
// 解析 card.order
const orderMatch = luaText.match(/card\.order\s*=\s*\{\s*"([^"]+)"\s*\}/);
if (orderMatch) {
defaultInfo.order = orderMatch[1];
}
// 解析 card.info.ego
const egoMatch = luaText.match(/card\.info\s*=\s*\{[^}]*ego\s*=\s*"([^"]+)"/);
if (egoMatch) {
defaultInfo.ego = egoMatch[1];
}
// 使用更精确的方法解析每张卡牌
const lines = luaText.split('\n');
let currentCard = null;
let inCard = false;
let inBase = false;
let inVar = false;
let inInspiration = false;
let inGodInspiration = false;
let currentGod = null;
let braceDepth = 0;
let baseContent = '';
let inspirationContent = '';
let godInspirationContent = '';
let godContent = '';
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trim();
// 检测新卡牌开始
const cardStartMatch = line.match(/^card\["([^"]+)"\]\s*=\s*\{/);
if (cardStartMatch) {
inCard = true;
currentCard = createEmptyCard();
currentCard.name = cardStartMatch[1];
braceDepth = 1;
continue;
}
if (!inCard) continue;
// 计算括号深度
for (let char of line) {
if (char === '{') braceDepth++;
if (char === '}') braceDepth--;
}
// 检测 base 开始
if (trimmed.startsWith('base = {')) {
inBase = true;
baseContent = '';
continue;
}
// 收集 base 内容
if (inBase) {
if (trimmed === '},') {
inBase = false;
// 解析 base 内容
parseBaseContent(currentCard, baseContent);
} else {
baseContent += line + '\n';
}
continue;
}
// 检测 var 开始
if (trimmed.startsWith('var = {')) {
inVar = true;
continue;
}
// 在 var 内部检测 inspiration
if (inVar && trimmed.startsWith('inspiration = {')) {
inInspiration = true;
inspirationContent = '';
continue;
}
// 收集 inspiration 内容
if (inInspiration) {
if (trimmed === '},') {
inInspiration = false;
currentCard.var.inspiration = parseInspirationList(inspirationContent);
} else {
inspirationContent += line + '\n';
}
continue;
}
// 检测 god_inspiration 开始
if (inVar && trimmed.startsWith('god_inspiration = {')) {
inGodInspiration = true;
godInspirationContent = '';
continue;
}
// 在 god_inspiration 内部
if (inGodInspiration) {
if (trimmed === '},') {
inGodInspiration = false;
inVar = false;
} else {
// 检测单个神明
const godMatch = trimmed.match(/^(circen|diallos|nihilum|secred|vitor)\s*=\s*\{/);
if (godMatch) {
if (currentGod && godContent) {
// 保存前一个神明的数据
currentCard.var.god_inspiration[currentGod] = parseInspirationList(godContent);
}
currentGod = godMatch[1];
godContent = '';
} else if (trimmed === '},') {
// 当前神明结束
if (currentGod && godContent) {
currentCard.var.god_inspiration[currentGod] = parseInspirationList(godContent);
currentGod = null;
godContent = '';
}
} else if (currentGod) {
godContent += line + '\n';
}
}
continue;
}
// 检测卡牌结束
if (braceDepth === 0 && inCard) {
cards.push(currentCard);
currentCard = null;
inCard = false;
inBase = false;
inVar = false;
inInspiration = false;
inGodInspiration = false;
baseContent = '';
inspirationContent = '';
godContent = '';
currentGod = null;
}
}
// 处理最后一张卡牌
if (currentCard) {
cards.push(currentCard);
}
} catch (error) {
console.error('解析Lua代码失败:', error);
console.error('错误堆栈:', error.stack);
}
return { cards, defaultInfo };
}
// 解析 base 内容
function parseBaseContent(card, content) {
card.base.displayname = extractStringValue(content, 'displayname') || '';
card.base.art = extractStringValue(content, 'art') || '';
card.base.group = extractStringValue(content, 'group') || '';
card.base.rarity = extractStringValue(content, 'rarity') || '';
card.base.god = extractStringValue(content, 'god') || '';
card.base.type = extractStringValue(content, 'type') || '';
card.base.dict = extractStringValue(content, 'dict') || '';
card.base.desc_global = extractStringValue(content, 'desc_global') || '';
card.base.sub = extractStringValue(content, 'sub') || '';
// 解析 ap(可能是数字、字符串或特殊值)
const apMatch = content.match(/ap\s*=\s*(?:"([^"]*)"|(\d+)|([^,\n]+))/);
if (apMatch) {
card.base.ap = apMatch[1] || apMatch[2] || (apMatch[3] ? apMatch[3].trim() : '');
}
card.base.isinspiration = extractNumberValue(content, 'isinspiration') || 0;
card.base.isgod_inspiration = extractNumberValue(content, 'isgod_inspiration') || 0;
}
// 辅助函数:提取字符串值(支持转义)
function extractStringValue(text, key) {
const pattern = new RegExp(key + '\\s*=\\s*"((?:[^"\\\\]|\\\\.)*)"', 'm');
const match = text.match(pattern);
if (match && match[1]) {
// 处理转义字符
return match[1]
.replace(/\\"/g, '"')
.replace(/\\n/g, '\n')
.replace(/\\t/g, '\t')
.replace(/\\\\/g, '\\');
}
return '';
}
// Lua字符串转义
function escapeLuaString(str) {
if (!str) return '';
return str.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t');
}
// 生成Lua代码
function generateLuaCode() {
let code = 'local card = {}\n\n';
// 生成 card.order
if (state.defaultInfo.order) {
code += `card.order = { "${escapeLuaString(state.defaultInfo.order)}" }\n\n`;
}
// 生成 card.info
if (state.defaultInfo.ego) {
code += 'card.info = {\n';
code += ` ego = "${escapeLuaString(state.defaultInfo.ego)}",\n`;
code += '}\n\n';
}
// 生成每张卡牌
state.cards.forEach(card => {
code += `card["${escapeLuaString(card.name)}"] = {\n`;
code += ' base = {\n';
const base = card.base;
if (base.displayname) code += ` displayname = "${escapeLuaString(base.displayname)}",\n`;
if (base.art) code += ` art = "${escapeLuaString(base.art)}",\n`;
if (base.group) code += ` group = "${escapeLuaString(base.group)}",\n`;
if (base.rarity) code += ` rarity = "${escapeLuaString(base.rarity)}",\n`;
if (base.god) code += ` god = "${escapeLuaString(base.god)}",\n`;
if (base.ap !== '') {
if (isNaN(base.ap)) {
code += ` ap = "${escapeLuaString(base.ap)}",\n`;
} else {
code += ` ap = ${base.ap},\n`;
}
}
if (base.type) code += ` type = "${escapeLuaString(base.type)}",\n`;
if (base.dict) code += ` dict = "${escapeLuaString(base.dict)}",\n`;
if (base.desc_global) code += ` desc_global = "${escapeLuaString(base.desc_global)}",\n`;
if (base.sub) code += ` sub = "${escapeLuaString(base.sub)}",\n`;
if (base.isinspiration) code += ` isinspiration = ${base.isinspiration},\n`;
if (base.isgod_inspiration) code += ` isgod_inspiration = ${base.isgod_inspiration},\n`;
code += ' },\n';
// 生成 var 部分
const hasInspiration = card.var.inspiration.length > 0;
const hasGodInspiration = Object.values(card.var.god_inspiration).some(arr => arr.length > 0);
if (hasInspiration || hasGodInspiration) {
code += ' var = {\n';
// 灵光一闪
if (hasInspiration) {
code += ' inspiration = {\n';
card.var.inspiration.forEach(insp => {
code += ' {\n';
if (insp.ap !== '' && insp.ap !== undefined) {
if (isNaN(insp.ap)) {
code += ` ap = "${escapeLuaString(insp.ap)}",\n`;
} else {
code += ` ap = ${insp.ap},\n`;
}
}
if (insp.type) code += ` type = "${escapeLuaString(insp.type)}",\n`;
if (insp.desc_global) code += ` desc_global = "${escapeLuaString(insp.desc_global)}",\n`;
if (insp.dict) code += ` dict = "${escapeLuaString(insp.dict)}",\n`;
code += ' },\n';
});
code += ' },\n';
}
// 神光一闪
if (hasGodInspiration) {
code += ' god_inspiration = {\n';
Object.entries(card.var.god_inspiration).forEach(([god, inspList]) => {
if (inspList.length > 0) {
code += ` ${god} = {\n`;
inspList.forEach(insp => {
code += ' {\n';
if (insp.ap !== '' && insp.ap !== undefined) {
if (isNaN(insp.ap)) {
code += ` ap = "${escapeLuaString(insp.ap)}",\n`;
} else {
code += ` ap = ${insp.ap},\n`;
}
}
if (insp.type) code += ` type = "${escapeLuaString(insp.type)}",\n`;
if (insp.desc_global) code += ` desc_global = "${escapeLuaString(insp.desc_global)}",\n`;
if (insp.dict) code += ` dict = "${escapeLuaString(insp.dict)}",\n`;
code += ' },\n';
});
code += ' },\n';
}
});
code += ' },\n';
}
code += ' },\n';
}
code += '}\n\n';
});
code += 'return card';
return code;
}
// 加载战斗员列表
async function loadFighters() {
try {
const api = new mw.Api();
const result = await api.get({
action: 'query',
list: 'allpages',
apprefix: '卡牌/',
apnamespace: 828, // Module namespace
aplimit: 500
});
if (result.query && result.query.allpages) {
state.fighters = result.query.allpages.map(page => {
return page.title.replace('模块:卡牌/', '');
});
}
} catch (error) {
console.error('加载战斗员列表失败:', error);
}
}
// 加载战斗员卡牌数据
async function loadFighterCards(fighter) {
if (!fighter) return [];
try {
const api = new mw.Api();
// 获取页面内容
const result = await api.get({
action: 'query',
prop: 'revisions',
titles: '模块:卡牌/' + fighter,
rvprop: 'content',
rvslots: 'main',
formatversion: 2
});
if (!result.query || !result.query.pages || result.query.pages.length === 0) {
console.error('未找到页面');
return [];
}
const page = result.query.pages[0];
if (page.missing) {
console.error('页面不存在');
return [];
}
const content = page.revisions[0].slots.main.content;
// 解析Lua代码
const parsed = parseLuaCode(content);
// 更新默认信息
state.defaultInfo = parsed.defaultInfo;
return parsed.cards;
} catch (error) {
console.error('加载卡牌数据失败:', error);
return [];
}
}
// 渲染函数
function render() {
const container = document.getElementById('card-manager-container');
if (!container) return;
container.innerHTML = '';
const manager = createElement('div', 'card-manager');
// 左侧输入区
const inputSection = createElement('div', 'card-input-section');
// 战斗员选择
const title = createElement('div', 'section-title');
title.textContent = '卡牌管理器';
inputSection.appendChild(title);
const fighterSelect = createSelect(state.fighters, state.currentFighter, async (value) => {
state.currentFighter = value;
// 显示加载提示
const loading = createElement('div', 'loading-indicator');
loading.textContent = '正在加载卡牌数据...';
document.body.appendChild(loading);
try {
const cards = await loadFighterCards(value);
state.cards = cards;
state.currentCard = cards.length > 0 ? cards[0] : null;
render();
} catch (error) {
alert('加载失败:' + error.message);
} finally {
if (loading.parentNode) {
document.body.removeChild(loading);
}
}
});
inputSection.appendChild(createFormGroup('选择战斗员:', fighterSelect.wrapper));
// 默认信息区
if (state.currentFighter) {
const defaultSection = createElement('div', 'default-info-section');
const defaultTitle = createElement('div', 'section-title');
defaultTitle.textContent = '默认信息';
defaultTitle.style.borderColor = '#ff9800';
defaultSection.appendChild(defaultTitle);
const orderInput = createInput('text', state.defaultInfo.order, (value) => {
state.defaultInfo.order = value;
render();
}, 'card.order');
defaultSection.appendChild(createFormGroup('卡牌顺序:', orderInput));
const egoInput = createInput('text', state.defaultInfo.ego, (value) => {
state.defaultInfo.ego = value;
render();
}, 'card.info.ego');
defaultSection.appendChild(createFormGroup('EGO:', egoInput));
inputSection.appendChild(defaultSection);
}
// 卡牌编辑区
if (state.currentCard) {
const cardSection = createElement('div', '');
cardSection.style.marginTop = '20px';
const cardTitle = createElement('div', 'section-title');
cardTitle.textContent = '卡牌信息';
cardSection.appendChild(cardTitle);
// 卡牌名称
const nameInput = createInput('text', state.currentCard.name, (value) => {
state.currentCard.name = value;
render();
}, '卡牌名称');
cardSection.appendChild(createFormGroup('卡牌名称:', nameInput));
// 显示名称
const displaynameInput = createInput('text', state.currentCard.base.displayname, (value) => {
state.currentCard.base.displayname = value;
render();
}, '显示名称');
cardSection.appendChild(createFormGroup('显示名称:', displaynameInput));
// 图片
const artInput = createInput('text', state.currentCard.base.art, (value) => {
state.currentCard.base.art = value;
render();
}, '图片文件名');
cardSection.appendChild(createFormGroup('图片:', artInput));
// 分组
const groupInput = createInput('text', state.currentCard.base.group, (value) => {
state.currentCard.base.group = value;
render();
}, '分组');
cardSection.appendChild(createFormGroup('分组:', groupInput));
// 稀有度
const raritySelect = createSelect(
['', 'ZAYIN', 'TETH', 'HE', 'WAW', 'ALEPH'],
state.currentCard.base.rarity || '',
(value) => {
state.currentCard.base.rarity = value;
render();
}
);
cardSection.appendChild(createFormGroup('稀有度:', raritySelect.wrapper));
// 神明
const godSelect = createSelect(
['', 'circen', 'diallos', 'nihilum', 'secred', 'vitor'],
state.currentCard.base.god || '',
(value) => {
state.currentCard.base.god = value;
render();
}
);
cardSection.appendChild(createFormGroup('神明:', godSelect.wrapper));
// AP
const apInput = createInput('text', state.currentCard.base.ap, (value) => {
state.currentCard.base.ap = value;
render();
}, 'AP (可以是数字、X、Ø等)');
cardSection.appendChild(createFormGroup('AP:', apInput));
// 类型
const typeSelect = createSelect(
['', '攻击', '技能', '强化'],
state.currentCard.base.type || '',
(value) => {
state.currentCard.base.type = value;
render();
}
);
cardSection.appendChild(createFormGroup('类型:', typeSelect.wrapper));
// 词典
const dictInput = createInput('text', state.currentCard.base.dict, (value) => {
state.currentCard.base.dict = value;
render();
}, '词典关键词');
cardSection.appendChild(createFormGroup('词典:', dictInput));
// 描述
const descSection = createElement('div', 'form-group');
const descLabel = createElement('div', 'form-label');
descLabel.textContent = '描述:';
descSection.appendChild(descLabel);
// 描述工具栏
const toolbar = createElement('div', 'button-group');
const blueTextBtn = createButton('蓝色文本', 'btn btn-blue', () => {
insertTextAtCursor(descInput, '{blue|选择文字|}', true);
});
toolbar.appendChild(blueTextBtn);
const greenTextBtn = createButton('绿色文本', 'btn btn-green', () => {
insertTextAtCursor(descInput, '{green|选择文字|}', true);
});
toolbar.appendChild(greenTextBtn);
const strokeTextBtn = createButton('绿色描边', 'btn btn-green', () => {
insertTextAtCursor(descInput, '{stroke|选择文字|}', true);
});
toolbar.appendChild(strokeTextBtn);
const dictBtn = createButton('词典', 'btn', () => {
const keyword = prompt('请输入词典关键词:');
if (keyword) {
insertTextAtCursor(descInput, `{dict|${keyword}|}`);
}
});
toolbar.appendChild(dictBtn);
const wrapBtn = createButton('换行', 'btn', () => {
insertTextAtCursor(descInput, '{wrap|}');
});
toolbar.appendChild(wrapBtn);
descSection.appendChild(toolbar);
const descInput = createInput('textarea', state.currentCard.base.desc_global, (value) => {
state.currentCard.base.desc_global = value;
render();
}, '卡牌描述');
descInput.style.minHeight = '150px';
descSection.appendChild(descInput);
cardSection.appendChild(descSection);
// 副描述
const subInput = createInput('textarea', state.currentCard.base.sub, (value) => {
state.currentCard.base.sub = value;
render();
}, '副描述');
subInput.style.minHeight = '80px';
cardSection.appendChild(createFormGroup('副描述:', subInput));
// 是否存在灵光一闪
const inspirationCheckbox = createCheckbox(state.currentCard.base.isinspiration, (value) => {
state.currentCard.base.isinspiration = value;
if (!value) {
state.currentCard.var.inspiration = [];
}
render();
});
cardSection.appendChild(createFormGroup('是否存在灵光一闪:', inspirationCheckbox));
// 是否存在神光一闪
const godInspirationCheckbox = createCheckbox(state.currentCard.base.isgod_inspiration, (value) => {
state.currentCard.base.isgod_inspiration = value;
if (!value) {
state.currentCard.var.god_inspiration = {
circen: [],
diallos: [],
nihilum: [],
secred: [],
vitor: []
};
}
render();
});
cardSection.appendChild(createFormGroup('是否存在神光一闪:', godInspirationCheckbox));
inputSection.appendChild(cardSection);
}
// 添加新卡牌按钮
const addCardBtn = createButton('+ 新增卡牌', 'btn-success', () => {
const newCard = createEmptyCard();
newCard.name = '新卡牌' + (state.cards.length + 1);
state.cards.push(newCard);
state.currentCard = newCard;
render();
});
addCardBtn.style.marginTop = '20px';
addCardBtn.style.width = '100%';
inputSection.appendChild(addCardBtn);
manager.appendChild(inputSection);
// 中间列表区
const listSection = createElement('div', 'card-list-section');
// 卡牌列表
const cardListContainer = createElement('div', 'list-container');
const cardListHeader = createElement('div', 'list-header');
const cardListTitle = createElement('div', 'list-title');
cardListTitle.textContent = '卡牌列表';
cardListHeader.appendChild(cardListTitle);
cardListContainer.appendChild(cardListHeader);
if (state.cards.length === 0) {
const emptyState = createElement('div', 'empty-state');
const emptyIcon = createElement('div', 'empty-state-icon');
emptyIcon.textContent = '📋';
const emptyText = createElement('div', 'empty-state-text');
emptyText.textContent = '暂无卡牌,点击"新增卡牌"开始创建';
emptyState.appendChild(emptyIcon);
emptyState.appendChild(emptyText);
cardListContainer.appendChild(emptyState);
} else {
state.cards.forEach((card, index) => {
const cardItem = createElement('div', 'card-item' + (state.currentCard === card ? ' active' : ''));
const cardName = createElement('div', 'card-item-name');
cardName.textContent = card.name || '未命名卡牌';
const cardInfo = createElement('div', 'card-item-info');
const infoText = [];
if (card.base.type) infoText.push(card.base.type);
if (card.base.ap !== '') infoText.push('AP:' + card.base.ap);
if (card.base.rarity) infoText.push(card.base.rarity);
cardInfo.textContent = infoText.join(' | ') || '暂无信息';
cardItem.appendChild(cardName);
cardItem.appendChild(cardInfo);
cardItem.addEventListener('click', () => {
state.currentCard = card;
render();
});
// 删除按钮
const deleteBtn = createButton('删除', 'btn-danger', (e) => {
e.stopPropagation();
if (confirm('确定要删除卡牌"' + card.name + '"吗?')) {
state.cards.splice(index, 1);
if (state.currentCard === card) {
state.currentCard = state.cards[0] || null;
}
render();
}
});
deleteBtn.style.cssText = 'margin-top:8px;width:100%;';
cardItem.appendChild(deleteBtn);
cardListContainer.appendChild(cardItem);
});
}
listSection.appendChild(cardListContainer);
// 灵光一闪列表
if (state.currentCard && state.currentCard.base.isinspiration) {
const inspirationSection = createElement('div', 'inspiration-section');
const inspirationHeader = createElement('div', 'list-header');
const inspirationTitle = createElement('div', 'list-title');
inspirationTitle.textContent = '灵光一闪列表';
inspirationHeader.appendChild(inspirationTitle);
const addInspirationBtn = createButton('+ 添加', 'btn-primary', () => {
state.currentCard.var.inspiration.push({
ap: '',
type: '',
desc_global: ''
});
render();
});
inspirationHeader.appendChild(addInspirationBtn);
inspirationSection.appendChild(inspirationHeader);
if (state.currentCard.var.inspiration.length === 0) {
const emptyState = createElement('div', 'empty-state');
emptyState.style.padding = '20px';
const emptyText = createElement('div', 'empty-state-text');
emptyText.textContent = '暂无灵光一闪,点击"添加"创建';
emptyState.appendChild(emptyText);
inspirationSection.appendChild(emptyState);
} else {
state.currentCard.var.inspiration.forEach((insp, idx) => {
const inspItem = createElement('div', 'inspiration-item');
const inspHeader = createElement('div', 'inspiration-item-header');
const inspTitle = createElement('div', 'inspiration-item-title');
inspTitle.textContent = `灵光 #${idx + 1}`;
inspHeader.appendChild(inspTitle);
const deleteInspBtn = createButton('删除', 'btn-danger', () => {
if (confirm('确定要删除这个灵光一闪吗?')) {
state.currentCard.var.inspiration.splice(idx, 1);
render();
}
});
deleteInspBtn.style.padding = '4px 8px';
deleteInspBtn.style.fontSize = '12px';
inspHeader.appendChild(deleteInspBtn);
inspItem.appendChild(inspHeader);
// AP输入
const inspApInput = createInput('text', insp.ap, (value) => {
insp.ap = value;
}, 'AP');
inspApInput.style.marginBottom = '8px';
inspApInput.style.fontSize = '13px';
inspItem.appendChild(inspApInput);
// 类型选择
const inspTypeSelect = createSelect(
['', '攻击', '技能', '强化'],
insp.type || '',
(value) => {
insp.type = value;
}
);
inspTypeSelect.wrapper.style.marginBottom = '8px';
inspItem.appendChild(inspTypeSelect.wrapper);
// 描述输入
const inspDescInput = createInput('textarea', insp.desc_global, (value) => {
insp.desc_global = value;
}, '描述');
inspDescInput.style.minHeight = '60px';
inspDescInput.style.fontSize = '13px';
inspItem.appendChild(inspDescInput);
inspirationSection.appendChild(inspItem);
});
}
listSection.appendChild(inspirationSection);
}
// 神光一闪列表
if (state.currentCard && state.currentCard.base.isgod_inspiration) {
const godInspirationSection = createElement('div', 'inspiration-section');
const godInspirationHeader = createElement('div', 'list-header');
const godInspirationTitle = createElement('div', 'list-title');
godInspirationTitle.textContent = '神光一闪列表';
godInspirationHeader.appendChild(godInspirationTitle);
godInspirationSection.appendChild(godInspirationHeader);
// 神明选择标签
const godSelectGroup = createElement('div', 'god-select-group');
['circen', 'diallos', 'nihilum', 'secred', 'vitor'].forEach(god => {
const godTab = createElement('div', 'god-tab' + (state.currentGod === god ? ' active' : ''));
godTab.textContent = god;
godTab.addEventListener('click', () => {
state.currentGod = god;
render();
});
godSelectGroup.appendChild(godTab);
});
godInspirationSection.appendChild(godSelectGroup);
// 当前神明的神光一闪列表
const currentGodInspirations = state.currentCard.var.god_inspiration[state.currentGod];
const addGodInspirationBtn = createButton('+ 添加 ' + state.currentGod + ' 神光', 'btn-primary', () => {
currentGodInspirations.push({
ap: '',
type: '',
desc_global: ''
});
render();
});
addGodInspirationBtn.style.marginBottom = '10px';
addGodInspirationBtn.style.width = '100%';
godInspirationSection.appendChild(addGodInspirationBtn);
if (currentGodInspirations.length === 0) {
const emptyState = createElement('div', 'empty-state');
emptyState.style.padding = '20px';
const emptyText = createElement('div', 'empty-state-text');
emptyText.textContent = `暂无 ${state.currentGod} 神光一闪`;
emptyState.appendChild(emptyText);
godInspirationSection.appendChild(emptyState);
} else {
currentGodInspirations.forEach((insp, idx) => {
const inspItem = createElement('div', 'inspiration-item');
const inspHeader = createElement('div', 'inspiration-item-header');
const inspTitle = createElement('div', 'inspiration-item-title');
inspTitle.textContent = `${state.currentGod} 神光 #${idx + 1}`;
inspHeader.appendChild(inspTitle);
const deleteInspBtn = createButton('删除', 'btn-danger', () => {
if (confirm('确定要删除这个神光一闪吗?')) {
currentGodInspirations.splice(idx, 1);
render();
}
});
deleteInspBtn.style.padding = '4px 8px';
deleteInspBtn.style.fontSize = '12px';
inspHeader.appendChild(deleteInspBtn);
inspItem.appendChild(inspHeader);
// AP输入
const inspApInput = createInput('text', insp.ap, (value) => {
insp.ap = value;
}, 'AP');
inspApInput.style.marginBottom = '8px';
inspApInput.style.fontSize = '13px';
inspItem.appendChild(inspApInput);
// 类型选择
const inspTypeSelect = createSelect(
['', '攻击', '技能', '强化'],
insp.type || '',
(value) => {
insp.type = value;
}
);
inspTypeSelect.wrapper.style.marginBottom = '8px';
inspItem.appendChild(inspTypeSelect.wrapper);
// 描述输入
const inspDescInput = createInput('textarea', insp.desc_global, (value) => {
insp.desc_global = value;
}, '描述');
inspDescInput.style.minHeight = '60px';
inspDescInput.style.fontSize = '13px';
inspItem.appendChild(inspDescInput);
godInspirationSection.appendChild(inspItem);
});
}
listSection.appendChild(godInspirationSection);
}
manager.appendChild(listSection);
// 右侧预览区
const previewSection = createElement('div', 'card-preview-section');
const previewTitle = createElement('div', 'section-title');
previewTitle.textContent = 'Lua 代码预览';
previewSection.appendChild(previewTitle);
// 复制按钮
const copyBtn = createButton('复制代码', 'btn-primary', () => {
const code = generateLuaCode();
navigator.clipboard.writeText(code).then(() => {
alert('代码已复制到剪贴板!');
}).catch(err => {
console.error('复制失败:', err);
// 降级方案
const textarea = document.createElement('textarea');
textarea.value = code;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert('代码已复制到剪贴板!');
});
});
copyBtn.style.marginBottom = '10px';
copyBtn.style.width = '100%';
previewSection.appendChild(copyBtn);
// 保存按钮
const saveBtn = createButton('保存到Wiki', 'btn-success', async () => {
if (!state.currentFighter) {
alert('请先选择战斗员!');
return;
}
const code = generateLuaCode();
const pageName = '模块:卡牌/' + state.currentFighter;
const loading = createElement('div', 'loading-indicator');
loading.textContent = '正在保存...';
document.body.appendChild(loading);
try {
const api = new mw.Api();
await api.postWithToken('csrf', {
action: 'edit',
title: pageName,
text: code,
summary: '通过卡牌管理器更新卡牌数据',
contentmodel: 'Scribunto'
});
alert('保存成功!');
} catch (error) {
console.error('保存失败:', error);
alert('保存失败:' + error);
} finally {
if (loading.parentNode) {
document.body.removeChild(loading);
}
}
});
saveBtn.style.marginBottom = '10px';
saveBtn.style.width = '100%';
previewSection.appendChild(saveBtn);
// 代码显示
const codePreview = createElement('div', 'code-preview');
codePreview.textContent = generateLuaCode();
previewSection.appendChild(codePreview);
manager.appendChild(previewSection);
container.appendChild(manager);
}
// 初始化
async function init() {
// 创建容器
let container = document.getElementById('card-manager-container');
if (!container) {
container = createElement('div', '');
container.id = 'card-manager-container';
// 查找插入位置
const content = document.getElementById('mw-content-text');
if (content) {
content.insertBefore(container, content.firstChild);
} else {
document.body.appendChild(container);
}
}
// 显示加载提示
const loading = createElement('div', 'loading-indicator');
loading.textContent = '正在初始化...';
document.body.appendChild(loading);
try {
// 加载战斗员列表
await loadFighters();
// 如果有战斗员,默认选择第一个
if (state.fighters.length > 0) {
state.currentFighter = state.fighters[0];
state.cards = await loadFighterCards(state.currentFighter);
if (state.cards.length > 0) {
state.currentCard = state.cards[0];
}
}
// 渲染界面
render();
} catch (error) {
console.error('初始化失败:', error);
alert('初始化失败:' + error.message);
} finally {
if (loading.parentNode) {
document.body.removeChild(loading);
}
}
}
// 等待DOM加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// 导出API供外部使用
window.CardManager = {
init: init,
render: render,
state: state,
createCard: createEmptyCard,
generateCode: generateLuaCode
};
})();