import Logger from "js-logger"; import { Instruction, InstructionSegment, Segment, SegmentType, } from "../../../scripting/instructions"; import { Opcode } from "../../../scripting/opcodes"; import { prs_compress } from "../../compression/prs/compress"; import { prs_decompress } from "../../compression/prs/decompress"; import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor"; import { Cursor } from "../../cursor/Cursor"; import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor"; import { Endianness } from "../../Endianness"; import { Vec3 } from "../../vector"; import { AreaVariant, get_area_variant } from "./areas"; import { BinFile, parse_bin, write_bin } from "./bin"; import { DatFile, DatNpc, DatObject, DatUnknown, parse_dat, write_dat } from "./dat"; import { QuestNpc, QuestObject } from "./entities"; import { Episode } from "./Episode"; import { object_data, pso_id_to_object_type } from "./object_types"; import { parse_qst, QstContainedFile, write_qst } from "./qst"; import { NpcType } from "./npc_types"; const logger = Logger.get("data_formats/parsing/quest"); export type Quest = { readonly id: number; readonly language: number; readonly name: string; readonly short_description: string; readonly long_description: string; readonly episode: Episode; readonly objects: QuestObject[]; readonly npcs: QuestNpc[]; /** * (Partial) raw DAT data that can't be parsed yet by Phantasmal. */ readonly dat_unknowns: DatUnknown[]; readonly object_code: Segment[]; readonly shop_items: number[]; readonly area_variants: AreaVariant[]; }; /** * High level parsing function that delegates to lower level parsing functions. * * Always delegates to parse_qst at the moment. */ export function parse_quest(cursor: Cursor, lenient: boolean = false): Quest | undefined { const qst = parse_qst(cursor); if (!qst) { return; } let dat_file: QstContainedFile | undefined; let bin_file: QstContainedFile | undefined; for (const file of qst.files) { const file_name = file.name.trim().toLowerCase(); if (file_name.endsWith(".dat")) { dat_file = file; } else if (file_name.endsWith(".bin")) { bin_file = file; } } if (!dat_file) { logger.error("File contains no DAT file."); return; } if (!bin_file) { logger.error("File contains no BIN file."); return; } const dat_decompressed = prs_decompress( new ArrayBufferCursor(dat_file.data, Endianness.Little), ); const dat = parse_dat(dat_decompressed); const bin_decompressed = prs_decompress( new ArrayBufferCursor(bin_file.data, Endianness.Little), ); const bin = parse_bin(bin_decompressed, [0], lenient); let episode = Episode.I; let area_variants: AreaVariant[] = []; if (bin.object_code.length) { let label_0_segment: InstructionSegment | undefined; for (const segment of bin.object_code) { if (segment.type === SegmentType.Instructions && segment.labels.includes(0)) { label_0_segment = segment; break; } } if (label_0_segment) { episode = get_episode(label_0_segment.instructions); area_variants = extract_area_variants( dat, episode, label_0_segment.instructions, lenient, ); } else { logger.warn(`No instruction for label 0 found.`); } } else { logger.warn("File contains no instruction labels."); } return { id: bin.quest_id, language: bin.language, name: bin.quest_name, short_description: bin.short_description, long_description: bin.long_description, episode, objects: parse_obj_data(dat.objs), npcs: parse_npc_data(episode, dat.npcs), dat_unknowns: dat.unknowns, object_code: bin.object_code, shop_items: bin.shop_items, area_variants, }; } export function write_quest_qst(quest: Quest, file_name: string): ArrayBuffer { const dat = write_dat({ objs: objects_to_dat_data(quest.objects), npcs: npcs_to_dat_data(quest.npcs), unknowns: quest.dat_unknowns, }); const bin = write_bin( new BinFile( quest.id, quest.language, quest.name, quest.short_description, quest.long_description, quest.object_code, quest.shop_items, ), ); const ext_start = file_name.lastIndexOf("."); const base_file_name = ext_start === -1 ? file_name.slice(0, 11) : file_name.slice(0, Math.min(11, ext_start)); return write_qst({ files: [ { name: base_file_name + ".dat", id: quest.id, data: prs_compress( new ResizableBufferCursor(dat, Endianness.Little), ).array_buffer(), }, { name: base_file_name + ".bin", id: quest.id, data: prs_compress(new ArrayBufferCursor(bin, Endianness.Little)).array_buffer(), }, ], }); } /** * Defaults to episode I. */ function get_episode(func_0_instructions: Instruction[]): Episode { const set_episode = func_0_instructions.find( instruction => instruction.opcode === Opcode.SET_EPISODE, ); if (set_episode) { switch (set_episode.args[0].value) { default: case 0: return Episode.I; case 1: return Episode.II; case 2: return Episode.IV; } } else { logger.debug("Function 0 has no set_episode instruction."); return Episode.I; } } function extract_area_variants( dat: DatFile, episode: Episode, func_0_instructions: Instruction[], lenient: boolean, ): AreaVariant[] { // Add area variants that have npcs or objects even if there are no BB_Map_Designate instructions for them. const area_variants = new Map(); for (const npc of dat.npcs) { area_variants.set(npc.area_id, 0); } for (const obj of dat.objs) { area_variants.set(obj.area_id, 0); } const bb_maps = func_0_instructions.filter( instruction => instruction.opcode === Opcode.BB_MAP_DESIGNATE, ); for (const bb_map of bb_maps) { const area_id: number = bb_map.args[0].value; const variant_id: number = bb_map.args[2].value; area_variants.set(area_id, variant_id); } const area_variants_array: AreaVariant[] = []; for (const [area_id, variant_id] of area_variants.entries()) { try { area_variants_array.push(get_area_variant(episode, area_id, variant_id)); } catch (e) { if (lenient) { logger.error(`Unknown area variant.`, e); } else { throw e; } } } // Sort by area order and then variant id. return area_variants_array.sort((a, b) => a.area.order - b.area.order || a.id - b.id); } function parse_obj_data(objs: DatObject[]): QuestObject[] { return objs.map(obj_data => { return { type: pso_id_to_object_type(obj_data.type_id), area_id: obj_data.area_id, section_id: obj_data.section_id, position: obj_data.position, rotation: obj_data.rotation, scale: obj_data.scale, unknown: obj_data.unknown, }; }); } function parse_npc_data(episode: number, npcs: DatNpc[]): QuestNpc[] { return npcs.map(npc_data => { return { type: get_npc_type(episode, npc_data), area_id: npc_data.area_id, section_id: npc_data.section_id, position: npc_data.position, rotation: npc_data.rotation, scale: npc_data.scale, unknown: npc_data.unknown, pso_type_id: npc_data.type_id, pso_skin: npc_data.skin, }; }); } // TODO: detect Mothmant, St. Rappy, Hallo Rappy, Egg Rappy, Death Gunner, Bulk and Recon. function get_npc_type(episode: number, { type_id, scale, skin, area_id }: DatNpc): NpcType { const regular = Math.abs(scale.y - 1) > 0.00001; switch (`${type_id}, ${skin % 3}, ${episode}`) { case `${0x044}, 0, 1`: return NpcType.Booma; case `${0x044}, 1, 1`: return NpcType.Gobooma; case `${0x044}, 2, 1`: return NpcType.Gigobooma; case `${0x063}, 0, 1`: return NpcType.EvilShark; case `${0x063}, 1, 1`: return NpcType.PalShark; case `${0x063}, 2, 1`: return NpcType.GuilShark; case `${0x0a6}, 0, 1`: return NpcType.Dimenian; case `${0x0a6}, 0, 2`: return NpcType.Dimenian2; case `${0x0a6}, 1, 1`: return NpcType.LaDimenian; case `${0x0a6}, 1, 2`: return NpcType.LaDimenian2; case `${0x0a6}, 2, 1`: return NpcType.SoDimenian; case `${0x0a6}, 2, 2`: return NpcType.SoDimenian2; case `${0x0d6}, 0, 2`: return NpcType.Mericarol; case `${0x0d6}, 1, 2`: return NpcType.Mericus; case `${0x0d6}, 2, 2`: return NpcType.Merikle; case `${0x115}, 0, 4`: return NpcType.Boota; case `${0x115}, 1, 4`: return NpcType.ZeBoota; case `${0x115}, 2, 4`: return NpcType.BaBoota; case `${0x117}, 0, 4`: return NpcType.Goran; case `${0x117}, 1, 4`: return NpcType.PyroGoran; case `${0x117}, 2, 4`: return NpcType.GoranDetonator; } switch (`${type_id}, ${skin % 2}, ${episode}`) { case `${0x040}, 0, 1`: return NpcType.Hildebear; case `${0x040}, 0, 2`: return NpcType.Hildebear2; case `${0x040}, 1, 1`: return NpcType.Hildeblue; case `${0x040}, 1, 2`: return NpcType.Hildeblue2; case `${0x041}, 0, 1`: return NpcType.RagRappy; case `${0x041}, 0, 2`: return NpcType.RagRappy2; case `${0x041}, 0, 4`: return NpcType.SandRappy; case `${0x041}, 1, 1`: return NpcType.AlRappy; case `${0x041}, 1, 2`: return NpcType.LoveRappy; case `${0x041}, 1, 4`: return NpcType.DelRappy; case `${0x080}, 0, 1`: return NpcType.Dubchic; case `${0x080}, 0, 2`: return NpcType.Dubchic2; case `${0x080}, 1, 1`: return NpcType.Gilchic; case `${0x080}, 1, 2`: return NpcType.Gilchic2; case `${0x0d4}, 0, 2`: return NpcType.SinowBerill; case `${0x0d4}, 1, 2`: return NpcType.SinowSpigell; case `${0x0d5}, 0, 2`: return NpcType.Merillia; case `${0x0d5}, 1, 2`: return NpcType.Meriltas; case `${0x0d7}, 0, 2`: return NpcType.UlGibbon; case `${0x0d7}, 1, 2`: return NpcType.ZolGibbon; case `${0x0dd}, 0, 2`: return NpcType.Dolmolm; case `${0x0dd}, 1, 2`: return NpcType.Dolmdarl; case `${0x0e0}, 0, 2`: return area_id > 15 ? NpcType.Epsilon : NpcType.SinowZoa; case `${0x0e0}, 1, 2`: return area_id > 15 ? NpcType.Epsilon : NpcType.SinowZele; case `${0x112}, 0, 4`: return NpcType.MerissaA; case `${0x112}, 1, 4`: return NpcType.MerissaAA; case `${0x114}, 0, 4`: return NpcType.Zu; case `${0x114}, 1, 4`: return NpcType.Pazuzu; case `${0x116}, 0, 4`: return NpcType.Dorphon; case `${0x116}, 1, 4`: return NpcType.DorphonEclair; case `${0x119}, 0, 4`: return regular ? NpcType.SaintMilion : NpcType.Kondrieu; case `${0x119}, 1, 4`: return regular ? NpcType.Shambertin : NpcType.Kondrieu; } switch (`${type_id}, ${episode}`) { case `${0x042}, 1`: return NpcType.Monest; case `${0x042}, 2`: return NpcType.Monest2; case `${0x043}, 1`: return regular ? NpcType.SavageWolf : NpcType.BarbarousWolf; case `${0x043}, 2`: return regular ? NpcType.SavageWolf2 : NpcType.BarbarousWolf2; case `${0x060}, 1`: return NpcType.GrassAssassin; case `${0x060}, 2`: return NpcType.GrassAssassin2; case `${0x061}, 1`: return area_id > 15 ? NpcType.DelLily : regular ? NpcType.PoisonLily : NpcType.NarLily; case `${0x061}, 2`: return area_id > 15 ? NpcType.DelLily : regular ? NpcType.PoisonLily2 : NpcType.NarLily2; case `${0x062}, 1`: return NpcType.NanoDragon; case `${0x064}, 1`: return regular ? NpcType.PofuillySlime : NpcType.PouillySlime; case `${0x065}, 1`: return NpcType.PanArms; case `${0x065}, 2`: return NpcType.PanArms2; case `${0x081}, 1`: return NpcType.Garanz; case `${0x081}, 2`: return NpcType.Garanz2; case `${0x082}, 1`: return regular ? NpcType.SinowBeat : NpcType.SinowGold; case `${0x083}, 1`: return NpcType.Canadine; case `${0x084}, 1`: return NpcType.Canane; case `${0x085}, 1`: return NpcType.Dubswitch; case `${0x085}, 2`: return NpcType.Dubswitch2; case `${0x0a0}, 1`: return NpcType.Delsaber; case `${0x0a0}, 2`: return NpcType.Delsaber2; case `${0x0a1}, 1`: return NpcType.ChaosSorcerer; case `${0x0a1}, 2`: return NpcType.ChaosSorcerer2; case `${0x0a2}, 1`: return NpcType.DarkGunner; case `${0x0a4}, 1`: return NpcType.ChaosBringer; case `${0x0a5}, 1`: return NpcType.DarkBelra; case `${0x0a5}, 2`: return NpcType.DarkBelra2; case `${0x0a7}, 1`: return NpcType.Bulclaw; case `${0x0a8}, 1`: return NpcType.Claw; case `${0x0c0}, 1`: return NpcType.Dragon; case `${0x0c0}, 2`: return NpcType.GalGryphon; case `${0x0c1}, 1`: return NpcType.DeRolLe; // TODO: // case `${0x0C2}, 1`: return NpcType.VolOptPart1; case `${0x0c5}, 1`: return NpcType.VolOpt; case `${0x0c8}, 1`: return NpcType.DarkFalz; case `${0x0ca}, 2`: return NpcType.OlgaFlow; case `${0x0cb}, 2`: return NpcType.BarbaRay; case `${0x0cc}, 2`: return NpcType.GolDragon; case `${0x0d8}, 2`: return NpcType.Gibbles; case `${0x0d9}, 2`: return NpcType.Gee; case `${0x0da}, 2`: return NpcType.GiGue; case `${0x0db}, 2`: return NpcType.Deldepth; case `${0x0dc}, 2`: return NpcType.Delbiter; case `${0x0de}, 2`: return NpcType.Morfos; case `${0x0df}, 2`: return NpcType.Recobox; case `${0x0e1}, 2`: return NpcType.IllGill; case `${0x110}, 4`: return NpcType.Astark; case `${0x111}, 4`: return regular ? NpcType.SatelliteLizard : NpcType.Yowie; case `${0x113}, 4`: return NpcType.Girtablulu; } switch (type_id) { case 0x004: return NpcType.FemaleFat; case 0x005: return NpcType.FemaleMacho; case 0x007: return NpcType.FemaleTall; case 0x00a: return NpcType.MaleDwarf; case 0x00b: return NpcType.MaleFat; case 0x00c: return NpcType.MaleMacho; case 0x00d: return NpcType.MaleOld; case 0x019: return NpcType.BlueSoldier; case 0x01a: return NpcType.RedSoldier; case 0x01b: return NpcType.Principal; case 0x01c: return NpcType.Tekker; case 0x01d: return NpcType.GuildLady; case 0x01e: return NpcType.Scientist; case 0x01f: return NpcType.Nurse; case 0x020: return NpcType.Irene; case 0x0f1: return NpcType.ItemShop; case 0x0fe: return NpcType.Nurse2; } return NpcType.Unknown; } function objects_to_dat_data(objects: QuestObject[]): DatObject[] { return objects.map(object => ({ type_id: object_data(object.type).pso_id!, section_id: object.section_id, position: object.position, rotation: object.rotation, scale: object.scale, area_id: object.area_id, unknown: object.unknown, })); } function npcs_to_dat_data(npcs: QuestNpc[]): DatNpc[] { const dv = new DataView(new ArrayBuffer(4)); return npcs.map(npc => { const type_data = npc_type_to_dat_data(npc.type) || { type_id: npc.pso_type_id, skin: npc.pso_skin, regular: true, }; dv.setFloat32(0, npc.scale.y); dv.setUint32(0, (dv.getUint32(0) & ~0x800000) | (type_data.regular ? 0 : 0x800000)); const scale_y = dv.getFloat32(0); let scale = new Vec3(npc.scale.x, scale_y, npc.scale.z); return { type_id: type_data.type_id, section_id: npc.section_id, position: npc.position, rotation: npc.rotation, scale, skin: type_data.skin, area_id: npc.area_id, unknown: npc.unknown, }; }); } function npc_type_to_dat_data( type: NpcType, ): { type_id: number; skin: number; regular: boolean } | undefined { switch (type) { default: throw new Error(`Unexpected type ${NpcType[type]}.`); case NpcType.Unknown: return undefined; case NpcType.FemaleFat: return { type_id: 0x004, skin: 0, regular: true }; case NpcType.FemaleMacho: return { type_id: 0x005, skin: 0, regular: true }; case NpcType.FemaleTall: return { type_id: 0x007, skin: 0, regular: true }; case NpcType.MaleDwarf: return { type_id: 0x00a, skin: 0, regular: true }; case NpcType.MaleFat: return { type_id: 0x00b, skin: 0, regular: true }; case NpcType.MaleMacho: return { type_id: 0x00c, skin: 0, regular: true }; case NpcType.MaleOld: return { type_id: 0x00d, skin: 0, regular: true }; case NpcType.BlueSoldier: return { type_id: 0x019, skin: 0, regular: true }; case NpcType.RedSoldier: return { type_id: 0x01a, skin: 0, regular: true }; case NpcType.Principal: return { type_id: 0x01b, skin: 0, regular: true }; case NpcType.Tekker: return { type_id: 0x01c, skin: 0, regular: true }; case NpcType.GuildLady: return { type_id: 0x01d, skin: 0, regular: true }; case NpcType.Scientist: return { type_id: 0x01e, skin: 0, regular: true }; case NpcType.Nurse: return { type_id: 0x01f, skin: 0, regular: true }; case NpcType.Irene: return { type_id: 0x020, skin: 0, regular: true }; case NpcType.ItemShop: return { type_id: 0x0f1, skin: 0, regular: true }; case NpcType.Nurse2: return { type_id: 0x0fe, skin: 0, regular: true }; case NpcType.Hildebear: return { type_id: 0x040, skin: 0, regular: true }; case NpcType.Hildeblue: return { type_id: 0x040, skin: 1, regular: true }; case NpcType.RagRappy: return { type_id: 0x041, skin: 0, regular: true }; case NpcType.AlRappy: return { type_id: 0x041, skin: 1, regular: true }; case NpcType.Monest: return { type_id: 0x042, skin: 0, regular: true }; case NpcType.SavageWolf: return { type_id: 0x043, skin: 0, regular: true }; case NpcType.BarbarousWolf: return { type_id: 0x043, skin: 0, regular: false }; case NpcType.Booma: return { type_id: 0x044, skin: 0, regular: true }; case NpcType.Gobooma: return { type_id: 0x044, skin: 1, regular: true }; case NpcType.Gigobooma: return { type_id: 0x044, skin: 2, regular: true }; case NpcType.Dragon: return { type_id: 0x0c0, skin: 0, regular: true }; case NpcType.GrassAssassin: return { type_id: 0x060, skin: 0, regular: true }; case NpcType.PoisonLily: return { type_id: 0x061, skin: 0, regular: true }; case NpcType.NarLily: return { type_id: 0x061, skin: 1, regular: true }; case NpcType.NanoDragon: return { type_id: 0x062, skin: 0, regular: true }; case NpcType.EvilShark: return { type_id: 0x063, skin: 0, regular: true }; case NpcType.PalShark: return { type_id: 0x063, skin: 1, regular: true }; case NpcType.GuilShark: return { type_id: 0x063, skin: 2, regular: true }; case NpcType.PofuillySlime: return { type_id: 0x064, skin: 0, regular: true }; case NpcType.PouillySlime: return { type_id: 0x064, skin: 0, regular: false }; case NpcType.PanArms: return { type_id: 0x065, skin: 0, regular: true }; case NpcType.DeRolLe: return { type_id: 0x0c1, skin: 0, regular: true }; case NpcType.Dubchic: return { type_id: 0x080, skin: 0, regular: true }; case NpcType.Gilchic: return { type_id: 0x080, skin: 1, regular: true }; case NpcType.Garanz: return { type_id: 0x081, skin: 0, regular: true }; case NpcType.SinowBeat: return { type_id: 0x082, skin: 0, regular: true }; case NpcType.SinowGold: return { type_id: 0x082, skin: 0, regular: false }; case NpcType.Canadine: return { type_id: 0x083, skin: 0, regular: true }; case NpcType.Canane: return { type_id: 0x084, skin: 0, regular: true }; case NpcType.Dubswitch: return { type_id: 0x085, skin: 0, regular: true }; case NpcType.VolOpt: return { type_id: 0x0c5, skin: 0, regular: true }; case NpcType.Delsaber: return { type_id: 0x0a0, skin: 0, regular: true }; case NpcType.ChaosSorcerer: return { type_id: 0x0a1, skin: 0, regular: true }; case NpcType.DarkGunner: return { type_id: 0x0a2, skin: 0, regular: true }; case NpcType.ChaosBringer: return { type_id: 0x0a4, skin: 0, regular: true }; case NpcType.DarkBelra: return { type_id: 0x0a5, skin: 0, regular: true }; case NpcType.Dimenian: return { type_id: 0x0a6, skin: 0, regular: true }; case NpcType.LaDimenian: return { type_id: 0x0a6, skin: 1, regular: true }; case NpcType.SoDimenian: return { type_id: 0x0a6, skin: 2, regular: true }; case NpcType.Bulclaw: return { type_id: 0x0a7, skin: 0, regular: true }; case NpcType.Claw: return { type_id: 0x0a8, skin: 0, regular: true }; case NpcType.DarkFalz: return { type_id: 0x0c8, skin: 0, regular: true }; case NpcType.Hildebear2: return { type_id: 0x040, skin: 0, regular: true }; case NpcType.Hildeblue2: return { type_id: 0x040, skin: 1, regular: true }; case NpcType.RagRappy2: return { type_id: 0x041, skin: 0, regular: true }; case NpcType.LoveRappy: return { type_id: 0x041, skin: 1, regular: true }; case NpcType.Monest2: return { type_id: 0x042, skin: 0, regular: true }; case NpcType.PoisonLily2: return { type_id: 0x061, skin: 0, regular: true }; case NpcType.NarLily2: return { type_id: 0x061, skin: 1, regular: true }; case NpcType.GrassAssassin2: return { type_id: 0x060, skin: 0, regular: true }; case NpcType.Dimenian2: return { type_id: 0x0a6, skin: 0, regular: true }; case NpcType.LaDimenian2: return { type_id: 0x0a6, skin: 1, regular: true }; case NpcType.SoDimenian2: return { type_id: 0x0a6, skin: 2, regular: true }; case NpcType.DarkBelra2: return { type_id: 0x0a5, skin: 0, regular: true }; case NpcType.BarbaRay: return { type_id: 0x0cb, skin: 0, regular: true }; case NpcType.SavageWolf2: return { type_id: 0x043, skin: 0, regular: true }; case NpcType.BarbarousWolf2: return { type_id: 0x043, skin: 0, regular: false }; case NpcType.PanArms2: return { type_id: 0x065, skin: 0, regular: true }; case NpcType.Dubchic2: return { type_id: 0x080, skin: 0, regular: true }; case NpcType.Gilchic2: return { type_id: 0x080, skin: 1, regular: true }; case NpcType.Garanz2: return { type_id: 0x081, skin: 0, regular: true }; case NpcType.Dubswitch2: return { type_id: 0x085, skin: 0, regular: true }; case NpcType.Delsaber2: return { type_id: 0x0a0, skin: 0, regular: true }; case NpcType.ChaosSorcerer2: return { type_id: 0x0a1, skin: 0, regular: true }; case NpcType.GolDragon: return { type_id: 0x0cc, skin: 0, regular: true }; case NpcType.SinowBerill: return { type_id: 0x0d4, skin: 0, regular: true }; case NpcType.SinowSpigell: return { type_id: 0x0d4, skin: 1, regular: true }; case NpcType.Merillia: return { type_id: 0x0d5, skin: 0, regular: true }; case NpcType.Meriltas: return { type_id: 0x0d5, skin: 1, regular: true }; case NpcType.Mericarol: return { type_id: 0x0d6, skin: 0, regular: true }; case NpcType.Mericus: return { type_id: 0x0d6, skin: 1, regular: true }; case NpcType.Merikle: return { type_id: 0x0d6, skin: 2, regular: true }; case NpcType.UlGibbon: return { type_id: 0x0d7, skin: 0, regular: true }; case NpcType.ZolGibbon: return { type_id: 0x0d7, skin: 1, regular: true }; case NpcType.Gibbles: return { type_id: 0x0d8, skin: 0, regular: true }; case NpcType.Gee: return { type_id: 0x0d9, skin: 0, regular: true }; case NpcType.GiGue: return { type_id: 0x0da, skin: 0, regular: true }; case NpcType.GalGryphon: return { type_id: 0x0c0, skin: 0, regular: true }; case NpcType.Deldepth: return { type_id: 0x0db, skin: 0, regular: true }; case NpcType.Delbiter: return { type_id: 0x0dc, skin: 0, regular: true }; case NpcType.Dolmolm: return { type_id: 0x0dd, skin: 0, regular: true }; case NpcType.Dolmdarl: return { type_id: 0x0dd, skin: 1, regular: true }; case NpcType.Morfos: return { type_id: 0x0de, skin: 0, regular: true }; case NpcType.Recobox: return { type_id: 0x0df, skin: 0, regular: true }; case NpcType.Epsilon: return { type_id: 0x0e0, skin: 0, regular: true }; case NpcType.SinowZoa: return { type_id: 0x0e0, skin: 0, regular: true }; case NpcType.SinowZele: return { type_id: 0x0e0, skin: 1, regular: true }; case NpcType.IllGill: return { type_id: 0x0e1, skin: 0, regular: true }; case NpcType.DelLily: return { type_id: 0x061, skin: 0, regular: true }; case NpcType.OlgaFlow: return { type_id: 0x0ca, skin: 0, regular: true }; case NpcType.SandRappy: return { type_id: 0x041, skin: 0, regular: true }; case NpcType.DelRappy: return { type_id: 0x041, skin: 1, regular: true }; case NpcType.Astark: return { type_id: 0x110, skin: 0, regular: true }; case NpcType.SatelliteLizard: return { type_id: 0x111, skin: 0, regular: true }; case NpcType.Yowie: return { type_id: 0x111, skin: 0, regular: false }; case NpcType.MerissaA: return { type_id: 0x112, skin: 0, regular: true }; case NpcType.MerissaAA: return { type_id: 0x112, skin: 1, regular: true }; case NpcType.Girtablulu: return { type_id: 0x113, skin: 0, regular: true }; case NpcType.Zu: return { type_id: 0x114, skin: 0, regular: true }; case NpcType.Pazuzu: return { type_id: 0x114, skin: 1, regular: true }; case NpcType.Boota: return { type_id: 0x115, skin: 0, regular: true }; case NpcType.ZeBoota: return { type_id: 0x115, skin: 1, regular: true }; case NpcType.BaBoota: return { type_id: 0x115, skin: 2, regular: true }; case NpcType.Dorphon: return { type_id: 0x116, skin: 0, regular: true }; case NpcType.DorphonEclair: return { type_id: 0x116, skin: 1, regular: true }; case NpcType.Goran: return { type_id: 0x117, skin: 0, regular: true }; case NpcType.PyroGoran: return { type_id: 0x117, skin: 1, regular: true }; case NpcType.GoranDetonator: return { type_id: 0x117, skin: 2, regular: true }; case NpcType.SaintMilion: return { type_id: 0x119, skin: 0, regular: true }; case NpcType.Shambertin: return { type_id: 0x119, skin: 1, regular: true }; case NpcType.Kondrieu: return { type_id: 0x119, skin: 0, regular: false }; } }