Files
corrosion-admin-panel/frontend/src/stores/loot.ts
Vantz Stockwell 759bd0be2e
All checks were successful
Build Companion Agent / build (push) Successful in 26s
Test Asgard Runner / test (push) Successful in 3s
feat: Add loot builder backend + static data + DB migration
- Migration 013: loot_profiles table (JSONB loot_table + loot_groups, license-scoped)
- TypeORM entity matching migration schema exactly
- NestJS loot module: 10 endpoints (CRUD, duplicate, apply, import, export, containers)
- Multiplier logic recursively scales Min/Max/Scrap across loot tables and groups
- Apply-to-server writes BetterLoot JSON via NATS file manager + RCON reload
- Frontend static data: 191 Rust items, 51 container prefabs
- TypeScript types for BetterLoot data model (PrefabLoot, LootEntry, LootRNG, etc.)
- Fix vue-tsc errors: UngroupedItems uses LootRNG, null safety in store/view

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 00:30:11 -05:00

180 lines
5.0 KiB
TypeScript

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useApi } from '@/composables/useApi'
import { useToastStore } from '@/stores/toast'
import type { LootProfileSummary, LootProfileFull, LootApplyResult } from '@/types'
export const useLootStore = defineStore('loot', () => {
const profiles = ref<LootProfileSummary[]>([])
const currentProfile = ref<LootProfileFull | null>(null)
const selectedContainer = ref<string | null>(null)
const isLoading = ref(false)
const isSaving = ref(false)
const isApplying = ref(false)
const isDirty = ref(false)
const api = useApi()
const toast = useToastStore()
const activeProfile = computed(() => profiles.value.find(p => p.is_active) || null)
async function fetchProfiles() {
isLoading.value = true
try {
const res = await api.get<{ profiles: LootProfileSummary[] }>('/loot/profiles')
profiles.value = res.profiles
} catch (err) {
toast.error((err as Error).message)
} finally {
isLoading.value = false
}
}
async function loadProfile(id: string) {
isLoading.value = true
try {
const res = await api.get<{ profile: LootProfileFull }>(`/loot/profiles/${id}`)
currentProfile.value = res.profile
isDirty.value = false
// Select first container if none selected
if (!selectedContainer.value && currentProfile.value.loot_table) {
const keys = Object.keys(currentProfile.value.loot_table)
if (keys.length > 0) selectedContainer.value = keys[0] ?? null
}
} catch (err) {
toast.error((err as Error).message)
} finally {
isLoading.value = false
}
}
async function createProfile(name: string, description?: string) {
try {
const res = await api.post<{ profile: LootProfileFull }>('/loot/profiles', {
profile_name: name,
description,
})
await fetchProfiles()
currentProfile.value = res.profile
isDirty.value = false
toast.success(`Profile "${name}" created`)
return res.profile
} catch (err) {
toast.error((err as Error).message)
return null
}
}
async function saveCurrentProfile() {
if (!currentProfile.value) return
isSaving.value = true
try {
await api.put(`/loot/profiles/${currentProfile.value.id}`, {
profile_name: currentProfile.value.profile_name,
description: currentProfile.value.description,
loot_table: currentProfile.value.loot_table,
loot_groups: currentProfile.value.loot_groups,
})
isDirty.value = false
await fetchProfiles()
toast.success('Profile saved')
} catch (err) {
toast.error((err as Error).message)
} finally {
isSaving.value = false
}
}
async function deleteProfile(id: string) {
try {
await api.del(`/loot/profiles/${id}`)
if (currentProfile.value?.id === id) {
currentProfile.value = null
selectedContainer.value = null
}
await fetchProfiles()
toast.success('Profile deleted')
} catch (err) {
toast.error((err as Error).message)
}
}
async function duplicateProfile(id: string) {
try {
const res = await api.post<{ profile: LootProfileFull }>(`/loot/profiles/${id}/duplicate`)
await fetchProfiles()
toast.success(`Profile duplicated as "${res.profile.profile_name}"`)
return res.profile
} catch (err) {
toast.error((err as Error).message)
return null
}
}
async function applyToServer(id: string, multiplier: number) {
isApplying.value = true
try {
const res = await api.post<LootApplyResult>(`/loot/profiles/${id}/apply`, { multiplier })
await fetchProfiles()
toast.success(res.message)
return res
} catch (err) {
toast.error((err as Error).message)
return null
} finally {
isApplying.value = false
}
}
async function importProfile(name: string, lootTable: Record<string, any>, lootGroups?: Record<string, any>) {
try {
const res = await api.post<{ profile: LootProfileFull }>('/loot/import', {
profile_name: name,
loot_table: lootTable,
loot_groups: lootGroups || {},
})
await fetchProfiles()
toast.success(`Profile "${name}" imported`)
return res.profile
} catch (err) {
toast.error((err as Error).message)
return null
}
}
async function exportProfile(id: string, multiplier: number) {
try {
return await api.get<{ profile_name: string; multiplier: number; loot_table: any; loot_groups: any }>(
`/loot/export/${id}?multiplier=${multiplier}`,
)
} catch (err) {
toast.error((err as Error).message)
return null
}
}
function markDirty() {
isDirty.value = true
}
return {
profiles,
currentProfile,
selectedContainer,
isLoading,
isSaving,
isApplying,
isDirty,
activeProfile,
fetchProfiles,
loadProfile,
createProfile,
saveCurrentProfile,
deleteProfile,
duplicateProfile,
applyToServer,
importProfile,
exportProfile,
markDirty,
}
})