phantasmal-world/src/quest_editor/loading/EntityAssetLoader.ts

286 lines
11 KiB
TypeScript
Raw Normal View History

import { BufferGeometry, CylinderBufferGeometry, Texture } from "three";
2019-07-18 01:37:48 +08:00
import { LoadingCache } from "./LoadingCache";
import { Endianness } from "../../core/data_formats/Endianness";
import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCursor";
import { ninja_object_to_buffer_geometry } from "../../core/rendering/conversion/ninja_geometry";
import { parse_nj, parse_xj } from "../../core/data_formats/parsing/ninja";
import { parse_xvm } from "../../core/data_formats/parsing/ninja/texture";
import { xvm_to_textures } from "../../core/rendering/conversion/ninja_textures";
import { object_data, ObjectType } from "../../core/data_formats/parsing/quest/object_types";
import { NpcType } from "../../core/data_formats/parsing/quest/npc_types";
import {
entity_type_to_string,
EntityType,
is_npc_type,
} from "../../core/data_formats/parsing/quest/entities";
import { HttpClient } from "../../core/HttpClient";
import { Disposable } from "../../core/observable/Disposable";
import { LogManager } from "../../core/Logger";
import { DisposablePromise } from "../../core/DisposablePromise";
import { Disposer } from "../../core/observable/Disposer";
const logger = LogManager.get("quest_editor/loading/EntityAssetLoader");
const DEFAULT_ENTITY = new CylinderBufferGeometry(3, 3, 20);
DEFAULT_ENTITY.translate(0, 10, 0);
DEFAULT_ENTITY.computeBoundingBox();
DEFAULT_ENTITY.computeBoundingSphere();
const DEFAULT_ENTITY_PROMISE = DisposablePromise.resolve<BufferGeometry>(DEFAULT_ENTITY);
const DEFAULT_ENTITY_TEX: Texture[] = [];
const DEFAULT_ENTITY_TEX_PROMISE = DisposablePromise.resolve<Texture[]>(DEFAULT_ENTITY_TEX);
export class EntityAssetLoader implements Disposable {
private readonly disposer = new Disposer();
private readonly geom_cache = this.disposer.add(new LoadingCache<EntityType, BufferGeometry>());
private readonly tex_cache = this.disposer.add(new LoadingCache<EntityType, Texture[]>());
constructor(private readonly http_client: HttpClient) {
this.warm_up_caches();
}
dispose(): void {
this.disposer.dispose();
}
load_geometry(type: EntityType): DisposablePromise<BufferGeometry> {
return this.geom_cache.get_or_set(type, () =>
this.load_data(type, AssetType.Geometry)
.then(({ url, data }) => {
const cursor = new ArrayBufferCursor(data, Endianness.Little);
const nj_objects = url.endsWith(".nj") ? parse_nj(cursor) : parse_xj(cursor);
if (nj_objects.success && nj_objects.value.length) {
return ninja_object_to_buffer_geometry(nj_objects.value[0]);
} else {
2020-01-17 04:45:20 +08:00
logger.warn(`Couldn't parse ${url} for ${entity_type_to_string(type)}.`);
return DEFAULT_ENTITY;
}
})
.catch(e => {
2020-01-17 04:45:20 +08:00
logger.warn(
`Couldn't load geometry file for ${entity_type_to_string(type)}.`,
e,
);
return DEFAULT_ENTITY;
}),
);
}
load_textures(type: EntityType): DisposablePromise<Texture[]> {
return this.tex_cache.get_or_set(type, () =>
this.load_data(type, AssetType.Texture)
.then(({ data }) => {
const cursor = new ArrayBufferCursor(data, Endianness.Little);
const xvm = parse_xvm(cursor);
return xvm.success ? xvm_to_textures(xvm.value) : [];
})
.catch(e => {
2020-01-17 04:45:20 +08:00
logger.warn(
`Couldn't load texture file for ${entity_type_to_string(type)}.`,
e,
);
return DEFAULT_ENTITY_TEX;
}),
);
}
private load_data(
type: EntityType,
asset_type: AssetType,
): DisposablePromise<{ url: string; data: ArrayBuffer }> {
const url = entity_type_to_url(type, asset_type);
return this.http_client
.get(url)
.array_buffer()
.then(data => ({ url, data }));
}
2019-07-18 01:37:48 +08:00
/**
* Warms up the caches with default data for all entities without assets.
*/
private warm_up_caches(): void {
for (const type of [
NpcType.Unknown,
NpcType.Migium,
NpcType.Hidoom,
NpcType.DeathGunner,
NpcType.StRappy,
NpcType.HalloRappy,
NpcType.EggRappy,
NpcType.Migium2,
NpcType.Hidoom2,
NpcType.Recon,
ObjectType.Unknown,
ObjectType.PlayerSet,
ObjectType.Particle,
ObjectType.LightCollision,
ObjectType.EnvSound,
ObjectType.FogCollision,
ObjectType.EventCollision,
ObjectType.CharaCollision,
ObjectType.ObjRoomID,
ObjectType.LensFlare,
ObjectType.ScriptCollision,
ObjectType.MapCollision,
ObjectType.ScriptCollisionA,
ObjectType.ItemLight,
ObjectType.RadarCollision,
ObjectType.FogCollisionSW,
ObjectType.ImageBoard,
ObjectType.UnknownItem29,
ObjectType.UnknownItem30,
ObjectType.UnknownItem31,
ObjectType.MenuActivation,
ObjectType.BoxDetectObject,
ObjectType.SymbolChatObject,
ObjectType.TouchPlateObject,
ObjectType.TargetableObject,
ObjectType.EffectObject,
ObjectType.CountDownObject,
ObjectType.UnknownItem38,
ObjectType.UnknownItem39,
ObjectType.UnknownItem40,
ObjectType.UnknownItem41,
ObjectType.TelepipeLocation,
ObjectType.BGMCollision,
ObjectType.Pioneer2InvisibleTouchplate,
ObjectType.TempleMapDetect,
ObjectType.Firework,
ObjectType.MainRagolTeleporterBattleInNextArea,
ObjectType.Rainbow,
ObjectType.FloatingBlueLight,
ObjectType.PopupTrapNoTech,
ObjectType.Poison,
ObjectType.EnemyTypeBoxYellow,
ObjectType.EnemyTypeBoxBlue,
ObjectType.EmptyTypeBoxBlue,
ObjectType.FloatingRocks,
ObjectType.FloatingSoul,
ObjectType.Butterfly,
ObjectType.UnknownItem400,
ObjectType.CCAAreaTeleporter,
ObjectType.UnknownItem523,
ObjectType.WhiteBird,
ObjectType.OrangeBird,
ObjectType.UnknownItem529,
ObjectType.UnknownItem530,
ObjectType.Seagull,
ObjectType.UnknownItem576,
ObjectType.WarpInBarbaRayRoom,
ObjectType.UnknownItem672,
ObjectType.InstaWarp,
ObjectType.LabInvisibleObject,
ObjectType.UnknownItem700,
]) {
this.geom_cache.set(type, DEFAULT_ENTITY_PROMISE);
this.tex_cache.set(type, DEFAULT_ENTITY_TEX_PROMISE);
}
}
2019-07-18 01:37:48 +08:00
}
enum AssetType {
Geometry,
Texture,
}
/**
* @param type
* @param asset_type
* @param no - Asset number. Some entities have multiple assets that need to be combined.
*/
function entity_type_to_url(type: EntityType, asset_type: AssetType, no?: number): string {
const no_str = no == undefined ? "" : `-${no}`;
if (is_npc_type(type)) {
switch (type) {
// The dubswitch model is in XJ format.
case NpcType.Dubswitch:
return `/npcs/${NpcType[type]}.${asset_type === AssetType.Geometry ? "xj" : "xvm"}`;
// Episode II VR Temple
case NpcType.Hildebear2:
return entity_type_to_url(NpcType.Hildebear, asset_type, no);
case NpcType.Hildeblue2:
return entity_type_to_url(NpcType.Hildeblue, asset_type, no);
case NpcType.RagRappy2:
return entity_type_to_url(NpcType.RagRappy, asset_type, no);
case NpcType.Monest2:
return entity_type_to_url(NpcType.Monest, asset_type, no);
case NpcType.Mothmant2:
return entity_type_to_url(NpcType.Mothmant, asset_type, no);
case NpcType.PoisonLily2:
return entity_type_to_url(NpcType.PoisonLily, asset_type, no);
case NpcType.NarLily2:
return entity_type_to_url(NpcType.NarLily, asset_type, no);
case NpcType.GrassAssassin2:
return entity_type_to_url(NpcType.GrassAssassin, asset_type, no);
case NpcType.Dimenian2:
return entity_type_to_url(NpcType.Dimenian, asset_type, no);
case NpcType.LaDimenian2:
return entity_type_to_url(NpcType.LaDimenian, asset_type, no);
case NpcType.SoDimenian2:
return entity_type_to_url(NpcType.SoDimenian, asset_type, no);
case NpcType.DarkBelra2:
return entity_type_to_url(NpcType.DarkBelra, asset_type, no);
// Episode II VR Spaceship
case NpcType.SavageWolf2:
return entity_type_to_url(NpcType.SavageWolf, asset_type, no);
case NpcType.BarbarousWolf2:
return entity_type_to_url(NpcType.BarbarousWolf, asset_type, no);
case NpcType.PanArms2:
return entity_type_to_url(NpcType.PanArms, asset_type, no);
case NpcType.Dubchic2:
return entity_type_to_url(NpcType.Dubchic, asset_type, no);
case NpcType.Gilchic2:
return entity_type_to_url(NpcType.Gilchic, asset_type, no);
case NpcType.Garanz2:
return entity_type_to_url(NpcType.Garanz, asset_type, no);
case NpcType.Dubswitch2:
return entity_type_to_url(NpcType.Dubswitch, asset_type, no);
case NpcType.Delsaber2:
return entity_type_to_url(NpcType.Delsaber, asset_type, no);
case NpcType.ChaosSorcerer2:
return entity_type_to_url(NpcType.ChaosSorcerer, asset_type, no);
2019-07-18 01:37:48 +08:00
default:
return `/npcs/${NpcType[type]}${no_str}.${
asset_type === AssetType.Geometry ? "nj" : "xvm"
}`;
2019-07-18 01:37:48 +08:00
}
} else {
if (asset_type === AssetType.Geometry) {
switch (type) {
case ObjectType.EasterEgg:
case ObjectType.ChristmasTree:
case ObjectType.ChristmasWreath:
case ObjectType.TwentyFirstCentury:
case ObjectType.Sonic:
case ObjectType.WelcomeBoard:
case ObjectType.FloatingJellyfish:
case ObjectType.RuinsSeal:
case ObjectType.Dolphin:
case ObjectType.Cacti:
case ObjectType.BigBrownRock:
case ObjectType.PoisonPlant:
case ObjectType.BigBlackRocks:
case ObjectType.FallingRock:
case ObjectType.DesertFixedTypeBoxBreakableCrystals:
case ObjectType.BeeHive:
return `/objects/${object_data(type).pso_id}${no_str}.nj`;
default:
return `/objects/${object_data(type).pso_id}${no_str}.xj`;
}
} else {
return `/objects/${object_data(type).pso_id}${no_str}.xvm`;
}
2019-07-18 01:37:48 +08:00
}
}