mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-11-17 18:52:06 +00:00
chore: restructure ModMan into list and details
This commit is contained in:
@@ -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)
|
||||
{
|
||||
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,7 +164,7 @@ namespace ui
|
||||
{
|
||||
void RegisterAssetBinds(webview::webview& wv)
|
||||
{
|
||||
Bind<std::string, ZoneAssetsDto>(wv,
|
||||
Bind<std::string, std::optional<ZoneAssetsDto>>(wv,
|
||||
"getAssetsForZone",
|
||||
[](const std::string& zoneName)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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">
|
||||
<div class="header">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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,30 +51,23 @@ 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">
|
||||
<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"
|
||||
@@ -81,23 +85,14 @@ watch(
|
||||
</template>
|
||||
</Listbox>
|
||||
</div>
|
||||
|
||||
<div class="zone-actions">
|
||||
<Button label="Unload" :disabled="!selectedZone" @click="onUnloadClicked" />
|
||||
</div>
|
||||
</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 {
|
||||
@@ -38,7 +38,6 @@ body {
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
text-align: center;
|
||||
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
89
src/ModManUi/src/native/AssetBinds.ts
Normal file
89
src/ModManUi/src/native/AssetBinds.ts
Normal 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>;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user