phantasmal-world/src/domain/index.ts

266 lines
7.5 KiB
TypeScript
Raw Normal View History

import { Object3D } from 'three';
import { computed, observable } from 'mobx';
import { NpcType } from './NpcType';
import { ObjectType } from './ObjectType';
export { NpcType } from './NpcType';
export { ObjectType } from './ObjectType';
export class Vec3 {
x: number;
y: number;
z: number;
constructor(x?: number, y?: number, z?: number) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
}
add(v: Vec3): Vec3 {
this.x += v.x;
this.y += v.y;
this.z += v.z;
return this;
}
clone(x?: number, y?: number, z?: number) {
return new Vec3(
typeof x === 'number' ? x : this.x,
typeof y === 'number' ? y : this.y,
typeof z === 'number' ? z : this.z);
}
};
export class Section {
id: number;
@observable position: Vec3;
@observable y_axis_rotation: number;
@computed get sin_y_axis_rotation(): number {
return Math.sin(this.y_axis_rotation);
}
@computed get cos_y_axis_rotation(): number {
return Math.cos(this.y_axis_rotation);
}
constructor(
id: number,
position: Vec3,
y_axis_rotation: number
) {
if (!Number.isInteger(id) || id < -1)
throw new Error(`Expected id to be an integer greater than or equal to -1, got ${id}.`);
if (!position) throw new Error('position is required.');
if (typeof y_axis_rotation !== 'number') throw new Error('y_axis_rotation is required.');
this.id = id;
this.position = position;
this.y_axis_rotation = y_axis_rotation;
}
}
export class Quest {
@observable name: string;
@observable short_description: string;
@observable long_description: string;
@observable quest_no?: number;
@observable episode: number;
@observable area_variants: AreaVariant[];
@observable objects: QuestObject[];
@observable npcs: QuestNpc[];
/**
* (Partial) raw DAT data that can't be parsed yet by Phantasmal.
*/
dat: any;
/**
* (Partial) raw BIN data that can't be parsed yet by Phantasmal.
*/
bin: any;
constructor(
name: string,
short_description: string,
long_description: string,
quest_no: number | undefined,
episode: number,
area_variants: AreaVariant[],
objects: QuestObject[],
npcs: QuestNpc[],
dat: any,
bin: any
) {
if (quest_no != null && (!Number.isInteger(quest_no) || quest_no < 0)) throw new Error('quest_no should be null or a non-negative integer.');
if (episode !== 1 && episode !== 2 && episode !== 4) throw new Error('episode should be 1, 2 or 4.');
if (!objects || !(objects instanceof Array)) throw new Error('objs is required.');
if (!npcs || !(npcs instanceof Array)) throw new Error('npcs is required.');
this.name = name;
this.short_description = short_description;
this.long_description = long_description;
this.quest_no = quest_no;
this.episode = episode;
this.area_variants = area_variants;
this.objects = objects;
this.npcs = npcs;
this.dat = dat;
this.bin = bin;
}
}
export class VisibleQuestEntity {
@observable area_id: number;
@computed get section_id(): number {
return this.section ? this.section.id : this._section_id;
}
@observable section?: Section;
/**
* World position
*/
@observable position: Vec3;
@observable rotation: Vec3;
/**
* Section-relative position
*/
@computed get section_position(): Vec3 {
let { x, y, z } = this.position;
if (this.section) {
const rel_x = x - this.section.position.x;
const rel_y = y - this.section.position.y;
const rel_z = z - this.section.position.z;
const sin = -this.section.sin_y_axis_rotation;
const cos = this.section.cos_y_axis_rotation;
const rot_x = cos * rel_x + sin * rel_z;
const rot_z = -sin * rel_x + cos * rel_z;
x = rot_x;
y = rel_y;
z = rot_z;
}
return new Vec3(x, y, z);
}
set section_position(sect_pos: Vec3) {
let { x: rel_x, y: rel_y, z: rel_z } = sect_pos;
if (this.section) {
const sin = -this.section.sin_y_axis_rotation;
const cos = this.section.cos_y_axis_rotation;
const rot_x = cos * rel_x - sin * rel_z;
const rot_z = sin * rel_x + cos * rel_z;
const x = rot_x + this.section.position.x;
const y = rel_y + this.section.position.y;
const z = rot_z + this.section.position.z;
this.position = new Vec3(x, y, z);
}
}
object3d?: Object3D;
constructor(
area_id: number,
section_id: number,
position: Vec3,
rotation: Vec3
) {
if (Object.getPrototypeOf(this) === Object.getPrototypeOf(VisibleQuestEntity))
throw new Error('Abstract class should not be instantiated directly.');
if (!Number.isInteger(area_id) || area_id < 0)
throw new Error(`Expected area_id to be a non-negative integer, got ${area_id}.`);
if (!Number.isInteger(section_id) || section_id < 0)
throw new Error(`Expected section_id to be a non-negative integer, got ${section_id}.`);
if (!position) throw new Error('position is required.');
if (!rotation) throw new Error('rotation is required.');
this.area_id = area_id;
this._section_id = section_id;
this.position = position;
this.rotation = rotation;
}
private _section_id: number;
}
export class QuestObject extends VisibleQuestEntity {
@observable type: ObjectType;
/**
* The raw data from a DAT file.
*/
dat: any;
constructor(
area_id: number,
section_id: number,
position: Vec3,
rotation: Vec3,
type: ObjectType,
dat: any
) {
super(area_id, section_id, position, rotation);
if (!type) throw new Error('type is required.');
this.type = type;
this.dat = dat;
}
}
export class QuestNpc extends VisibleQuestEntity {
@observable type: NpcType;
/**
* The raw data from a DAT file.
*/
dat: any;
constructor(
area_id: number,
section_id: number,
position: Vec3,
rotation: Vec3,
type: NpcType,
dat: any
) {
super(area_id, section_id, position, rotation);
if (!type) throw new Error('type is required.');
this.type = type;
this.dat = dat;
}
}
export class Area {
id: number;
name: string;
order: number;
area_variants: AreaVariant[];
constructor(id: number, name: string, order: number, area_variants: AreaVariant[]) {
if (!Number.isInteger(id) || id < 0)
throw new Error(`Expected id to be a non-negative integer, got ${id}.`);
if (!name) throw new Error('name is required.');
if (!area_variants) throw new Error('area_variants is required.');
this.id = id;
this.name = name;
this.order = order;
this.area_variants = area_variants;
}
}
export class AreaVariant {
@observable sections: Section[] = [];
constructor(public id: number, public area: Area) {
if (!Number.isInteger(id) || id < 0)
throw new Error(`Expected id to be a non-negative integer, got ${id}.`);
}
}