2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2025-11-17 18:52:06 +00:00

feat: show zone and asset statistics in modman

This commit is contained in:
Jan Laupetin
2025-10-21 23:44:59 +01:00
parent 2bfa4112fb
commit c9e6a1fc64
20 changed files with 261 additions and 25 deletions

View File

@@ -1,26 +1,139 @@
<script setup lang="ts">
import Button from "primevue/button";
import Tag from "primevue/tag";
import MeterGroup, { type MeterItem } from "primevue/metergroup";
import Skeleton from "primevue/skeleton";
import { dt } from "@primeuix/themes";
import type { ZoneDto } from "@/native/ZoneBinds";
import { useZoneStore } from "@/stores/ZoneStore";
import { computed } from "vue";
import { computed, ref, watch } from "vue";
import type { CommonAssetType, ZoneAssetsDto } from "@/native/AssetBinds";
import { webviewBinds } from "@/native";
import { getAssetTypeNameCapitalized } from "@/utils/AssetTypeName";
const zoneStore = useZoneStore();
const props = defineProps<{
selectedZone: string;
selectedZone: string | null;
}>();
const assets = ref<ZoneAssetsDto | null>(null);
const assetCount = computed(() => assets.value?.assets.length ?? 0);
const referenceCount = computed(() => assets.value?.references.length ?? 0);
const METER_COLORS = [
dt("blue.600"),
dt("red.600"),
dt("yellow.600"),
dt("green.600"),
dt("purple.600"),
dt("orange.600"),
dt("teal.600"),
dt("lime.600"),
dt("pink.600"),
];
const meterItems = computed<MeterItem[]>(() => {
const assetTypeCounts: Partial<Record<CommonAssetType, number>> = {};
for (const asset of assets.value?.assets ?? []) {
if (!assetTypeCounts[asset.type]) {
assetTypeCounts[asset.type] = 1;
} else {
assetTypeCounts[asset.type]!++;
}
}
// Do not display asset types with under 3 percent representation
const minItemCountForDisplay = Math.floor(assetCount.value * 0.03);
const assetMeterItems: MeterItem[] = Object.entries(assetTypeCounts)
.filter((entry) => entry[1] > minItemCountForDisplay)
.sort((e0, e1) => e1[1] - e0[1])
.map((entry) => ({
label: getAssetTypeNameCapitalized(entry[0] as CommonAssetType),
value: Math.round((entry[1] / assetCount.value) * 100),
}));
// Since the PrimeVue component rounds to percent we want to fill up the bar completely
const otherCount = 100 - assetMeterItems.reduce((val, entry) => val + entry.value, 0);
if (otherCount > 0) {
assetMeterItems.push({
label: "Other",
value: otherCount,
});
}
return assetMeterItems.map(
(item, index) =>
({
...item,
color: METER_COLORS[index % METER_COLORS.length],
}) satisfies MeterItem,
);
});
const selectedZoneDetails = computed<ZoneDto | null>(
() => zoneStore.loadedZones.find((zone) => zone.name === props.selectedZone) ?? null,
);
watch(
() => props.selectedZone,
(newValue) => {
assets.value = null;
if (!newValue) return;
webviewBinds.getAssetsForZone(newValue).then((res) => {
if (props.selectedZone === newValue) {
assets.value = res;
}
});
},
{ immediate: true },
);
</script>
<template>
<div class="zone-details">
<h2>{{ selectedZone ?? "No zone selected" }}</h2>
<Button label="Show assets" :disabled="!selectedZone" />
<div v-if="selectedZoneDetails" class="zone-tags">
<Tag :value="selectedZoneDetails.game" />
<Tag :value="selectedZoneDetails.platform" />
</div>
<div class="zone-assets">
<template v-if="assets">
<div>{{ assetCount }} assets</div>
<div>{{ referenceCount }} references</div>
<MeterGroup class="asset-meter" :value="meterItems" />
</template>
<template v-else-if="selectedZone">
<Skeleton class="count-skeleton" width="10em" />
<Skeleton class="count-skeleton" width="10em" />
<Skeleton class="count-skeleton" height="0.5lh" />
</template>
</div>
</div>
</template>
<style lang="scss" scoped>
.zone-details {
.zone-tags {
display: flex;
margin-top: 0.5em;
column-gap: 0.5em;
row-gap: 0.5em;
}
.zone-assets {
margin-top: 0.5em;
display: flex;
flex-direction: column;
}
.asset-meter {
padding-top: 0.5em;
}
.count-skeleton {
margin-bottom: 0.5em;
}
</style>

View File

@@ -17,7 +17,7 @@ export enum CommonAssetType {
LIGHT_DEF = "LIGHT_DEF",
UI_MAP = "UI_MAP",
FONT = "FONT",
MENULIST = "MENULIST",
MENU_LIST = "MENU_LIST",
MENU = "MENU",
LOCALIZE_ENTRY = "LOCALIZE_ENTRY",
WEAPON = "WEAPON",

View File

@@ -1,6 +1,22 @@
export enum GameId {
IW3 = "IW3",
IW4 = "IW4",
IW5 = "IW5",
T5 = "T5",
T6 = "T6",
}
export enum GamePlatform {
PC = "PC",
XBOX = "XBOX",
PS3 = "PS3",
}
export interface ZoneDto {
name: string;
filePath: string;
game: GameId;
platform: GamePlatform;
}
export interface ZoneLoadProgressDto {

View File

@@ -0,0 +1,85 @@
import { CommonAssetType } from "@/native/AssetBinds";
const LOOKUP_CAPITALIZED: Record<CommonAssetType, string> = {
[CommonAssetType.PHYS_PRESET]: "Phys preset",
[CommonAssetType.XANIM]: "XAnim",
[CommonAssetType.XMODEL]: "XModel",
[CommonAssetType.MATERIAL]: "Material",
[CommonAssetType.TECHNIQUE_SET]: "Technique set",
[CommonAssetType.IMAGE]: "Image",
[CommonAssetType.SOUND]: "Sound",
[CommonAssetType.SOUND_CURVE]: "Sound curve",
[CommonAssetType.LOADED_SOUND]: "Loaded sound",
[CommonAssetType.CLIP_MAP]: "Clip map",
[CommonAssetType.COM_WORLD]: "Com world",
[CommonAssetType.GAME_WORLD_SP]: "Game world SP",
[CommonAssetType.GAME_WORLD_MP]: "Game world MP",
[CommonAssetType.MAP_ENTS]: "Map ents",
[CommonAssetType.GFX_WORLD]: "Gfx world",
[CommonAssetType.LIGHT_DEF]: "Light def",
[CommonAssetType.UI_MAP]: "UI map",
[CommonAssetType.FONT]: "Font",
[CommonAssetType.MENU_LIST]: "Menu list",
[CommonAssetType.MENU]: "Menu",
[CommonAssetType.LOCALIZE_ENTRY]: "Localize entry",
[CommonAssetType.WEAPON]: "Weapon",
[CommonAssetType.SOUND_DRIVER_GLOBALS]: "Sound driver globals",
[CommonAssetType.FX]: "FX",
[CommonAssetType.IMPACT_FX]: "Impact FX",
[CommonAssetType.AI_TYPE]: "AI type",
[CommonAssetType.MP_TYPE]: "MP type",
[CommonAssetType.CHARACTER]: "Character",
[CommonAssetType.XMODEL_ALIAS]: "XModel alias",
[CommonAssetType.RAW_FILE]: "Raw file",
[CommonAssetType.STRING_TABLE]: "String table",
[CommonAssetType.XMODEL_PIECES]: "XModel pieces",
[CommonAssetType.PHYS_COLL_MAP]: "Phys coll map",
[CommonAssetType.XMODEL_SURFS]: "XModel surfs",
[CommonAssetType.PIXEL_SHADER]: "Pixel shader",
[CommonAssetType.VERTEX_SHADER]: "Vertex shader",
[CommonAssetType.VERTEX_DECL]: "Vertex decl",
[CommonAssetType.FX_WORLD]: "FX world",
[CommonAssetType.LEADERBOARD]: "Leaderboard",
[CommonAssetType.STRUCTURED_DATA_DEF]: "Structured data def",
[CommonAssetType.TRACER]: "Tracer",
[CommonAssetType.VEHICLE]: "Vehicle",
[CommonAssetType.ADDON_MAP_ENTS]: "Addon map ents",
[CommonAssetType.GLASS_WORLD]: "Glass world",
[CommonAssetType.PATH_DATA]: "Path data",
[CommonAssetType.VEHICLE_TRACK]: "Vehicle track",
[CommonAssetType.ATTACHMENT]: "Attachment",
[CommonAssetType.SURFACE_FX]: "Surface FX",
[CommonAssetType.SCRIPT]: "Script",
[CommonAssetType.PHYS_CONSTRAINTS]: "Phys constraints",
[CommonAssetType.DESTRUCTIBLE_DEF]: "Destructible def",
[CommonAssetType.SOUND_PATCH]: "Sound patch",
[CommonAssetType.WEAPON_DEF]: "Weapon def",
[CommonAssetType.WEAPON_VARIANT]: "Weapon variant",
[CommonAssetType.MP_BODY]: "MP body",
[CommonAssetType.MP_HEAD]: "MP head",
[CommonAssetType.PACK_INDEX]: "Pack index",
[CommonAssetType.XGLOBALS]: "XGlobals",
[CommonAssetType.DDL]: "DDL",
[CommonAssetType.GLASSES]: "Glasses",
[CommonAssetType.EMBLEM_SET]: "Emblem set",
[CommonAssetType.FONT_ICON]: "Font icon",
[CommonAssetType.WEAPON_FULL]: "Weapon full",
[CommonAssetType.ATTACHMENT_UNIQUE]: "Attachment unique",
[CommonAssetType.WEAPON_CAMO]: "Weapon camo",
[CommonAssetType.KEY_VALUE_PAIRS]: "Key value pairs",
[CommonAssetType.MEMORY_BLOCK]: "Memory block",
[CommonAssetType.SKINNED_VERTS]: "Skinned verts",
[CommonAssetType.QDB]: "Qdb",
[CommonAssetType.SLUG]: "Slug",
[CommonAssetType.FOOTSTEP_TABLE]: "Footstep table",
[CommonAssetType.FOOTSTEP_FX_TABLE]: "Footstep FX table",
[CommonAssetType.ZBARRIER]: "ZBarrier",
};
export function getAssetTypeNameCapitalized(assetType: CommonAssetType): string {
return LOOKUP_CAPITALIZED[assetType];
}
export function getAssetTypeNameLower(assetType: CommonAssetType): string {
return getAssetTypeNameCapitalized(assetType).toLocaleLowerCase();
}