From 43161ae7e75bb4e4513c44180f304bd710feae20 Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Tue, 29 Sep 2020 22:16:48 +0200 Subject: [PATCH] Sensible default values are now set for most NPC properties when creating a new NPC. --- assets_generation/quest_stats.ts | 132 ++- assets_generation/update_ephinea_data.ts | 4 +- assets_generation/walk_quests.ts | 86 +- .../data_formats/parsing/quest/QuestNpc.ts | 40 +- .../parsing/quest/set_npc_default_data.ts | 809 ++++++++++++++++++ .../model/QuestEntityModel.test.ts | 2 +- .../rendering/QuestEntityControls.ts | 7 +- test/src/utils.ts | 6 +- 8 files changed, 1009 insertions(+), 77 deletions(-) create mode 100644 src/core/data_formats/parsing/quest/set_npc_default_data.ts diff --git a/assets_generation/quest_stats.ts b/assets_generation/quest_stats.ts index ee5efa2a..d460bce1 100644 --- a/assets_generation/quest_stats.ts +++ b/assets_generation/quest_stats.ts @@ -12,26 +12,34 @@ print_quest_stats(); function print_quest_stats(): void { const type_data: Map = new Map(); - walk_quests(`${RESOURCE_DIR}/tethealla_v0.143_quests`, ({ quest }) => { - for (const npc of quest.npcs) { - const type = get_npc_type(npc); - const npcs = type_data.get(type); + walk_quests( + { + path: `${RESOURCE_DIR}/tethealla_v0.143_quests`, + exclude: ["/battle", "/chl/ep1", "/chl/ep4", "/shop"], + }, + ({ quest }) => { + for (const npc of quest.npcs) { + const type = get_npc_type(npc); + const npcs = type_data.get(type); - if (npcs == undefined) { - type_data.set(type, [{ npc, quest: quest.name, count: 1 }]); - } else { - const found = npcs.find(({ npc: npc_2 }) => entities_equal(npc, npc_2, type)); - - if (found) { - found.count++; + if (npcs == undefined) { + type_data.set(type, [{ npc, quest: quest.name, count: 1 }]); } else { - npcs.push({ npc, quest: quest.name, count: 1 }); + const found = npcs.find(({ npc: npc_2 }) => entities_equal(npc, npc_2, type)); + + if (found) { + found.count++; + } else { + npcs.push({ npc, quest: quest.name, count: 1 }); + } } } - } - }); + }, + ); - for (const [type, npcs] of type_data) { + for (const [type, npcs] of [...type_data.entries()].sort( + ([a_type], [b_type]) => a_type - b_type, + )) { const props = get_properties(type); /* eslint-disable no-console */ @@ -47,6 +55,75 @@ function print_quest_stats(): void { } /* eslint-enable no-console */ } + + // Used to populate the switch in set_npc_default_data. + // Prints code of the following form (view is assumed to be a DataView). + // + // case NpcType.$NPC_TYPE: + // view.set$PROP_TYPE($OFFSET, $VALUE, true); // $PROP_NAME + // break; + // + // for (const [type, npcs] of [...type_data.entries()].sort( + // ([a_type], [b_type]) => a_type - b_type, + // )) { + // const { npc } = npcs.sort((a, b) => b.count - a.count)[0]; + // + // const props = get_properties(type) + // .map(prop => { + // const value = + // prop.type === EntityPropType.Angle + // ? npc.view.getInt32(prop.offset, true) + // : get_entity_prop_value(npc, prop); + // return [prop, value] as const; + // }) + // .filter(([, value]) => value !== 0); + // + // if (props.length === 0) continue; + // + // /* eslint-disable no-console */ + // console.log(`case NpcType.${NpcType[type]}:`); + // + // for (const [prop, value] of props) { + // let prop_type: string; + // + // switch (prop.type) { + // case EntityPropType.U8: + // prop_type = "Uint8"; + // break; + // case EntityPropType.U16: + // prop_type = "Uint16"; + // break; + // case EntityPropType.U32: + // prop_type = "Uint32"; + // break; + // case EntityPropType.I8: + // prop_type = "Int8"; + // break; + // case EntityPropType.I16: + // prop_type = "Int16"; + // break; + // case EntityPropType.I32: + // prop_type = "Int32"; + // break; + // case EntityPropType.F32: + // prop_type = "Float32"; + // break; + // case EntityPropType.Angle: + // prop_type = "Int32"; + // break; + // default: + // throw new Error(`EntityPropType.${EntityPropType[prop.type]} not supported.`); + // } + // + // const offset = prop.offset; + // const comment = prop.name === "Unknown" ? "" : ` // ${prop.name}`; + // + // console.log(` view.set${prop_type}(${offset}, ${value}, true);${comment}`); + // } + // + // console.log(" break;"); + // /* eslint-enable no-console */ + // } } /** @@ -63,52 +140,47 @@ function get_properties(type: NpcType): EntityProp[] { props = [ { - name: "unknwn", + name: "Unknown", offset: 2, type: EntityPropType.I16, }, { - name: "unknwn", + name: "Unknown", offset: 4, type: EntityPropType.I16, }, { - name: "clncnt", + name: "Clone count", offset: 6, type: EntityPropType.I16, }, { - name: "unknwn", + name: "Unknown", offset: 8, type: EntityPropType.I16, }, { - name: "unknwn", + name: "Unknown", offset: 10, type: EntityPropType.I16, }, { - name: "scale x", + name: "Scale x", offset: 44, type: EntityPropType.F32, }, { - name: "scale y", - offset: 48, - type: EntityPropType.F32, - }, - { - name: "scale z", + name: "Scale z", offset: 52, type: EntityPropType.F32, }, { - name: "unknwn", + name: "Unknown", offset: 68, type: EntityPropType.I16, }, { - name: "unknwn", + name: "Unknown", offset: 70, type: EntityPropType.I16, }, @@ -165,6 +237,8 @@ function col_width(prop: EntityProp): number { return 10; case EntityPropType.Angle: return 4; + default: + throw new Error(`EntityPropType.${EntityPropType[prop.type]} not supported.`); } } diff --git a/assets_generation/update_ephinea_data.ts b/assets_generation/update_ephinea_data.ts index 4ece0a7d..f0390488 100644 --- a/assets_generation/update_ephinea_data.ts +++ b/assets_generation/update_ephinea_data.ts @@ -77,7 +77,9 @@ function update_quests(): void { logger.info("Updating quest data."); const quests: QuestDto[] = []; - walk_quests(`${EPHINEA_RESOURCE_DIR}/ship-config/quest`, q => process_quest(quests, q)); + walk_quests({ path: `${EPHINEA_RESOURCE_DIR}/ship-config/quest` }, q => + process_quest(quests, q), + ); quests.sort((a, b) => a.episode - b.episode || a.name.localeCompare(b.name)); diff --git a/assets_generation/walk_quests.ts b/assets_generation/walk_quests.ts index 257ce521..85ae6f70 100644 --- a/assets_generation/walk_quests.ts +++ b/assets_generation/walk_quests.ts @@ -7,47 +7,47 @@ import { Severity } from "../src/core/Severity"; const logger = LogManager.get("assets_generation/walk_quests"); +/** + * Applies process to all QST files in a directory. Uses the 106 QST files + * provided with Tethealla version 0.143 by default. + */ export function walk_quests( - path: string, + config: { path: string; suppress_parser_log?: boolean; exclude?: readonly string[] }, process: (quest: QuestData) => void, - suppress_parser_log: boolean = true, ): void { - const loggers = (suppress_parser_log + const loggers = (config.suppress_parser_log !== false ? [ - LogManager.get("core/data_formats/asm/data_flow_analysis/register_value"), - LogManager.get("core/data_formats/parsing/quest"), - LogManager.get("core/data_formats/parsing/quest/bin"), - LogManager.get("core/data_formats/parsing/quest/object_code"), - LogManager.get("core/data_formats/parsing/quest/qst"), + "core/data_formats/asm/data_flow_analysis/register_value", + "core/data_formats/parsing/quest", + "core/data_formats/parsing/quest/bin", + "core/data_formats/parsing/quest/object_code", + "core/data_formats/parsing/quest/qst", ] : [] - ).map(logger => { + ).map(logger_name => { + const logger = LogManager.get(logger_name); const old = logger.severity; logger.severity = Severity.Error; return [logger, old] as const; }); try { - walk_qst_files( - (p, _, contents) => { - try { - const result = parse_qst_to_quest( - new BufferCursor(contents, Endianness.Little), - true, - ); + walk_qst_files(config, (p, _, contents) => { + try { + const result = parse_qst_to_quest( + new BufferCursor(contents, Endianness.Little), + true, + ); - if (result.success) { - process(result.value); - } else { - logger.error(`Couldn't process ${p}.`); - } - } catch (e) { - logger.error(`Couldn't parse ${p}.`, e); + if (result.success) { + process(result.value); + } else { + logger.error(`Couldn't process ${p}.`); } - }, - path, - [], - ); + } catch (e) { + logger.error(`Couldn't parse ${p}.`, e); + } + }); } finally { for (const [logger, severity] of loggers) { logger.severity = severity; @@ -56,21 +56,31 @@ export function walk_quests( } /** - * Applies f to all QST files in a directory. + * Applies f to all 106 QST files provided with Tethealla version 0.143. * f is called with the path to the file, the file name and the content of the file. - * Uses the 106 QST files provided with Tethealla version 0.143 by default. */ export function walk_qst_files( + config: { path?: string; exclude?: readonly string[] }, f: (path: string, file_name: string, contents: Buffer) => void, - path = "assets_generation/resources/tethealla_v0.143_quests", +): void { + const path = config.path ?? "assets_generation/resources/tethealla_v0.143_quests"; + // BUG: Battle quests are not always parsed in the same way. // Could be a bug in Jest or Node as the quest parsing code has no randomness or dependency on mutable state. // TODO: Some quests can not yet be parsed correctly. - exclude: readonly string[] = [ - "/battle/", // Battle mode quests. - "ep2/event/ma4-a.qst", // .qst seems corrupt, doesn't work in qedit either. - ], -): void { + let exclude: readonly string[]; + + if (config.exclude) { + exclude = config.exclude; + } else if (config.path == undefined) { + exclude = [ + "/battle", // Battle mode quests. + "/ep2/event/ma4-a.qst", // .qst seems corrupt, doesn't work in qedit either. + ]; + } else { + exclude = []; + } + const idx = path.lastIndexOf("/"); walk_qst_files_internal( f, @@ -86,10 +96,14 @@ function walk_qst_files_internal( name: string, exclude: readonly string[], ): void { + if (exclude.some(e => path.includes(e))) { + return; + } + const stat = statSync(path); if (stat.isFile()) { - if (path.endsWith(".qst") && !exclude.some(e => path.includes(e))) { + if (path.endsWith(".qst")) { f(path, name, readFileSync(path)); } } else if (stat.isDirectory()) { diff --git a/src/core/data_formats/parsing/quest/QuestNpc.ts b/src/core/data_formats/parsing/quest/QuestNpc.ts index 7ac73aa7..38c33bdd 100644 --- a/src/core/data_formats/parsing/quest/QuestNpc.ts +++ b/src/core/data_formats/parsing/quest/QuestNpc.ts @@ -4,6 +4,7 @@ import { Episode } from "./Episode"; import { NPC_BYTE_SIZE } from "./dat"; import { assert } from "../../../util"; import { angle_to_rad, rad_to_angle } from "../ninja/angle"; +import { set_npc_default_data } from "./set_npc_default_data"; export type QuestNpc = { episode: Episode; @@ -12,15 +13,22 @@ export type QuestNpc = { readonly view: DataView; }; -export function create_quest_npc(type: NpcType, area_id: number, wave: number): QuestNpc { +export function create_quest_npc( + type: NpcType, + episode: Episode, + area_id: number, + wave: number, +): QuestNpc { const data = new ArrayBuffer(NPC_BYTE_SIZE); const npc: QuestNpc = { - episode: Episode.I, + episode, area_id, data, view: new DataView(data), }; + set_npc_default_data(type, npc.view); + set_npc_type(npc, type); // Set area_id after type, because you might want to overwrite the area_id that type has // determined. @@ -448,7 +456,29 @@ export function set_npc_type(npc: QuestNpc, type: NpcType): void { } set_npc_type_id(npc, data.type_id ?? 0); - set_npc_regular(npc, data.regular ?? true); + + switch (type) { + case NpcType.SaintMilion: + case NpcType.SavageWolf: + case NpcType.BarbarousWolf: + case NpcType.PoisonLily: + case NpcType.NarLily: + case NpcType.PofuillySlime: + case NpcType.PouillySlime: + case NpcType.PoisonLily2: + case NpcType.NarLily2: + case NpcType.SavageWolf2: + case NpcType.BarbarousWolf2: + case NpcType.Kondrieu: + case NpcType.Shambertin: + case NpcType.SinowBeat: + case NpcType.SinowGold: + case NpcType.SatelliteLizard: + case NpcType.Yowie: + set_npc_regular(npc, data.regular ?? true); + break; + } + set_npc_skin(npc, data.skin ?? 0); if (data.area_ids.length > 0 && !data.area_ids.includes(npc.area_id)) { @@ -456,10 +486,10 @@ export function set_npc_type(npc: QuestNpc, type: NpcType): void { } } -export function is_npc_regular(npc: QuestNpc): boolean { +function is_npc_regular(npc: QuestNpc): boolean { return Math.round(npc.view.getFloat32(48, true)) !== 1; } -export function set_npc_regular(npc: QuestNpc, regular: boolean): void { +function set_npc_regular(npc: QuestNpc, regular: boolean): void { npc.view.setFloat32(48, regular ? 0 : 1, true); } diff --git a/src/core/data_formats/parsing/quest/set_npc_default_data.ts b/src/core/data_formats/parsing/quest/set_npc_default_data.ts new file mode 100644 index 00000000..495081c3 --- /dev/null +++ b/src/core/data_formats/parsing/quest/set_npc_default_data.ts @@ -0,0 +1,809 @@ +import { NpcType } from "./npc_types"; + +// TODO: set properties of friendly NPCs based on episode. +export function set_npc_default_data(type: NpcType, view: DataView): void { + switch (type) { + case NpcType.FemaleFat: + view.setInt16(2, 2, true); + view.setInt16(4, 1872, true); + view.setInt16(10, 5969, true); + view.setFloat32(44, 24.000009536743164, true); // Movement distance + view.setFloat32(56, 1016.0010986328125, true); // Character ID + view.setFloat32(60, 100.00004577636719, true); // Script label + view.setInt32(64, 1, true); // Movement flag + view.setInt16(68, -16432, true); + view.setInt16(70, 1834, true); + break; + case NpcType.FemaleMacho: + view.setInt16(2, 2, true); + view.setInt16(4, 1876, true); + view.setInt16(10, 5973, true); + view.setFloat32(44, 26.000009536743164, true); // Movement distance + view.setFloat32(56, 1014.0010986328125, true); // Character ID + view.setFloat32(60, 80.00006866455078, true); // Script label + view.setInt32(64, 1, true); // Movement flag + view.setInt16(68, -12528, true); + view.setInt16(70, 1834, true); + break; + case NpcType.FemaleTall: + view.setInt16(2, 2, true); + view.setInt16(4, 1883, true); + view.setInt16(10, 5980, true); + view.setFloat32(44, 22.000009536743164, true); // Movement distance + view.setFloat32(56, 1021.0010986328125, true); // Character ID + view.setFloat32(60, 140.00006103515625, true); // Script label + view.setInt32(64, 1, true); // Movement flag + view.setInt16(68, -5504, true); + view.setInt16(70, 1834, true); + break; + case NpcType.MaleDwarf: + view.setInt16(2, 2, true); + view.setInt16(4, 1873, true); + view.setInt16(10, 5970, true); + view.setFloat32(44, 30.000009536743164, true); // Movement distance + view.setFloat32(56, 1015.0010986328125, true); // Character ID + view.setFloat32(60, 90.00004577636719, true); // Script label + view.setInt32(64, 1, true); // Movement flag + view.setInt16(68, -15456, true); + view.setInt16(70, 1834, true); + break; + case NpcType.MaleFat: + view.setInt16(2, 2, true); + view.setInt16(4, 1882, true); + view.setInt16(10, 5979, true); + view.setFloat32(56, 1007, true); // Character ID + view.setFloat32(60, 810, true); // Script label + view.setInt16(68, -6528, true); + view.setInt16(70, 1834, true); + break; + case NpcType.MaleMacho: + view.setInt16(2, 2, true); + view.setInt16(4, 1880, true); + view.setInt16(10, 5977, true); + view.setFloat32(56, 1006.0010986328125, true); // Character ID + view.setFloat32(60, 800, true); // Script label + view.setInt16(68, -8576, true); + view.setInt16(70, 1834, true); + break; + case NpcType.MaleOld: + view.setInt16(2, 2, true); + view.setInt16(4, 1878, true); + view.setInt16(10, 5975, true); + view.setFloat32(44, 37.0000114440918, true); // Movement distance + view.setFloat32(56, 1012.0010986328125, true); // Character ID + view.setFloat32(60, 60.000057220458984, true); // Script label + view.setInt32(64, 1, true); // Movement flag + view.setInt16(68, -10576, true); + view.setInt16(70, 1834, true); + break; + case NpcType.BlueSoldier: + view.setInt16(2, 2, true); + view.setInt16(4, 1875, true); + view.setInt16(10, 5972, true); + view.setFloat32(56, 1020.0010986328125, true); // Character ID + view.setFloat32(60, 130.00009155273438, true); // Script label + view.setInt16(68, -13504, true); + view.setInt16(70, 1834, true); + break; + case NpcType.RedSoldier: + view.setInt16(2, 2, true); + view.setInt16(4, 1874, true); + view.setInt16(10, 5971, true); + view.setFloat32(56, 1019.0010986328125, true); // Character ID + view.setFloat32(60, 120.00008392333984, true); // Script label + view.setInt16(68, -14480, true); + view.setInt16(70, 1834, true); + break; + case NpcType.Principal: + view.setInt16(4, 1888, true); + view.setInt16(10, 5985, true); + view.setFloat32(56, 110, true); // Character ID + view.setFloat32(60, 110, true); // Script label + view.setInt16(68, -384, true); + view.setInt16(70, 1834, true); + break; + case NpcType.Tekker: + view.setInt16(2, 2, true); + view.setInt16(4, 1879, true); + view.setInt16(10, 5976, true); + view.setFloat32(56, 1009.0010986328125, true); // Character ID + view.setFloat32(60, 830, true); // Script label + view.setInt16(68, -9600, true); + view.setInt16(70, 1834, true); + break; + case NpcType.GuildLady: + view.setInt16(2, 2, true); + view.setInt16(4, 1891, true); + view.setInt16(10, 5988, true); + view.setFloat32(56, 1010.0010986328125, true); // Character ID + view.setFloat32(60, 840, true); // Script label + view.setInt16(68, 11584, true); + view.setInt16(70, 1835, true); + break; + case NpcType.Scientist: + view.setInt16(2, 2, true); + view.setInt16(4, 1877, true); + view.setInt16(10, 5974, true); + view.setFloat32(44, 30.000009536743164, true); // Movement distance + view.setFloat32(56, 1013.0010986328125, true); // Character ID + view.setFloat32(60, 70.00006103515625, true); // Script label + view.setInt32(64, 1, true); // Movement flag + view.setInt16(68, -11552, true); + view.setInt16(70, 1834, true); + break; + case NpcType.Nurse: + view.setInt16(2, 2, true); + view.setInt16(4, 1884, true); + view.setInt16(10, 5981, true); + view.setFloat32(56, 1017, true); // Character ID + view.setFloat32(60, 860, true); // Script label + view.setInt16(68, -4480, true); + view.setInt16(70, 1834, true); + break; + case NpcType.Irene: + view.setInt16(4, 1889, true); + view.setInt16(10, 5986, true); + view.setFloat32(56, 111, true); // Character ID + view.setFloat32(60, 111, true); // Script label + view.setInt16(68, 640, true); + view.setInt16(70, 1835, true); + break; + case NpcType.ItemShop: + view.setInt16(4, 8, true); + view.setInt16(10, 6453, true); + view.setFloat32(56, 1063, true); // Character ID + view.setFloat32(60, 900, true); // Script label + view.setInt16(68, 16560, true); + view.setInt16(70, 1176, true); + break; + case NpcType.Nurse2: + view.setInt16(4, 2330, true); + view.setInt16(10, 6496, true); + view.setFloat32(56, 1068, true); // Character ID + view.setFloat32(60, 950, true); // Script label + view.setInt16(68, -13280, true); + view.setInt16(70, 1200, true); + break; + case NpcType.Hildebear: + view.setInt16(4, -1, true); + view.setInt16(8, 2, true); + view.setInt16(10, -1, true); + view.setFloat32(44, 1.000000238418579, true); // Scale x + view.setInt16(68, 29968, true); + view.setInt16(70, -29446, true); + break; + case NpcType.RagRappy: + view.setInt16(8, 1, true); + view.setInt16(10, -1, true); + view.setInt16(68, 1072, true); + view.setInt16(70, -29444, true); + break; + case NpcType.Monest: + view.setFloat32(48, 5.000000953674316, true); // Start number + view.setFloat32(52, 10.000004768371582, true); // Total number + break; + case NpcType.BarbarousWolf: + view.setInt16(4, -1, true); + view.setInt16(8, 1, true); + view.setInt16(10, 6475, true); + view.setInt16(68, 8576, true); + view.setInt16(70, -29445, true); + break; + case NpcType.Booma: + view.setFloat32(44, 0.30000001192092896, true); // Scale x + view.setFloat32(48, 40.00001907348633, true); // Idle distance + break; + case NpcType.Gobooma: + view.setInt16(8, 1, true); + view.setInt16(10, -1, true); + view.setFloat32(44, 0.30000001192092896, true); // Scale x + view.setFloat32(48, 40.00001907348633, true); // Idle distance + view.setInt16(68, 11600, true); + view.setInt16(70, -29444, true); + break; + case NpcType.Gigobooma: + view.setInt16(4, -1, true); + view.setInt16(8, 1, true); + view.setInt16(10, 6492, true); + view.setInt16(68, -4000, true); + view.setInt16(70, -29446, true); + break; + case NpcType.Dragon: + view.setInt16(4, 1173, true); + view.setInt16(8, 11, true); + view.setInt16(10, -1, true); + view.setInt16(68, 24624, true); + view.setInt16(70, -29446, true); + break; + case NpcType.GrassAssassin: + view.setInt16(8, 4, true); + break; + case NpcType.PoisonLily: + view.setInt16(8, 4, true); + break; + case NpcType.NanoDragon: + view.setInt16(8, 3, true); + break; + case NpcType.EvilShark: + view.setInt16(8, 3, true); + break; + case NpcType.PalShark: + view.setInt16(8, 3, true); + break; + case NpcType.GuilShark: + view.setInt16(8, 4, true); + break; + case NpcType.PofuillySlime: + view.setInt16(8, 4, true); + break; + case NpcType.PanArms: + view.setInt16(8, 5, true); + break; + case NpcType.DeRolLe: + view.setInt16(4, 1485, true); + view.setInt16(6, 19, true); // Clone count + view.setInt16(8, 12, true); + view.setInt16(10, -1, true); + view.setInt16(68, -11088, true); + view.setInt16(70, -29445, true); + break; + case NpcType.Dubchic: + view.setInt16(4, 2626, true); + view.setInt16(8, 7, true); + view.setInt16(10, 7272, true); + view.setInt16(68, -25504, true); + view.setInt16(70, 561, true); + break; + case NpcType.Gilchic: + view.setInt16(4, -1, true); + view.setInt16(8, 6, true); + view.setInt16(10, -1, true); + view.setFloat32(44, 1.000000238418579, true); // Scale x + view.setInt16(68, 5968, true); + view.setInt16(70, -29444, true); + break; + case NpcType.Garanz: + view.setInt16(4, -1, true); + view.setInt16(8, 7, true); + view.setInt16(10, 6319, true); + view.setInt16(68, -26128, true); + view.setInt16(70, 561, true); + break; + case NpcType.SinowBeat: + view.setInt16(4, -1, true); + view.setInt16(8, 6, true); + view.setInt16(10, -1, true); + view.setInt16(68, 6288, true); + view.setInt16(70, -29444, true); + break; + case NpcType.SinowGold: + view.setInt16(4, -1, true); + view.setInt16(8, 6, true); + view.setInt16(10, -1, true); + view.setFloat32(44, -1.000000238418579, true); // Scale x + view.setInt16(68, 8048, true); + view.setInt16(70, -29444, true); + break; + case NpcType.Canadine: + view.setInt16(4, -1, true); + view.setInt16(8, 6, true); + view.setInt16(10, -1, true); + view.setInt16(68, 8496, true); + view.setInt16(70, -29444, true); + break; + case NpcType.Canane: + view.setInt16(4, -1, true); + view.setInt16(8, 6, true); + view.setInt16(10, -1, true); + view.setInt16(68, 7264, true); + view.setInt16(70, -29444, true); + break; + case NpcType.Dubswitch: + view.setInt16(4, 2626, true); + view.setInt16(8, 7, true); + view.setInt16(10, 7298, true); + view.setInt16(68, -16736, true); + view.setInt16(70, 561, true); + break; + case NpcType.VolOptPart1: + view.setInt16(6, 35, true); // Clone count + break; + case NpcType.VolOptPart2: + view.setInt16(8, 13, true); + break; + case NpcType.DarkFalz: + view.setInt16(4, -1, true); + view.setInt16(8, 14, true); + view.setInt16(10, 7458, true); + view.setInt16(68, 25008, true); + view.setInt16(70, -29446, true); + break; + case NpcType.Hildebear2: + view.setInt16(4, -1, true); + view.setInt16(8, 1, true); + view.setInt16(10, 30745, true); + view.setFloat32(44, 1, true); // Scale x + view.setInt16(68, -7296, true); + view.setInt16(70, -32759, true); + break; + case NpcType.RagRappy2: + view.setInt16(4, -1, true); + view.setInt16(8, 1, true); + view.setInt16(10, -7401, true); + view.setInt16(68, -7296, true); + view.setInt16(70, 8201, true); + break; + case NpcType.Monest2: + view.setInt16(4, -1, true); + view.setInt16(8, 1, true); + view.setInt16(10, 4122, true); + view.setFloat32(48, 3, true); // Start number + view.setFloat32(52, 9, true); // Total number + view.setInt16(68, -7296, true); + view.setInt16(70, -12252, true); + break; + case NpcType.PoisonLily2: + view.setInt16(4, -1, true); + view.setInt16(8, 1, true); + view.setInt16(10, 26648, true); + view.setInt16(68, -7296, true); + view.setInt16(70, 8230, true); + break; + case NpcType.GrassAssassin2: + view.setInt16(4, -1, true); + view.setInt16(8, 1, true); + view.setInt16(10, 20761, true); + view.setInt16(68, -7296, true); + view.setInt16(70, 24595, true); + break; + case NpcType.Dimenian2: + view.setInt16(4, -1, true); + view.setInt16(8, 1, true); + view.setInt16(10, 28696, true); + view.setInt16(68, -7296, true); + view.setInt16(70, -4086, true); + break; + case NpcType.LaDimenian2: + view.setInt16(4, -1, true); + view.setInt16(8, 1, true); + view.setInt16(10, 7449, true); + view.setInt16(68, -7296, true); + view.setInt16(70, -16367, true); + break; + case NpcType.SoDimenian2: + view.setInt16(4, -1, true); + view.setInt16(8, 1, true); + view.setInt16(10, -1254, true); + view.setFloat32(48, 100, true); // Idle distance + view.setInt16(68, -7040, true); + view.setInt16(70, 8372, true); + break; + case NpcType.DarkBelra2: + view.setInt16(4, -1, true); + view.setInt16(8, 1, true); + view.setInt16(10, -17895, true); + view.setInt16(68, -7040, true); + view.setInt16(70, -32642, true); + break; + case NpcType.BarbaRay: + view.setInt16(4, -1, true); + view.setInt16(8, 14, true); + view.setInt16(10, 23572, true); + view.setInt16(68, -2688, true); + view.setInt16(70, 24576, true); + break; + case NpcType.SavageWolf2: + view.setInt16(4, 11785, true); + view.setInt16(8, 3, true); + view.setInt16(10, -20711, true); + view.setInt16(68, -7552, true); + view.setInt16(70, 8250, true); + break; + case NpcType.BarbarousWolf2: + view.setInt16(4, -1, true); + view.setInt16(8, 3, true); + view.setInt16(10, -14056, true); + view.setInt16(68, -7552, true); + view.setInt16(70, -32650, true); + break; + case NpcType.PanArms2: + view.setInt16(4, -1, true); + view.setInt16(8, 3, true); + view.setInt16(10, -6632, true); + view.setInt16(68, -7552, true); + view.setInt16(70, -16251, true); + break; + case NpcType.Dubchic2: + view.setInt16(4, -1, true); + view.setInt16(8, 3, true); + view.setInt16(10, -3560, true); + view.setInt16(68, -7552, true); + view.setInt16(70, 16513, true); + break; + case NpcType.Gilchic2: + view.setInt16(4, -1, true); + view.setInt16(8, 3, true); + view.setInt16(10, -23272, true); + view.setInt16(68, -7552, true); + view.setInt16(70, -32654, true); + break; + case NpcType.Garanz2: + view.setInt16(4, 27144, true); + view.setInt16(8, 3, true); + view.setInt16(10, 27928, true); + view.setInt16(68, -7552, true); + view.setInt16(70, 24683, true); + break; + case NpcType.Dubswitch2: + view.setInt16(4, -1, true); + view.setInt16(8, 3, true); + view.setInt16(10, -4840, true); + view.setInt16(68, -7552, true); + view.setInt16(70, -20363, true); + break; + case NpcType.Delsaber2: + view.setInt16(4, -1, true); + view.setInt16(8, 3, true); + view.setInt16(10, 2841, true); + view.setInt16(68, -7552, true); + view.setInt16(70, 16513, true); + break; + case NpcType.ChaosSorcerer2: + view.setInt16(4, -1, true); + view.setInt16(8, 4, true); + view.setInt16(10, 9754, true); + view.setInt16(68, -7296, true); + view.setInt16(70, -7963, true); + break; + case NpcType.GolDragon: + view.setInt16(4, -19963, true); + view.setInt16(8, 15, true); + view.setInt16(10, -18411, true); + view.setInt16(68, -3712, true); + view.setInt16(70, 16555, true); + break; + case NpcType.SinowBerill: + view.setInt16(4, -1, true); + view.setInt16(8, 5, true); + view.setInt16(10, 15896, true); + view.setFloat32(44, 3, true); // Scale x + view.setFloat32(52, -0.19999998807907104, true); // Scale z + view.setInt16(68, -7552, true); + view.setInt16(70, 47, true); + break; + case NpcType.SinowSpigell: + view.setInt16(4, 880, true); + view.setInt16(8, 5, true); + view.setInt16(10, 7101, true); + view.setInt16(68, -11584, true); + view.setInt16(70, 1163, true); + break; + case NpcType.Merillia: + view.setInt16(4, -1, true); + view.setInt16(8, 6, true); + view.setInt16(10, 28439, true); + view.setFloat32(44, 1, true); // Scale x + view.setFloat32(52, -0.09999999403953552, true); // Scale z + view.setInt16(68, -7552, true); + view.setInt16(70, 16456, true); + break; + case NpcType.Meriltas: + view.setInt16(4, -1, true); + view.setInt16(8, 6, true); + view.setInt16(10, 30999, true); + view.setFloat32(44, -1, true); // Scale x + view.setFloat32(52, 0.09999999403953552, true); // Scale z + view.setInt16(68, -7552, true); + view.setInt16(70, 16456, true); + break; + case NpcType.Mericarol: + view.setInt16(4, -1, true); + view.setInt16(8, 17, true); + view.setInt16(10, 30232, true); + view.setFloat32(44, 0.19999998807907104, true); // Scale x + view.setFloat32(52, 0.19999998807907104, true); // Scale z + view.setInt16(68, -7552, true); + view.setInt16(70, -4016, true); + break; + case NpcType.Mericus: + view.setInt16(4, 32010, true); + view.setInt16(8, 17, true); + view.setInt16(10, 3356, true); + view.setFloat32(44, 0.19999998807907104, true); // Scale x + view.setFloat32(52, 0.19999998807907104, true); // Scale z + view.setInt16(68, -7552, true); + view.setInt16(70, 28762, true); + break; + case NpcType.Merikle: + view.setInt16(4, 32010, true); + view.setInt16(8, 17, true); + view.setInt16(10, 3868, true); + view.setFloat32(44, 0.19999998807907104, true); // Scale x + view.setFloat32(52, 0.19999998807907104, true); // Scale z + view.setInt16(68, -7552, true); + view.setInt16(70, -3997, true); + break; + case NpcType.UlGibbon: + view.setInt16(4, -1, true); + view.setInt16(8, 6, true); + view.setInt16(10, -27881, true); + view.setFloat32(48, 1, true); // Jump appear + view.setInt16(68, -7552, true); + view.setInt16(70, 20554, true); + break; + case NpcType.ZolGibbon: + view.setInt16(4, -1, true); + view.setInt16(8, 5, true); + view.setInt16(10, 6331, true); + view.setInt16(68, -26688, true); + view.setInt16(70, 565, true); + break; + case NpcType.Gibbles: + view.setInt16(4, -1, true); + view.setInt16(8, 17, true); + view.setInt16(10, -24296, true); + view.setFloat32(44, 500, true); // Scale x + view.setFloat32(52, 0.7999999523162842, true); // Scale z + view.setInt16(68, -7552, true); + view.setInt16(70, -12210, true); + break; + case NpcType.Gee: + view.setInt16(4, -1, true); + view.setInt16(8, 6, true); + view.setInt16(10, -20457, true); + view.setInt16(68, -7552, true); + view.setInt16(70, -4024, true); + break; + case NpcType.GiGue: + view.setInt16(4, 32010, true); + view.setInt16(8, 17, true); + view.setInt16(10, 13852, true); + view.setFloat32(44, 501, true); // Scale x + view.setFloat32(52, 50, true); // Scale z + view.setInt16(68, -7552, true); + view.setInt16(70, 12374, true); + break; + case NpcType.IllGill: + view.setInt16(4, 4104, true); + view.setInt16(8, 17, true); + view.setInt16(10, 7192, true); + view.setInt16(68, -7552, true); + view.setInt16(70, 24639, true); + break; + case NpcType.DelLily: + view.setInt16(4, -1, true); + view.setInt16(8, 17, true); + view.setInt16(10, 6388, true); + view.setInt16(68, -26576, true); + view.setInt16(70, 564, true); + break; + case NpcType.Epsilon: + view.setInt16(4, -1, true); + view.setInt16(8, 17, true); + view.setInt16(10, -7914, true); + view.setInt16(68, -4224, true); + view.setInt16(70, -16379, true); + break; + case NpcType.GalGryphon: + view.setInt16(4, 1173, true); + view.setInt16(8, 11, true); + view.setInt16(10, -1, true); + view.setInt16(68, 24624, true); + view.setInt16(70, -29446, true); + break; + case NpcType.Deldepth: + view.setInt16(4, 2095, true); + view.setInt16(8, 11, true); + view.setInt16(10, 6251, true); + view.setInt16(68, -26352, true); + view.setInt16(70, 665, true); + break; + case NpcType.Delbiter: + view.setInt16(4, -1, true); + view.setInt16(8, 11, true); + view.setInt16(10, -27880, true); + view.setFloat32(48, 0.19999998807907104, true); // Confuse percent + view.setFloat32(52, 20, true); // Confuse distance + view.setFloat32(60, 0.5, true); // Charge percent + view.setInt32(64, 1, true); // Type + view.setInt16(68, -7552, true); + view.setInt16(70, 24639, true); + break; + case NpcType.Dolmolm: + view.setInt16(4, -1, true); + view.setInt16(8, 11, true); + view.setInt16(10, 28441, true); + view.setFloat32(52, 1, true); // Scale z + view.setInt16(68, -7552, true); + view.setInt16(70, 12370, true); + break; + case NpcType.Dolmdarl: + view.setInt16(4, -1, true); + view.setInt16(8, 11, true); + view.setInt16(10, 31513, true); + view.setFloat32(52, -1, true); // Scale z + view.setInt16(68, -7552, true); + view.setInt16(70, -4001, true); + break; + case NpcType.Morfos: + view.setInt16(4, 1993, true); + view.setInt16(8, 11, true); + view.setInt16(10, 6115, true); + view.setInt16(68, -25424, true); + view.setInt16(70, 561, true); + break; + case NpcType.Recobox: + view.setInt16(4, -1, true); + view.setInt16(8, 11, true); + view.setInt16(10, 6107, true); + view.setInt16(68, -26160, true); + view.setInt16(70, 686, true); + break; + case NpcType.SinowZoa: + view.setInt16(4, 2634, true); + view.setInt16(8, 11, true); + view.setInt16(10, 6999, true); + view.setFloat32(44, 1, true); // Scale x + view.setInt16(68, -19488, true); + view.setInt16(70, 665, true); + break; + case NpcType.SinowZele: + view.setInt16(4, 2634, true); + view.setInt16(8, 11, true); + view.setInt16(10, 7027, true); + view.setInt16(68, -25152, true); + view.setInt16(70, 665, true); + break; + case NpcType.OlgaFlow: + view.setInt16(4, -1, true); + view.setInt16(8, 13, true); + view.setInt16(10, 8466, true); + view.setInt16(68, -4480, true); + view.setInt16(70, -28572, true); + break; + case NpcType.SandRappy: + view.setInt16(4, -1, true); + view.setInt16(8, 5, true); + view.setInt16(10, 5471, true); + view.setInt16(68, -27344, true); + view.setInt16(70, 616, true); + break; + case NpcType.DelRappy: + view.setInt16(4, -1, true); + view.setInt16(8, 3, true); + view.setInt16(10, 5039, true); + view.setInt16(68, -17168, true); + view.setInt16(70, 410, true); + break; + case NpcType.Astark: + view.setInt16(4, -1, true); + view.setInt16(8, 5, true); + view.setInt16(10, 5653, true); + view.setInt16(68, -26896, true); + view.setInt16(70, 616, true); + break; + case NpcType.SatelliteLizard: + view.setInt16(4, -1, true); + view.setInt16(8, 5, true); + view.setInt16(10, 5524, true); + view.setInt16(68, -27088, true); + view.setInt16(70, 616, true); + break; + case NpcType.Yowie: + view.setInt16(4, -1, true); + view.setInt16(8, 5, true); + view.setInt16(10, 5547, true); + view.setFloat32(44, 1, true); // Scale x + view.setInt16(68, -25872, true); + view.setInt16(70, 616, true); + break; + case NpcType.MerissaA: + view.setInt16(4, -1, true); + view.setInt16(8, 7, true); + view.setInt16(10, 5322, true); + view.setInt16(68, -16512, true); + view.setInt16(70, 542, true); + break; + case NpcType.MerissaAA: + view.setInt16(4, -1, true); + view.setInt16(8, 8, true); + view.setInt16(10, 5651, true); + view.setInt16(68, -27328, true); + view.setInt16(70, 1230, true); + break; + case NpcType.Girtablulu: + view.setInt16(4, -1, true); + view.setInt16(8, 7, true); + view.setInt16(10, 5007, true); + view.setInt16(68, -26256, true); + view.setInt16(70, 459, true); + break; + case NpcType.Zu: + view.setInt16(4, -1, true); + view.setInt16(8, 5, true); + view.setInt16(10, 5734, true); + view.setInt16(68, -28304, true); + view.setInt16(70, 616, true); + break; + case NpcType.Pazuzu: + view.setInt16(4, 937, true); + view.setInt16(8, 3, true); + view.setInt16(10, 5054, true); + view.setInt16(68, -15216, true); + view.setInt16(70, 410, true); + break; + case NpcType.Boota: + view.setInt16(4, -1, true); + view.setInt16(8, 5, true); + view.setInt16(10, 5496, true); + view.setInt16(68, -27216, true); + view.setInt16(70, 616, true); + break; + case NpcType.ZeBoota: + view.setInt16(4, -1, true); + view.setInt16(8, 5, true); + view.setInt16(10, 5504, true); + view.setInt16(68, -20304, true); + view.setInt16(70, 616, true); + break; + case NpcType.BaBoota: + view.setInt16(4, -1, true); + view.setInt16(8, 5, true); + view.setInt16(10, 5513, true); + view.setInt16(68, -14800, true); + view.setInt16(70, 616, true); + break; + case NpcType.Dorphon: + view.setInt16(4, 2308, true); + view.setInt16(8, 5, true); + view.setInt16(10, 6840, true); + view.setInt16(68, -26480, true); + view.setInt16(70, 616, true); + break; + case NpcType.DorphonEclair: + view.setInt16(4, 951, true); + view.setInt16(8, 3, true); + view.setInt16(10, 5101, true); + view.setInt16(68, -30064, true); + view.setInt16(70, 410, true); + break; + case NpcType.Goran: + view.setInt16(4, -1, true); + view.setInt16(8, 8, true); + view.setInt16(10, 5439, true); + view.setInt16(68, -27216, true); + view.setInt16(70, 610, true); + break; + case NpcType.PyroGoran: + view.setInt16(4, -1, true); + view.setInt16(8, 7, true); + view.setInt16(10, 5375, true); + view.setInt16(68, -16384, true); + view.setInt16(70, 542, true); + break; + case NpcType.GoranDetonator: + view.setInt16(4, -1, true); + view.setInt16(8, 7, true); + view.setInt16(10, 5373, true); + view.setInt16(68, -16384, true); + view.setInt16(70, 542, true); + break; + case NpcType.SaintMilion: + view.setInt16(4, 1297, true); + view.setInt16(6, 24, true); // Clone count + view.setInt16(8, 9, true); + view.setInt16(10, 5521, true); + view.setInt16(68, 28144, true); + view.setInt16(70, 673, true); + break; + case NpcType.Shambertin: + view.setInt16(4, 1362, true); + view.setInt16(6, 24, true); // Clone count + view.setInt16(8, 9, true); + view.setInt16(10, 5662, true); + view.setFloat32(44, 1, true); // Scale x + view.setInt16(68, 31280, true); + view.setInt16(70, 491, true); + break; + } +} diff --git a/src/quest_editor/model/QuestEntityModel.test.ts b/src/quest_editor/model/QuestEntityModel.test.ts index 6c881313..e06d641d 100644 --- a/src/quest_editor/model/QuestEntityModel.test.ts +++ b/src/quest_editor/model/QuestEntityModel.test.ts @@ -45,7 +45,7 @@ test("After changing section, world position should change accordingly.", () => function create_entity(): QuestEntityModel { const entity = new QuestNpcModel( - create_quest_npc(NpcType.AlRappy, area_store.get_area(Episode.I, 0).id, 0), + create_quest_npc(NpcType.AlRappy, Episode.I, area_store.get_area(Episode.I, 0).id, 0), ); entity.set_position(new Vector3(5, 5, 5)); return entity; diff --git a/src/quest_editor/rendering/QuestEntityControls.ts b/src/quest_editor/rendering/QuestEntityControls.ts index 5fee5033..09602f7c 100644 --- a/src/quest_editor/rendering/QuestEntityControls.ts +++ b/src/quest_editor/rendering/QuestEntityControls.ts @@ -24,6 +24,7 @@ import { TranslateEntityAction } from "../actions/TranslateEntityAction"; import { Object3D } from "three/src/core/Object3D"; import { create_quest_npc } from "../../core/data_formats/parsing/quest/QuestNpc"; import { create_quest_object } from "../../core/data_formats/parsing/quest/QuestObject"; +import { Episode } from "../../core/data_formats/parsing/quest/Episode"; const ZERO_VECTOR = Object.freeze(new Vector3(0, 0, 0)); const UP_VECTOR = Object.freeze(new Vector3(0, 1, 0)); @@ -175,6 +176,7 @@ export class QuestEntityControls implements Disposable { shift_key: e.event.shiftKey, pointer_device_position: this.pointer_device_position, entity_type: e.entity_type, + episode: this.quest_editor_store.current_quest.val?.episode ?? Episode.I, drag_element: e.drag_element, data_transfer: e.event.dataTransfer, prevent_default: () => e.event.preventDefault(), @@ -190,6 +192,7 @@ export class QuestEntityControls implements Disposable { shift_key: e.event.shiftKey, pointer_device_position: this.pointer_device_position, entity_type: e.entity_type, + episode: this.quest_editor_store.current_quest.val?.episode ?? Episode.I, drag_element: e.drag_element, data_transfer: e.event.dataTransfer, prevent_default: () => e.event.preventDefault(), @@ -205,6 +208,7 @@ export class QuestEntityControls implements Disposable { shift_key: e.event.shiftKey, pointer_device_position: this.pointer_device_position, entity_type: e.entity_type, + episode: this.quest_editor_store.current_quest.val?.episode ?? Episode.I, drag_element: e.drag_element, data_transfer: e.event.dataTransfer, prevent_default: () => e.event.preventDefault(), @@ -292,6 +296,7 @@ type EntityDragEvt = { readonly shift_key: boolean; readonly pointer_device_position: Vector2; readonly entity_type: EntityType; + readonly episode: Episode; readonly drag_element: HTMLElement; readonly data_transfer: DataTransfer | null; stop_propagation(): void; @@ -640,7 +645,7 @@ class CreationState implements State { const wave = quest_editor_store.selected_wave.val; this.entity = new QuestNpcModel( - create_quest_npc(evt.entity_type, area.id, wave?.id.val ?? 0), + create_quest_npc(evt.entity_type, evt.episode, area.id, wave?.id.val ?? 0), wave, ); } else { diff --git a/test/src/utils.ts b/test/src/utils.ts index 427fd302..b11b4e8d 100644 --- a/test/src/utils.ts +++ b/test/src/utils.ts @@ -55,15 +55,13 @@ export function next_animation_frame(): Promise { } /** - * Applies f to all QST files in a directory. + * Applies f to all 106 QST files provided with Tethealla version 0.143. * f is called with the path to the file, the file name and the content of the file. - * Uses the 106 QST files provided with Tethealla version 0.143 by default. */ export function walk_qst_files( f: (path: string, file_name: string, contents: Buffer) => void, - dir?: string, ): void { - walk_quests.walk_qst_files(f, dir); + walk_quests.walk_qst_files({}, f); } export function load_default_quest_model(area_store: AreaStore): QuestModel {