feat: defer advanced build calculations
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<script lang="ts">
|
||||
import GameIcon from '$lib/components/GameIcon.svelte';
|
||||
import type { GameIconName } from '$lib/game-icons';
|
||||
import type { LoadoutSummary } from '$lib/builder/loadout-summary';
|
||||
|
||||
let {
|
||||
summary,
|
||||
attributes,
|
||||
talents = [],
|
||||
headingId,
|
||||
layout = 'rail'
|
||||
}: {
|
||||
summary: LoadoutSummary;
|
||||
attributes: any[];
|
||||
talents?: any[];
|
||||
headingId: string;
|
||||
layout?: 'rail' | 'page';
|
||||
} = $props();
|
||||
|
||||
const attribute = (id: string) => attributes.find((item) => item.id === id);
|
||||
const talent = (id: string) => talents.find((item) => item.id === id);
|
||||
const unitValue = (value: unknown, statId: string) => {
|
||||
const numeric = Number(value);
|
||||
const rendered = Number.isInteger(numeric)
|
||||
? numeric.toLocaleString()
|
||||
: numeric.toFixed(2).replace(/\.?0+$/, '');
|
||||
const unit = attribute(statId)?.unit ?? '';
|
||||
return unit === 'Shots' ? `${rendered} ${unit}` : `${rendered}${unit}`;
|
||||
};
|
||||
const talentCooldown = (id: string) => {
|
||||
const value = talent(id)?.cooldown_seconds;
|
||||
if (value === null || value === undefined || value === '') return '';
|
||||
return `Cooldown: ${value} ${Number(value) === 1 ? 'second' : 'seconds'}`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class:page={layout === 'page'} class="loadout-known-stats" aria-labelledby={headingId}>
|
||||
<div class="summary-head">
|
||||
<div>
|
||||
<p class="eyebrow"><span></span> KNOWN EQUIPMENT TOTALS</p>
|
||||
<h2 id={headingId}>Agent stats</h2>
|
||||
</div>
|
||||
<b>{summary.stats.length}</b>
|
||||
</div>
|
||||
<div class="summary-core">
|
||||
{#each ['Firepower', 'Toughness', 'Engineering'] as name}
|
||||
{@const stat = summary.stats.find((entry) => attribute(entry.id)?.name === name)}
|
||||
<div>
|
||||
<GameIcon name={name.toLowerCase() as GameIconName} decorative />
|
||||
<span>{name}<b>{stat ? unitValue(stat.value, stat.id) : '0'}</b></span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="summary-scroll">
|
||||
<div class="summary-section">
|
||||
<header><span>COMBINED ATTRIBUTES</span><b>{summary.stats.length}</b></header>
|
||||
{#each summary.stats as stat}
|
||||
<div class="summary-stat">
|
||||
<span>{attribute(stat.id)?.name}<small>{attribute(stat.id)?.category} · {stat.sourceCount} SOURCE{stat.sourceCount === 1 ? '' : 'S'}</small></span>
|
||||
<b>{unitValue(stat.value, stat.id)}</b>
|
||||
</div>
|
||||
{:else}
|
||||
<p>Equip and configure items to calculate their known Attributes.</p>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="summary-section">
|
||||
<header><span>ACTIVE SET BONUSES</span><b>{summary.activeBonuses.length}</b></header>
|
||||
{#each summary.activeBonuses as bonus}
|
||||
<div class="summary-bonus">
|
||||
<span><b>{bonus.name}</b><small>{bonus.pieces}-PIECE · {bonus.count} EQUIPPED</small></span>
|
||||
<em>+{unitValue(bonus.value, bonus.statId)} {attribute(bonus.statId)?.name}</em>
|
||||
</div>
|
||||
{:else}
|
||||
<p>Matching Brand Sets and Skill Chips activate bonuses here.</p>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="summary-section">
|
||||
<header><span>EQUIPPED TALENTS</span><b>{summary.equippedTalents.length}</b></header>
|
||||
{#each summary.equippedTalents as equipped}
|
||||
<div class="summary-talent">
|
||||
<span>
|
||||
<b>{equipped.custom?.name ?? talent(equipped.id)?.name ?? 'Talent'}</b>
|
||||
<small>{equipped.source} · CONDITIONAL EFFECT NOT INCLUDED</small>
|
||||
<em>{equipped.description}</em>
|
||||
{#if equipped.id && talentCooldown(equipped.id)}<i class="talent-cooldown">{talentCooldown(equipped.id)}</i>{/if}
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
<p>Selected equipment Talents appear here.</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<p class="known-stats-note">Totals reflect this saved loadout and the current canonical item definitions. They include fixed values, rolls, inherited profiles, enabled augments, set bonuses, Protocol effects, and static Specialization rank effects. Conditional Talent outcomes are reserved for the advanced combat model.</p>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.loadout-known-stats{background:var(--shd-surface)}
|
||||
.summary-talent{grid-template-columns:1fr;cursor:default}
|
||||
.known-stats-note{margin:0;padding:11px;border-top:1px solid var(--shd-border);color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);line-height:1.45}
|
||||
.page{border:1px solid var(--shd-border);border-top:2px solid var(--shd-success)}
|
||||
.page .summary-scroll{max-height:none;display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}
|
||||
.page .summary-section{border-left:1px solid var(--shd-border)}
|
||||
.page .summary-section:first-child{border-left:0}
|
||||
@media(max-width:900px){.page .summary-scroll{grid-template-columns:1fr}.page .summary-section{border-left:0;border-top:1px solid var(--shd-border)}.page .summary-section:first-child{border-top:0}}
|
||||
</style>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
let {
|
||||
headingId,
|
||||
variant = 'full',
|
||||
headingLevel = 'h3'
|
||||
}: {
|
||||
headingId: string;
|
||||
variant?: 'full' | 'compact';
|
||||
headingLevel?: 'h2' | 'h3';
|
||||
} = $props();
|
||||
|
||||
const capabilities = [
|
||||
{ label: 'WEAPON OUTPUT', detail: 'Hit values, burst DPS, sustained DPS, and reload-aware firing cycles' },
|
||||
{ label: 'SKILL SIMULATION', detail: 'Ability cadence, target counts, critical hits, and multi-shot behavior' },
|
||||
{ label: 'ENCOUNTER MODEL', detail: 'Duration, accuracy, movement, target flow, rotations, and downtime' },
|
||||
{ label: 'CONDITIONAL EFFECTS', detail: 'Talent triggers, stack buildup, cooldowns, uptime, and resource changes' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<section class:compact={variant === 'compact'} class="planned-combat-model" aria-labelledby={headingId}>
|
||||
<header>
|
||||
<div><span>ADVANCED COMBAT MODEL</span>{#if headingLevel === 'h2'}<h2 id={headingId}>DPS calculations</h2>{:else}<h3 id={headingId}>DPS calculations</h3>{/if}</div>
|
||||
<b>COMING SOON</b>
|
||||
</header>
|
||||
{#if variant === 'full'}
|
||||
<p>Known equipment totals remain available now. Advanced damage projections will return after their calculation rules and scenario controls are ready for player use.</p>
|
||||
{/if}
|
||||
<div class="planned-capabilities" aria-label="Planned combat-model capabilities">
|
||||
{#each capabilities as capability}
|
||||
<div><span><strong>{capability.label}</strong><small>{capability.detail}</small></span><em>PLANNED</em></div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.planned-combat-model{min-width:0;display:grid;grid-template-columns:minmax(0,1fr);gap:var(--shd-gap-md);padding:var(--shd-gap-md);overflow:hidden;border:1px solid var(--shd-model-border);border-top:2px solid var(--shd-info);background:var(--shd-model-surface)}
|
||||
.planned-combat-model>*{min-width:0}
|
||||
header{display:flex;align-items:start;justify-content:space-between;gap:var(--shd-gap-md);padding:0}
|
||||
header div{display:grid;gap:3px}header span{color:var(--shd-info);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:1px}h2,h3{margin:0;color:var(--shd-text);font-size:var(--shd-type-body);letter-spacing:.7px;text-transform:uppercase}header b{padding:5px 7px;border:1px solid var(--shd-info);color:var(--shd-info);font-size:var(--shd-type-telemetry);letter-spacing:.8px;white-space:nowrap}
|
||||
p{max-width:72ch;margin:0;color:var(--shd-text-muted);font-size:var(--shd-type-supporting);line-height:1.5}
|
||||
.planned-capabilities{width:100%;min-width:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));border:1px solid var(--shd-model-border)}
|
||||
.planned-capabilities>div{display:flex;align-items:center;justify-content:space-between;gap:var(--shd-gap-sm);min-width:0;padding:9px;border-top:1px solid var(--shd-model-border);border-left:1px solid var(--shd-model-border)}
|
||||
.planned-capabilities>div:nth-child(-n+2){border-top:0}.planned-capabilities>div:nth-child(odd){border-left:0}
|
||||
.planned-capabilities span{display:grid;gap:3px;min-width:0}.planned-capabilities strong,.planned-capabilities em{font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.7px}.planned-capabilities small{color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);line-height:1.35}.planned-capabilities em{color:var(--shd-text-dim);font-style:normal;white-space:nowrap}
|
||||
.compact{gap:var(--shd-gap-sm);padding:var(--shd-gap-sm)}.compact .planned-capabilities{grid-template-columns:minmax(0,1fr)}.compact .planned-capabilities>div{border-left:0}.compact .planned-capabilities>div:nth-child(2){border-top:1px solid var(--shd-model-border)}
|
||||
@media(max-width:620px){header{display:grid}.planned-capabilities{grid-template-columns:1fr}.planned-capabilities>div{min-height:44px;border-left:0}.planned-capabilities>div:nth-child(2){border-top:1px solid var(--shd-model-border)}}
|
||||
</style>
|
||||
@@ -793,7 +793,7 @@
|
||||
|
||||
<section class="scenario-panel">
|
||||
<header><div><span>BUILDER CONTROLS</span><strong>SCENARIO ASSUMPTIONS</strong></div><button type="button" class="secondary compact-action" onclick={addScenarioInput}>ADD CONTROL +</button></header>
|
||||
<p>These controls appear in a build’s advanced mechanic settings. Maximum Potential uses the best-case value; Custom Scenario uses the Agent’s saved value.</p>
|
||||
<p>These controls are retained for the planned advanced build model. Maximum Potential will use the best-case value; Custom Scenario will use the Agent’s saved value.</p>
|
||||
{#each rule.scenarioInputs as input, index}
|
||||
<div class="scenario-row">
|
||||
<label class="field">LABEL<input value={input.label} oninput={(event) => updateScenarioInput(index,{label:event.currentTarget.value,key:slug(event.currentTarget.value)})}/></label>
|
||||
|
||||
Reference in New Issue
Block a user