2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2025-10-31 02:26:59 +00:00

chore: restructure ModMan into list and details

This commit is contained in:
Jan Laupetin
2025-10-21 20:00:39 +01:00
parent 3995596e6c
commit 2bfa4112fb
12 changed files with 252 additions and 126 deletions

View File

@@ -99,29 +99,47 @@ namespace
{
public:
std::vector<AssetDto> assets;
std::vector<AssetDto> references;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneAssetsDto, assets);
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneAssetsDto, assets, references);
ZoneAssetsDto CreateZoneAssetsDto(const Zone& zone)
{
std::vector<AssetDto> assets;
std::vector<AssetDto> references;
// Reserve some entries already. Numbers are arbitrary.
const auto assetCount = zone.m_pools->GetTotalAssetCount();
assets.reserve(assetCount / 2);
references.reserve(assetCount / 8);
const auto& assetTypeMapper = *ICommonAssetTypeMapper::GetCommonAssetMapperByGame(zone.m_game_id);
for (const auto& asset : *zone.m_pools)
{
assets.emplace_back(AssetDto{
.type = assetTypeMapper.GameToCommonAssetType(asset->m_type),
.name = asset->m_name,
});
if (asset->IsReference())
{
references.emplace_back(AssetDto{
.type = assetTypeMapper.GameToCommonAssetType(asset->m_type),
.name = asset->ReferencedAssetName(),
});
}
else
{
assets.emplace_back(AssetDto{
.type = assetTypeMapper.GameToCommonAssetType(asset->m_type),
.name = asset->m_name,
});
}
}
return ZoneAssetsDto{
.assets = std::move(assets),
.references = std::move(references),
};
}
ZoneAssetsDto GetZonesForZone(const std::string& zoneName)
std::optional<ZoneAssetsDto> GetZonesForZone(const std::string& zoneName)
{
auto& context = ModManContext::Get().m_fast_file;
@@ -138,7 +156,7 @@ namespace
}
}
return ZoneAssetsDto{.assets = std::vector<AssetDto>()};
return std::nullopt;
}
} // namespace
@@ -146,11 +164,11 @@ namespace ui
{
void RegisterAssetBinds(webview::webview& wv)
{
Bind<std::string, ZoneAssetsDto>(wv,
"getAssetsForZone",
[](const std::string& zoneName)
{
return GetZonesForZone(zoneName);
});
Bind<std::string, std::optional<ZoneAssetsDto>>(wv,
"getAssetsForZone",
[](const std::string& zoneName)
{
return GetZonesForZone(zoneName);
});
}
} // namespace ui

View File

@@ -63,7 +63,7 @@ namespace
newWindow.set_title("OpenAssetTools ModMan");
newWindow.set_size(1280, 640, WEBVIEW_HINT_NONE);
newWindow.set_size(480, 320, WEBVIEW_HINT_MIN);
newWindow.set_size(640, 480, WEBVIEW_HINT_MIN);
InstallAssetHandler(newWindow);
ui::RegisterAllBinds(newWindow);

View File

