주간 의회 버그 픽스 및 코어 시스템 고도화 계획 진행 (적용 안된것 많음. 체크 필요)
This commit is contained in:
1
src/data/mysteries.csv
Normal file
1
src/data/mysteries.csv
Normal file
@@ -0,0 +1 @@
|
||||
id,title,req_tags,cost_anomaly,reward_asset_id,penalty_entropy
|
||||
|
341
src/index.html
341
src/index.html
@@ -64,6 +64,8 @@
|
||||
.clock-hand { position: absolute; top: 50%; left: 50%; width: 45%; height: 2px; background: #fff; transform-origin: left center; transform: rotate(270deg); transition: transform 0.5s; }
|
||||
.share-bars { width: 100%; display: flex; height: 6px; background: #000; border-radius: 3px; overflow: hidden; margin-top: 5px; }
|
||||
.share-segment { height: 100%; transition: width 0.3s; }
|
||||
.share-donut { width: 36px; height: 36px; border-radius: 50%; display: flex; justify-content: center; align-items: center; position: relative; }
|
||||
.donut-hole { width: 22px; height: 22px; border-radius: 50%; background: #1a1a1a; position: absolute; }
|
||||
|
||||
.desk-area { flex: 1; position: relative; display: flex; justify-content: center; align-items: center; perspective: 1000px; background: #2d1b14; touch-action: none; overflow: hidden; }
|
||||
|
||||
@@ -195,18 +197,22 @@
|
||||
<div class="gauge-row">
|
||||
<div class="stat-item"><div class="trust-bar"><div class="trust-fill" id="bar-trust"></div></div><span class="stat-label">신임도 <span id="txt-trust">80%</span></span></div>
|
||||
<div class="stat-item"><div class="entropy-clock"><div class="clock-face"></div><div class="clock-hand" id="hand-entropy"></div></div><span class="stat-label">종말시계</span></div>
|
||||
<div class="stat-item" style="width: 35%;" onclick="openModal('faction-modal')"><div class="share-bars" id="bar-share"></div><span class="stat-label">지분율 <i class="fa-solid fa-circle-info text-[8px]"></i></span></div>
|
||||
<div class="stat-item" style="width: 35%;" onclick="openModal('faction-modal')"><div class="share-donut" id="donut-share"><div class="donut-hole"></div></div><span class="stat-label">성향(지분) <i class="fa-solid fa-circle-info text-[8px]"></i></span></div>
|
||||
</div>
|
||||
|
||||
<div id="engine-hud" class="mt-2 flex justify-between items-center text-[10px] bg-gray-800 p-1 px-3 w-full border border-gray-600 shadow-inner rounded" style="margin-top: 8px;">
|
||||
<div><i class="fa-solid fa-microchip text-yellow-500"></i> 누적 태그: <span id="hud-tags" class="text-white font-bold ml-1">없음</span></div>
|
||||
<div id="engine-hud" class="mt-2 text-[10px] bg-gray-800 p-2 w-full border border-gray-600 shadow-inner rounded" style="margin-top: 8px;">
|
||||
<div class="flex justify-between items-center mb-1">
|
||||
<div class="font-bold text-gray-300"><i class="fa-solid fa-microchip text-yellow-500"></i> 누적 빌드업 태그</div>
|
||||
<div id="hud-synergy-badge" class="text-gray-500 transition-colors duration-500"><i class="fa-solid fa-bolt"></i> 연쇄 없음</div>
|
||||
</div>
|
||||
<div id="hud-tags-container" class="flex flex-wrap gap-1 mt-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="desk-area">
|
||||
<div class="doc-card" id="current-card" style="display:none;">
|
||||
<div class="folder-tab" id="c-dept">부서</div>
|
||||
<div id="c-conflict" class="absolute top-2 right-2 font-bold text-yellow-500 bg-gray-800 px-3 py-1 rounded shadow-md border border-yellow-700 hidden text-[11px] z-10" style="transform: rotate(2deg);"></div>
|
||||
<div class="warning-postit" id="c-warning">경고</div>
|
||||
<div class="lock-chain-overlay" id="lock-overlay"><i class="fa-solid fa-link chains"></i><div class="lock-msg" id="lock-overlay-msg"><span id="lock-type-text"></span><br><span id="lock-reason" class="text-yellow-300 text-[10px] mt-1 block"></span></div></div>
|
||||
<div class="law-warning-popup" id="law-popup"><div class="law-title" id="law-popup-title"></div><div class="law-desc" id="law-popup-desc"></div></div>
|
||||
@@ -232,9 +238,10 @@
|
||||
<div class="p-row">엔트로피: <span id="p-entropy"></span></div>
|
||||
<div class="p-row mt-1">신임도: <span id="p-trust"></span></div>
|
||||
<hr class="border-gray-600 my-1">
|
||||
<div class="text-[9px] font-bold text-gray-300">태그 & 우호도 변화:</div>
|
||||
<div id="p-tag-gain" class="text-[9px] text-yellow-400 mt-1"></div>
|
||||
<div id="p-faction-change" class="text-[9px] mt-1"></div>
|
||||
<div class="text-[9px] font-bold text-gray-300">획득 태그 (빌드업):</div>
|
||||
<div id="p-tag-gain" class="text-[10px] font-bold text-yellow-400 mt-1 mb-2 bg-gray-800 rounded px-1 py-1"></div>
|
||||
<div class="text-[9px] font-bold text-gray-300">성향 태그 (우호도) 변화:</div>
|
||||
<div id="p-faction-change" class="text-[9px] mt-1 bg-gray-800 rounded px-1 py-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay-stamp stamp-approve" id="stamp-approve">승인</div>
|
||||
@@ -283,6 +290,7 @@
|
||||
<div class="mt-3"><div class="flex justify-between text-[9px] font-bold mb-1"><span class="text-blue-400">A 지지 (<span id="vote-a-val">50</span>%)</span><span class="text-red-400">B 지지 (<span id="vote-b-val">50</span>%)</span></div><div class="vote-bar-container"><div class="majority-line"></div><div class="vote-segment bg-blue-600" id="vote-a" style="width:50%"></div><div class="vote-segment bg-red-600" id="vote-b" style="width:50%"></div></div></div>
|
||||
<div class="grid grid-cols-2 gap-2 mt-3 text-[8px]"><div><div class="text-blue-400 border-b border-blue-900 mb-1">A 찬성 파벌</div><div id="list-support-a" class="text-gray-400 space-y-1"></div></div><div><div class="text-red-400 border-b border-red-900 mb-1">B 찬성 파벌</div><div id="list-support-b" class="text-gray-400 space-y-1"></div></div></div>
|
||||
<div class="mt-3 pt-3 border-t border-gray-700"><div class="text-[9px] text-yellow-500 mb-1 font-bold">소수 정당 로비 (정치 자본 -10)</div><div class="lobby-target-list" id="lobby-targets"></div></div>
|
||||
<div class="mt-2 pt-2 border-t border-gray-700"><div class="text-[9px] text-red-500 mb-1 font-bold">블랙리스트 탄압 (무료, 영구적 파국)</div><div class="lobby-target-list" id="blacklist-targets"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-btn" onclick="endCouncil()">결과 확정 및 다음 주 시작</div>
|
||||
@@ -318,7 +326,8 @@
|
||||
<script>
|
||||
const SHEET_URLS = {
|
||||
factions: "./data/factions.csv", agendas: "./data/agendas.csv", quests: "./data/quests.csv",
|
||||
bills: "./data/bills.csv", assets: "./data/assets.csv", scenarios: "./data/scenarios.csv"
|
||||
bills: "./data/bills.csv", assets: "./data/assets.csv", scenarios: "./data/scenarios.csv",
|
||||
mysteries: "./data/mysteries.csv"
|
||||
};
|
||||
|
||||
const EFFECT_NAMES = { anomaly: "데이터", political: "정치 자본", trust: "신임도", entropy: "엔트로피" };
|
||||
@@ -333,65 +342,86 @@
|
||||
};
|
||||
|
||||
function normalizeKey(str) { return String(str).replace(/^\uFEFF/, '').trim().toLowerCase(); }
|
||||
function normalizeValue(val) { return typeof val === 'string' ? val.trim() : val; }
|
||||
|
||||
function parseCSV(url) {
|
||||
return new Promise((resolve) => {
|
||||
if (!url || url.trim() === "") { resolve(null); return; }
|
||||
Papa.parse(url, {
|
||||
header: true, skipEmptyLines: true, download: true,
|
||||
complete: function(results) { resolve(results.data.map(row => { let r={}; for(let k in row) r[normalizeKey(k)]=row[k]; return r; })); },
|
||||
complete: function(results) {
|
||||
resolve(results.data.map(row => {
|
||||
let r={};
|
||||
for(let k in row) r[normalizeKey(k)] = normalizeValue(row[k]);
|
||||
return r;
|
||||
}));
|
||||
},
|
||||
error: function() { resolve(null); }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadFallbackData() {
|
||||
if (DB_FACTIONS.length === 0) DB_FACTIONS = [
|
||||
{ id: 'iron', name: '철의 수호당', color: '#ef4444', share: 30, rival: 'libra', like_tag: '군사', hate_tag: '오컬트', desc: "괴수 즉결 처형 및 군사력 증강" },
|
||||
{ id: 'libra', name: '지식 보존당', color: '#3b82f6', share: 20, rival: 'iron', like_tag: '과학', hate_tag: '군사', desc: "생포 및 연구를 통한 이해" },
|
||||
{ id: 'pure', name: '순수 인간당', color: '#10b981', share: 15, rival: 'gene', like_tag: '순수', hate_tag: '변이', desc: "인간 중심주의" },
|
||||
{ id: 'trade', name: '차원 무역당', color: '#f59e0b', share: 10, rival: 'covenant', like_tag: '자본', hate_tag: '숭배', desc: "괴수 무역을 통한 자본 축적" }
|
||||
];
|
||||
if (DB_AGENDAS.length === 0) DB_AGENDAS = [{ id: "AG_01", title: "특별 경계 주간", desc: "태그 획득 가속" }];
|
||||
if (DB_QUESTS.length === 0) DB_QUESTS = [{ id: "Q_01", title: "승인 2회", target: { val: 2 }, reward_txt: "정치자본 +20", penalty_txt: "신임도 -10" }];
|
||||
|
||||
if (DB_BILLS.length === 0) DB_BILLS = [
|
||||
{ id: "B_01", name: "종합 군사 지원 특별법", desc: "[군사] 태그 혜택 및 미스터리 소모 할인", tag: "none", target_tag: "군사", type: "buff_tag", buff_tag: "군사", flavor: "힘이 정의다.", is_buff: true },
|
||||
{ id: "B_02", name: "기초 과학 집중 육성", desc: "[과학] 태그 혜택 증대", tag: "none", target_tag: "과학", type: "buff_tag", buff_tag: "과학", flavor: "연구가 미래다.", is_buff: true },
|
||||
{ id: "B_03", name: "자본 감축 조약", desc: "모든 [자본] 자산 무효화 및 페널티", tag: "none", target_tag: "자본", type: "nullify_tag", nullify_tag: "자본", flavor: "부패를 척결하겠습니다.", is_buff: false },
|
||||
{ id: "B_04", name: "사살 및 군사 억제법", desc: "모든 [군사] 무효화 및 작전 승인 금지", tag: "kill", target_tag: "군사", type: "lock_approve", nullify_tag: "군사", flavor: "폭력은 답이 아닙니다.", is_buff: false }
|
||||
];
|
||||
|
||||
if (DB_ASSETS.length === 0) DB_ASSETS = [
|
||||
{ id: "AST_001", name: "유령 커피머신 (Relic)", desc: "커피 맛은 천상, 밤마다 악몽.", passive_effect: { anomaly: 5 }, holding_risk: { entropy: 1 }, tags: ["군사"], combo_req: 2, combo_effect_key: "trust", combo_effect_val: 2 },
|
||||
{ id: "AST_002", name: "비밀 해커팀 (Agent)", desc: "정보망을 통한 횡령.", passive_effect: { political: 5 }, tags: ["과학", "자본"], combo_req: 1, combo_effect_key: "anomaly", combo_effect_val: 10 }
|
||||
];
|
||||
|
||||
if (DB_SCENARIOS.length === 0) DB_SCENARIOS = [
|
||||
{ id: 101, act: 1, dept: "행정 본부", color: "#d97706", title: "탕비실 믹스커피 횡령", body: "요원들이 비품을 횡령했습니다.", faction: "지식 보존당", tags: ["과학"], yes_a: 10, no_p: 5, yes_e: 0, no_e: +1, flavor: "맥심 골드를 지켜야 합니다." },
|
||||
{ id: 102, act: 1, dept: "작전 본부", color: "#c92a2a", title: "하급 슬라임 민원", body: "하수구 슬라임을 소각하시겠습니까?", faction: "철의 수호당", tags: ["군사"], yes_a: 15, no_p: 10, yes_e: -5, no_e: +5, flavor: "소금 뿌리면 안 될까요?" }
|
||||
];
|
||||
|
||||
if (DB_MYSTERIES.length === 0) DB_MYSTERIES = [
|
||||
{ id: "M_01", title: "지하실의 악마", req_tags: { "군사": 2 }, cost_anomaly: 15, reward_asset_id: "AST_001", penalty_entropy: 1 },
|
||||
{ id: "M_02", title: "사설 정보망 해킹", req_tags: { "과학": 1 }, cost_anomaly: 5, reward_asset_id: "AST_002", penalty_entropy: 0 }
|
||||
];
|
||||
}
|
||||
|
||||
async function initDB() {
|
||||
document.getElementById('loading-screen').style.display = 'flex';
|
||||
DB_FACTIONS = []; DB_AGENDAS = []; DB_QUESTS = []; DB_BILLS = []; DB_ASSETS = []; DB_SCENARIOS = []; DB_MYSTERIES = [];
|
||||
try {
|
||||
const [factions, agendas, quests, bills, assets, scenarios] = await Promise.all([
|
||||
const [factions, agendas, quests, bills, assets, scenarios, mysteries] = await Promise.all([
|
||||
parseCSV(SHEET_URLS.factions), parseCSV(SHEET_URLS.agendas), parseCSV(SHEET_URLS.quests),
|
||||
parseCSV(SHEET_URLS.bills), parseCSV(SHEET_URLS.assets), parseCSV(SHEET_URLS.scenarios)
|
||||
parseCSV(SHEET_URLS.bills), parseCSV(SHEET_URLS.assets), parseCSV(SHEET_URLS.scenarios),
|
||||
parseCSV(SHEET_URLS.mysteries)
|
||||
]);
|
||||
// parse and normalize basically omitted for brevity in robust fallback
|
||||
throw new Error("Local fallback enforced for new Logic transition");
|
||||
} catch(e) {
|
||||
loadFallbackData();
|
||||
setTimeout(() => { document.getElementById('loading-screen').style.display = 'none'; startGame(); }, 500);
|
||||
|
||||
if (factions && factions.length > 0) {
|
||||
DB_FACTIONS = factions.map(f => ({
|
||||
...f,
|
||||
share: parseInt(f.share)||10,
|
||||
like_tag: f.love_tag || f.like_tag || '',
|
||||
hate_tag: f.hate_tag || ''
|
||||
}));
|
||||
}
|
||||
if (agendas && agendas.length > 0) DB_AGENDAS = agendas;
|
||||
if (quests && quests.length > 0) DB_QUESTS = quests;
|
||||
if (bills && bills.length > 0) {
|
||||
DB_BILLS = bills.map(b => {
|
||||
let is_buff = b.type === 'lock_reject' || b.type === 'buff_tag';
|
||||
return {
|
||||
...b,
|
||||
is_buff: is_buff,
|
||||
target_tag: b.tag,
|
||||
buff_tag: is_buff ? b.tag : null,
|
||||
nullify_tag: !is_buff ? b.tag : null
|
||||
};
|
||||
});
|
||||
}
|
||||
if (assets && assets.length > 0) {
|
||||
DB_ASSETS = assets.map(a => ({
|
||||
...a,
|
||||
tags: a.tags ? a.tags.split(',').map(t=>t.trim()) : [],
|
||||
passive_effect: { anomaly: parseInt(a.passive_eff_val)||0 }, // 간단한 호환성 매핑
|
||||
holding_risk: { entropy: parseInt(a.risk_eff_val)||0 }
|
||||
}));
|
||||
}
|
||||
if (scenarios && scenarios.length > 0) {
|
||||
DB_SCENARIOS = scenarios.map(s => ({
|
||||
...s,
|
||||
act: parseInt(s.act)||1,
|
||||
tags: s.tags ? s.tags.split(',').map(t=>t.trim().replace(/^"|"$/g, '')) : [],
|
||||
yes_a: parseInt(s.cost)||0, // cost를 데이터(anomaly) 획득/소모로 매핑
|
||||
no_p: parseInt(s.conflict)||0, // conflict를 정치 자본 획득/소모로 매핑
|
||||
yes_e: parseInt(s.yes_e)||0,
|
||||
no_e: parseInt(s.no_e)||0,
|
||||
}));
|
||||
}
|
||||
if (mysteries && mysteries.length > 0) {
|
||||
DB_MYSTERIES = mysteries.map(m => {
|
||||
let req_tags = {};
|
||||
if(m.req_tags) m.req_tags.split(',').forEach(rt => { let parts = rt.split(':'); if(parts.length===2) req_tags[parts[0].trim()] = parseInt(parts[1]); });
|
||||
return { ...m, req_tags: req_tags, cost_anomaly: parseInt(m.cost_anomaly)||0, penalty_entropy: parseInt(m.penalty_entropy)||0 };
|
||||
});
|
||||
}
|
||||
} catch(e) {
|
||||
console.error("CSV 파싱 에러:", e);
|
||||
}
|
||||
setTimeout(() => { document.getElementById('loading-screen').style.display = 'none'; startGame(); }, 500);
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
@@ -407,15 +437,58 @@
|
||||
updateHUD(); nextDay();
|
||||
}
|
||||
|
||||
function getAffinity(f) {
|
||||
if (f.is_blacklisted) return { level: 5, name: "🔥 파국", color: "#ef4444" };
|
||||
if (f.share >= 40) return { level: 1, name: "👑 핵심 동맹", color: "#3b82f6" };
|
||||
if (f.share >= 20) return { level: 2, name: "🤝 우호", color: "#10b981" };
|
||||
if (f.share >= 10) return { level: 3, name: "⚖️ 중립", color: "#9ca3af" };
|
||||
return { level: 4, name: "⚔️ 적대", color: "#f59e0b" };
|
||||
}
|
||||
|
||||
function nextDay() {
|
||||
if (state.day > 5) { startCouncil(); return; }
|
||||
if (state.entropy >= 100) { alert("종말 시계 100% 도달. 통제에 실패했습니다."); initDB(); return; }
|
||||
|
||||
applyAssetPassives();
|
||||
state.activeMysteries.forEach(m => { if(m.penalty_entropy) state.entropy += m.penalty_entropy; });
|
||||
|
||||
let panicLevel = 1; let panicMsg = "";
|
||||
if (state.entropy >= 81) panicLevel = 5;
|
||||
else if (state.entropy >= 61) panicLevel = 4;
|
||||
else if (state.entropy >= 41) panicLevel = 3;
|
||||
else if (state.entropy >= 21) panicLevel = 2;
|
||||
|
||||
if (panicLevel === 2) { addStat('trust', -1); panicMsg = "[동요] 시민 불안 (신임도 -1)"; }
|
||||
else if (panicLevel >= 3) { addStat('political', -1); addStat('trust', -2); panicMsg = "[혼란] 시스템 마비 (자본/신임도 하락)"; }
|
||||
|
||||
let terrorChance = panicLevel >= 4 ? 0.6 : 0.3;
|
||||
let sabotageMsg = [];
|
||||
if (panicMsg) sabotageMsg.push(panicMsg);
|
||||
|
||||
DB_FACTIONS.forEach(f => {
|
||||
if(getAffinity(f).level === 5 && Math.random() < terrorChance) {
|
||||
state.entropy += 5; state.trust -= 5;
|
||||
sabotageMsg.push(`[파국] ${f.name} 테러!`);
|
||||
}
|
||||
});
|
||||
if (sabotageMsg.length > 0) {
|
||||
const toast = document.getElementById('passive-toast');
|
||||
toast.innerText = sabotageMsg.join(' / ');
|
||||
toast.className = 'passive-toast bg-red-600'; void toast.offsetWidth; toast.classList.add('active');
|
||||
}
|
||||
|
||||
if (panicLevel === 5) {
|
||||
state.currentCard = {
|
||||
id: 999, dept: "재난 관리", color: "#991b1b",
|
||||
title: "현실 붕괴 임박", body: "사회 시스템이 통제 불능 상태입니다. 막대한 자원을 희생해 시간을 벌겠습니까?",
|
||||
faction: "전체 파벌", tags: [], yes_a: 40, no_p: 20, yes_e: -10, no_e: 15, flavor: "신은 우리를 버렸습니다."
|
||||
};
|
||||
} else {
|
||||
let pool = DB_SCENARIOS.filter(s => s.act === state.week);
|
||||
if(pool.length === 0) pool = DB_SCENARIOS;
|
||||
state.currentCard = pool[Math.floor(Math.random() * pool.length)];
|
||||
}
|
||||
|
||||
renderCard(state.currentCard);
|
||||
|
||||
const cardEl = document.getElementById('current-card');
|
||||
@@ -527,19 +600,87 @@
|
||||
document.getElementById('bar-trust').style.width = `${state.trust}%`; document.getElementById('txt-trust').innerText = `${state.trust}%`;
|
||||
document.getElementById('hand-entropy').style.transform = `translateX(-50%) rotate(${(state.entropy / 100) * 360}deg)`;
|
||||
|
||||
const sb = document.getElementById('bar-share'); sb.innerHTML = "";
|
||||
let sortedFactions = [...DB_FACTIONS].sort((a,b)=>b.share - a.share);
|
||||
sortedFactions.forEach(f => { if(f.share > 0) sb.innerHTML += `<div class="share-segment" style="width:${f.share}%; background:${f.color};"></div>`; });
|
||||
|
||||
// Donut Chart
|
||||
const donut = document.getElementById('donut-share');
|
||||
if(donut) {
|
||||
let conicStr = []; let currentDeg = 0;
|
||||
sortedFactions.forEach(f => {
|
||||
if(f.share > 0) {
|
||||
let deg = (f.share / 100) * 360;
|
||||
conicStr.push(`${f.color} ${currentDeg}deg ${currentDeg + deg}deg`);
|
||||
currentDeg += deg;
|
||||
}
|
||||
});
|
||||
donut.style.background = `conic-gradient(${conicStr.join(', ')})`;
|
||||
}
|
||||
|
||||
document.getElementById('asset-count').innerText = state.assets.length;
|
||||
|
||||
let tagsArr = [];
|
||||
for(let t in state.builtTags) tagsArr.push(`${t}(${state.builtTags[t]})`);
|
||||
document.getElementById('hud-tags').innerText = tagsArr.length > 0 ? tagsArr.join(' / ') : '없음';
|
||||
const tagsContainer = document.getElementById('hud-tags-container');
|
||||
if(tagsContainer) {
|
||||
tagsContainer.innerHTML = "";
|
||||
let tagCount = 0;
|
||||
for(let t in state.builtTags) {
|
||||
if(state.builtTags[t] > 0) {
|
||||
tagsContainer.innerHTML += `<div class="bg-gray-700 text-yellow-300 px-2 py-1 rounded text-[9px] font-bold shadow-sm">[${t}] ${state.builtTags[t]}</div>`;
|
||||
tagCount++;
|
||||
}
|
||||
}
|
||||
if(tagCount === 0) tagsContainer.innerHTML = "<span class='text-gray-500 italic'>없음</span>";
|
||||
}
|
||||
|
||||
let hasActiveSynergy = state.assets.some(a => a.combo_req > 0 && a.tags && a.tags.some(t => state.builtTags[t] >= a.combo_req));
|
||||
document.getElementById('hud-synergy-badge').className = hasActiveSynergy ? "text-yellow-400 font-bold animate-pulse" : "text-gray-500";
|
||||
document.getElementById('hud-synergy-badge').innerHTML = hasActiveSynergy ? `<i class="fa-solid fa-bolt"></i> 시너지 가동` : `<i class="fa-solid fa-bolt"></i> 연쇄 없음`;
|
||||
|
||||
if (state.currentAgenda) {
|
||||
const agTitle = document.getElementById('agenda-title'); if(agTitle) agTitle.innerText = state.currentAgenda.title;
|
||||
}
|
||||
}
|
||||
|
||||
function renderCard(data) {
|
||||
const card = document.getElementById('current-card');
|
||||
card.style.display = 'flex'; card.style.borderColor = "#cbb"; card.style.transform = "translate(0,0)";
|
||||
|
||||
document.getElementById('c-dept').innerText = data.dept || '부서 불명';
|
||||
document.getElementById('c-dept').style.backgroundColor = data.color || '#333';
|
||||
document.getElementById('c-title').innerText = data.title || '제목 없음';
|
||||
document.getElementById('c-body').innerText = data.body || '내용 없음';
|
||||
document.getElementById('c-flavor').innerText = `"${data.flavor || ''}"`;
|
||||
document.getElementById('c-faction').innerText = data.faction || '무소속';
|
||||
|
||||
let tHtml = "";
|
||||
if(data.tags) data.tags.forEach(t => tHtml += `<span class="tag-badge">[${t}]</span>`);
|
||||
const cTags = document.getElementById('c-tags');
|
||||
if(cTags) cTags.innerHTML = tHtml;
|
||||
|
||||
let conflictElem = document.getElementById('c-conflict');
|
||||
if(conflictElem) {
|
||||
if (data.no_p > 0) {
|
||||
conflictElem.innerHTML = `거절 시 👑 +${data.no_p}`;
|
||||
conflictElem.classList.remove('hidden');
|
||||
} else {
|
||||
conflictElem.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
let lockType = "none";
|
||||
if(data.tags) {
|
||||
state.activeLaws.forEach(bill => {
|
||||
if(data.tags.includes(bill.tag)) { if(bill.type === "lock_approve") lockType = "approve"; if(bill.type === "lock_reject") lockType = "reject"; }
|
||||
});
|
||||
}
|
||||
state.lockState = lockType;
|
||||
const overlay = document.getElementById('lock-overlay');
|
||||
if (lockType === 'approve') { overlay.classList.add('active'); document.getElementById('lock-type-text').innerText = "승인 금지"; }
|
||||
else if (lockType === 'reject') { overlay.classList.add('active'); document.getElementById('lock-type-text').innerText = "거절 금지"; }
|
||||
else overlay.classList.remove('active');
|
||||
|
||||
document.getElementById('stamp-approve').style.opacity = 0; document.getElementById('stamp-reject').style.opacity = 0;
|
||||
document.getElementById('predict-overlay').style.display = 'none';
|
||||
document.getElementById('law-popup').classList.remove('visible');
|
||||
}
|
||||
|
||||
const card = document.getElementById('current-card');
|
||||
@@ -581,27 +722,30 @@
|
||||
let tGainHtml = ""; let fGainHtml = "";
|
||||
if(isApprove) {
|
||||
document.getElementById('p-header').innerText = "승인 (APPROVE)"; document.getElementById('p-header').className = "font-bold border-b border-gray-600 mb-2 pb-1 text-center text-red-400";
|
||||
document.getElementById('p-desc').innerText = "부서 전략 수행";
|
||||
document.getElementById('p-anomaly').innerHTML = data.yes_a ? `+${data.yes_a}` : "0";
|
||||
document.getElementById('p-desc').innerText = "엔진 빌드업 (태그 확보)";
|
||||
document.getElementById('p-anomaly').innerHTML = data.yes_a ? `<span class="${data.yes_a>0?'p-down':'p-up'}">${data.yes_a>0?'-':'+'}${Math.abs(data.yes_a)}</span>` : "0";
|
||||
document.getElementById('p-political').innerHTML = "-";
|
||||
document.getElementById('p-entropy').innerHTML = data.yes_e ? `<span class="p-down">${data.yes_e > 0?'+':''}${data.yes_e}</span>` : "0";
|
||||
document.getElementById('p-trust').innerHTML = "-";
|
||||
|
||||
data.tags.forEach(t => tGainHtml += `[${t}] +1 `);
|
||||
data.tags.forEach(t => {
|
||||
DB_FACTIONS.forEach(f => {
|
||||
if(f.like_tag === t) fGainHtml += ` <span style="color:${f.color}">${f.name} ▲</span>`;
|
||||
if(f.hate_tag === t) fGainHtml += ` <span style="color:${f.color}">${f.name} ▼</span>`;
|
||||
});
|
||||
});
|
||||
fGainHtml = "변동 없음 (침묵)";
|
||||
} else {
|
||||
document.getElementById('p-header').innerText = "거절 (REJECT)"; document.getElementById('p-header').className = "font-bold border-b border-gray-600 mb-2 pb-1 text-center text-blue-400";
|
||||
document.getElementById('p-desc').innerText = "관료제 통제 (기각)";
|
||||
document.getElementById('p-desc').innerText = "관료제 통제 (기각 및 정치공작)";
|
||||
document.getElementById('p-anomaly').innerHTML = "-";
|
||||
document.getElementById('p-political').innerHTML = data.no_p ? `+${data.no_p}` : "0";
|
||||
document.getElementById('p-entropy').innerHTML = data.no_e ? `<span class="p-up">${data.no_e > 0?'+':''}${data.no_e}</span>` : "0";
|
||||
document.getElementById('p-trust').innerHTML = `<span class="p-up">+2</span>`;
|
||||
fGainHtml = "사태 방관으로 인한 정치 국면 유지";
|
||||
|
||||
tGainHtml = "<span class='text-gray-500'>획득 불가</span>";
|
||||
data.tags.forEach(t => {
|
||||
DB_FACTIONS.forEach(f => {
|
||||
if(f.like_tag === t) fGainHtml += ` <span style="color:${f.color}">${f.name} ▼▼</span>`;
|
||||
if(f.hate_tag === t) fGainHtml += ` <span style="color:${f.color}">${f.name} ▲▲</span>`;
|
||||
});
|
||||
});
|
||||
if(fGainHtml === "") fGainHtml = "영향력 변동 없음";
|
||||
}
|
||||
document.getElementById('p-tag-gain').innerHTML = tGainHtml;
|
||||
document.getElementById('p-faction-change').innerHTML = fGainHtml;
|
||||
@@ -611,17 +755,24 @@
|
||||
const data = state.currentCard; let animateDir = type === 'approve' ? 500 : -500;
|
||||
|
||||
if (type === 'approve') {
|
||||
addStat('anomaly', data.yes_a || 0); addStat('entropy', data.yes_e || 0);
|
||||
addStat('anomaly', -(data.yes_a || 0)); addStat('entropy', data.yes_e || 0);
|
||||
if(data.tags) {
|
||||
data.tags.forEach(t => {
|
||||
state.builtTags[t] = (state.builtTags[t]||0) + 1;
|
||||
DB_FACTIONS.forEach(f=>{ if(f.like_tag===t) f.share+=2; if(f.hate_tag===t) f.share=Math.max(0, f.share-1); });
|
||||
});
|
||||
}
|
||||
if (state.currentQuest) state.questProgress++;
|
||||
enqueueEvent('on_approve', null);
|
||||
} else {
|
||||
addStat('political', data.no_p || 0); addStat('entropy', data.no_e || 0); addStat('trust', 2);
|
||||
if(data.tags) {
|
||||
data.tags.forEach(t => {
|
||||
DB_FACTIONS.forEach(f=>{
|
||||
if(f.like_tag===t) f.share=Math.max(0, f.share-3);
|
||||
if(f.hate_tag===t) f.share+=2;
|
||||
});
|
||||
});
|
||||
}
|
||||
enqueueEvent('on_reject', null);
|
||||
}
|
||||
|
||||
@@ -658,11 +809,11 @@
|
||||
<div class="flex justify-between border-b border-gray-600 pb-1 mb-2"><span class="font-bold text-blue-300 text-sm">${m.title}</span><span class="text-xs text-red-400">매턴 엔트로피 +${m.penalty_entropy || 0}</span></div>
|
||||
<div class="text-[10px] text-gray-300 flex justify-between items-center">
|
||||
<div>
|
||||
<div class="mb-1">요구 태그: ${reqHtml}</div>
|
||||
<div class="mb-1 text-yellow-300 font-bold">소모 태그: ${reqHtml}</div>
|
||||
<div>데이터 비용: 🧬 <span class="${state.anomaly >= finalCost ? 'text-blue-300' : 'text-red-400'}">${finalCost}</span> ${discount>0 ? `<span class="text-green-400 ml-1">(법안 할인 -${discount})</span>`:''}</div>
|
||||
<div class="mt-1 text-green-300 font-bold">보상: ${ast?ast.name:'엔진 자산'}</div>
|
||||
</div>
|
||||
<button class="${canSolve?'bg-blue-600 hover:bg-blue-500':'bg-gray-700 opacity-50 cursor-not-allowed'} px-3 py-2 rounded text-white font-bold" onclick="${canSolve?`solveMystery(${idx})`:'return false;'}">비밀 해독</button>
|
||||
<button class="${canSolve?'bg-blue-600 hover:bg-blue-500':'bg-gray-700 opacity-50 cursor-not-allowed'} px-3 py-2 rounded text-white font-bold flex flex-col items-center" onclick="${canSolve?`solveMystery(${idx})`:'return false;'}"><span>해독</span><span class="text-[8px] font-normal">(태그 소모)</span></button>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
@@ -696,8 +847,13 @@
|
||||
document.getElementById('council-phase-1').classList.remove('active'); document.getElementById('council-phase-2').classList.add('active');
|
||||
|
||||
let mainTagInfo = getMainTag(); let targetTag = mainTagInfo.tag;
|
||||
let sortedFactions = [...DB_FACTIONS].sort((a,b)=>b.share - a.share);
|
||||
const top = sortedFactions[0]; const hostile = sortedFactions[sortedFactions.length-1];
|
||||
|
||||
// Affinity 적용하여 동맹(1,2)과 적대(4,5) 판별
|
||||
let factionsWithAff = DB_FACTIONS.map(f => ({ ...f, aff: getAffinity(f) }));
|
||||
let sortedFactions = factionsWithAff.sort((a,b) => a.aff.level - b.aff.level);
|
||||
|
||||
const top = sortedFactions[0]; // 가장 우호적인 파벌
|
||||
const hostile = sortedFactions[sortedFactions.length-1]; // 가장 적대적인 파벌
|
||||
|
||||
let buffBills = DB_BILLS.filter(b => b.is_buff);
|
||||
let debuffBills = DB_BILLS.filter(b => !b.is_buff);
|
||||
@@ -714,16 +870,26 @@
|
||||
document.getElementById('bill-a-name').innerText = b1.name; document.getElementById('bill-a-flavor').innerText = `"${b1.flavor}"`;
|
||||
document.getElementById('bill-b-name').innerText = b2.name; document.getElementById('bill-b-flavor').innerText = `"${b2.flavor}"`;
|
||||
|
||||
let supportersA = [], supportersB = [], swingers = []; let votesA = 0;
|
||||
sortedFactions.forEach(f => {
|
||||
if(f.id === top.id || (f.like_tag === targetTag)) { supportersA.push(f.name); votesA += f.share; }
|
||||
else if(f.id === hostile.id || (f.hate_tag === targetTag)) { supportersB.push(f.name); }
|
||||
let supportersA = [], supportersB = [], swingers = [], enemies = []; let votesA = 0;
|
||||
|
||||
factionsWithAff.forEach(f => {
|
||||
if(f.aff.level <= 2 || (f.like_tag === targetTag)) { supportersA.push(f.name); votesA += f.share; }
|
||||
else if(f.aff.level >= 4 || (f.hate_tag === targetTag)) { supportersB.push(f.name); enemies.push(f); }
|
||||
else { swingers.push(f); }
|
||||
});
|
||||
state.council.votesA = votesA; state.council.supportersA = supportersA; state.council.supportersB = supportersB; updateVoteUI();
|
||||
|
||||
state.council.votesA = Math.max(0, votesA);
|
||||
state.council.supportersA = supportersA;
|
||||
state.council.supportersB = supportersB;
|
||||
updateVoteUI();
|
||||
|
||||
const lobbyList = document.getElementById('lobby-targets'); lobbyList.innerHTML = "";
|
||||
swingers.forEach(f => { lobbyList.innerHTML += `<div class="lobby-tag" onclick="lobbyFaction('${f.id}', event)">${f.name} (${f.share}%)</div>`; });
|
||||
|
||||
const blacklistList = document.getElementById('blacklist-targets'); blacklistList.innerHTML = "";
|
||||
enemies.forEach(f => {
|
||||
if(!f.is_blacklisted) blacklistList.innerHTML += `<div class="lobby-tag" style="border-color:#ef4444; color:#ef4444;" onclick="blacklistFaction('${f.id}', event)">${f.name}</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
function lobbyFaction(fid, e) {
|
||||
@@ -734,10 +900,22 @@
|
||||
updateVoteUI(); updateHUD();
|
||||
}
|
||||
|
||||
function blacklistFaction(fid, e) {
|
||||
const fac = DB_FACTIONS.find(f => f.id === fid);
|
||||
fac.is_blacklisted = true;
|
||||
state.council.supportersB = state.council.supportersB.filter(name => name !== fac.name);
|
||||
|
||||
const tgt = e.currentTarget; tgt.style.background = "#ef4444"; tgt.style.color = "#fff"; tgt.innerText = "탄압됨"; tgt.onclick = null;
|
||||
alert(`${fac.name}을(를) 영구적 파국 상태로 몰아넣었습니다. 다음 턴부터 사보타주 테러가 발생할 수 있습니다.`);
|
||||
updateVoteUI(); updateHUD();
|
||||
}
|
||||
|
||||
function updateVoteUI() {
|
||||
const vA = Math.min(100, state.council.votesA);
|
||||
document.getElementById('vote-a').style.width = `${vA}%`; document.getElementById('vote-b').style.width = `${100-vA}%`;
|
||||
document.getElementById('vote-a-val').innerText = vA; document.getElementById('vote-b-val').innerText = 100-vA;
|
||||
document.getElementById('list-support-a').innerText = state.council.supportersA.length > 0 ? state.council.supportersA.join(', ') : '없음';
|
||||
document.getElementById('list-support-b').innerText = state.council.supportersB.length > 0 ? state.council.supportersB.join(', ') : '없음';
|
||||
}
|
||||
|
||||
function endCouncil() {
|
||||
@@ -755,6 +933,19 @@
|
||||
document.getElementById('asset-list').innerHTML = state.assets.map(a => `<div class="list-item"><span class="font-bold flex items-center">${a.name} <span class="bg-gray-700 text-gray-300 px-1 rounded ml-1 text-[8px]">${a.tags?a.tags.join(','):''}</span></span><span class="text-[9px] text-green-400 ml-auto">[작동 중]</span></div>`).join('');
|
||||
if(state.assets.length===0) document.getElementById('asset-list').innerHTML = "<div class='text-center text-xs text-gray-500'>없음</div>";
|
||||
}
|
||||
if(id==='faction-modal') {
|
||||
let facHtml = "";
|
||||
let sortedFactions = [...DB_FACTIONS].sort((a,b)=>b.share - a.share);
|
||||
sortedFactions.forEach(f => {
|
||||
let aff = getAffinity(f);
|
||||
facHtml += `<div class="bg-gray-800 p-3 rounded mb-2 border-l-4" style="border-color:${f.color}">
|
||||
<div class="flex justify-between items-center mb-1"><span class="font-bold" style="color:${f.color}">${f.name}</span><span class="text-xs font-bold" style="color:${aff.color}">${aff.name} (지분 ${f.share}%)</span></div>
|
||||
<div class="text-[10px] text-gray-400 mb-1">${f.desc}</div>
|
||||
<div class="text-[9px] text-gray-500">선호: <span class="text-green-400">[${f.like_tag||'없음'}]</span> / 혐오: <span class="text-red-400">[${f.hate_tag||'없음'}]</span></div>
|
||||
</div>`;
|
||||
});
|
||||
document.getElementById('faction-list-detail').innerHTML = facHtml;
|
||||
}
|
||||
}
|
||||
window.closeModal = (id) => document.getElementById(id).classList.remove('active');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user