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:
		| @@ -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 | ||||
|   | ||||
| @@ -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"> | ||||
|     <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> | ||||
|   | ||||
| @@ -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,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 { | ||||
| @@ -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