@@ -1,30 +1,12 @@
<script setup lang="ts">
import Button from "primevue/button";
import { webviewBinds } from "@/native";
import ZoneSelector from "./components/ZoneSelector.vue";
import { useZoneStore } from "./stores/ZoneStore";
const zoneStore = useZoneStore();
async function openFastFileSelect() {
return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] });
}
async function onOpenFastFileClick() {
const fastFilePath = await openFastFileSelect();
if (!fastFilePath) return;
zoneStore.loadFastFile(fastFilePath);
}
import ZoneSelector from "./components/unlinking/zone_selector/ZoneSelector.vue";
</script>
<template>
<main class="container">
<h1>Welcome to ModMan</h1>
<small>Nothing to see here yet, this is mainly for testing</small>
<div class="actions">
<Button label="Load fastfile" @click="onOpenFastFileClick" />
<div class="header">
<h1>Welcome to ModMan</h1>
<small>Nothing to see here yet, this is mainly for testing</small>
</div>
<ZoneSelector />
@@ -32,38 +14,7 @@ async function onOpenFastFileClick() {
</template>
<style scoped lang="scss">
.actions {
display: flex;
justify-content: center;
column-gap: 0.5em;
margin-top: 1em;
}
.zone-list {
display: flex;
flex-direction: column;
row-gap: 0.5em;
}
.zone > button {
margin-left: 0.5em;
}
@starting-style {
.progressbar {
opacity: 0;
}
}
.progressbar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 0.4rem;
transition: opacity 0.2s ease-in-out;
opacity: 1;
.header {
text-align: center;
}
</style>

View File

@@ -0,0 +1,41 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { useZoneStore } from "@/stores/ZoneStore";
import ZoneSelectorDetails from "./ZoneSelectorDetails.vue";
import ZoneSelectorZoneList from "./ZoneSelectorZoneList.vue";
const zoneStore = useZoneStore();
const selectedZone = ref<string | null>(null);
watch(
() => zoneStore.loadedZones,
(newValue) => {
// Reset selection if unloaded
if (!selectedZone.value) return;
if (newValue.findIndex((loadedZone) => loadedZone.name === selectedZone.value) >= 0) return;
selectedZone.value = null;
},
{ deep: true },
);
</script>
<template>
<div class="zone-selector">
<ZoneSelectorZoneList v-model:selected-zone="selectedZone" />
<ZoneSelectorDetails :selected-zone="selectedZone" />
</div>
</template>
<style lang="scss" scoped>
.zone-selector {
display: flex;
flex-direction: row;
width: 100%;
& > * {
width: 50%;
padding: 1rem 2rem;
}
}
</style>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import type { ZoneDto } from "@/native/ZoneBinds";
import { useZoneStore } from "@/stores/ZoneStore";
import { computed } from "vue";
const zoneStore = useZoneStore();
const props = defineProps<{
selectedZone: string;
}>();
const selectedZoneDetails = computed<ZoneDto | null>(
() => zoneStore.loadedZones.find((zone) => zone.name === props.selectedZone) ?? null,
);
</script>
<template>
<div class="zone-details">
<h2>{{ selectedZone ?? "No zone selected" }}</h2>
</div>
</template>
<style lang="scss" scoped>
.zone-details {
}
</style>

View File

@@ -2,7 +2,7 @@
import Button from "primevue/button";
import ProgressBar from "primevue/progressbar";
import Listbox from "primevue/listbox";
import { computed, ref, watch } from "vue";
import { computed } from "vue";
import { useZoneStore } from "@/stores/ZoneStore";
import { webviewBinds } from "@/native";
@@ -12,7 +12,18 @@ interface SelectableZone {
}
const zoneStore = useZoneStore();
const selectedZone = ref<SelectableZone | null>(null);
const selectedZone = defineModel<string | null>("selectedZone");
async function openFastFileSelect() {
return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] });
}
async function onOpenFastFileClick() {
const fastFilePath = await openFastFileSelect();
if (!fastFilePath) return;
zoneStore.loadFastFile(fastFilePath);
}
const availableZones = computed<SelectableZone[]>(() => {
const result = [
@@ -40,64 +51,48 @@ const availableZones = computed<SelectableZone[]>(() => {
function onUnloadClicked() {
if (!selectedZone.value) return;
webviewBinds.unloadZone(selectedZone.value.zoneName).catch((e: string) => {
webviewBinds.unloadZone(selectedZone.value).catch((e: string) => {
console.error("Failed to unload zone:", e);
});
}
watch(
() => zoneStore.loadedZones,
(newValue) => {
// Reset selection if unloaded
if (!selectedZone.value) return;
if (newValue.findIndex((loadedZone) => loadedZone.name === selectedZone.value?.zoneName) >= 0)
return;
selectedZone.value = null;
},
{ deep: true },
);
</script>
<template>
<div class="zone-selector">
<div class="zone-list">
<Listbox
v-model="selectedZone"
:options="availableZones"
data-key="zoneName"
emptyMessage="No zones loaded"
class="zone"
>
<template #option="{ option }: { option: SelectableZone }">
<div class="selectable-zone">
<span>{{ option.zoneName }}</span>
<ProgressBar
v-if="option.isLoading"
class="zone-progressbar"
:value="zoneStore.getPercentageForZoneBeingLoaded(option.zoneName)"
:show-value="false"
/>
</div>
</template>
</Listbox>
</div>
<div class="zone-actions">
<div class="zone-list">
<div class="zone-list-actions">
<Button label="Load fastfile" @click="onOpenFastFileClick" />
<Button label="Unload" :disabled="!selectedZone" @click="onUnloadClicked" />
</div>
<Listbox
v-model="selectedZone"
:options="availableZones"
option-disabled="isLoading"
option-value="zoneName"
data-key="zoneName"
emptyMessage="No zones loaded"
class="zone"
>
<template #option="{ option }: { option: SelectableZone }">
<div class="selectable-zone">
<span>{{ option.zoneName }}</span>
<ProgressBar
v-if="option.isLoading"
class="zone-progressbar"
:value="zoneStore.getPercentageForZoneBeingLoaded(option.zoneName)"
:show-value="false"
/>
</div>
</template>
</Listbox>
</div>
</template>
<style lang="scss" scoped>
.zone-selector {
.zone-list-actions {
display: flex;
flex-direction: row;
width: 100%;
& > * {
width: 50%;
padding: 1rem 2rem;
}
column-gap: 0.25em;
row-gap: 0.25em;
padding: 0.25em 0;
}
.selectable-zone {

View File

@@ -38,7 +38,6 @@ body {
position: relative;
flex-direction: column;
justify-content: start;
text-align: center;
height: 100vh;
}

View File

@@ -0,0 +1,89 @@
export enum CommonAssetType {
PHYS_PRESET = "PHYS_PRESET",
XANIM = "XANIM",
XMODEL = "XMODEL",
MATERIAL = "MATERIAL",
TECHNIQUE_SET = "TECHNIQUE_SET",
IMAGE = "IMAGE",
SOUND = "SOUND",
SOUND_CURVE = "SOUND_CURVE",
LOADED_SOUND = "LOADED_SOUND",
CLIP_MAP = "CLIP_MAP",
COM_WORLD = "COM_WORLD",
GAME_WORLD_SP = "GAME_WORLD_SP",
GAME_WORLD_MP = "GAME_WORLD_MP",
MAP_ENTS = "MAP_ENTS",
GFX_WORLD = "GFX_WORLD",
LIGHT_DEF = "LIGHT_DEF",
UI_MAP = "UI_MAP",
FONT = "FONT",
MENULIST = "MENULIST",
MENU = "MENU",
LOCALIZE_ENTRY = "LOCALIZE_ENTRY",
WEAPON = "WEAPON",
SOUND_DRIVER_GLOBALS = "SOUND_DRIVER_GLOBALS",
FX = "FX",
IMPACT_FX = "IMPACT_FX",
AI_TYPE = "AI_TYPE",
MP_TYPE = "MP_TYPE",
CHARACTER = "CHARACTER",
XMODEL_ALIAS = "XMODEL_ALIAS",
RAW_FILE = "RAW_FILE",
STRING_TABLE = "STRING_TABLE",
XMODEL_PIECES = "XMODEL_PIECES",
PHYS_COLL_MAP = "PHYS_COLL_MAP",
XMODEL_SURFS = "XMODEL_SURFS",
PIXEL_SHADER = "PIXEL_SHADER",
VERTEX_SHADER = "VERTEX_SHADER",
VERTEX_DECL = "VERTEX_DECL",
FX_WORLD = "FX_WORLD",
LEADERBOARD = "LEADERBOARD",
STRUCTURED_DATA_DEF = "STRUCTURED_DATA_DEF",
TRACER = "TRACER",
VEHICLE = "VEHICLE",
ADDON_MAP_ENTS = "ADDON_MAP_ENTS",
GLASS_WORLD = "GLASS_WORLD",
PATH_DATA = "PATH_DATA",
VEHICLE_TRACK = "VEHICLE_TRACK",
ATTACHMENT = "ATTACHMENT",
SURFACE_FX = "SURFACE_FX",
SCRIPT = "SCRIPT",
PHYS_CONSTRAINTS = "PHYS_CONSTRAINTS",
DESTRUCTIBLE_DEF = "DESTRUCTIBLE_DEF",
SOUND_PATCH = "SOUND_PATCH",
WEAPON_DEF = "WEAPON_DEF",
WEAPON_VARIANT = "WEAPON_VARIANT",
MP_BODY = "MP_BODY",
MP_HEAD = "MP_HEAD",
PACK_INDEX = "PACK_INDEX",
XGLOBALS = "XGLOBALS",
DDL = "DDL",
GLASSES = "GLASSES",
EMBLEM_SET = "EMBLEM_SET",
FONT_ICON = "FONT_ICON",
WEAPON_FULL = "WEAPON_FULL",
ATTACHMENT_UNIQUE = "ATTACHMENT_UNIQUE",
WEAPON_CAMO = "WEAPON_CAMO",
KEY_VALUE_PAIRS = "KEY_VALUE_PAIRS",
MEMORY_BLOCK = "MEMORY_BLOCK",
SKINNED_VERTS = "SKINNED_VERTS",
QDB = "QDB",
SLUG = "SLUG",
FOOTSTEP_TABLE = "FOOTSTEP_TABLE",
FOOTSTEP_FX_TABLE = "FOOTSTEP_FX_TABLE",
ZBARRIER = "ZBARRIER",
}
export interface AssetDto {
type: CommonAssetType;
name: string;
}
export interface ZoneAssetsDto {
assets: AssetDto[];
references: AssetDto[];
}
export interface AssetBinds {
getAssetsForZone(zoneName: string): Promise<ZoneAssetsDto | null>;
}

View File

@@ -1,8 +1,9 @@
import type { AssetBinds } from "./AssetBinds";
import type { DialogBinds } from "./DialogBinds";
import type { UnlinkingBinds, UnlinkingEventMap } from "./UnlinkingBinds";
import type { ZoneBinds, ZoneEventMap } from "./ZoneBinds";
export type NativeMethods = DialogBinds & UnlinkingBinds & ZoneBinds;
export type NativeMethods = AssetBinds & DialogBinds & UnlinkingBinds & ZoneBinds;
type NativeEventMap = UnlinkingEventMap & ZoneEventMap;

View File

@@ -98,6 +98,11 @@ bool XAssetInfoGeneric::IsReference() const
return !m_name.empty() && m_name[0] == ',';
}
std::string XAssetInfoGeneric::ReferencedAssetName() const
{
return m_name.substr(1);
}
std::string XAssetInfoGeneric::NormalizeAssetName(std::string input)
{
utils::MakeStringLowerCase(input);

View File

@@ -55,6 +55,7 @@ public:
XAssetInfoGeneric& operator=(XAssetInfoGeneric&& other) noexcept = default;
[[nodiscard]] bool IsReference() const;
[[nodiscard]] std::string ReferencedAssetName() const;
static std::string NormalizeAssetName(std::string input);

View File

@@ -1,5 +1,5 @@
#pragma once
#include "Utils/ClassUtils.h"
#include "XAssetInfo.h"
#include "Zone/Zone.h"
#include "Zone/ZoneTypes.h"
@@ -33,16 +33,16 @@ public:
std::vector<XAssetInfoGeneric*> dependencies,
std::vector<scr_string_t> usedScriptStrings,
std::vector<IndirectAssetReference> indirectAssetReferences);
_NODISCARD virtual XAssetInfoGeneric* GetAsset(asset_type_t type, const std::string& name) const = 0;
_NODISCARD virtual XAssetInfoGeneric* GetAssetOrAssetReference(asset_type_t type, const std::string& name) const;
[[nodiscard]] virtual XAssetInfoGeneric* GetAsset(asset_type_t type, const std::string& name) const = 0;
[[nodiscard]] virtual XAssetInfoGeneric* GetAssetOrAssetReference(asset_type_t type, const std::string& name) const;
_NODISCARD virtual asset_type_t GetAssetTypeCount() const = 0;
_NODISCARD virtual std::optional<const char*> GetAssetTypeName(asset_type_t assetType) const = 0;
[[nodiscard]] virtual asset_type_t GetAssetTypeCount() const = 0;
[[nodiscard]] virtual std::optional<const char*> GetAssetTypeName(asset_type_t assetType) const = 0;
_NODISCARD size_t GetTotalAssetCount() const;
[[nodiscard]] size_t GetTotalAssetCount() const;
_NODISCARD iterator begin() const;
_NODISCARD iterator end() const;
[[nodiscard]] iterator begin() const;
[[nodiscard]] iterator end() const;
static std::unique_ptr<ZoneAssetPools> CreateForGame(GameId game, Zone* zone, zone_priority_t priority);