381 lines
24 KiB
JavaScript
381 lines
24 KiB
JavaScript
import { readFileSync } from 'node:fs';
|
||
|
||
const CARDS = JSON.parse(readFileSync('data/cards.json', 'utf8'));
|
||
const ENEMIES = JSON.parse(readFileSync('data/enemies.json', 'utf8'));
|
||
|
||
// 검증 (fail-fast): 잘못된 데이터면 생성 중단
|
||
const CLASSES = {
|
||
warrior: { label: '전사', maxHp: 80 },
|
||
rogue: { label: '도적', maxHp: 70 },
|
||
magician: { label: '마법사', maxHp: 70 },
|
||
};
|
||
for (const cls of Object.keys(CLASSES)) {
|
||
if (!CARDS.starterDecks?.[cls]) throw new Error(`[gen-slaydeck] starterDecks.${cls} 없음`);
|
||
for (const id of CARDS.starterDecks[cls]) {
|
||
if (!CARDS.cards[id]) throw new Error(`[gen-slaydeck] starterDecks.${cls}에 없는 카드 id 참조: ${id}`);
|
||
}
|
||
}
|
||
|
||
// 전직 옵션
|
||
const JOBS = {
|
||
warrior: [
|
||
{ id: 'fighter', name: '파이터', desc: '콤보와 다단 공격 특화\n공격으로 콤보를 쌓고\n추가타로 압박', starter: 'ComboAttack', tier: 2, parent: 'warrior' },
|
||
{ id: 'page', name: '페이지', desc: '홀리 포스와 방어 특화\n홀리 차지를 쌓아\n공격과 생존 강화', starter: 'HolyCharge', tier: 2, parent: 'warrior' },
|
||
{ id: 'spearman', name: '스피어맨', desc: '광역·장기전 계열\n대화재 · 소용돌이\n불의 심장', starter: 'Conflagration', tier: 2, parent: 'warrior' },
|
||
],
|
||
fighter: [
|
||
{ id: 'crusader', name: '크루세이더', desc: '파이터의 3차 전직\n콤보 상한과 연계 피해 강화\n파이터 카드 계승', starter: 'ComboSynergy', tier: 3, parent: 'fighter' },
|
||
],
|
||
page: [
|
||
{ id: 'knight', name: '나이트', desc: '페이지의 3차 전직\n홀리 차지를 공격·방어·회복으로 전환\n페이지 카드 계승', starter: 'DivineCharge', tier: 3, parent: 'page' },
|
||
],
|
||
spearman: [
|
||
{ id: 'berserker', name: '버서커', desc: '스피어맨의 3차 전직\n아이언클래드 장기전 풀 계승\n전사 카드 사용', starter: '', tier: 3, parent: 'spearman' },
|
||
],
|
||
magician: [
|
||
{ id: 'firepoison', name: '위자드(불·독)', desc: '화염·독 특화\n파이어 애로우\n포이즌 브레스 · 앰플', starter: 'FireArrow', tier: 2, parent: 'magician' },
|
||
{ id: 'icelightning', name: '위자드(썬·콜)', desc: '광역·빙결 특화\n썬더 볼트(전체)\n콜드 빔 · 칠링 스텝', starter: 'ThunderBolt', tier: 2, parent: 'magician' },
|
||
{ id: 'cleric', name: '클레릭', desc: '회복·축복 특화\n힐 · 블레스\n홀리 애로우', starter: 'Heal', tier: 2, parent: 'magician' },
|
||
],
|
||
rogue: [
|
||
{ id: 'assassin', name: 'Assassin', desc: '표창 중심 전직\n표창 생성과 연속 공격\n빠른 마무리', starter: 'JavelinAcceleration', tier: 2, parent: 'rogue' },
|
||
{ id: 'thief', name: 'Thief', desc: '단검 중심 전직\n드로우와 운영 강화\n빠른 연계', starter: 'DaggerAcceleration', tier: 2, parent: 'rogue' },
|
||
],
|
||
assassin: [
|
||
{ id: 'hermit', name: 'Hermit', desc: 'Assassin의 3차 전직\n표창 생성과 강화 심화\n연속 공격 완성', starter: 'SpiritJavelin', tier: 3, parent: 'assassin' },
|
||
],
|
||
thief: [
|
||
{ id: 'thiefmaster', name: 'Thief Master', desc: 'Thief의 3차 전직\n단검·교활·중독 심화\n연계 운영 완성', starter: 'Venom', tier: 3, parent: 'thief' },
|
||
],
|
||
};
|
||
for (const [cls, jobs] of Object.entries(JOBS)) {
|
||
for (const j of jobs) {
|
||
if (j.starter && !CARDS.cards[j.starter]) throw new Error(`[gen-slaydeck] JOBS.${cls}.${j.id} 대표 카드 없음: ${j.starter}`);
|
||
}
|
||
}
|
||
|
||
const CLASS_GROUPS = {
|
||
warrior: ['warrior', 'fighter', 'crusader', 'page', 'knight', 'spearman', 'berserker'],
|
||
magician: ['magician', 'firepoison', 'icelightning', 'cleric'],
|
||
rogue: ['rogue', 'assassin', 'hermit', 'thief', 'thiefmaster'],
|
||
};
|
||
|
||
const CLASS_LINEAGES = {
|
||
warrior: ['warrior'],
|
||
fighter: ['warrior', 'fighter'],
|
||
crusader: ['warrior', 'fighter', 'crusader'],
|
||
page: ['warrior', 'page'],
|
||
knight: ['warrior', 'page', 'knight'],
|
||
spearman: ['warrior', 'spearman'],
|
||
berserker: ['warrior', 'spearman', 'berserker'],
|
||
magician: ['magician'],
|
||
firepoison: ['magician', 'firepoison'],
|
||
icelightning: ['magician', 'icelightning'],
|
||
cleric: ['magician', 'cleric'],
|
||
rogue: ['rogue'],
|
||
assassin: ['rogue', 'assassin'],
|
||
hermit: ['rogue', 'assassin', 'hermit'],
|
||
thief: ['rogue', 'thief'],
|
||
thiefmaster: ['rogue', 'thief', 'thiefmaster'],
|
||
};
|
||
|
||
const JOB_META = {};
|
||
for (const [sourceClass, jobs] of Object.entries(JOBS)) {
|
||
for (const job of jobs) {
|
||
JOB_META[job.id] = {
|
||
name: job.name,
|
||
starter: job.starter,
|
||
tier: job.tier ?? 2,
|
||
parent: job.parent ?? sourceClass,
|
||
sourceClass,
|
||
};
|
||
}
|
||
}
|
||
|
||
// 영혼(soul) 메타 해금 — 2차 전직 후 보스 클리어로 영혼 적립, 로비 영혼상점에서 구매 → 다음 런 이점
|
||
const SOUL_UNLOCKS = [
|
||
{ key: 'meso', name: '두둑한 지갑', desc: '런 시작 시 메소 +60', cost: 3 },
|
||
{ key: 'hp', name: '단련된 육체', desc: '시작 최대 HP +15', cost: 4 },
|
||
{ key: 'trim', name: '덱 정제', desc: '시작 덱에서 기본 카드 1장 제거', cost: 5 },
|
||
{ key: 'relic', name: '유물 수집가', desc: '런 시작 시 유물 1개 추가', cost: 6 },
|
||
];
|
||
function luaSoulShopTable(unlocks) {
|
||
const items = unlocks.map((u) => `\t{ key = ${luaStr(u.key)}, name = ${luaStr(u.name)}, desc = ${luaStr(u.desc)}, cost = ${luaNum(u.cost)} },`).join('\n');
|
||
return `self.SoulShopDef = {\n${items}\n}`;
|
||
}
|
||
if (!ENEMIES.enemies[ENEMIES.activeEnemy]) {
|
||
throw new Error(`[gen-slaydeck] activeEnemy가 enemies에 없음: ${ENEMIES.activeEnemy}`);
|
||
}
|
||
|
||
// 카드 프레임 (사용자 제작 이미지 — 로컬 임포트 .sprite RUID, 직업 3종 × 등급 3종)
|
||
const CARDFRAMES = JSON.parse(readFileSync('data/cardframes.json', 'utf8'));
|
||
const RARITIES = ['normal', 'unique', 'legend'];
|
||
for (const [fid, fr] of Object.entries(CARDFRAMES.frames)) {
|
||
for (const r of RARITIES) {
|
||
if (!fr[r]) throw new Error(`[gen-slaydeck] cardframes.frames.${fid}.${r} RUID 없음`);
|
||
}
|
||
}
|
||
for (const [id, c] of Object.entries(CARDS.cards)) {
|
||
if (!RARITIES.includes(c.rarity)) throw new Error(`[gen-slaydeck] 카드 ${id} rarity 누락/오류: ${c.rarity}`);
|
||
const fc = CARDFRAMES.classToFrame[c.class];
|
||
if (!fc || !CARDFRAMES.frames[fc]) throw new Error(`[gen-slaydeck] 카드 ${id} class ${c.class} → 프레임 매핑 없음`);
|
||
}
|
||
function frameRuid(card) {
|
||
return CARDFRAMES.frames[CARDFRAMES.classToFrame[card.class]][card.rarity];
|
||
}
|
||
function luaFramesTable() {
|
||
const frames = Object.entries(CARDFRAMES.frames).map(([fid, fr]) =>
|
||
`\t${fid} = { normal = ${luaStr(fr.normal)}, unique = ${luaStr(fr.unique)}, legend = ${luaStr(fr.legend)} },`).join('\n');
|
||
const cls = Object.entries(CARDFRAMES.classToFrame).map(([c, f]) => `\t${c} = ${luaStr(f)},`).join('\n');
|
||
return `self.CardFrames = {\n${frames}\n}\nself.ClassToFrame = {\n${cls}\n}`;
|
||
}
|
||
function luaNodeIconsTable() {
|
||
const rows = Object.entries(NODEICONS.icons).map(([t, ruid]) => `\t${t} = ${luaStr(ruid)},`).join('\n');
|
||
return `self.NodeIcons = {\n${rows}\n}`;
|
||
}
|
||
function luaCharsTable() {
|
||
const rows = Object.entries(CHARS.portraits).map(([c, ruid]) => `\t${c} = ${luaStr(ruid)},`).join('\n');
|
||
return `self.ClassPortraits = {\n${rows}\n}`;
|
||
}
|
||
|
||
// 맵은 런타임 절차 생성(GenerateMap Lua ↔ tools/map/rogue-map.mjs 미러). 정적 data/map.json 제거됨.
|
||
const MAP_ROWS = 6;
|
||
const MAP_COLS = 4;
|
||
|
||
const CHEST_CLOSED_RUID = '43df67920c0d43298e0d93c02c6afa71';
|
||
const CHEST_OPEN_RUID = '09c5cee56fd640bf8ae3a18ce50f4759';
|
||
|
||
const NODEICONS = JSON.parse(readFileSync('data/nodeicons.json', 'utf8'));
|
||
for (const t of ['combat', 'elite', 'boss', 'shop', 'rest', 'treasure']) {
|
||
if (!/^[0-9a-f]{32}$/.test((NODEICONS.icons || {})[t] || '')) throw new Error(`[gen-slaydeck] nodeicons.json icons.${t} RUID 누락/형식오류`);
|
||
}
|
||
if (!/^[0-9a-f]{32}$/.test(NODEICONS.background || '')) throw new Error('[gen-slaydeck] nodeicons.json background RUID 누락/형식오류');
|
||
|
||
const CHARS = JSON.parse(readFileSync('data/characters.json', 'utf8'));
|
||
for (const c of ['warrior', 'magician', 'rogue']) {
|
||
if (!/^[0-9a-f]{32}$/.test((CHARS.portraits || {})[c] || '')) throw new Error(`[gen-slaydeck] characters.json portraits.${c} RUID 누락/형식오류`);
|
||
}
|
||
|
||
const CAM = JSON.parse(readFileSync('data/camera.json', 'utf8'));
|
||
|
||
const RELICS = JSON.parse(readFileSync('data/relics.json', 'utf8'));
|
||
if (!RELICS.relics[RELICS.startingRelic]) throw new Error(`[gen-slaydeck] startingRelic 없음: ${RELICS.startingRelic}`);
|
||
for (const id of RELICS.relicPool) {
|
||
if (!RELICS.relics[id]) throw new Error(`[gen-slaydeck] relicPool에 없는 유물 id: ${id}`);
|
||
}
|
||
function luaRelicsTable(relics) {
|
||
const lines = Object.entries(relics).map(([id, r]) =>
|
||
`\t${id} = { name = ${luaStr(r.name)}, desc = ${luaStr(r.desc)}, hook = ${luaStr(r.hook)}, effect = ${luaStr(r.effect)}, value = ${luaNum(r.value)}, icon = ${luaStr(r.icon || '')} },`);
|
||
return `self.Relics = {\n${lines.join('\n')}\n}`;
|
||
}
|
||
|
||
const POTIONS = JSON.parse(readFileSync('data/potions.json', 'utf8'));
|
||
for (const [pid, p] of Object.entries(POTIONS.potions)) {
|
||
if (!p.name || !p.effect || p.value == null) throw new Error(`[gen-slaydeck] potion 필드 누락: ${pid}`);
|
||
}
|
||
function luaPotionsTable(potions) {
|
||
const lines = Object.entries(potions).map(([id, p]) =>
|
||
`\t${id} = { name = ${luaStr(p.name)}, desc = ${luaStr(p.desc)}, effect = ${luaStr(p.effect)}, value = ${luaNum(p.value)}, icon = ${luaStr(p.icon || '')} },`);
|
||
return `self.Potions = {\n${lines.join('\n')}\n}`;
|
||
}
|
||
|
||
function luaIntentsArray(intents) {
|
||
return '{ ' + intents.map((it) => {
|
||
const fields = [`kind = ${luaStr(it.kind)}`, `value = ${luaNum(it.value != null ? it.value : 0)}`];
|
||
if (it.effect != null) fields.push(`effect = ${luaStr(it.effect)}`);
|
||
if (it.card != null) fields.push(`card = ${luaStr(it.card)}`);
|
||
if (it.count != null) fields.push(`count = ${luaNum(it.count)}`);
|
||
return `{ ${fields.join(', ')} }`;
|
||
}).join(', ') + ' }';
|
||
}
|
||
function luaEnemiesTable(enemies) {
|
||
const lines = Object.entries(enemies).map(([id, e]) =>
|
||
`\t${id} = { name = ${luaStr(e.name)}, maxHp = ${luaNum(e.maxHp)}, intents = ${luaIntentsArray(e.intents)} },`);
|
||
return `self.Enemies = {\n${lines.join('\n')}\n}`;
|
||
}
|
||
|
||
function luaStr(s) {
|
||
return '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n') + '"';
|
||
}
|
||
function luaNum(n) {
|
||
const v = Number(n);
|
||
if (!Number.isFinite(v)) return '0';
|
||
if (Number.isInteger(v)) return String(v);
|
||
return String(v);
|
||
}
|
||
function luaJobsTable(jobs) {
|
||
const cls = Object.entries(jobs).map(([clsId, list]) => {
|
||
const items = list.map((j) =>
|
||
`\t\t{ id = ${luaStr(j.id)}, name = ${luaStr(j.name)}, desc = ${luaStr(j.desc)}, starter = ${luaStr(j.starter)}, tier = ${luaNum(j.tier ?? 2)}, parent = ${luaStr(j.parent ?? clsId)} },`).join('\n');
|
||
return `\t${clsId} = {\n${items}\n\t},`;
|
||
}).join('\n');
|
||
return `self.Jobs = {\n${cls}\n}`;
|
||
}
|
||
function luaClassGroupsTable(groups) {
|
||
const rows = Object.entries(groups).map(([clsId, list]) =>
|
||
`\t${clsId} = { ${list.map(luaStr).join(', ')} },`).join('\n');
|
||
return `self.ClassGroups = {\n${rows}\n}`;
|
||
}
|
||
function luaClassLineagesTable(lineages) {
|
||
const rows = Object.entries(lineages).map(([clsId, list]) =>
|
||
`\t${clsId} = { ${list.map(luaStr).join(', ')} },`).join('\n');
|
||
return `self.ClassLineages = {\n${rows}\n}`;
|
||
}
|
||
function luaJobMetaTable(meta) {
|
||
const rows = Object.entries(meta).map(([jobId, entry]) =>
|
||
`\t${jobId} = { name = ${luaStr(entry.name)}, starter = ${luaStr(entry.starter)}, tier = ${luaNum(entry.tier)}, parent = ${luaStr(entry.parent)}, sourceClass = ${luaStr(entry.sourceClass)} },`);
|
||
return `self.JobMeta = {\n${rows.join('\n')}\n}`;
|
||
}
|
||
function luaCardsTable(cards) {
|
||
const lines = Object.entries(cards).map(([id, c]) => {
|
||
const fields = [`name = ${luaStr(c.name)}`, `cost = ${luaNum(c.cost)}`, `desc = ${luaStr(c.desc)}`, `kind = ${luaStr(c.kind)}`];
|
||
if (c.damage != null) fields.push(`damage = ${luaNum(c.damage)}`);
|
||
if (c.damageFromCurrentBlock != null) fields.push(`damageFromCurrentBlock = ${luaNum(c.damageFromCurrentBlock)}`);
|
||
if (c.damagePerCombo != null) fields.push(`damagePerCombo = ${luaNum(c.damagePerCombo)}`);
|
||
if (c.damagePerHolyCharge != null) fields.push(`damagePerHolyCharge = ${luaNum(c.damagePerHolyCharge)}`);
|
||
if (c.attackDamagePerCombo != null) fields.push(`attackDamagePerCombo = ${luaNum(c.attackDamagePerCombo)}`);
|
||
if (c.damageNameMatch != null) fields.push(`damageNameMatch = ${luaStr(c.damageNameMatch)}`);
|
||
if (c.damagePerOwnedNameMatch != null) fields.push(`damagePerOwnedNameMatch = ${luaNum(c.damagePerOwnedNameMatch)}`);
|
||
if (c.damagePerOtherHandCard != null) fields.push(`damagePerOtherHandCard = ${luaNum(c.damagePerOtherHandCard)}`);
|
||
if (c.damagePerAttackPlayedThisTurn != null) fields.push(`damagePerAttackPlayedThisTurn = ${luaNum(c.damagePerAttackPlayedThisTurn)}`);
|
||
if (c.damagePerDiscardedThisTurn != null) fields.push(`damagePerDiscardedThisTurn = ${luaNum(c.damagePerDiscardedThisTurn)}`);
|
||
if (c.damagePerSkillInHand != null) fields.push(`damagePerSkillInHand = ${luaNum(c.damagePerSkillInHand)}`);
|
||
if (c.damagePerCardDrawnThisCombat != null) fields.push(`damagePerCardDrawnThisCombat = ${luaNum(c.damagePerCardDrawnThisCombat)}`);
|
||
if (c.damagePerTurn != null) fields.push(`damagePerTurn = ${luaNum(c.damagePerTurn)}`);
|
||
if (c.cardPlayedDamage != null) fields.push(`cardPlayedDamage = ${luaNum(c.cardPlayedDamage)}`);
|
||
if (c.cardPlayedRandomDamage != null) fields.push(`cardPlayedRandomDamage = ${luaNum(c.cardPlayedRandomDamage)}`);
|
||
if (c.attackPlayedDamage != null) fields.push(`attackPlayedDamage = ${luaNum(c.attackPlayedDamage)}`);
|
||
if (c.firstCardDamageBonus != null) fields.push(`firstCardDamageBonus = ${luaNum(c.firstCardDamageBonus)}`);
|
||
if (c.rewardOnKill != null) fields.push(`rewardOnKill = ${luaNum(c.rewardOnKill)}`);
|
||
if (c.maxHpOnKill != null) fields.push(`maxHpOnKill = ${luaNum(c.maxHpOnKill)}`);
|
||
if (c.intangible != null) fields.push(`intangible = ${luaNum(c.intangible)}`);
|
||
if (c.endTurnDexLoss != null) fields.push(`endTurnDexLoss = ${luaNum(c.endTurnDexLoss)}`);
|
||
if (c.poisonPerTurn != null) fields.push(`poisonPerTurn = ${luaNum(c.poisonPerTurn)}`);
|
||
if (c.attackPoison != null) fields.push(`attackPoison = ${luaNum(c.attackPoison)}`);
|
||
if (c.otherHandAtLeast != null) fields.push(`otherHandAtLeast = ${luaNum(c.otherHandAtLeast)}`);
|
||
if (c.bonusHitsWhenOtherHandAtLeast != null) fields.push(`bonusHitsWhenOtherHandAtLeast = ${luaNum(c.bonusHitsWhenOtherHandAtLeast)}`);
|
||
if (c.block != null) fields.push(`block = ${luaNum(c.block)}`);
|
||
if (c.blockGainMultiplier != null) fields.push(`blockGainMultiplier = ${luaNum(c.blockGainMultiplier)}`);
|
||
if (c.blockPerDamageDealtThisTurn != null) fields.push(`blockPerDamageDealtThisTurn = ${luaNum(c.blockPerDamageDealtThisTurn)}`);
|
||
if (c.strength != null) fields.push(`strength = ${luaNum(c.strength)}`);
|
||
if (c.dex != null) fields.push(`dex = ${luaNum(c.dex)}`);
|
||
if (c.thorns != null) fields.push(`thorns = ${luaNum(c.thorns)}`);
|
||
if (c.cardPlayedBlock != null) fields.push(`cardPlayedBlock = ${luaNum(c.cardPlayedBlock)}`);
|
||
if (c.comboOnAttack != null) fields.push(`comboOnAttack = ${luaNum(c.comboOnAttack)}`);
|
||
if (c.comboMax != null) fields.push(`comboMax = ${luaNum(c.comboMax)}`);
|
||
if (c.attackWeak != null) fields.push(`attackWeak = ${luaNum(c.attackWeak)}`);
|
||
if (c.holyChargeOnHolyForce != null) fields.push(`holyChargeOnHolyForce = ${luaNum(c.holyChargeOnHolyForce)}`);
|
||
if (c.holyChargeMax != null) fields.push(`holyChargeMax = ${luaNum(c.holyChargeMax)}`);
|
||
if (c.damageTakenReduction != null) fields.push(`damageTakenReduction = ${luaNum(c.damageTakenReduction)}`);
|
||
if (c.blockOnDamaged != null) fields.push(`blockOnDamaged = ${luaNum(c.blockOnDamaged)}`);
|
||
if (c.strengthOnDamagedOnce != null) fields.push(`strengthOnDamagedOnce = ${luaNum(c.strengthOnDamagedOnce)}`);
|
||
if (c.drawOnExhaust != null) fields.push(`drawOnExhaust = ${luaNum(c.drawOnExhaust)}`);
|
||
if (c.drawNameMatchAutoPlay != null) fields.push(`drawNameMatchAutoPlay = ${luaStr(c.drawNameMatchAutoPlay)}`);
|
||
if (c.weak != null) fields.push(`weak = ${luaNum(c.weak)}`);
|
||
if (c.vuln != null) fields.push(`vuln = ${luaNum(c.vuln)}`);
|
||
if (c.powerEffect != null) fields.push(`powerEffect = ${luaStr(c.powerEffect)}`);
|
||
if (c.value != null) fields.push(`value = ${luaNum(c.value)}`);
|
||
if (!c.class) throw new Error(`[gen-slaydeck] 카드 ${id}에 class 누락`);
|
||
fields.push(`class = ${luaStr(c.class)}`);
|
||
fields.push(`rarity = ${luaStr(c.rarity)}`);
|
||
if (c.hits != null) fields.push(`hits = ${luaNum(c.hits)}`);
|
||
if (c.pierce === true) fields.push('pierce = true');
|
||
if (c.selfVuln != null) fields.push(`selfVuln = ${luaNum(c.selfVuln)}`);
|
||
if (c.draw != null) fields.push(`draw = ${luaNum(c.draw)}`);
|
||
if (c.drawUntilHandSize != null) fields.push(`drawUntilHandSize = ${luaNum(c.drawUntilHandSize)}`);
|
||
if (c.drawSkillBlock != null) fields.push(`drawSkillBlock = ${luaNum(c.drawSkillBlock)}`);
|
||
if (c.drawDamage != null) fields.push(`drawDamage = ${luaNum(c.drawDamage)}`);
|
||
if (c.drawPoison != null) fields.push(`drawPoison = ${luaNum(c.drawPoison)}`);
|
||
if (c.exhaustHandNonAttack === true) fields.push('exhaustHandNonAttack = true');
|
||
if (c.exhaustHandAll === true) fields.push('exhaustHandAll = true');
|
||
if (c.drawPerExhausted != null) fields.push(`drawPerExhausted = ${luaNum(c.drawPerExhausted)}`);
|
||
if (c.blockPerExhaustedCard != null) fields.push(`blockPerExhaustedCard = ${luaNum(c.blockPerExhaustedCard)}`);
|
||
if (c.addRandomCardCount != null) fields.push(`addRandomCardCount = ${luaNum(c.addRandomCardCount)}`);
|
||
if (c.addRandomCardPerExhausted != null) fields.push(`addRandomCardPerExhausted = ${luaNum(c.addRandomCardPerExhausted)}`);
|
||
if (c.addRandomCardKind != null) fields.push(`addRandomCardKind = ${luaStr(c.addRandomCardKind)}`);
|
||
if (c.addRandomCardSameClass === true) fields.push('addRandomCardSameClass = true');
|
||
if (c.addedCardsCostZeroThisTurn === true) fields.push('addedCardsCostZeroThisTurn = true');
|
||
if (c.playTopDrawPileCount != null) fields.push(`playTopDrawPileCount = ${luaNum(c.playTopDrawPileCount)}`);
|
||
if (c.playTopDrawPileCountPerEnergy != null) fields.push(`playTopDrawPileCountPerEnergy = ${luaNum(c.playTopDrawPileCountPerEnergy)}`);
|
||
if (c.heal != null) fields.push(`heal = ${luaNum(c.heal)}`);
|
||
if (c.healPerHolyCharge != null) fields.push(`healPerHolyCharge = ${luaNum(c.healPerHolyCharge)}`);
|
||
if (c.gainEnergy != null) fields.push(`gainEnergy = ${luaNum(c.gainEnergy)}`);
|
||
if (c.comboGain != null) fields.push(`comboGain = ${luaNum(c.comboGain)}`);
|
||
if (c.removePlayerDebuffs === true) fields.push('removePlayerDebuffs = true');
|
||
if (c.holyForce === true) fields.push('holyForce = true');
|
||
if (c.holyChargeGain != null) fields.push(`holyChargeGain = ${luaNum(c.holyChargeGain)}`);
|
||
if (c.blockPerHolyCharge != null) fields.push(`blockPerHolyCharge = ${luaNum(c.blockPerHolyCharge)}`);
|
||
if (c.holyChargeSpendAll === true) fields.push('holyChargeSpendAll = true');
|
||
if (c.poison != null) fields.push(`poison = ${luaNum(c.poison)}`);
|
||
if (c.discard != null) fields.push(`discard = ${luaNum(c.discard)}`);
|
||
if (c.discardAll === true) fields.push('discardAll = true');
|
||
if (c.drawPerDiscarded != null) fields.push(`drawPerDiscarded = ${luaNum(c.drawPerDiscarded)}`);
|
||
if (c.addShiv != null) fields.push(`addShiv = ${luaNum(c.addShiv)}`);
|
||
if (c.turnStartShiv != null) fields.push(`turnStartShiv = ${luaNum(c.turnStartShiv)}`);
|
||
if (c.turnStartDraw != null) fields.push(`turnStartDraw = ${luaNum(c.turnStartDraw)}`);
|
||
if (c.turnStartDiscard != null) fields.push(`turnStartDiscard = ${luaNum(c.turnStartDiscard)}`);
|
||
if (c.handCostZeroThisTurn === true) fields.push('handCostZeroThisTurn = true');
|
||
if (c.drawDisabledThisTurn === true) fields.push('drawDisabledThisTurn = true');
|
||
if (c.addShivPerDiscard === true) fields.push('addShivPerDiscard = true');
|
||
if (c.useAllEnergy === true) fields.push('useAllEnergy = true');
|
||
if (c.shivDamageBonus != null) fields.push(`shivDamageBonus = ${luaNum(c.shivDamageBonus)}`);
|
||
if (c.firstShivDamageBonus != null) fields.push(`firstShivDamageBonus = ${luaNum(c.firstShivDamageBonus)}`);
|
||
if (c.shivRetain === true) fields.push('shivRetain = true');
|
||
if (c.shivAoe === true) fields.push('shivAoe = true');
|
||
if (c.attackDamageVsWeakMultiplier != null) fields.push(`attackDamageVsWeakMultiplier = ${luaNum(c.attackDamageVsWeakMultiplier)}`);
|
||
if (c.poisonHits != null) fields.push(`poisonHits = ${luaNum(c.poisonHits)}`);
|
||
if (c.poisonRandomTargets === true) fields.push('poisonRandomTargets = true');
|
||
if (c.poisonIfTargetPoisoned === true) fields.push('poisonIfTargetPoisoned = true');
|
||
if (c.xDamagePerEnergy != null) fields.push(`xDamagePerEnergy = ${luaNum(c.xDamagePerEnergy)}`);
|
||
if (c.xWeakPerEnergy != null) fields.push(`xWeakPerEnergy = ${luaNum(c.xWeakPerEnergy)}`);
|
||
if (c.nextTurnBlock != null) fields.push(`nextTurnBlock = ${luaNum(c.nextTurnBlock)}`);
|
||
if (c.nextTurnDraw != null) fields.push(`nextTurnDraw = ${luaNum(c.nextTurnDraw)}`);
|
||
if (c.nextTurnKeepBlock === true) fields.push('nextTurnKeepBlock = true');
|
||
if (c.nextTurnAttackMultiplier != null) fields.push(`nextTurnAttackMultiplier = ${luaNum(c.nextTurnAttackMultiplier)}`);
|
||
if (c.nextTurnCopies != null) fields.push(`nextTurnCopies = ${luaNum(c.nextTurnCopies)}`);
|
||
if (c.nextTurnSelectHandCard === true) fields.push('nextTurnSelectHandCard = true');
|
||
if (c.nextTurnSelectPrompt != null) fields.push(`nextTurnSelectPrompt = ${luaStr(c.nextTurnSelectPrompt)}`);
|
||
if (c.nextSkillRepeatCount != null) fields.push(`nextSkillRepeatCount = ${luaNum(c.nextSkillRepeatCount)}`);
|
||
if (c.nextSkillCostZero === true) fields.push('nextSkillCostZero = true');
|
||
if (c.skillCostReductionThisTurn != null) fields.push(`skillCostReductionThisTurn = ${luaNum(c.skillCostReductionThisTurn)}`);
|
||
if (c.skillSlyOnPlay === true) fields.push('skillSlyOnPlay = true');
|
||
if (c.turnHandSlyCount != null) fields.push(`turnHandSlyCount = ${luaNum(c.turnHandSlyCount)}`);
|
||
if (c.combatCostReductionOnPlay != null) fields.push(`combatCostReductionOnPlay = ${luaNum(c.combatCostReductionOnPlay)}`);
|
||
if (c.randomTargetEachHit === true) fields.push('randomTargetEachHit = true');
|
||
if (c.repeatOnKill === true) fields.push('repeatOnKill = true');
|
||
if (c.affectsAllEnemies === true) fields.push('affectsAllEnemies = true');
|
||
if (c.removeEnemyBlock === true) fields.push('removeEnemyBlock = true');
|
||
if (c.removeEnemyArtifact === true) fields.push('removeEnemyArtifact = true');
|
||
if (c.enemyStrengthLossThisTurn != null) fields.push(`enemyStrengthLossThisTurn = ${luaNum(c.enemyStrengthLossThisTurn)}`);
|
||
if (c.extraPoisonTicks != null) fields.push(`extraPoisonTicks = ${luaNum(c.extraPoisonTicks)}`);
|
||
if (c.poisonApplicationBurstEvery != null) fields.push(`poisonApplicationBurstEvery = ${luaNum(c.poisonApplicationBurstEvery)}`);
|
||
if (c.poisonApplicationBurstDamage != null) fields.push(`poisonApplicationBurstDamage = ${luaNum(c.poisonApplicationBurstDamage)}`);
|
||
if (c.innate === true) fields.push('innate = true');
|
||
if (c.playableWhenDrawPileEmpty === true) fields.push('playableWhenDrawPileEmpty = true');
|
||
if (c.sly === true) fields.push('sly = true');
|
||
if (c.retain === true) fields.push('retain = true');
|
||
if (c.exhaust === true || String(c.desc || '').includes('소멸.')) fields.push('exhaust = true');
|
||
if (c.aoe === true) fields.push('aoe = true');
|
||
if (c.unplayable === true) fields.push('unplayable = true');
|
||
if (c.curse === true) fields.push('curse = true');
|
||
if (c.token === true) fields.push('token = true');
|
||
if (c.endTurnDamage != null) fields.push(`endTurnDamage = ${luaNum(c.endTurnDamage)}`);
|
||
if (c.fx != null) fields.push(`fx = ${luaStr(c.fx)}`);
|
||
if (c.image != null) fields.push(`image = ${luaStr(c.image)}`);
|
||
return `\t${id} = { ${fields.join(', ')} },`;
|
||
});
|
||
return `self.Cards = {\n${lines.join('\n')}\n}`;
|
||
}
|
||
function luaDeckTable(deck) {
|
||
return `self.DrawPile = { ${deck.map(luaStr).join(', ')} }`;
|
||
}
|
||
|
||
export {
|
||
CARDS, ENEMIES, CLASSES, JOBS, JOB_META, CLASS_GROUPS, CLASS_LINEAGES, SOUL_UNLOCKS,
|
||
luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable,
|
||
luaCharsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS,
|
||
CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable,
|
||
luaStr, luaJobsTable, luaClassGroupsTable, luaClassLineagesTable, luaJobMetaTable,
|
||
luaCardsTable, luaDeckTable,
|
||
};
|