feat: release version 1.0.7
This commit is contained in:
@@ -58,6 +58,8 @@
|
||||
type AutomaticPlacementResult = {
|
||||
memberId: string;
|
||||
memberName: string;
|
||||
socketId: string;
|
||||
socketName: string;
|
||||
cardId: string;
|
||||
cardName: string;
|
||||
rarity: number;
|
||||
@@ -155,7 +157,8 @@
|
||||
: [];
|
||||
$: efficiencyFormationReady = efficiencyFormation.cardIds.length === 5
|
||||
&& efficiencyFormation.cardIds.every((id) => cardById.has(id))
|
||||
&& new Set(efficiencyFormation.cardIds.map((id) => cardById.get(id)?.characterId)).size === 5;
|
||||
&& new Set(efficiencyFormation.cardIds.map((id) => cardById.get(id)?.characterId)).size === 5
|
||||
&& efficiencyFormation.cardIds.some((id) => cardById.get(id)?.characterId === efficiencyFormation.leaderCharacterId);
|
||||
$: dataView = $page.url.pathname.endsWith('/cards') ? 'cards' : $page.url.pathname.endsWith('/boards') ? 'boards' : 'home';
|
||||
onMount(() => {
|
||||
try {
|
||||
@@ -241,11 +244,12 @@
|
||||
|
||||
function importAnalysisFormation() {
|
||||
if (!savedAnalysis) return;
|
||||
const analysis = savedAnalysis;
|
||||
saveEfficiencyFormation({
|
||||
cardIds: [...savedAnalysis.cardIds],
|
||||
awakenings: savedAnalysis.awakenings.map(normalizeEfficiencyAwakening),
|
||||
leaderCharacterId: savedAnalysis.leaderCharacterId,
|
||||
leaderOutfitCardId: savedAnalysis.leaderOutfitCardId,
|
||||
cardIds: [...analysis.cardIds],
|
||||
awakenings: analysis.awakenings.map(normalizeEfficiencyAwakening),
|
||||
leaderCharacterId: analysis.leaderCharacterId,
|
||||
leaderOutfitCardId: analysis.leaderOutfitCardId,
|
||||
coreCubeFreeOnly: efficiencyFormation.coreCubeFreeOnly
|
||||
});
|
||||
}
|
||||
@@ -570,7 +574,7 @@
|
||||
}, { points: 0, greenCubes: 0, greenCoreCubes: 0 });
|
||||
}
|
||||
|
||||
function currentFormationPower(memberBoards: Record<string, string[]>): number {
|
||||
function currentFormationPower(memberBoards: Record<string, string[]>, boardCards: BoardCardPlacements = userData.boardCards): number {
|
||||
if (!efficiencyFormationReady) return 0;
|
||||
const members = efficiencyFormation.cardIds.map((id, index) => ({
|
||||
card: cardById.get(id)!,
|
||||
@@ -584,7 +588,7 @@
|
||||
connect: card.details.connect
|
||||
}));
|
||||
const boardEffects = calculateBoardEffects(boardSummary, memberBoards, sourceCards, efficiencyFormation.leaderCharacterId, null, {
|
||||
placements: userData.boardCards,
|
||||
placements: boardCards,
|
||||
cards: connections
|
||||
});
|
||||
const leaderOutfit = cardById.get(efficiencyFormation.leaderOutfitCardId)?.details.costume ?? null;
|
||||
@@ -626,15 +630,6 @@
|
||||
|| right.cost.greenCubes - left.cost.greenCubes;
|
||||
}
|
||||
|
||||
function upgradeBeatsRemoval(upgrade: GreenNodeEfficiency, removal: GreenNodeEfficiency): boolean {
|
||||
const cubeDifference = upgrade.greenCubeEfficiency - removal.greenCubeEfficiency;
|
||||
if (Math.abs(cubeDifference) > 1e-9) return cubeDifference > 0;
|
||||
if (upgrade.cost.greenCoreCubes === 0 && removal.cost.greenCoreCubes > 0) return true;
|
||||
if (upgrade.cost.greenCoreCubes > 0 && removal.cost.greenCoreCubes === 0) return false;
|
||||
if (upgrade.cost.greenCoreCubes === 0 && removal.cost.greenCoreCubes === 0) return false;
|
||||
return upgrade.greenCoreCubeEfficiency > removal.greenCoreCubeEfficiency;
|
||||
}
|
||||
|
||||
async function analyzeGreenNodeEfficiency() {
|
||||
if (!efficiencyFormationReady) {
|
||||
greenEfficiencyError = '서로 다른 멤버의 5성 카드 5장을 모두 선택해 주세요.';
|
||||
@@ -702,11 +697,9 @@
|
||||
}
|
||||
|
||||
const sortedRemovals = removals.sort(compareRemovalEfficiency);
|
||||
const removalThreshold = sortedRemovals[0];
|
||||
greenEfficiencyResult = {
|
||||
basePower,
|
||||
upgrades: upgrades.sort(compareUpgradeEfficiency)
|
||||
.filter((upgrade) => !removalThreshold || upgradeBeatsRemoval(upgrade, removalThreshold)),
|
||||
upgrades: upgrades.sort(compareUpgradeEfficiency),
|
||||
removals: sortedRemovals
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -818,45 +811,37 @@
|
||||
if (!socket || userData.boardCards[member.id]?.[socket.groupId]) return [];
|
||||
return [{ member, socket }];
|
||||
});
|
||||
|
||||
if (candidateCards.length === 0) {
|
||||
showMessage('자동 배치에 사용할 수 있는 미배치 보유 카드가 없습니다.', true);
|
||||
return;
|
||||
}
|
||||
if (emptyGreenSlots.length === 0) {
|
||||
showMessage('비어 있는 초록색 카드 슬롯이 없습니다. 기존 배치는 변경하지 않았습니다.');
|
||||
showMessage('비어 있는 중앙 연결 노드가 없습니다. 기존 배치는 변경하지 않았습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const weights = candidateCards.map((card) => emptyGreenSlots.map(({ member, socket }) => greenSlotScore(member, socket, card)));
|
||||
const matches = maximumWeightMatching(weights);
|
||||
const matches = maximumWeightMatching(candidateCards.map((card) =>
|
||||
emptyGreenSlots.map(({ member, socket }) => greenSlotScore(member, socket, card))
|
||||
));
|
||||
if (matches.length === 0) {
|
||||
showMessage('활성화된 초록 노드에 효과를 주는 배치 조합을 찾지 못했습니다.', true);
|
||||
showMessage('활성화된 초록 노드에 효과를 주는 중앙 배치를 찾지 못했습니다.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
automaticPlacementSnapshot = cloneBoardCards(userData.boardCards);
|
||||
const boardCards = cloneBoardCards(userData.boardCards);
|
||||
const placementResult: AutomaticPlacementResult[] = [];
|
||||
for (const match of matches) {
|
||||
automaticPlacementResult = matches.map((match) => {
|
||||
const card = candidateCards[match.rowIndex];
|
||||
const slot = emptyGreenSlots[match.columnIndex];
|
||||
if (!card || !slot) continue;
|
||||
boardCards[slot.member.id] = { ...(boardCards[slot.member.id] ?? {}), [slot.socket.groupId]: card.id };
|
||||
placementResult.push({
|
||||
memberId: slot.member.id,
|
||||
memberName: slot.member.name,
|
||||
cardId: card.id,
|
||||
cardName: cardLabel(card),
|
||||
rarity: card.rarity
|
||||
});
|
||||
}
|
||||
automaticPlacementResult = placementResult.sort((left, right) =>
|
||||
const { member, socket } = emptyGreenSlots[match.columnIndex];
|
||||
boardCards[member.id] = { ...(boardCards[member.id] ?? {}), [socket.groupId]: card.id };
|
||||
return {
|
||||
memberId: member.id, memberName: member.name, socketId: socket.groupId, socketName: '중앙',
|
||||
cardId: card.id, cardName: cardLabel(card), rarity: card.rarity
|
||||
};
|
||||
}).sort((left, right) =>
|
||||
(boardMembers.find((member) => member.id === left.memberId)?.order ?? Number.MAX_SAFE_INTEGER)
|
||||
- (boardMembers.find((member) => member.id === right.memberId)?.order ?? Number.MAX_SAFE_INTEGER)
|
||||
);
|
||||
persist({ ...userData, boardCards });
|
||||
showMessage(`기존 배치를 유지하고 초록색 빈 슬롯 ${matches.length}곳에 보유 카드를 최적 배치했습니다.`);
|
||||
showMessage(`기존 배치를 유지하고 중앙 연결 노드 ${matches.length}곳에 보유 카드를 자동 배치했습니다.`);
|
||||
}
|
||||
|
||||
function undoAutomaticPlacement() {
|
||||
@@ -865,7 +850,7 @@
|
||||
automaticPlacementSnapshot = null;
|
||||
automaticPlacementResult = [];
|
||||
persist({ ...userData, boardCards });
|
||||
showMessage('방금 실행한 초록색 카드 자동 배치를 취소했습니다.');
|
||||
showMessage('방금 실행한 중앙 연결 노드 자동 배치를 취소했습니다.');
|
||||
}
|
||||
|
||||
function cardLabel(card: CardSummary): string {
|
||||
@@ -875,7 +860,7 @@
|
||||
function placedCardName(cardId: string | undefined): string {
|
||||
if (!cardId) return '연결 노드';
|
||||
const card = connectCardById.get(cardId);
|
||||
return card ? cardLabel(card) : cardId;
|
||||
return card ? `${card.rarity}★ ${cardLabel(card)}` : cardId;
|
||||
}
|
||||
|
||||
function connectionBoostsForMember(
|
||||
@@ -917,6 +902,7 @@
|
||||
function assignBoardCard(cardId: string) {
|
||||
if (!selectedSocketGroupId) return;
|
||||
automaticPlacementSnapshot = null;
|
||||
automaticPlacementResult = [];
|
||||
const boardCards = Object.fromEntries(Object.entries(userData.boardCards).map(([memberId, placements]) => [memberId, { ...placements }]));
|
||||
for (const [memberId, placements] of Object.entries(boardCards)) {
|
||||
for (const [socketId, placedCardId] of Object.entries(placements)) {
|
||||
@@ -932,6 +918,7 @@
|
||||
function removeBoardCard() {
|
||||
if (!selectedSocketGroupId) return;
|
||||
automaticPlacementSnapshot = null;
|
||||
automaticPlacementResult = [];
|
||||
const placements = { ...(userData.boardCards[selectedMemberId] ?? {}) };
|
||||
delete placements[selectedSocketGroupId];
|
||||
const boardCards = { ...userData.boardCards };
|
||||
@@ -1168,7 +1155,7 @@
|
||||
<div class="automatic-placement">
|
||||
<div>
|
||||
<strong>간편 설정</strong>
|
||||
<small>모든 멤버의 기본 초록 노드를 활성화하거나, 보유 카드를 중앙 연결 노드의 빈 슬롯에 자동 배치합니다.</small>
|
||||
<small>모든 멤버의 기본 초록 노드를 활성화하거나, 기존 배치를 유지한 채 중앙 연결 노드의 빈 슬롯에 보유 카드를 자동 배치합니다.</small>
|
||||
</div>
|
||||
<div class="automatic-placement-actions">
|
||||
<button type="button" on:click={activateCommonGreenNodesForAllMembers}>모든 멤버 초록 3칸 활성화</button>
|
||||
@@ -1271,7 +1258,7 @@
|
||||
<div class="efficiency-columns">
|
||||
<section class="efficiency-column upgrade" aria-labelledby="upgrade-efficiency-title">
|
||||
<div class="efficiency-column-heading">
|
||||
<div><h3 id="upgrade-efficiency-title">우선 활성화</h3><p>초기화 검토 1순위보다 효율이 높은 노드만 표시</p></div>
|
||||
<div><h3 id="upgrade-efficiency-title">우선 활성화</h3><p>초기화 효율보다 낮은 후보도 포함해 효율순으로 최대 20개 표시</p></div>
|
||||
</div>
|
||||
{#if greenEfficiencyResult.upgrades.length > 0}
|
||||
<div class="efficiency-list">
|
||||
@@ -1367,7 +1354,7 @@
|
||||
class:efficiency-focus={position.groupId === efficiencyFocusGroupId}
|
||||
style={`left: ${point.x}px; top: ${point.y}px; width: ${size}px; height: ${size}px;`}
|
||||
title={`${boardNodeLabel(node.type)} · ${boardEffectDescription(node.effect, selectedBoardMember.name) || placedCardName(selectedBoardCardPlacements[position.groupId])}${selectedConnectionBoosts[position.groupId] ? ` · ${active ? '연결 효과 적용' : '연결 범위'} +${selectedConnectionBoosts[position.groupId].percent}%` : ''}`}
|
||||
aria-label={`${boardNodeLabel(node.type)} ${node.grade}등급 노드, ${active ? '활성화됨' : '비활성화됨'}${selectedConnectionBoosts[position.groupId] ? `, 연결 ${active ? '효과 적용 중' : '범위에 포함'}` : ''}`}
|
||||
aria-label={`${boardNodeLabel(node.type)} ${node.grade}등급 노드, ${active ? '활성화됨' : '비활성화됨'}${node.type === 'connection' && selectedBoardCardPlacements[position.groupId] ? `, 배치 카드 ${placedCardName(selectedBoardCardPlacements[position.groupId])}` : ''}${selectedConnectionBoosts[position.groupId] ? `, 연결 ${active ? '효과 적용 중' : '범위에 포함'}` : ''}`}
|
||||
aria-pressed={active}
|
||||
on:click={() => handleBoardNodeClick(position.groupId, node)}
|
||||
>
|
||||
@@ -1380,7 +1367,12 @@
|
||||
{#if boardEffectIcon(node)}<img class="board-node-icon" src={boardEffectIcon(node) ?? ''} alt="" draggable="false" />{/if}
|
||||
{#if node.type === 'connection' && selectedBoardCardPlacements[position.groupId]}
|
||||
{@const placedCard = connectCardById.get(selectedBoardCardPlacements[position.groupId])}
|
||||
{#if placedCard}<span class="socket-card" aria-hidden="true">{placedCard.name.slice(0, 2)}</span>{/if}
|
||||
{#if placedCard}
|
||||
<span class="socket-card" aria-hidden="true">
|
||||
<span class="socket-card-name">{placedCard.name.slice(0, 2)}</span>
|
||||
<small class:rarity-five={placedCard.rarity === 5}>{placedCard.rarity}★</small>
|
||||
</span>
|
||||
{/if}
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
@@ -1564,7 +1556,7 @@
|
||||
.automatic-placement-result-item span { color: #91a59a; font-size: 12px; }
|
||||
.automatic-placement-result-item b { display: flex; align-items: center; gap: 7px; min-width: 0; color: #dbe8df; font-size: 13px; }
|
||||
.automatic-placement-result-item i { flex: 0 0 auto; color: #e7bd70; font-size: 11px; font-style: normal; }
|
||||
.green-efficiency { order: 2; margin: 18px 0 0; padding: 0 16px 16px; border: 1px solid #394453; border-radius: 16px; background: #151b23; }
|
||||
.green-efficiency { order: 3; margin: 18px 0 0; padding: 0 16px 16px; border: 1px solid #394453; border-radius: 16px; background: #151b23; }
|
||||
.green-efficiency > summary { display: flex; align-items: center; min-height: 62px; margin: 0 -16px; padding: 12px 16px; color: #dce6e0; cursor: pointer; list-style: none; }
|
||||
.green-efficiency > summary::-webkit-details-marker { display: none; }
|
||||
.green-efficiency > summary:hover { background: rgb(255 255 255 / 3%); }
|
||||
@@ -1657,7 +1649,10 @@
|
||||
.board-node.connect-applied::after { inset: -8px; border: 4px solid #fff; }
|
||||
.board-node.connect-applied::before { content: '+'; position: absolute; z-index: 4; top: -13px; right: -13px; display: grid; place-items: center; width: 19px; height: 19px; border: 2px solid #151a22; border-radius: 50%; color: #111820; background: #fff; font-size: 13px; font-weight: 950; line-height: 1; pointer-events: none; }
|
||||
.board-node.efficiency-focus { z-index: 6; outline: 4px solid #fff; outline-offset: 6px; border-radius: 50%; }
|
||||
.socket-card { position: absolute; inset: 19%; z-index: 2; display: grid; place-items: center; overflow: hidden; border-radius: 50%; color: #fff; background: #3b2d46; font-size: 12px; font-weight: 900; line-height: 1; pointer-events: none; }
|
||||
.socket-card { position: absolute; inset: 19%; z-index: 2; pointer-events: none; }
|
||||
.socket-card-name { display: grid; width: 100%; height: 100%; place-items: center; overflow: hidden; border-radius: 50%; color: #fff; background: #3b2d46; font-size: 12px; font-weight: 900; line-height: 1; }
|
||||
.socket-card small { position: absolute; right: -12px; bottom: -8px; min-width: 24px; padding: 2px 4px; border: 2px solid #151a22; border-radius: 999px; color: #fff; background: #7562c9; font-size: 9px; font-weight: 950; line-height: 1.1; white-space: nowrap; }
|
||||
.socket-card small.rarity-five { background: #d96f9d; }
|
||||
.board-node:hover { z-index: 4; transform: translate(-50%, -50%) scale(1.1); }
|
||||
.board-node.center { cursor: default; }
|
||||
.board-node:focus-visible { z-index: 5; outline: 3px solid rgb(255 255 255 / 55%); outline-offset: 3px; border-radius: 50%; }
|
||||
|
||||
Reference in New Issue
Block a user