Sensible default values are now set for most NPC properties when creating a new NPC.

This commit is contained in:
Daan Vanden Bosch 2020-09-29 22:16:48 +02:00
parent 7296df7b00
commit 43161ae7e7
8 changed files with 1009 additions and 77 deletions

View File

@ -12,26 +12,34 @@ print_quest_stats();
function print_quest_stats(): void {
const type_data: Map<NpcType, { npc: QuestNpc; quest: string; count: number }[]> = 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.`);
}
}

View File

@ -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));

View File

@ -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()) {

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -55,15 +55,13 @@ export function next_animation_frame(): Promise<void> {
}
/**
* 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 {