Add shared gear tactical augment profiles
This commit is contained in:
@@ -9,12 +9,35 @@
|
||||
const armoredSlots = ['Gloves', 'Knee Pads', 'Body Armor'];
|
||||
const blank = (): Editor => ({ id: '', name: '', slot: 'Holster', tier: 0, level: 1, base_health_min: 0, base_health_max: 0, base_armor_min: null, base_armor_max: null, brandIds: [], talentIds: [] });
|
||||
let editing = $state<Editor | null>(null);
|
||||
let augmentEditing = $state<any | null>(null);
|
||||
let handledResultToken = $state('');
|
||||
const stat = (id: string) => data.stats.find((item) => item.id === id);
|
||||
const format = (value: number | string, statId: string) => {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric)) return '—';
|
||||
const rendered = Number.isInteger(numeric) ? String(numeric) : numeric.toFixed(2).replace(/\.?0+$/, '');
|
||||
const unit = stat(statId)?.unit ?? '';
|
||||
return unit === 'Shots' ? `${rendered} ${unit}` : `${rendered}${unit}`;
|
||||
};
|
||||
const brandNames = (ids: string[]) => ids.map((id) => data.brands.find((brand) => brand.id === id)?.name ?? 'Archived brand').join(', ');
|
||||
const edit = (item: Editor) => { editing = { ...item, brandIds: [...item.brandIds], talentIds: [...item.talentIds] }; };
|
||||
const gearTalents = (slot: string) => data.talents.filter((talent) => talent.category === slot);
|
||||
const changeSlot = (slot: string) => { if (editing) editing = { ...editing, slot, talentIds: [] }; };
|
||||
const editAugments = (profile: any) => {
|
||||
augmentEditing = {
|
||||
...profile,
|
||||
augments: Array.from({ length: profile.requiredSlots }, (_, index) => {
|
||||
const augment = profile.augments.find((item: any) => Number(item.augment_slot) === index + 1);
|
||||
return augment ? { statId: augment.stat_attribute_id, value: augment.value, unlockCost: augment.unlock_cost } : { statId: '', value: '', unlockCost: '' };
|
||||
})
|
||||
};
|
||||
editing = null;
|
||||
};
|
||||
$effect(() => {
|
||||
if (form?.augmentProfileSaved && form.resultToken && form.resultToken !== handledResultToken) {
|
||||
handledResultToken = form.resultToken;
|
||||
augmentEditing = null;
|
||||
}
|
||||
if (form?.gearSaved && form.resultToken && form.resultToken !== handledResultToken) {
|
||||
handledResultToken = form.resultToken;
|
||||
editing = form.gearMode === 'add' ? blank() : null;
|
||||
@@ -23,7 +46,40 @@
|
||||
</script>
|
||||
|
||||
<svelte:head><title>Gear — SHD.build admin</title></svelte:head>
|
||||
<div class="module-toolbar"><div><p class="eyebrow">EQUIPMENT DEFINITION REGISTRY</p><h2>GEAR</h2><p>Configure slot, progression, base survivability ranges, and compatible brands.</p></div><button class="primary" onclick={() => editing = blank()}>ADD GEAR <span>+</span></button></div>
|
||||
<div class="module-toolbar"><div><p class="eyebrow">EQUIPMENT DEFINITION REGISTRY</p><h2>GEAR</h2><p>Configure shared tactical augments, slot progression, survivability, brands, and talents.</p></div><button class="primary" onclick={() => { editing = blank(); augmentEditing = null; }}>ADD GEAR <span>+</span></button></div>
|
||||
<details class="gear-augment-registry">
|
||||
<summary class="selection-head"><div><span>SHARED TACTICAL AUGMENT PROFILES</span><small>ONE PROFILE PER GEAR TYPE · REUSED BY EVERY MATCHING ITEM</small></div><div><b>{data.augmentProfiles.filter((profile) => profile.augments.length === profile.requiredSlots).length} / 6 CONFIGURED</b><i aria-hidden="true">+</i></div></summary>
|
||||
<div class="gear-augment-profile-grid">
|
||||
{#each data.augmentProfiles as profile}
|
||||
<button type="button" class:configured={profile.augments.length === profile.requiredSlots} onclick={() => editAugments(profile)}>
|
||||
<header><div><strong>{profile.slot}</strong><small>{profile.requiredSlots} AUGMENT SLOTS</small></div><span>{profile.augments.length === profile.requiredSlots ? 'CONFIGURED' : 'REQUIRED'}</span></header>
|
||||
<div>{#each Array(profile.requiredSlots) as _, index}{@const augment = profile.augments.find((item) => Number(item.augment_slot) === index + 1)}<span><small>0{index + 1}</small><b>{augment ? `${stat(augment.stat_attribute_id)?.name ?? 'UNKNOWN'} · ${format(augment.value, augment.stat_attribute_id)}` : 'NOT SET'}</b><em>{augment ? `${augment.unlock_cost} COST` : '—'}</em></span>{/each}</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</details>
|
||||
{#if augmentEditing}
|
||||
<div class="dashboard-editor gear-augment-editor">
|
||||
<div class="editor-title"><div><p class="eyebrow">SHARED TACTICAL AUGMENTS</p><h3>{augmentEditing.slot.toUpperCase()} PROFILE</h3></div><button aria-label="Close tactical augment editor" onclick={() => augmentEditing = null}>×</button></div>
|
||||
<PendingForm action="?/saveAugmentProfile">
|
||||
<input type="hidden" name="slot" value={augmentEditing.slot}/>
|
||||
<input type="hidden" name="name" value={`${augmentEditing.slot} tactical augments`}/>
|
||||
<p class="console-note">These {augmentEditing.requiredSlots} augment slots apply to every {augmentEditing.slot} item. Each slot grants exactly one attribute and unlocks at its configured tactical-augment cost.</p>
|
||||
<div class="gear-augment-slots">
|
||||
{#each augmentEditing.augments as augment: any, index}
|
||||
<section>
|
||||
<header><strong>TACTICAL AUGMENT 0{index + 1}</strong><span>SHARED SLOT</span></header>
|
||||
<div class="field"><span>ATTRIBUTE</span><SearchableAssignment items={data.stats} fieldName={`statId${index + 1}`} mode="single" compact subjectLabel={`AUGMENT ${index + 1} ATTRIBUTE`} searchPlaceholder="Search attributes…" bind:value={augment.statId}/></div>
|
||||
<label class="field">VALUE<input name={`value${index + 1}`} type="number" step="any" bind:value={augment.value} required/></label>
|
||||
<label class="field">UNLOCK COST<input name={`unlockCost${index + 1}`} type="number" min="0" step="1" bind:value={augment.unlockCost} required/></label>
|
||||
</section>
|
||||
{/each}
|
||||
</div>
|
||||
{#if form?.augmentProfileError}<p class="error">{form.augmentProfileError}</p>{/if}
|
||||
<div class="form-actions"><button type="button" class="secondary" onclick={() => augmentEditing = null}>CANCEL</button><button class="primary">SAVE SHARED PROFILE →</button></div>
|
||||
</PendingForm>
|
||||
</div>
|
||||
{/if}
|
||||
<KpiFilters active={data.filter} items={[{value:'',label:'TOTAL GEAR',count:data.summary.total,description:'Show all definitions'},{value:'active',label:'ACTIVE',count:data.summary.active,description:'Available to builder'},{value:'armored',label:'ARMOR-BEARING',count:data.summary.armored,description:'Gloves, knees, and body armor'},{value:'talented',label:'TALENT-BEARING',count:data.summary.talented,description:'Backpacks and body armor'}]}/>
|
||||
{#if editing}
|
||||
<div class="dashboard-editor">
|
||||
|
||||
Reference in New Issue
Block a user