phantasmal-world/src/data/parsing/quest.ts

534 lines
24 KiB
TypeScript
Raw Normal View History

import { ArrayBufferCursor } from '../ArrayBufferCursor';
import * as prs from '../compression/prs';
import { parse_dat, write_dat } from './dat';
import { parse_bin, write_bin, Instruction } from './bin';
import { parse_qst, write_qst } from './qst';
import {
Vec3,
AreaVariant,
QuestNpc,
QuestObject,
Quest,
ObjectType,
NpcType
} from '../../domain';
import { area_store } from '../../store';
/**
* High level parsing function that delegates to lower level parsing functions.
*
* Always delegates to parse_qst at the moment.
*/
export function parse_quest(cursor: ArrayBufferCursor): Quest | null {
const qst = parse_qst(cursor);
if (!qst) {
return null;
}
let dat_file = null;
let bin_file = null;
for (const file of qst.files) {
if (file.name.endsWith('.dat')) {
dat_file = file;
} else if (file.name.endsWith('.bin')) {
bin_file = file;
}
}
// TODO: deal with missing/multiple DAT or BIN file.
if (!dat_file || !bin_file) {
return null;
}
const dat = parse_dat(prs.decompress(dat_file.data));
const bin = parse_bin(prs.decompress(bin_file.data));
let episode = 1;
let area_variants: AreaVariant[] = [];
if (bin.function_offsets.length) {
const func_0_ops = get_func_operations(bin.instructions, bin.function_offsets[0]);
if (func_0_ops) {
episode = get_episode(func_0_ops);
area_variants = get_area_variants(episode, func_0_ops);
} else {
console.warn(`Function 0 offset ${bin.function_offsets[0]} is invalid.`);
}
} else {
console.warn('File contains no functions.');
}
return new Quest(
bin.quest_name,
bin.short_description,
bin.long_description,
dat_file.quest_no,
episode,
area_variants,
parse_obj_data(dat.objs),
parse_npc_data(episode, dat.npcs),
{ unknowns: dat.unknowns },
bin.data
);
}
export function write_quest_qst(quest: Quest, file_name: string): ArrayBufferCursor {
const dat = write_dat({
objs: objects_to_dat_data(quest.episode, quest.objects),
npcs: npcs_to_dat_data(quest.episode, quest.npcs),
unknowns: quest.dat.unknowns
});
const bin = write_bin({ data: quest.bin });
const ext_start = file_name.lastIndexOf('.');
const base_file_name = ext_start === -1 ? file_name : file_name.slice(0, ext_start);
return write_qst({
files: [
{
name: base_file_name + '.dat',
quest_no: quest.quest_no,
data: prs.compress(dat)
},
{
name: base_file_name + '.bin',
quest_no: quest.quest_no,
data: prs.compress(bin)
}
]
});
}
/**
* Defaults to episode I.
*/
function get_episode(func_0_ops: Instruction[]): number {
const set_episode = func_0_ops.find(op => op.mnemonic === 'set_episode');
if (set_episode) {
switch (set_episode.args[0]) {
default:
case 0: return 1;
case 1: return 2;
case 2: return 4;
}
} else {
console.warn('Function 0 has no set_episode instruction.');
return 1;
}
}
function get_area_variants(episode: number, func_0_ops: Instruction[]): AreaVariant[] {
const area_variants = new Map();
const bb_maps = func_0_ops.filter(op => op.mnemonic === 'BB_Map_Designate');
for (const bb_map of bb_maps) {
const area_id = bb_map.args[0];
const variant_id = bb_map.args[2];
area_variants.set(area_id, variant_id);
}
// Sort by area order and then variant id.
return (
Array.from(area_variants)
.map(([area_id, variant_id]) =>
area_store.get_variant(episode, area_id, variant_id))
.sort((a, b) => a.area.order - b.area.order || a.id - b.id)
);
}
function get_func_operations(operations: Instruction[], func_offset: number) {
let position = 0;
let func_found = false;
const func_ops = [];
for (const operation of operations) {
if (position === func_offset) {
func_found = true;
}
if (func_found) {
func_ops.push(operation);
// Break when ret is encountered.
if (operation.opcode === 1) {
break;
}
}
position += operation.size;
}
return func_found ? func_ops : null;
}
function parse_obj_data(objs: any[]): QuestObject[] {
return objs.map(obj_data => {
const { x, y, z } = obj_data.position;
const rot = obj_data.rotation;
return new QuestObject(
obj_data.area_id,
obj_data.section_id,
new Vec3(x, y, z),
new Vec3(rot.x, rot.y, rot.z),
ObjectType.from_pso_id(obj_data.type_id),
obj_data
);
});
}
function parse_npc_data(episode: number, npcs: any[]): QuestNpc[] {
return npcs.map(npc_data => {
const { x, y, z } = npc_data.position;
const rot = npc_data.rotation;
return new QuestNpc(
npc_data.area_id,
npc_data.section_id,
new Vec3(x, y, z),
new Vec3(rot.x, rot.y, rot.z),
get_npc_type(episode, npc_data),
npc_data
);
});
}
function get_npc_type(episode: number, { type_id, unknown, skin, area_id }: any): NpcType {
const regular = (unknown[2][18] & 0x80) === 0;
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 `${0x061}, 0, 1`: return area_id > 15 ? NpcType.DelLily : NpcType.PoisonLily;
case `${0x061}, 0, 2`: return area_id > 15 ? NpcType.DelLily : NpcType.PoisonLily2;
case `${0x061}, 1, 1`: return area_id > 15 ? NpcType.DelLily : NpcType.NarLily;
case `${0x061}, 1, 2`: return area_id > 15 ? NpcType.DelLily : NpcType.NarLily2;
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.SaintMillion : 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 `${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;
}
// TODO: remove log statement:
console.log(`Unknown type ID: ${type_id} (0x${type_id.toString(16)}).`);
return NpcType.Unknown;
}
function objects_to_dat_data(episode: number, objects: QuestObject[]): any[] {
return objects.map(object => ({
type_id: object.type.pso_id,
section_id: object.section_id,
position: object.section_position,
rotation: object.rotation,
area_id: object.area_id,
unknown: object.dat.unknown
}));
}
function npcs_to_dat_data(episode: number, npcs: QuestNpc[]): any[] {
return npcs.map(npc => {
// If the type is unknown, type_data will be null and we use the raw data from the DAT file.
const type_data = npc_type_to_dat_data(npc.type);
if (type_data) {
npc.dat.unknown[2][18] = (npc.dat.unknown[2][18] & ~0x80) | (type_data.regular ? 0 : 0x80);
}
return {
type_id: type_data ? type_data.type_id : npc.dat.type_id,
section_id: npc.section_id,
position: npc.section_position,
rotation: npc.rotation,
skin: type_data ? type_data.skin : npc.dat.skin,
area_id: npc.area_id,
unknown: npc.dat.unknown
};
});
}
function npc_type_to_dat_data(
type: NpcType
): { type_id: number, skin: number, regular: boolean } | null {
switch (type) {
default: throw new Error(`Unexpected type ${type.code}.`);
case NpcType.Unknown: return null;
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.SaintMillion: 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 };
}
}