mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 07:18:29 +08:00
Replaced js-logger. Improved testability with mocks, improved test configuration and code improvements.
This commit is contained in:
parent
243638879c
commit
99d50d754d
@ -1,10 +1,11 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
preset: "ts-jest",
|
preset: "ts-jest",
|
||||||
testEnvironment: "node",
|
|
||||||
moduleDirectories: ["node_modules"],
|
moduleDirectories: ["node_modules"],
|
||||||
setupFiles: ["./test/src/setup.js"],
|
setupFiles: ["./test/src/setup.js"],
|
||||||
|
roots: ["./src", "./test"],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
"\\.(css|gif|jpg|png|svg|ttf)$": "<rootDir>/src/__mocks__/static_files.js",
|
"\\.(css|gif|jpg|png|svg|ttf)$": "<rootDir>/src/__mocks__/static_files.js",
|
||||||
"monaco-editor": "<rootDir>/node_modules/monaco-editor/dev/vs/editor/editor.main.js",
|
"^monaco-editor$": "<rootDir>/node_modules/monaco-editor/esm/vs/editor/editor.main.js",
|
||||||
|
"^worker-loader!": "<rootDir>/src/__mocks__/webworkers.js",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -9,14 +9,13 @@
|
|||||||
"camera-controls": "^1.16.2",
|
"camera-controls": "^1.16.2",
|
||||||
"golden-layout": "^1.5.9",
|
"golden-layout": "^1.5.9",
|
||||||
"javascript-lp-solver": "0.4.17",
|
"javascript-lp-solver": "0.4.17",
|
||||||
"js-logger": "^1.6.0",
|
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"luxon": "^1.21.3",
|
"luxon": "^1.21.3",
|
||||||
"monaco-editor": "^0.19.0",
|
"monaco-editor": "^0.19.0",
|
||||||
"three": "^0.111.0"
|
"three": "^0.111.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack-dev-server --port 1623 --config webpack.dev.js",
|
"start": "webpack-dev-server --config webpack.dev.js",
|
||||||
"build": "webpack --config webpack.prod.js",
|
"build": "webpack --config webpack.prod.js",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"update_generic_data": "ts-node --project=tsconfig-scripts.json assets_generation/update_generic_data.ts",
|
"update_generic_data": "ts-node --project=tsconfig-scripts.json assets_generation/update_generic_data.ts",
|
||||||
|
89
src/__mocks__/monaco-editor.js
Normal file
89
src/__mocks__/monaco-editor.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
class Editor {
|
||||||
|
addCommand() {}
|
||||||
|
getAction() {}
|
||||||
|
addAction() {
|
||||||
|
return { dispose() {} };
|
||||||
|
}
|
||||||
|
trigger() {}
|
||||||
|
updateOptions() {}
|
||||||
|
setModel() {}
|
||||||
|
setPosition() {}
|
||||||
|
getLineDecorations() {}
|
||||||
|
deltaDecorations() {}
|
||||||
|
revealLineInCenterIfOutsideViewport() {}
|
||||||
|
revealPositionInCenterIfOutsideViewport() {}
|
||||||
|
onDidFocusEditorWidget() {
|
||||||
|
return { dispose() {} };
|
||||||
|
}
|
||||||
|
onMouseDown() {
|
||||||
|
return { dispose() {} };
|
||||||
|
}
|
||||||
|
onMouseUp() {
|
||||||
|
return { dispose() {} };
|
||||||
|
}
|
||||||
|
focus() {}
|
||||||
|
layout() {}
|
||||||
|
onDidChangeCursorPosition() {
|
||||||
|
return { dispose() {} };
|
||||||
|
}
|
||||||
|
dispose() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.editor = {
|
||||||
|
defineTheme() {},
|
||||||
|
createModel() {},
|
||||||
|
create() {
|
||||||
|
return new Editor();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.languages = {
|
||||||
|
CompletionItemKind: {
|
||||||
|
Method: 0,
|
||||||
|
Function: 1,
|
||||||
|
Constructor: 2,
|
||||||
|
Field: 3,
|
||||||
|
Variable: 4,
|
||||||
|
Class: 5,
|
||||||
|
Struct: 6,
|
||||||
|
Interface: 7,
|
||||||
|
Module: 8,
|
||||||
|
Property: 9,
|
||||||
|
Event: 10,
|
||||||
|
Operator: 11,
|
||||||
|
Unit: 12,
|
||||||
|
Value: 13,
|
||||||
|
Constant: 14,
|
||||||
|
Enum: 15,
|
||||||
|
EnumMember: 16,
|
||||||
|
Keyword: 17,
|
||||||
|
Text: 18,
|
||||||
|
Color: 19,
|
||||||
|
File: 20,
|
||||||
|
Reference: 21,
|
||||||
|
Customcolor: 22,
|
||||||
|
Folder: 23,
|
||||||
|
TypeParameter: 24,
|
||||||
|
Snippet: 25,
|
||||||
|
},
|
||||||
|
register() {},
|
||||||
|
setMonarchTokensProvider() {},
|
||||||
|
registerCompletionItemProvider() {},
|
||||||
|
registerSignatureHelpProvider() {},
|
||||||
|
setLanguageConfiguration() {},
|
||||||
|
registerDefinitionProvider() {},
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.KeyMod = {
|
||||||
|
CtrlCmd: 0,
|
||||||
|
Shift: 1,
|
||||||
|
Alt: 2,
|
||||||
|
WinCtrl: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.KeyCode = {
|
||||||
|
LeftArrow: 15,
|
||||||
|
UpArrow: 16,
|
||||||
|
RightArrow: 17,
|
||||||
|
DownArrow: 18,
|
||||||
|
};
|
6
src/__mocks__/webworkers.js
Normal file
6
src/__mocks__/webworkers.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
class Worker {
|
||||||
|
onmessage() {}
|
||||||
|
postMessage() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Worker;
|
49
src/application/index.test.ts
Normal file
49
src/application/index.test.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { initialize_application } from "./index";
|
||||||
|
import { DisposableThreeRenderer } from "../core/rendering/Renderer";
|
||||||
|
import { LogManager, LogHandler, LogLevel } from "../core/Logger";
|
||||||
|
import { FileSystemHttpClient } from "../../test/src/core/FileSystemHttpClient";
|
||||||
|
import { timeout } from "../../test/src/utils";
|
||||||
|
|
||||||
|
for (const path of [undefined, "/viewer", "/quest_editor", "/hunt_optimizer"]) {
|
||||||
|
const with_path = path == undefined ? "without specific path" : `with path ${path}`;
|
||||||
|
|
||||||
|
test(`Initialization and shutdown ${with_path} should succeed without throwing errors or logging with level Warn or above.`, async () => {
|
||||||
|
const logged_errors: string[] = [];
|
||||||
|
|
||||||
|
const handler: LogHandler = ({ level, message }) => {
|
||||||
|
if (level >= LogLevel.Warn) {
|
||||||
|
logged_errors.push(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return LogManager.with_default_handler(handler, async () => {
|
||||||
|
if (path != undefined) {
|
||||||
|
window.location.hash = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = initialize_application(
|
||||||
|
new FileSystemHttpClient(),
|
||||||
|
() => new StubRenderer(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
expect(logged_errors).toEqual([]);
|
||||||
|
|
||||||
|
await timeout(2000);
|
||||||
|
|
||||||
|
app.dispose();
|
||||||
|
|
||||||
|
expect(logged_errors).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class StubRenderer implements DisposableThreeRenderer {
|
||||||
|
domElement: HTMLCanvasElement = document.createElement("canvas");
|
||||||
|
|
||||||
|
dispose(): void {} // eslint-disable-line
|
||||||
|
|
||||||
|
render(): void {} // eslint-disable-line
|
||||||
|
|
||||||
|
setSize(): void {} // eslint-disable-line
|
||||||
|
}
|
132
src/application/index.ts
Normal file
132
src/application/index.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { HttpClient } from "../core/HttpClient";
|
||||||
|
import { Disposable } from "../core/observable/Disposable";
|
||||||
|
import { GuiStore, GuiTool } from "../core/stores/GuiStore";
|
||||||
|
import { create_item_type_stores } from "../core/stores/ItemTypeStore";
|
||||||
|
import { create_item_drop_stores } from "../hunt_optimizer/stores/ItemDropStore";
|
||||||
|
import { ApplicationView } from "./gui/ApplicationView";
|
||||||
|
import { throttle } from "lodash";
|
||||||
|
import { DisposableThreeRenderer } from "../core/rendering/Renderer";
|
||||||
|
import { Disposer } from "../core/observable/Disposer";
|
||||||
|
import { disposable_custom_listener, disposable_listener } from "../core/gui/dom";
|
||||||
|
|
||||||
|
export function initialize_application(
|
||||||
|
http_client: HttpClient,
|
||||||
|
create_three_renderer: () => DisposableThreeRenderer,
|
||||||
|
): Disposable {
|
||||||
|
const disposer = new Disposer();
|
||||||
|
|
||||||
|
// Disable native undo/redo.
|
||||||
|
disposer.add(disposable_custom_listener(document, "beforeinput", before_input));
|
||||||
|
// Work-around for FireFox:
|
||||||
|
disposer.add(disposable_listener(document, "keydown", keydown));
|
||||||
|
|
||||||
|
// Disable native drag-and-drop to avoid users dragging in unsupported file formats and leaving
|
||||||
|
// the application unexpectedly.
|
||||||
|
disposer.add_all(
|
||||||
|
disposable_listener(document, "dragenter", dragenter),
|
||||||
|
disposable_listener(document, "dragover", dragover),
|
||||||
|
disposable_listener(document, "drop", drop),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initialize core stores shared by several submodules.
|
||||||
|
const gui_store = disposer.add(new GuiStore());
|
||||||
|
const item_type_stores = disposer.add(create_item_type_stores(http_client, gui_store));
|
||||||
|
const item_drop_stores = disposer.add(
|
||||||
|
create_item_drop_stores(http_client, gui_store, item_type_stores),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initialize application view.
|
||||||
|
const application_view = disposer.add(
|
||||||
|
new ApplicationView(gui_store, [
|
||||||
|
[
|
||||||
|
GuiTool.Viewer,
|
||||||
|
async () => {
|
||||||
|
const { initialize_viewer } = await import("../viewer");
|
||||||
|
const viewer = disposer.add(
|
||||||
|
initialize_viewer(http_client, gui_store, create_three_renderer),
|
||||||
|
);
|
||||||
|
|
||||||
|
return viewer.view;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GuiTool.QuestEditor,
|
||||||
|
async () => {
|
||||||
|
const { initialize_quest_editor } = await import("../quest_editor");
|
||||||
|
const quest_editor = disposer.add(
|
||||||
|
initialize_quest_editor(http_client, gui_store, create_three_renderer),
|
||||||
|
);
|
||||||
|
|
||||||
|
return quest_editor.view;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
GuiTool.HuntOptimizer,
|
||||||
|
async () => {
|
||||||
|
const { initialize_hunt_optimizer } = await import("../hunt_optimizer");
|
||||||
|
const hunt_optimizer = disposer.add(
|
||||||
|
initialize_hunt_optimizer(
|
||||||
|
http_client,
|
||||||
|
gui_store,
|
||||||
|
item_type_stores,
|
||||||
|
item_drop_stores,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return hunt_optimizer.view;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Resize the view on window resize.
|
||||||
|
const resize = throttle(
|
||||||
|
() => {
|
||||||
|
application_view.resize(window.innerWidth, window.innerHeight);
|
||||||
|
},
|
||||||
|
100,
|
||||||
|
{ leading: true, trailing: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
resize();
|
||||||
|
document.body.append(application_view.element);
|
||||||
|
disposer.add(disposable_listener(window, "resize", resize));
|
||||||
|
|
||||||
|
return {
|
||||||
|
dispose(): void {
|
||||||
|
disposer.dispose();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function before_input(e: Event): void {
|
||||||
|
const ie = e as any;
|
||||||
|
|
||||||
|
if (ie.inputType === "historyUndo" || ie.inputType === "historyRedo") {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function keydown(e: Event): void {
|
||||||
|
const kbe = e as KeyboardEvent;
|
||||||
|
|
||||||
|
if (kbe.ctrlKey && !kbe.altKey && kbe.key.toUpperCase() === "Z") {
|
||||||
|
kbe.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragenter(e: DragEvent): void {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.dataTransfer.dropEffect = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragover(e: DragEvent): void {
|
||||||
|
dragenter(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drop(e: DragEvent): void {
|
||||||
|
dragenter(e);
|
||||||
|
}
|
@ -33,12 +33,12 @@ export class StubHttpClient implements HttpClient {
|
|||||||
get(url: string): HttpResponse {
|
get(url: string): HttpResponse {
|
||||||
return {
|
return {
|
||||||
json<T>(): Promise<T> {
|
json<T>(): Promise<T> {
|
||||||
throw new Error(`Dummy client's json method invoked for get request to "${url}".`);
|
throw new Error(`Stub client's json method invoked for get request to "${url}".`);
|
||||||
},
|
},
|
||||||
|
|
||||||
array_buffer(): Promise<ArrayBuffer> {
|
array_buffer(): Promise<ArrayBuffer> {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Dummy client's array_buffer method invoked for get request to "${url}".`,
|
`Stub client's array_buffer method invoked for get request to "${url}".`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
154
src/core/Logger.ts
Normal file
154
src/core/Logger.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import { enum_values } from "./enums";
|
||||||
|
import { assert } from "./util";
|
||||||
|
|
||||||
|
// Log level names in order of importance.
|
||||||
|
export enum LogLevel {
|
||||||
|
Trace,
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Warn,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LogLevels = enum_values<LogLevel>(LogLevel);
|
||||||
|
|
||||||
|
export function log_level_from_string(str: string): LogLevel {
|
||||||
|
const level = (LogLevel as any)[str.slice(0, 1).toUpperCase() + str.slice(1).toLowerCase()];
|
||||||
|
assert(level != undefined, () => `"${str}" is not a valid log level.`);
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LogEntry = {
|
||||||
|
readonly time: Date;
|
||||||
|
readonly message: string;
|
||||||
|
readonly level: LogLevel;
|
||||||
|
readonly logger: Logger;
|
||||||
|
readonly cause?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LogHandler = (entry: LogEntry, logger_name: string) => void;
|
||||||
|
|
||||||
|
function default_log_handler({ time, message, level, logger, cause }: LogEntry): void {
|
||||||
|
const str = `${time_to_string(time)} [${LogLevel[level]}] ${logger.name} - ${message}`;
|
||||||
|
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
let method: (...args: any[]) => void;
|
||||||
|
|
||||||
|
switch (level) {
|
||||||
|
case LogLevel.Trace:
|
||||||
|
method = console.trace;
|
||||||
|
break;
|
||||||
|
case LogLevel.Debug:
|
||||||
|
method = console.debug;
|
||||||
|
break;
|
||||||
|
case LogLevel.Info:
|
||||||
|
method = console.info;
|
||||||
|
break;
|
||||||
|
case LogLevel.Warn:
|
||||||
|
method = console.warn;
|
||||||
|
break;
|
||||||
|
case LogLevel.Error:
|
||||||
|
method = console.error;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
method = console.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cause == undefined) {
|
||||||
|
method.call(console, str);
|
||||||
|
} else {
|
||||||
|
method.call(console, str, cause);
|
||||||
|
}
|
||||||
|
/* eslint-enable no-console */
|
||||||
|
}
|
||||||
|
|
||||||
|
export function time_to_string(time: Date): string {
|
||||||
|
const hours = time_part_to_string(time.getHours(), 2);
|
||||||
|
const minutes = time_part_to_string(time.getMinutes(), 2);
|
||||||
|
const seconds = time_part_to_string(time.getSeconds(), 2);
|
||||||
|
const millis = time_part_to_string(time.getMilliseconds(), 3);
|
||||||
|
return `${hours}:${minutes}:${seconds}.${millis}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function time_part_to_string(value: number, n: number): string {
|
||||||
|
return value.toString().padStart(n, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Logger {
|
||||||
|
private _level?: LogLevel;
|
||||||
|
|
||||||
|
get level(): LogLevel {
|
||||||
|
return this._level ?? LogManager.default_level;
|
||||||
|
}
|
||||||
|
|
||||||
|
set level(level: LogLevel) {
|
||||||
|
this._level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handler?: LogHandler;
|
||||||
|
|
||||||
|
get handler(): LogHandler {
|
||||||
|
return this._handler ?? LogManager.default_handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
set handler(handler: LogHandler) {
|
||||||
|
this._handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(readonly name: string) {}
|
||||||
|
|
||||||
|
trace = (message: string, cause?: any): void => {
|
||||||
|
this.handle(LogLevel.Trace, message, cause);
|
||||||
|
};
|
||||||
|
|
||||||
|
debug = (message: string, cause?: any): void => {
|
||||||
|
this.handle(LogLevel.Debug, message, cause);
|
||||||
|
};
|
||||||
|
|
||||||
|
info = (message: string, cause?: any): void => {
|
||||||
|
this.handle(LogLevel.Info, message, cause);
|
||||||
|
};
|
||||||
|
|
||||||
|
warn = (message: string, cause?: any): void => {
|
||||||
|
this.handle(LogLevel.Warn, message, cause);
|
||||||
|
};
|
||||||
|
|
||||||
|
error = (message: string, cause?: any): void => {
|
||||||
|
this.handle(LogLevel.Error, message, cause);
|
||||||
|
};
|
||||||
|
|
||||||
|
private handle(level: LogLevel, message: string, cause?: any): void {
|
||||||
|
if (level >= this.level) {
|
||||||
|
this.handler({ time: new Date(), message, level, logger: this, cause }, this.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LogManager {
|
||||||
|
private static readonly loggers = new Map<string, Logger>();
|
||||||
|
|
||||||
|
static default_level: LogLevel = log_level_from_string(process.env["LOG_LEVEL"] ?? "Info");
|
||||||
|
static default_handler: LogHandler = default_log_handler;
|
||||||
|
|
||||||
|
static get(name: string): Logger {
|
||||||
|
let logger = this.loggers.get(name);
|
||||||
|
|
||||||
|
if (!logger) {
|
||||||
|
logger = new Logger(name);
|
||||||
|
this.loggers.set(name, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
static with_default_handler<T>(handler: LogHandler, f: () => T): T {
|
||||||
|
const orig_handler = this.default_handler;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.default_handler = handler;
|
||||||
|
return f();
|
||||||
|
} finally {
|
||||||
|
this.default_handler = orig_handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { Server } from "./model";
|
import { Server } from "./model";
|
||||||
|
import { LogManager } from "./Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/persistence/Persister");
|
const logger = LogManager.get("core/persistence/Persister");
|
||||||
|
|
||||||
export abstract class Persister {
|
export abstract class Persister {
|
||||||
protected persist_for_server(server: Server, key: string, data: any): void {
|
protected persist_for_server(server: Server, key: string, data: any): void {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { Cursor } from "../../cursor/Cursor";
|
import { Cursor } from "../../cursor/Cursor";
|
||||||
import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor";
|
import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor";
|
||||||
import { WritableCursor } from "../../cursor/WritableCursor";
|
import { WritableCursor } from "../../cursor/WritableCursor";
|
||||||
import { ResizableBuffer } from "../../ResizableBuffer";
|
import { ResizableBuffer } from "../../ResizableBuffer";
|
||||||
|
import { LogManager } from "../../../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/data_formats/compression/prs/decompress");
|
const logger = LogManager.get("core/data_formats/compression/prs/decompress");
|
||||||
|
|
||||||
export function prs_decompress(cursor: Cursor): Cursor {
|
export function prs_decompress(cursor: Cursor): Cursor {
|
||||||
const ctx = new Context(cursor);
|
const ctx = new Context(cursor);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { Cursor } from "../../cursor/Cursor";
|
import { Cursor } from "../../cursor/Cursor";
|
||||||
import { Vec2, Vec3 } from "../../vector";
|
import { Vec2, Vec3 } from "../../vector";
|
||||||
|
import { LogManager } from "../../../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/data_formats/parsing/ninja/njcm");
|
const logger = LogManager.get("core/data_formats/parsing/ninja/njcm");
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - colors
|
// - colors
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { Cursor } from "../../cursor/Cursor";
|
import { Cursor } from "../../cursor/Cursor";
|
||||||
import { parse_iff } from "../iff";
|
import { parse_iff } from "../iff";
|
||||||
|
import { LogManager } from "../../../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/data_formats/parsing/ninja/texture");
|
const logger = LogManager.get("core/data_formats/parsing/ninja/texture");
|
||||||
|
|
||||||
export type Xvm = {
|
export type Xvm = {
|
||||||
textures: XvmTexture[];
|
textures: XvmTexture[];
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { Cursor } from "../../cursor/Cursor";
|
import { Cursor } from "../../cursor/Cursor";
|
||||||
import { Vec2, Vec3 } from "../../vector";
|
import { Vec2, Vec3 } from "../../vector";
|
||||||
|
import { LogManager } from "../../../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/data_formats/parsing/ninja/xj");
|
const logger = LogManager.get("core/data_formats/parsing/ninja/xj");
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - vertex colors
|
// - vertex colors
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { prs_decompress } from "../compression/prs/decompress";
|
import { prs_decompress } from "../compression/prs/decompress";
|
||||||
import { Cursor } from "../cursor/Cursor";
|
import { Cursor } from "../cursor/Cursor";
|
||||||
import { prc_decrypt } from "../encryption/prc";
|
import { prc_decrypt } from "../encryption/prc";
|
||||||
|
import { LogManager } from "../../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/data_formats/parsing/prc");
|
const logger = LogManager.get("core/data_formats/parsing/prc");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypts and decompresses a .prc file.
|
* Decrypts and decompresses a .prc file.
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { Endianness } from "../../Endianness";
|
import { Endianness } from "../../Endianness";
|
||||||
import { ControlFlowGraph } from "../../../../quest_editor/scripting/data_flow_analysis/ControlFlowGraph";
|
import { ControlFlowGraph } from "../../../../quest_editor/scripting/data_flow_analysis/ControlFlowGraph";
|
||||||
import { register_value } from "../../../../quest_editor/scripting/data_flow_analysis/register_value";
|
import { register_value } from "../../../../quest_editor/scripting/data_flow_analysis/register_value";
|
||||||
@ -27,8 +26,9 @@ import { Cursor } from "../../cursor/Cursor";
|
|||||||
import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor";
|
import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor";
|
||||||
import { WritableCursor } from "../../cursor/WritableCursor";
|
import { WritableCursor } from "../../cursor/WritableCursor";
|
||||||
import { ResizableBuffer } from "../../ResizableBuffer";
|
import { ResizableBuffer } from "../../ResizableBuffer";
|
||||||
|
import { LogManager } from "../../../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/data_formats/parsing/quest/bin");
|
const logger = LogManager.get("core/data_formats/parsing/quest/bin");
|
||||||
|
|
||||||
export type BinFile = {
|
export type BinFile = {
|
||||||
readonly quest_id: number;
|
readonly quest_id: number;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { groupBy } from "lodash";
|
import { groupBy } from "lodash";
|
||||||
import { Endianness } from "../../Endianness";
|
import { Endianness } from "../../Endianness";
|
||||||
import { Cursor } from "../../cursor/Cursor";
|
import { Cursor } from "../../cursor/Cursor";
|
||||||
@ -7,8 +6,9 @@ import { ResizableBuffer } from "../../ResizableBuffer";
|
|||||||
import { Vec3 } from "../../vector";
|
import { Vec3 } from "../../vector";
|
||||||
import { WritableCursor } from "../../cursor/WritableCursor";
|
import { WritableCursor } from "../../cursor/WritableCursor";
|
||||||
import { assert } from "../../../util";
|
import { assert } from "../../../util";
|
||||||
|
import { LogManager } from "../../../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/data_formats/parsing/quest/dat");
|
const logger = LogManager.get("core/data_formats/parsing/quest/dat");
|
||||||
|
|
||||||
const OBJECT_SIZE = 68;
|
const OBJECT_SIZE = 68;
|
||||||
const NPC_SIZE = 72;
|
const NPC_SIZE = 72;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import {
|
import {
|
||||||
Instruction,
|
Instruction,
|
||||||
InstructionSegment,
|
InstructionSegment,
|
||||||
@ -20,8 +19,9 @@ import { object_data, ObjectType, pso_id_to_object_type } from "./object_types";
|
|||||||
import { parse_qst, QstContainedFile, write_qst } from "./qst";
|
import { parse_qst, QstContainedFile, write_qst } from "./qst";
|
||||||
import { npc_data, NpcType } from "./npc_types";
|
import { npc_data, NpcType } from "./npc_types";
|
||||||
import { reinterpret_f32_as_i32, reinterpret_i32_as_f32 } from "../../../primitive_conversion";
|
import { reinterpret_f32_as_i32, reinterpret_i32_as_f32 } from "../../../primitive_conversion";
|
||||||
|
import { LogManager } from "../../../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/data_formats/parsing/quest");
|
const logger = LogManager.get("core/data_formats/parsing/quest");
|
||||||
|
|
||||||
export type Quest = {
|
export type Quest = {
|
||||||
readonly id: number;
|
readonly id: number;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { Endianness } from "../../Endianness";
|
import { Endianness } from "../../Endianness";
|
||||||
import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor";
|
import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor";
|
||||||
import { Cursor } from "../../cursor/Cursor";
|
import { Cursor } from "../../cursor/Cursor";
|
||||||
@ -6,8 +5,9 @@ import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor";
|
|||||||
import { WritableCursor } from "../../cursor/WritableCursor";
|
import { WritableCursor } from "../../cursor/WritableCursor";
|
||||||
import { ResizableBuffer } from "../../ResizableBuffer";
|
import { ResizableBuffer } from "../../ResizableBuffer";
|
||||||
import { basename } from "../../../util";
|
import { basename } from "../../../util";
|
||||||
|
import { LogManager } from "../../../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/data_formats/parsing/quest/qst");
|
const logger = LogManager.get("core/data_formats/parsing/quest/qst");
|
||||||
|
|
||||||
export type QstContainedFile = {
|
export type QstContainedFile = {
|
||||||
id?: number;
|
id?: number;
|
||||||
@ -114,8 +114,8 @@ type QstHeader = {
|
|||||||
function parse_headers(cursor: Cursor): QstHeader[] {
|
function parse_headers(cursor: Cursor): QstHeader[] {
|
||||||
const headers: QstHeader[] = [];
|
const headers: QstHeader[] = [];
|
||||||
|
|
||||||
let prev_quest_id: number | undefined;
|
let prev_quest_id: number | undefined = undefined;
|
||||||
let prev_file_name: string | undefined;
|
let prev_file_name: string | undefined = undefined;
|
||||||
|
|
||||||
for (let i = 0; i < 4; ++i) {
|
for (let i = 0; i < 4; ++i) {
|
||||||
cursor.seek(4);
|
cursor.seek(4);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { Endianness } from "../Endianness";
|
import { Endianness } from "../Endianness";
|
||||||
import { Cursor } from "../cursor/Cursor";
|
import { Cursor } from "../cursor/Cursor";
|
||||||
import { parse_prc } from "./prc";
|
import { parse_prc } from "./prc";
|
||||||
|
import { LogManager } from "../../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/data_formats/parsing/rlc");
|
const logger = LogManager.get("core/data_formats/parsing/rlc");
|
||||||
const MARKER = "RelChunkVer0.20";
|
const MARKER = "RelChunkVer0.20";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@import "./Button.css";
|
||||||
|
|
||||||
.core_FileButton_input {
|
.core_FileButton_input {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
clip: rect(0, 0, 0, 0);
|
clip: rect(0, 0, 0, 0);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { create_element, el, icon, Icon } from "./dom";
|
import { create_element, el, icon, Icon } from "./dom";
|
||||||
import "./FileButton.css";
|
import "./FileButton.css";
|
||||||
import "./Button.css";
|
|
||||||
import { property } from "../observable";
|
import { property } from "../observable";
|
||||||
import { Property } from "../observable/property/Property";
|
import { Property } from "../observable/property/Property";
|
||||||
import { Control, ControlOptions } from "./Control";
|
import { Control, ControlOptions } from "./Control";
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable no-dupe-class-members */
|
|
||||||
import { LabelledControl, LabelledControlOptions } from "./LabelledControl";
|
import { LabelledControl, LabelledControlOptions } from "./LabelledControl";
|
||||||
import { create_element, el } from "./dom";
|
import { create_element, el } from "./dom";
|
||||||
import { WritableProperty } from "../observable/property/WritableProperty";
|
import { WritableProperty } from "../observable/property/WritableProperty";
|
||||||
@ -57,13 +56,7 @@ export abstract class Input<T> extends LabelledControl {
|
|||||||
|
|
||||||
protected abstract set_value(value: T): void;
|
protected abstract set_value(value: T): void;
|
||||||
|
|
||||||
protected set_attr<T>(attr: InputAttrsOfType<T>, value?: T | Property<T>): void;
|
protected set_attr<T, U = T>(
|
||||||
protected set_attr<T, U>(
|
|
||||||
attr: InputAttrsOfType<U>,
|
|
||||||
value: T | Property<T> | undefined,
|
|
||||||
convert: (value: T) => U,
|
|
||||||
): void;
|
|
||||||
protected set_attr<T, U>(
|
|
||||||
attr: InputAttrsOfType<U>,
|
attr: InputAttrsOfType<U>,
|
||||||
value?: T | Property<T>,
|
value?: T | Property<T>,
|
||||||
convert?: (value: T) => U,
|
convert?: (value: T) => U,
|
||||||
|
@ -22,7 +22,9 @@ export class LazyWidget extends ResizableWidget {
|
|||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
|
|
||||||
this.create_view().then(view => {
|
this.create_view().then(view => {
|
||||||
if (!this.disposed) {
|
if (this.disposed) {
|
||||||
|
view.dispose();
|
||||||
|
} else {
|
||||||
this.view = this.disposable(view);
|
this.view = this.disposable(view);
|
||||||
this.view.resize(this.width, this.height);
|
this.view.resize(this.width, this.height);
|
||||||
this.element.append(view.element);
|
this.element.append(view.element);
|
||||||
|
@ -39,7 +39,7 @@ export class TabContainer extends ResizableWidget {
|
|||||||
});
|
});
|
||||||
this.bar_element.append(tab_element);
|
this.bar_element.append(tab_element);
|
||||||
|
|
||||||
const lazy_view = new LazyWidget(tab.create_view);
|
const lazy_view = this.disposable(new LazyWidget(tab.create_view));
|
||||||
|
|
||||||
this.tabs.push({
|
this.tabs.push({
|
||||||
...tab,
|
...tab,
|
||||||
@ -48,7 +48,6 @@ export class TabContainer extends ResizableWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.panes_element.append(lazy_view.element);
|
this.panes_element.append(lazy_view.element);
|
||||||
this.disposable(lazy_view);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.tabs.length) {
|
if (this.tabs.length) {
|
||||||
|
@ -4,9 +4,9 @@ import { ListProperty } from "../observable/property/list/ListProperty";
|
|||||||
import { Disposer } from "../observable/Disposer";
|
import { Disposer } from "../observable/Disposer";
|
||||||
import "./Table.css";
|
import "./Table.css";
|
||||||
import { Disposable } from "../observable/Disposable";
|
import { Disposable } from "../observable/Disposable";
|
||||||
import Logger = require("js-logger");
|
import { LogManager } from "../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/gui/Table");
|
const logger = LogManager.get("core/gui/Table");
|
||||||
|
|
||||||
export type Column<T> = {
|
export type Column<T> = {
|
||||||
key?: string;
|
key?: string;
|
||||||
|
@ -5,9 +5,9 @@ import { bind_hidden } from "./dom";
|
|||||||
import { WritableProperty } from "../observable/property/WritableProperty";
|
import { WritableProperty } from "../observable/property/WritableProperty";
|
||||||
import { WidgetProperty } from "../observable/property/WidgetProperty";
|
import { WidgetProperty } from "../observable/property/WidgetProperty";
|
||||||
import { Property } from "../observable/property/Property";
|
import { Property } from "../observable/property/Property";
|
||||||
import Logger from "js-logger";
|
import { LogManager } from "../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/gui/Widget");
|
const logger = LogManager.get("core/gui/Widget");
|
||||||
|
|
||||||
export type WidgetOptions = {
|
export type WidgetOptions = {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -234,17 +234,60 @@ export function section_id_icon(section_id: SectionId, options?: { size?: number
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function disposable_listener<K extends keyof GlobalEventHandlersEventMap>(
|
||||||
|
target: GlobalEventHandlers,
|
||||||
|
type: K,
|
||||||
|
listener: (this: GlobalEventHandlers, ev: GlobalEventHandlersEventMap[K]) => any,
|
||||||
|
options?: AddEventListenerOptions,
|
||||||
|
): Disposable;
|
||||||
|
export function disposable_listener<K extends keyof WindowEventHandlersEventMap>(
|
||||||
|
target: WindowEventHandlers,
|
||||||
|
type: K,
|
||||||
|
listener: (this: WindowEventHandlers, ev: WindowEventHandlersEventMap[K]) => any,
|
||||||
|
options?: AddEventListenerOptions,
|
||||||
|
): Disposable;
|
||||||
|
export function disposable_listener<K extends keyof DocumentAndElementEventHandlersEventMap>(
|
||||||
|
target: DocumentAndElementEventHandlers,
|
||||||
|
type: K,
|
||||||
|
listener: (
|
||||||
|
this: DocumentAndElementEventHandlers,
|
||||||
|
ev: DocumentAndElementEventHandlersEventMap[K],
|
||||||
|
) => any,
|
||||||
|
options?: AddEventListenerOptions,
|
||||||
|
): Disposable;
|
||||||
export function disposable_listener(
|
export function disposable_listener(
|
||||||
element: EventTarget,
|
target:
|
||||||
event: string,
|
| GlobalEventHandlers
|
||||||
|
| DocumentAndElementEventHandlers
|
||||||
|
| WindowEventHandlers
|
||||||
|
| EventTarget,
|
||||||
|
type: string,
|
||||||
listener: EventListenerOrEventListenerObject,
|
listener: EventListenerOrEventListenerObject,
|
||||||
options?: AddEventListenerOptions,
|
options?: AddEventListenerOptions,
|
||||||
): Disposable {
|
): Disposable {
|
||||||
element.addEventListener(event, listener, options);
|
target.addEventListener(type, listener, options);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
element.removeEventListener(event, listener);
|
target.removeEventListener(type, listener);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* More lax definition of {@link disposable_listener} for custom and experimental event types.
|
||||||
|
*/
|
||||||
|
export function disposable_custom_listener(
|
||||||
|
target: EventTarget,
|
||||||
|
type: string,
|
||||||
|
listener: EventListenerOrEventListenerObject,
|
||||||
|
options?: AddEventListenerOptions,
|
||||||
|
): Disposable {
|
||||||
|
target.addEventListener(type, listener, options);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dispose(): void {
|
||||||
|
target.removeEventListener(type, listener);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Disposable } from "./Disposable";
|
import { Disposable } from "./Disposable";
|
||||||
import Logger = require("js-logger");
|
import { LogManager } from "../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/observable/Disposer");
|
const logger = LogManager.get("core/observable/Disposer");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container for disposables.
|
* Container for disposables.
|
||||||
@ -29,7 +29,9 @@ export class Disposer implements Disposable {
|
|||||||
* Add a single disposable and return the given disposable.
|
* Add a single disposable and return the given disposable.
|
||||||
*/
|
*/
|
||||||
add<T extends Disposable>(disposable: T): T {
|
add<T extends Disposable>(disposable: T): T {
|
||||||
if (!this._disposed) {
|
if (this.disposed) {
|
||||||
|
disposable.dispose();
|
||||||
|
} else {
|
||||||
this.disposables.push(disposable);
|
this.disposables.push(disposable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Disposable } from "./Disposable";
|
import { Disposable } from "./Disposable";
|
||||||
import Logger from "js-logger";
|
|
||||||
import { Emitter } from "./Emitter";
|
import { Emitter } from "./Emitter";
|
||||||
import { ChangeEvent } from "./Observable";
|
import { ChangeEvent } from "./Observable";
|
||||||
|
import { LogManager } from "../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/observable/SimpleEmitter");
|
const logger = LogManager.get("core/observable/SimpleEmitter");
|
||||||
|
|
||||||
export class SimpleEmitter<T> implements Emitter<T> {
|
export class SimpleEmitter<T> implements Emitter<T> {
|
||||||
protected readonly observers: ((event: ChangeEvent<T>) => void)[] = [];
|
protected readonly observers: ((event: ChangeEvent<T>) => void)[] = [];
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Disposable } from "../Disposable";
|
import { Disposable } from "../Disposable";
|
||||||
import Logger from "js-logger";
|
|
||||||
import { Property, PropertyChangeEvent } from "./Property";
|
import { Property, PropertyChangeEvent } from "./Property";
|
||||||
|
import { LogManager } from "../../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/observable/property/AbstractMinimalProperty");
|
const logger = LogManager.get("core/observable/property/AbstractMinimalProperty");
|
||||||
|
|
||||||
// This class exists purely because otherwise the resulting cyclic dependency graph would trip up commonjs.
|
// This class exists purely because otherwise the resulting cyclic dependency graph would trip up commonjs.
|
||||||
// The dependency graph is still cyclic but for some reason it's not a problem this way.
|
// The dependency graph is still cyclic but for some reason it's not a problem this way.
|
||||||
|
@ -3,9 +3,9 @@ import { AbstractProperty } from "../AbstractProperty";
|
|||||||
import { Disposable } from "../../Disposable";
|
import { Disposable } from "../../Disposable";
|
||||||
import { Observable } from "../../Observable";
|
import { Observable } from "../../Observable";
|
||||||
import { Property } from "../Property";
|
import { Property } from "../Property";
|
||||||
import Logger from "js-logger";
|
import { LogManager } from "../../../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/observable/property/list/AbstractListProperty");
|
const logger = LogManager.get("core/observable/property/list/AbstractListProperty");
|
||||||
|
|
||||||
class LengthProperty extends AbstractProperty<number> {
|
class LengthProperty extends AbstractProperty<number> {
|
||||||
private length = 0;
|
private length = 0;
|
||||||
|
27
src/core/stores/DisposableServerMap.ts
Normal file
27
src/core/stores/DisposableServerMap.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Server } from "../model";
|
||||||
|
import { GuiStore } from "./GuiStore";
|
||||||
|
import { ServerMap } from "./ServerMap";
|
||||||
|
import { Disposable } from "../observable/Disposable";
|
||||||
|
import { Disposer } from "../observable/Disposer";
|
||||||
|
|
||||||
|
export class DisposableServerMap<T extends Disposable> extends ServerMap<T> implements Disposable {
|
||||||
|
private readonly disposer = new Disposer();
|
||||||
|
|
||||||
|
constructor(gui_store: GuiStore, get_value: (server: Server) => Promise<T>) {
|
||||||
|
super(gui_store, async server => {
|
||||||
|
const value = await get_value(server);
|
||||||
|
|
||||||
|
if (this.disposer.disposed) {
|
||||||
|
value.dispose();
|
||||||
|
} else {
|
||||||
|
this.disposer.add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.disposer.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@ import { Disposable } from "../observable/Disposable";
|
|||||||
import { property } from "../observable";
|
import { property } from "../observable";
|
||||||
import { Property } from "../observable/property/Property";
|
import { Property } from "../observable/property/Property";
|
||||||
import { Server } from "../model";
|
import { Server } from "../model";
|
||||||
|
import { Store } from "./Store";
|
||||||
|
import { disposable_listener } from "../gui/dom";
|
||||||
|
|
||||||
export enum GuiTool {
|
export enum GuiTool {
|
||||||
Viewer,
|
Viewer,
|
||||||
@ -17,24 +19,17 @@ const GUI_TOOL_TO_STRING = new Map([
|
|||||||
]);
|
]);
|
||||||
const STRING_TO_GUI_TOOL = new Map([...GUI_TOOL_TO_STRING.entries()].map(([k, v]) => [v, k]));
|
const STRING_TO_GUI_TOOL = new Map([...GUI_TOOL_TO_STRING.entries()].map(([k, v]) => [v, k]));
|
||||||
|
|
||||||
export class GuiStore implements Disposable {
|
export class GuiStore extends Store {
|
||||||
readonly tool: WritableProperty<GuiTool> = property(GuiTool.Viewer);
|
|
||||||
readonly server: Property<Server>;
|
|
||||||
|
|
||||||
private readonly _server: WritableProperty<Server> = property(Server.Ephinea);
|
private readonly _server: WritableProperty<Server> = property(Server.Ephinea);
|
||||||
private readonly hash_disposer = this.tool.observe(({ value: tool }) => {
|
|
||||||
let hash = `#/${gui_tool_to_string(tool)}`;
|
|
||||||
|
|
||||||
if (this.features.size) {
|
|
||||||
hash += "?features=" + [...this.features].join(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.hash = hash;
|
|
||||||
});
|
|
||||||
private readonly global_keydown_handlers = new Map<string, (e: KeyboardEvent) => void>();
|
private readonly global_keydown_handlers = new Map<string, (e: KeyboardEvent) => void>();
|
||||||
private readonly features: Set<string> = new Set();
|
private readonly features: Set<string> = new Set();
|
||||||
|
|
||||||
|
readonly tool: WritableProperty<GuiTool> = property(GuiTool.Viewer);
|
||||||
|
readonly server: Property<Server> = this._server;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
const url = window.location.hash.slice(2);
|
const url = window.location.hash.slice(2);
|
||||||
const [tool_str, params_str] = url.split("?");
|
const [tool_str, params_str] = url.split("?");
|
||||||
|
|
||||||
@ -51,18 +46,21 @@ export class GuiStore implements Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.disposables(
|
||||||
|
this.tool.observe(({ value: tool }) => {
|
||||||
|
let hash = `#/${gui_tool_to_string(tool)}`;
|
||||||
|
|
||||||
|
if (this.features.size) {
|
||||||
|
hash += "?features=" + [...this.features].join(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.hash = hash;
|
||||||
|
}),
|
||||||
|
|
||||||
|
disposable_listener(window, "keydown", this.dispatch_global_keydown),
|
||||||
|
);
|
||||||
|
|
||||||
this.tool.val = string_to_gui_tool(tool_str) || GuiTool.Viewer;
|
this.tool.val = string_to_gui_tool(tool_str) || GuiTool.Viewer;
|
||||||
|
|
||||||
this.server = this._server;
|
|
||||||
|
|
||||||
window.addEventListener("keydown", this.dispatch_global_keydown);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose(): void {
|
|
||||||
this.hash_disposer.dispose();
|
|
||||||
this.global_keydown_handlers.clear();
|
|
||||||
|
|
||||||
window.removeEventListener("keydown", this.dispatch_global_keydown);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
on_global_keydown(
|
on_global_keydown(
|
||||||
|
@ -6,23 +6,25 @@ import {
|
|||||||
UnitItemType,
|
UnitItemType,
|
||||||
WeaponItemType,
|
WeaponItemType,
|
||||||
} from "../model/items";
|
} from "../model/items";
|
||||||
import { ServerMap } from "./ServerMap";
|
|
||||||
import { Server } from "../model";
|
import { Server } from "../model";
|
||||||
import { ItemTypeDto } from "../dto/ItemTypeDto";
|
import { ItemTypeDto } from "../dto/ItemTypeDto";
|
||||||
import { GuiStore } from "./GuiStore";
|
import { GuiStore } from "./GuiStore";
|
||||||
import { HttpClient } from "../HttpClient";
|
import { HttpClient } from "../HttpClient";
|
||||||
|
import { DisposableServerMap } from "./DisposableServerMap";
|
||||||
|
import { Store } from "./Store";
|
||||||
|
|
||||||
export function create_item_type_stores(
|
export function create_item_type_stores(
|
||||||
http_client: HttpClient,
|
http_client: HttpClient,
|
||||||
gui_store: GuiStore,
|
gui_store: GuiStore,
|
||||||
): ServerMap<ItemTypeStore> {
|
): DisposableServerMap<ItemTypeStore> {
|
||||||
return new ServerMap(gui_store, create_loader(http_client));
|
return new DisposableServerMap(gui_store, create_loader(http_client));
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ItemTypeStore {
|
export class ItemTypeStore extends Store {
|
||||||
readonly item_types: ItemType[];
|
readonly item_types: ItemType[];
|
||||||
|
|
||||||
constructor(item_types: ItemType[], private readonly id_to_item_type: ItemType[]) {
|
constructor(item_types: ItemType[], private readonly id_to_item_type: ItemType[]) {
|
||||||
|
super();
|
||||||
this.item_types = item_types;
|
this.item_types = item_types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
18
src/core/stores/Store.ts
Normal file
18
src/core/stores/Store.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Disposable } from "../observable/Disposable";
|
||||||
|
import { Disposer } from "../observable/Disposer";
|
||||||
|
|
||||||
|
export abstract class Store implements Disposable {
|
||||||
|
private readonly disposer = new Disposer();
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.disposer.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected disposable<T extends Disposable>(disposable: T): T {
|
||||||
|
return this.disposer.add(disposable);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected disposables(...disposables: Disposable[]): void {
|
||||||
|
this.disposer.add_all(...disposables);
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,9 @@ import { WritableListProperty } from "../observable/property/list/WritableListPr
|
|||||||
import { Action } from "./Action";
|
import { Action } from "./Action";
|
||||||
import { list_property, map, property } from "../observable";
|
import { list_property, map, property } from "../observable";
|
||||||
import { undo_manager } from "./UndoManager";
|
import { undo_manager } from "./UndoManager";
|
||||||
import Logger = require("js-logger");
|
import { LogManager } from "../Logger";
|
||||||
|
|
||||||
const logger = Logger.get("core/undo/UndoStack");
|
const logger = LogManager.get("core/undo/UndoStack");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Full-fledged linear undo/redo implementation.
|
* Full-fledged linear undo/redo implementation.
|
||||||
|
@ -15,9 +15,9 @@ import { SortDirection, Table } from "../../core/gui/Table";
|
|||||||
import { list_property } from "../../core/observable";
|
import { list_property } from "../../core/observable";
|
||||||
import { ServerMap } from "../../core/stores/ServerMap";
|
import { ServerMap } from "../../core/stores/ServerMap";
|
||||||
import { HuntMethodStore } from "../stores/HuntMethodStore";
|
import { HuntMethodStore } from "../stores/HuntMethodStore";
|
||||||
import Logger from "js-logger";
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("hunt_optimizer/gui/MethodsForEpisodeView");
|
const logger = LogManager.get("hunt_optimizer/gui/MethodsForEpisodeView");
|
||||||
|
|
||||||
export class MethodsForEpisodeView extends ResizableWidget {
|
export class MethodsForEpisodeView extends ResizableWidget {
|
||||||
readonly element = el.div({ class: "hunt_optimizer_MethodsForEpisodeView" });
|
readonly element = el.div({ class: "hunt_optimizer_MethodsForEpisodeView" });
|
||||||
|
@ -10,9 +10,9 @@ import "./OptimizationResultView.css";
|
|||||||
import { Duration } from "luxon";
|
import { Duration } from "luxon";
|
||||||
import { ServerMap } from "../../core/stores/ServerMap";
|
import { ServerMap } from "../../core/stores/ServerMap";
|
||||||
import { HuntOptimizerStore } from "../stores/HuntOptimizerStore";
|
import { HuntOptimizerStore } from "../stores/HuntOptimizerStore";
|
||||||
import Logger from "js-logger";
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("hunt_optimizer/gui/OptimizationResultView");
|
const logger = LogManager.get("hunt_optimizer/gui/OptimizationResultView");
|
||||||
|
|
||||||
export class OptimizationResultView extends Widget {
|
export class OptimizationResultView extends Widget {
|
||||||
readonly element = el.div(
|
readonly element = el.div(
|
||||||
@ -31,6 +31,7 @@ export class OptimizationResultView extends Widget {
|
|||||||
async ({ value }) => {
|
async ({ value }) => {
|
||||||
try {
|
try {
|
||||||
const hunt_optimizer_store = await value;
|
const hunt_optimizer_store = await value;
|
||||||
|
if (this.disposed) return;
|
||||||
|
|
||||||
if (this.results_observer) {
|
if (this.results_observer) {
|
||||||
this.results_observer.dispose();
|
this.results_observer.dispose();
|
||||||
|
@ -11,9 +11,9 @@ import { ItemType } from "../../core/model/items";
|
|||||||
import { Disposable } from "../../core/observable/Disposable";
|
import { Disposable } from "../../core/observable/Disposable";
|
||||||
import { ServerMap } from "../../core/stores/ServerMap";
|
import { ServerMap } from "../../core/stores/ServerMap";
|
||||||
import { HuntOptimizerStore } from "../stores/HuntOptimizerStore";
|
import { HuntOptimizerStore } from "../stores/HuntOptimizerStore";
|
||||||
import Logger from "js-logger";
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("hunt_optimizer/gui/WantedItemsView");
|
const logger = LogManager.get("hunt_optimizer/gui/WantedItemsView");
|
||||||
|
|
||||||
export class WantedItemsView extends Widget {
|
export class WantedItemsView extends Widget {
|
||||||
readonly element = el.div({ class: "hunt_optimizer_WantedItemsView" });
|
readonly element = el.div({ class: "hunt_optimizer_WantedItemsView" });
|
||||||
|
@ -1,32 +1,43 @@
|
|||||||
import { HuntOptimizerView } from "./gui/HuntOptimizerView";
|
import { HuntOptimizerView } from "./gui/HuntOptimizerView";
|
||||||
import { ServerMap } from "../core/stores/ServerMap";
|
import { ServerMap } from "../core/stores/ServerMap";
|
||||||
import { HuntMethodStore, create_hunt_method_stores } from "./stores/HuntMethodStore";
|
import { create_hunt_method_stores, HuntMethodStore } from "./stores/HuntMethodStore";
|
||||||
import { GuiStore } from "../core/stores/GuiStore";
|
import { GuiStore } from "../core/stores/GuiStore";
|
||||||
import { HuntOptimizerStore, create_hunt_optimizer_stores } from "./stores/HuntOptimizerStore";
|
import { create_hunt_optimizer_stores, HuntOptimizerStore } from "./stores/HuntOptimizerStore";
|
||||||
import { ItemTypeStore } from "../core/stores/ItemTypeStore";
|
import { ItemTypeStore } from "../core/stores/ItemTypeStore";
|
||||||
import { HuntMethodPersister } from "./persistence/HuntMethodPersister";
|
import { HuntMethodPersister } from "./persistence/HuntMethodPersister";
|
||||||
import { HuntOptimizerPersister } from "./persistence/HuntOptimizerPersister";
|
import { HuntOptimizerPersister } from "./persistence/HuntOptimizerPersister";
|
||||||
import { ItemDropStore } from "./stores/ItemDropStore";
|
import { ItemDropStore } from "./stores/ItemDropStore";
|
||||||
import { HttpClient } from "../core/HttpClient";
|
import { HttpClient } from "../core/HttpClient";
|
||||||
|
import { Disposable } from "../core/observable/Disposable";
|
||||||
|
import { Disposer } from "../core/observable/Disposer";
|
||||||
|
|
||||||
export function initialize_hunt_optimizer(
|
export function initialize_hunt_optimizer(
|
||||||
http_client: HttpClient,
|
http_client: HttpClient,
|
||||||
gui_store: GuiStore,
|
gui_store: GuiStore,
|
||||||
item_type_stores: ServerMap<ItemTypeStore>,
|
item_type_stores: ServerMap<ItemTypeStore>,
|
||||||
item_drop_stores: ServerMap<ItemDropStore>,
|
item_drop_stores: ServerMap<ItemDropStore>,
|
||||||
): HuntOptimizerView {
|
): { view: HuntOptimizerView } & Disposable {
|
||||||
const hunt_method_stores: ServerMap<HuntMethodStore> = create_hunt_method_stores(
|
const disposer = new Disposer();
|
||||||
http_client,
|
|
||||||
gui_store,
|
const hunt_method_stores: ServerMap<HuntMethodStore> = disposer.add(
|
||||||
new HuntMethodPersister(),
|
create_hunt_method_stores(http_client, gui_store, new HuntMethodPersister()),
|
||||||
);
|
);
|
||||||
const hunt_optimizer_stores: ServerMap<HuntOptimizerStore> = create_hunt_optimizer_stores(
|
const hunt_optimizer_stores: ServerMap<HuntOptimizerStore> = disposer.add(
|
||||||
gui_store,
|
create_hunt_optimizer_stores(
|
||||||
new HuntOptimizerPersister(item_type_stores),
|
gui_store,
|
||||||
item_type_stores,
|
new HuntOptimizerPersister(item_type_stores),
|
||||||
item_drop_stores,
|
item_type_stores,
|
||||||
hunt_method_stores,
|
item_drop_stores,
|
||||||
|
hunt_method_stores,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return new HuntOptimizerView(hunt_optimizer_stores, hunt_method_stores);
|
const view = disposer.add(new HuntOptimizerView(hunt_optimizer_stores, hunt_method_stores));
|
||||||
|
|
||||||
|
return {
|
||||||
|
view,
|
||||||
|
dispose(): void {
|
||||||
|
disposer.dispose();
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Persister } from "../../core/Persister";
|
|
||||||
import { Server } from "../../core/model";
|
import { Server } from "../../core/model";
|
||||||
import { HuntMethodModel } from "../model/HuntMethodModel";
|
import { HuntMethodModel } from "../model/HuntMethodModel";
|
||||||
import { Duration } from "luxon";
|
import { Duration } from "luxon";
|
||||||
|
import { Persister } from "../../core/Persister";
|
||||||
|
|
||||||
const METHOD_USER_TIMES_KEY = "HuntMethodStore.methodUserTimes";
|
const METHOD_USER_TIMES_KEY = "HuntMethodStore.methodUserTimes";
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { Server } from "../../core/model";
|
import { Server } from "../../core/model";
|
||||||
import { QuestDto } from "../dto/QuestDto";
|
import { QuestDto } from "../dto/QuestDto";
|
||||||
import { NpcType } from "../../core/data_formats/parsing/quest/npc_types";
|
import { NpcType } from "../../core/data_formats/parsing/quest/npc_types";
|
||||||
@ -8,13 +7,13 @@ import { HuntMethodPersister } from "../persistence/HuntMethodPersister";
|
|||||||
import { Duration } from "luxon";
|
import { Duration } from "luxon";
|
||||||
import { ListProperty } from "../../core/observable/property/list/ListProperty";
|
import { ListProperty } from "../../core/observable/property/list/ListProperty";
|
||||||
import { list_property } from "../../core/observable";
|
import { list_property } from "../../core/observable";
|
||||||
import { Disposable } from "../../core/observable/Disposable";
|
|
||||||
import { Disposer } from "../../core/observable/Disposer";
|
|
||||||
import { GuiStore } from "../../core/stores/GuiStore";
|
import { GuiStore } from "../../core/stores/GuiStore";
|
||||||
import { ServerMap } from "../../core/stores/ServerMap";
|
|
||||||
import { HttpClient } from "../../core/HttpClient";
|
import { HttpClient } from "../../core/HttpClient";
|
||||||
|
import { Store } from "../../core/stores/Store";
|
||||||
|
import { DisposableServerMap } from "../../core/stores/DisposableServerMap";
|
||||||
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("hunt_optimizer/stores/HuntMethodStore");
|
const logger = LogManager.get("hunt_optimizer/stores/HuntMethodStore");
|
||||||
|
|
||||||
const DEFAULT_DURATION = Duration.fromObject({ minutes: 30 });
|
const DEFAULT_DURATION = Duration.fromObject({ minutes: 30 });
|
||||||
const DEFAULT_GOVERNMENT_TEST_DURATION = Duration.fromObject({ minutes: 45 });
|
const DEFAULT_GOVERNMENT_TEST_DURATION = Duration.fromObject({ minutes: 45 });
|
||||||
@ -24,32 +23,28 @@ export function create_hunt_method_stores(
|
|||||||
http_client: HttpClient,
|
http_client: HttpClient,
|
||||||
gui_store: GuiStore,
|
gui_store: GuiStore,
|
||||||
hunt_method_persister: HuntMethodPersister,
|
hunt_method_persister: HuntMethodPersister,
|
||||||
): ServerMap<HuntMethodStore> {
|
): DisposableServerMap<HuntMethodStore> {
|
||||||
return new ServerMap(gui_store, create_loader(http_client, hunt_method_persister));
|
return new DisposableServerMap(gui_store, create_loader(http_client, hunt_method_persister));
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HuntMethodStore implements Disposable {
|
export class HuntMethodStore extends Store {
|
||||||
readonly methods: ListProperty<HuntMethodModel>;
|
readonly methods: ListProperty<HuntMethodModel>;
|
||||||
|
|
||||||
private readonly disposer = new Disposer();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
hunt_method_persister: HuntMethodPersister,
|
hunt_method_persister: HuntMethodPersister,
|
||||||
server: Server,
|
server: Server,
|
||||||
methods: HuntMethodModel[],
|
methods: HuntMethodModel[],
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.methods = list_property(method => [method.user_time], ...methods);
|
this.methods = list_property(method => [method.user_time], ...methods);
|
||||||
|
|
||||||
this.disposer.add(
|
this.disposables(
|
||||||
this.methods.observe_list(() =>
|
this.methods.observe_list(() =>
|
||||||
hunt_method_persister.persist_method_user_times(this.methods.val, server),
|
hunt_method_persister.persist_method_user_times(this.methods.val, server),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
|
||||||
this.disposer.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_loader(
|
function create_loader(
|
||||||
|
@ -19,11 +19,11 @@ import { WritableListProperty } from "../../core/observable/property/list/Writab
|
|||||||
import { HuntMethodStore } from "./HuntMethodStore";
|
import { HuntMethodStore } from "./HuntMethodStore";
|
||||||
import { ItemDropStore } from "./ItemDropStore";
|
import { ItemDropStore } from "./ItemDropStore";
|
||||||
import { ItemTypeStore } from "../../core/stores/ItemTypeStore";
|
import { ItemTypeStore } from "../../core/stores/ItemTypeStore";
|
||||||
import { Disposable } from "../../core/observable/Disposable";
|
|
||||||
import { Disposer } from "../../core/observable/Disposer";
|
|
||||||
import { ServerMap } from "../../core/stores/ServerMap";
|
import { ServerMap } from "../../core/stores/ServerMap";
|
||||||
import { GuiStore } from "../../core/stores/GuiStore";
|
import { GuiStore } from "../../core/stores/GuiStore";
|
||||||
import { HuntOptimizerPersister } from "../persistence/HuntOptimizerPersister";
|
import { HuntOptimizerPersister } from "../persistence/HuntOptimizerPersister";
|
||||||
|
import { DisposableServerMap } from "../../core/stores/DisposableServerMap";
|
||||||
|
import { Store } from "../../core/stores/Store";
|
||||||
|
|
||||||
export function create_hunt_optimizer_stores(
|
export function create_hunt_optimizer_stores(
|
||||||
gui_store: GuiStore,
|
gui_store: GuiStore,
|
||||||
@ -31,8 +31,8 @@ export function create_hunt_optimizer_stores(
|
|||||||
item_type_stores: ServerMap<ItemTypeStore>,
|
item_type_stores: ServerMap<ItemTypeStore>,
|
||||||
item_drop_stores: ServerMap<ItemDropStore>,
|
item_drop_stores: ServerMap<ItemDropStore>,
|
||||||
hunt_method_stores: ServerMap<HuntMethodStore>,
|
hunt_method_stores: ServerMap<HuntMethodStore>,
|
||||||
): ServerMap<HuntOptimizerStore> {
|
): DisposableServerMap<HuntOptimizerStore> {
|
||||||
return new ServerMap(
|
return new DisposableServerMap(
|
||||||
gui_store,
|
gui_store,
|
||||||
create_loader(
|
create_loader(
|
||||||
hunt_optimizer_persister,
|
hunt_optimizer_persister,
|
||||||
@ -50,16 +50,15 @@ export function create_hunt_optimizer_stores(
|
|||||||
// TODO: Show expected value or probability per item per method.
|
// TODO: Show expected value or probability per item per method.
|
||||||
// Can be useful when deciding which item to hunt first.
|
// Can be useful when deciding which item to hunt first.
|
||||||
// TODO: boxes.
|
// TODO: boxes.
|
||||||
export class HuntOptimizerStore implements Disposable {
|
export class HuntOptimizerStore extends Store {
|
||||||
readonly huntable_item_types: ItemType[];
|
|
||||||
// TODO: wanted items per server.
|
|
||||||
readonly wanted_items: ListProperty<WantedItemModel>;
|
|
||||||
readonly result: Property<OptimalResultModel | undefined>;
|
|
||||||
|
|
||||||
private readonly _wanted_items: WritableListProperty<
|
private readonly _wanted_items: WritableListProperty<
|
||||||
WantedItemModel
|
WantedItemModel
|
||||||
> = list_property(wanted_item => [wanted_item.amount]);
|
> = list_property(wanted_item => [wanted_item.amount]);
|
||||||
private readonly disposer = new Disposer();
|
|
||||||
|
readonly huntable_item_types: ItemType[];
|
||||||
|
// TODO: wanted items per server.
|
||||||
|
readonly wanted_items: ListProperty<WantedItemModel> = this._wanted_items;
|
||||||
|
readonly result: Property<OptimalResultModel | undefined>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly hunt_optimizer_persister: HuntOptimizerPersister,
|
private readonly hunt_optimizer_persister: HuntOptimizerPersister,
|
||||||
@ -68,21 +67,17 @@ export class HuntOptimizerStore implements Disposable {
|
|||||||
private readonly item_drop_store: ItemDropStore,
|
private readonly item_drop_store: ItemDropStore,
|
||||||
hunt_method_store: HuntMethodStore,
|
hunt_method_store: HuntMethodStore,
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.huntable_item_types = item_type_store.item_types.filter(
|
this.huntable_item_types = item_type_store.item_types.filter(
|
||||||
item_type => item_drop_store.enemy_drops.get_drops_for_item_type(item_type.id).length,
|
item_type => item_drop_store.enemy_drops.get_drops_for_item_type(item_type.id).length,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.wanted_items = this._wanted_items;
|
|
||||||
|
|
||||||
this.result = map(this.optimize, this.wanted_items, hunt_method_store.methods);
|
this.result = map(this.optimize, this.wanted_items, hunt_method_store.methods);
|
||||||
|
|
||||||
this.initialize_persistence();
|
this.initialize_persistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
|
||||||
this.disposer.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
add_wanted_item(item_type: ItemType): void {
|
add_wanted_item(item_type: ItemType): void {
|
||||||
if (!this._wanted_items.val.find(wanted => wanted.item_type === item_type)) {
|
if (!this._wanted_items.val.find(wanted => wanted.item_type === item_type)) {
|
||||||
this._wanted_items.push(new WantedItemModel(item_type, 1));
|
this._wanted_items.push(new WantedItemModel(item_type, 1));
|
||||||
@ -336,7 +331,7 @@ export class HuntOptimizerStore implements Disposable {
|
|||||||
private initialize_persistence = async (): Promise<void> => {
|
private initialize_persistence = async (): Promise<void> => {
|
||||||
this._wanted_items.val = await this.hunt_optimizer_persister.load_wanted_items(this.server);
|
this._wanted_items.val = await this.hunt_optimizer_persister.load_wanted_items(this.server);
|
||||||
|
|
||||||
this.disposer.add(
|
this.disposable(
|
||||||
this._wanted_items.observe(({ value }) => {
|
this._wanted_items.observe(({ value }) => {
|
||||||
this.hunt_optimizer_persister.persist_wanted_items(this.server, value);
|
this.hunt_optimizer_persister.persist_wanted_items(this.server, value);
|
||||||
}),
|
}),
|
||||||
|
@ -1,27 +1,30 @@
|
|||||||
import { Difficulties, Difficulty, SectionId, SectionIds, Server } from "../../core/model";
|
import { Difficulties, Difficulty, SectionId, SectionIds, Server } from "../../core/model";
|
||||||
import { ServerMap } from "../../core/stores/ServerMap";
|
import { ServerMap } from "../../core/stores/ServerMap";
|
||||||
import Logger from "js-logger";
|
|
||||||
import { NpcType } from "../../core/data_formats/parsing/quest/npc_types";
|
import { NpcType } from "../../core/data_formats/parsing/quest/npc_types";
|
||||||
import { EnemyDrop } from "../model/ItemDrop";
|
import { EnemyDrop } from "../model/ItemDrop";
|
||||||
import { EnemyDropDto } from "../dto/drops";
|
import { EnemyDropDto } from "../dto/drops";
|
||||||
import { GuiStore } from "../../core/stores/GuiStore";
|
import { GuiStore } from "../../core/stores/GuiStore";
|
||||||
import { ItemTypeStore } from "../../core/stores/ItemTypeStore";
|
import { ItemTypeStore } from "../../core/stores/ItemTypeStore";
|
||||||
import { HttpClient } from "../../core/HttpClient";
|
import { HttpClient } from "../../core/HttpClient";
|
||||||
|
import { DisposableServerMap } from "../../core/stores/DisposableServerMap";
|
||||||
|
import { Store } from "../../core/stores/Store";
|
||||||
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("stores/ItemDropStore");
|
const logger = LogManager.get("stores/ItemDropStore");
|
||||||
|
|
||||||
export function create_item_drop_stores(
|
export function create_item_drop_stores(
|
||||||
http_client: HttpClient,
|
http_client: HttpClient,
|
||||||
gui_store: GuiStore,
|
gui_store: GuiStore,
|
||||||
item_type_stores: ServerMap<ItemTypeStore>,
|
item_type_stores: ServerMap<ItemTypeStore>,
|
||||||
): ServerMap<ItemDropStore> {
|
): DisposableServerMap<ItemDropStore> {
|
||||||
return new ServerMap(gui_store, create_loader(http_client, item_type_stores));
|
return new DisposableServerMap(gui_store, create_loader(http_client, item_type_stores));
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ItemDropStore {
|
export class ItemDropStore extends Store {
|
||||||
readonly enemy_drops: EnemyDropTable;
|
readonly enemy_drops: EnemyDropTable;
|
||||||
|
|
||||||
constructor(enemy_drops: EnemyDropTable) {
|
constructor(enemy_drops: EnemyDropTable) {
|
||||||
|
super();
|
||||||
this.enemy_drops = enemy_drops;
|
this.enemy_drops = enemy_drops;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,17 @@
|
|||||||
import "./core/gui/index.css";
|
import "./core/gui/index.css";
|
||||||
import Logger from "js-logger";
|
|
||||||
import "@fortawesome/fontawesome-free/js/fontawesome";
|
import "@fortawesome/fontawesome-free/js/fontawesome";
|
||||||
import "@fortawesome/fontawesome-free/js/solid";
|
import "@fortawesome/fontawesome-free/js/solid";
|
||||||
import "@fortawesome/fontawesome-free/js/regular";
|
import "@fortawesome/fontawesome-free/js/regular";
|
||||||
import "@fortawesome/fontawesome-free/js/brands";
|
import "@fortawesome/fontawesome-free/js/brands";
|
||||||
import { initialize } from "./initialize";
|
import { initialize_application } from "./application";
|
||||||
import { FetchClient } from "./core/HttpClient";
|
import { FetchClient } from "./core/HttpClient";
|
||||||
import { WebGLRenderer } from "three";
|
import { WebGLRenderer } from "three";
|
||||||
import { DisposableThreeRenderer } from "./core/rendering/Renderer";
|
import { DisposableThreeRenderer } from "./core/rendering/Renderer";
|
||||||
|
|
||||||
Logger.useDefaults({
|
|
||||||
defaultLevel: (Logger as any)[process.env["LOG_LEVEL"] ?? "INFO"],
|
|
||||||
});
|
|
||||||
|
|
||||||
function create_three_renderer(): DisposableThreeRenderer {
|
function create_three_renderer(): DisposableThreeRenderer {
|
||||||
const renderer = new WebGLRenderer({ antialias: true, alpha: true });
|
const renderer = new WebGLRenderer({ antialias: true, alpha: true });
|
||||||
renderer.setPixelRatio(window.devicePixelRatio);
|
renderer.setPixelRatio(window.devicePixelRatio);
|
||||||
return renderer;
|
return renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize(new FetchClient(), create_three_renderer);
|
initialize_application(new FetchClient(), create_three_renderer);
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* @jest-environment jsdom
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Logger from "js-logger";
|
|
||||||
import { IContext } from "js-logger/src/types";
|
|
||||||
import { initialize } from "./initialize";
|
|
||||||
import { StubHttpClient } from "./core/HttpClient";
|
|
||||||
import { DisposableThreeRenderer } from "./core/rendering/Renderer";
|
|
||||||
|
|
||||||
for (const path of [undefined, "/viewer", "/quest_editor", "/hunt_optimizer"]) {
|
|
||||||
const with_path = path == undefined ? "without specific path" : `with path ${path}`;
|
|
||||||
|
|
||||||
test(`Initialization and shutdown ${with_path} should succeed without throwing errors or logging with level WARN or above.`, () => {
|
|
||||||
const logged_errors: string[] = [];
|
|
||||||
|
|
||||||
Logger.setHandler((messages: any[], context: IContext) => {
|
|
||||||
if (context.level.value >= Logger.WARN.value) {
|
|
||||||
logged_errors.push(Array.prototype.join.call(messages, " "));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (path != undefined) {
|
|
||||||
window.location.hash = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
const app = initialize(new StubHttpClient(), () => new StubRenderer());
|
|
||||||
|
|
||||||
expect(app).toBeDefined();
|
|
||||||
expect(logged_errors).toEqual([]);
|
|
||||||
|
|
||||||
app.dispose();
|
|
||||||
|
|
||||||
expect(logged_errors).toEqual([]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class StubRenderer implements DisposableThreeRenderer {
|
|
||||||
domElement: HTMLCanvasElement = document.createElement("canvas");
|
|
||||||
|
|
||||||
dispose(): void {} // eslint-disable-line
|
|
||||||
|
|
||||||
render(): void {} // eslint-disable-line
|
|
||||||
|
|
||||||
setSize(): void {} // eslint-disable-line
|
|
||||||
}
|
|
@ -1,121 +0,0 @@
|
|||||||
import { HttpClient } from "./core/HttpClient";
|
|
||||||
import { Disposable } from "./core/observable/Disposable";
|
|
||||||
import { GuiStore, GuiTool } from "./core/stores/GuiStore";
|
|
||||||
import { create_item_type_stores } from "./core/stores/ItemTypeStore";
|
|
||||||
import { create_item_drop_stores } from "./hunt_optimizer/stores/ItemDropStore";
|
|
||||||
import { ApplicationView } from "./application/gui/ApplicationView";
|
|
||||||
import { throttle } from "lodash";
|
|
||||||
import { DisposableThreeRenderer } from "./core/rendering/Renderer";
|
|
||||||
|
|
||||||
export function initialize(
|
|
||||||
http_client: HttpClient,
|
|
||||||
create_three_renderer: () => DisposableThreeRenderer,
|
|
||||||
): Disposable {
|
|
||||||
// Disable native undo/redo.
|
|
||||||
document.addEventListener("beforeinput", before_input);
|
|
||||||
// Work-around for FireFox:
|
|
||||||
document.addEventListener("keydown", keydown);
|
|
||||||
|
|
||||||
// Disable native drag-and-drop.
|
|
||||||
document.addEventListener("dragenter", dragenter);
|
|
||||||
document.addEventListener("dragover", dragover);
|
|
||||||
document.addEventListener("drop", drop);
|
|
||||||
|
|
||||||
// Initialize core stores shared by several submodules.
|
|
||||||
const gui_store = new GuiStore();
|
|
||||||
const item_type_stores = create_item_type_stores(http_client, gui_store);
|
|
||||||
const item_drop_stores = create_item_drop_stores(http_client, gui_store, item_type_stores);
|
|
||||||
|
|
||||||
// Initialize application view.
|
|
||||||
const application_view = new ApplicationView(gui_store, [
|
|
||||||
[
|
|
||||||
GuiTool.Viewer,
|
|
||||||
async () => {
|
|
||||||
return (await import("./viewer/index")).initialize_viewer(
|
|
||||||
http_client,
|
|
||||||
gui_store,
|
|
||||||
create_three_renderer,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GuiTool.QuestEditor,
|
|
||||||
async () => {
|
|
||||||
return (await import("./quest_editor/index")).initialize_quest_editor(
|
|
||||||
http_client,
|
|
||||||
gui_store,
|
|
||||||
create_three_renderer,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
GuiTool.HuntOptimizer,
|
|
||||||
async () => {
|
|
||||||
return (await import("./hunt_optimizer/index")).initialize_hunt_optimizer(
|
|
||||||
http_client,
|
|
||||||
gui_store,
|
|
||||||
item_type_stores,
|
|
||||||
item_drop_stores,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Resize the view on window resize.
|
|
||||||
const resize = throttle(
|
|
||||||
() => {
|
|
||||||
application_view.resize(window.innerWidth, window.innerHeight);
|
|
||||||
},
|
|
||||||
100,
|
|
||||||
{ leading: true, trailing: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
resize();
|
|
||||||
document.body.append(application_view.element);
|
|
||||||
window.addEventListener("resize", resize);
|
|
||||||
|
|
||||||
// Dispose view and global event listeners when necessary.
|
|
||||||
return {
|
|
||||||
dispose(): void {
|
|
||||||
window.removeEventListener("beforeinput", before_input);
|
|
||||||
window.removeEventListener("keydown", keydown);
|
|
||||||
window.removeEventListener("resize", resize);
|
|
||||||
window.removeEventListener("dragenter", dragenter);
|
|
||||||
window.removeEventListener("dragover", dragover);
|
|
||||||
window.removeEventListener("drop", drop);
|
|
||||||
application_view.dispose();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function before_input(e: Event): void {
|
|
||||||
const ie = e as any;
|
|
||||||
|
|
||||||
if (ie.inputType === "historyUndo" || ie.inputType === "historyRedo") {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function keydown(e: Event): void {
|
|
||||||
const kbe = e as KeyboardEvent;
|
|
||||||
|
|
||||||
if (kbe.ctrlKey && !kbe.altKey && kbe.key.toUpperCase() === "Z") {
|
|
||||||
kbe.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragenter(e: DragEvent): void {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (e.dataTransfer) {
|
|
||||||
e.dataTransfer.dropEffect = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragover(e: DragEvent): void {
|
|
||||||
dragenter(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
function drop(e: DragEvent): void {
|
|
||||||
dragenter(e);
|
|
||||||
}
|
|
@ -54,7 +54,7 @@ export type GameState = Readonly<GameStateInternal>;
|
|||||||
* delegates to {@link Debugger}.
|
* delegates to {@link Debugger}.
|
||||||
*/
|
*/
|
||||||
export class QuestRunner {
|
export class QuestRunner {
|
||||||
private quest_logger = log_store.get_logger("quest_editor/QuestRunner");
|
private logger = log_store.get_logger("quest_editor/QuestRunner");
|
||||||
private animation_frame?: number;
|
private animation_frame?: number;
|
||||||
private startup = true;
|
private startup = true;
|
||||||
private readonly _state: WritableProperty<QuestRunnerState> = property(
|
private readonly _state: WritableProperty<QuestRunnerState> = property(
|
||||||
@ -102,7 +102,7 @@ export class QuestRunner {
|
|||||||
this.stop();
|
this.stop();
|
||||||
|
|
||||||
// Runner state.
|
// Runner state.
|
||||||
this.quest_logger.info("Starting debugger.");
|
this.logger.info("Starting debugger.");
|
||||||
this.startup = true;
|
this.startup = true;
|
||||||
this.initial_area_id = 0;
|
this.initial_area_id = 0;
|
||||||
this.npcs.splice(0, this.npcs.length, ...quest.npcs.val);
|
this.npcs.splice(0, this.npcs.length, ...quest.npcs.val);
|
||||||
@ -148,7 +148,7 @@ export class QuestRunner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.quest_logger.info("Stopping debugger.");
|
this.logger.info("Stopping debugger.");
|
||||||
|
|
||||||
if (this.animation_frame != undefined) {
|
if (this.animation_frame != undefined) {
|
||||||
cancelAnimationFrame(this.animation_frame);
|
cancelAnimationFrame(this.animation_frame);
|
||||||
@ -292,15 +292,15 @@ export class QuestRunner {
|
|||||||
},
|
},
|
||||||
|
|
||||||
window_msg: (msg: string): void => {
|
window_msg: (msg: string): void => {
|
||||||
this.quest_logger.info(`window_msg "${msg}"`);
|
this.logger.info(`window_msg "${msg}"`);
|
||||||
},
|
},
|
||||||
|
|
||||||
message: (msg: string): void => {
|
message: (msg: string): void => {
|
||||||
this.quest_logger.info(`message "${msg}"`);
|
this.logger.info(`message "${msg}"`);
|
||||||
},
|
},
|
||||||
|
|
||||||
add_msg: (msg: string): void => {
|
add_msg: (msg: string): void => {
|
||||||
this.quest_logger.info(`add_msg "${msg}"`);
|
this.logger.info(`add_msg "${msg}"`);
|
||||||
},
|
},
|
||||||
|
|
||||||
winend: (): void => {
|
winend: (): void => {
|
||||||
@ -317,15 +317,15 @@ export class QuestRunner {
|
|||||||
},
|
},
|
||||||
|
|
||||||
list: (list_items: string[]): void => {
|
list: (list_items: string[]): void => {
|
||||||
this.quest_logger.info(`list "[${list_items}]"`);
|
this.logger.info(`list "[${list_items}]"`);
|
||||||
},
|
},
|
||||||
|
|
||||||
warning: (msg: string, inst_ptr?: InstructionPointer): void => {
|
warning: (msg: string, inst_ptr?: InstructionPointer): void => {
|
||||||
this.quest_logger.warning(message_with_inst_ptr(msg, inst_ptr));
|
this.logger.warn(message_with_inst_ptr(msg, inst_ptr));
|
||||||
},
|
},
|
||||||
|
|
||||||
error: (err: Error, inst_ptr?: InstructionPointer): void => {
|
error: (err: Error, inst_ptr?: InstructionPointer): void => {
|
||||||
this.quest_logger.error(message_with_inst_ptr(err.message, inst_ptr));
|
this.logger.error(message_with_inst_ptr(err.message, inst_ptr));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -339,7 +339,7 @@ export class QuestRunner {
|
|||||||
const label = this._game_state.floor_handlers.get(area_id);
|
const label = this._game_state.floor_handlers.get(area_id);
|
||||||
|
|
||||||
if (label == undefined) {
|
if (label == undefined) {
|
||||||
this.quest_logger.debug(`No floor handler registered for floor ${area_id}.`);
|
this.logger.debug(`No floor handler registered for floor ${area_id}.`);
|
||||||
} else {
|
} else {
|
||||||
this.vm.start_thread(label);
|
this.vm.start_thread(label);
|
||||||
this.schedule_frame();
|
this.schedule_frame();
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
/**
|
|
||||||
* @jest-environment jsdom
|
|
||||||
*/
|
|
||||||
import { GuiStore } from "../../core/stores/GuiStore";
|
import { GuiStore } from "../../core/stores/GuiStore";
|
||||||
import { create_area_store } from "../../../test/src/quest_editor/stores/store_creation";
|
import { create_area_store } from "../../../test/src/quest_editor/stores/store_creation";
|
||||||
import { QuestEditorStore } from "../stores/QuestEditorStore";
|
import { QuestEditorStore } from "../stores/QuestEditorStore";
|
||||||
import { QuestEditorToolBarController } from "./QuestEditorToolBarController";
|
import { QuestEditorToolBarController } from "./QuestEditorToolBarController";
|
||||||
import { Episode } from "../../core/data_formats/parsing/quest/Episode";
|
import { Episode } from "../../core/data_formats/parsing/quest/Episode";
|
||||||
|
import { next_animation_frame } from "../../../test/src/utils";
|
||||||
|
|
||||||
test("Some widgets should only be enabled when a quest is loaded.", async () => {
|
test("Some widgets should only be enabled when a quest is loaded.", async () => {
|
||||||
const gui_store = new GuiStore();
|
const gui_store = new GuiStore();
|
||||||
@ -59,7 +57,3 @@ test("Debugging controls should be enabled and disabled at the right times.", as
|
|||||||
ctrl.stop();
|
ctrl.stop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function next_animation_frame(): Promise<void> {
|
|
||||||
return new Promise(resolve => requestAnimationFrame(() => resolve()));
|
|
||||||
}
|
|
||||||
|
@ -13,10 +13,10 @@ import { parse_quest, write_quest_qst } from "../../core/data_formats/parsing/qu
|
|||||||
import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCursor";
|
import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCursor";
|
||||||
import { Endianness } from "../../core/data_formats/Endianness";
|
import { Endianness } from "../../core/data_formats/Endianness";
|
||||||
import { convert_quest_from_model, convert_quest_to_model } from "../stores/model_conversion";
|
import { convert_quest_from_model, convert_quest_to_model } from "../stores/model_conversion";
|
||||||
import Logger from "js-logger";
|
|
||||||
import { create_element } from "../../core/gui/dom";
|
import { create_element } from "../../core/gui/dom";
|
||||||
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/controllers/QuestEditorToolBarController");
|
const logger = LogManager.get("quest_editor/controllers/QuestEditorToolBarController");
|
||||||
|
|
||||||
export type AreaAndLabel = { readonly area: AreaModel; readonly label: string };
|
export type AreaAndLabel = { readonly area: AreaModel; readonly label: string };
|
||||||
|
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
/**
|
|
||||||
* @jest-environment jsdom
|
|
||||||
*/
|
|
||||||
import {
|
import {
|
||||||
create_area_store,
|
create_area_store,
|
||||||
create_quest_editor_store,
|
create_quest_editor_store,
|
||||||
|
@ -39,6 +39,6 @@
|
|||||||
color: hsl(0, 80%, 50%);
|
color: hsl(0, 80%, 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quest_editor_LogView .quest_editor_LogView_Warning_message .quest_editor_LogView_message_level {
|
.quest_editor_LogView .quest_editor_LogView_Warn_message .quest_editor_LogView_message_level {
|
||||||
color: hsl(30, 80%, 50%);
|
color: hsl(30, 80%, 50%);
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,9 @@ import { bind_children_to, el } from "../../core/gui/dom";
|
|||||||
import { ResizableWidget } from "../../core/gui/ResizableWidget";
|
import { ResizableWidget } from "../../core/gui/ResizableWidget";
|
||||||
import { ToolBar } from "../../core/gui/ToolBar";
|
import { ToolBar } from "../../core/gui/ToolBar";
|
||||||
import "./LogView.css";
|
import "./LogView.css";
|
||||||
import { log_store, LogLevel, LogLevels, LogMessage } from "../stores/LogStore";
|
import { log_store } from "../stores/LogStore";
|
||||||
import { Select } from "../../core/gui/Select";
|
import { Select } from "../../core/gui/Select";
|
||||||
|
import { LogEntry, LogLevel, LogLevels, time_to_string } from "../../core/Logger";
|
||||||
|
|
||||||
const AUTOSCROLL_TRESHOLD = 5;
|
const AUTOSCROLL_TRESHOLD = 5;
|
||||||
|
|
||||||
@ -44,18 +45,15 @@ export class LogView extends ResizableWidget {
|
|||||||
this.list_container.addEventListener("scroll", this.scrolled);
|
this.list_container.addEventListener("scroll", this.scrolled);
|
||||||
|
|
||||||
this.disposables(
|
this.disposables(
|
||||||
bind_children_to(
|
bind_children_to(this.list_element, log_store.log, this.create_message_element, {
|
||||||
this.list_element,
|
after: this.scroll_to_bottom,
|
||||||
log_store.log_messages,
|
}),
|
||||||
this.create_message_element,
|
|
||||||
{ after: this.scroll_to_bottom },
|
|
||||||
),
|
|
||||||
|
|
||||||
this.level_filter.selected.observe(
|
this.level_filter.selected.observe(
|
||||||
({ value }) => value != undefined && log_store.set_log_level(value),
|
({ value }) => value != undefined && log_store.set_level(value),
|
||||||
),
|
),
|
||||||
|
|
||||||
log_store.log_level.observe(
|
log_store.level.observe(
|
||||||
({ value }) => {
|
({ value }) => {
|
||||||
this.level_filter.selected.val = value;
|
this.level_filter.selected.val = value;
|
||||||
},
|
},
|
||||||
@ -88,25 +86,25 @@ export class LogView extends ResizableWidget {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private create_message_element = (msg: LogMessage): HTMLElement => {
|
private create_message_element = ({ time, level, message }: LogEntry): HTMLElement => {
|
||||||
return el.div(
|
return el.div(
|
||||||
{
|
{
|
||||||
class: [
|
class: [
|
||||||
"quest_editor_LogView_message",
|
"quest_editor_LogView_message",
|
||||||
"quest_editor_LogView_" + LogLevel[msg.level] + "_message",
|
"quest_editor_LogView_" + LogLevel[level] + "_message",
|
||||||
].join(" "),
|
].join(" "),
|
||||||
},
|
},
|
||||||
el.div({
|
el.div({
|
||||||
class: "quest_editor_LogView_message_timestamp",
|
class: "quest_editor_LogView_message_timestamp",
|
||||||
text: msg.time.toTimeString().slice(0, 8),
|
text: time_to_string(time),
|
||||||
}),
|
}),
|
||||||
el.div({
|
el.div({
|
||||||
class: "quest_editor_LogView_message_level",
|
class: "quest_editor_LogView_message_level",
|
||||||
text: "[" + LogLevel[msg.level] + "]",
|
text: "[" + LogLevel[level] + "]",
|
||||||
}),
|
}),
|
||||||
el.div({
|
el.div({
|
||||||
class: "quest_editor_LogView_message_contents",
|
class: "quest_editor_LogView_message_contents",
|
||||||
text: msg.message,
|
text: message,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
/**
|
|
||||||
* @jest-environment jsdom
|
|
||||||
*/
|
|
||||||
import { QuestEditorToolBarController } from "../controllers/QuestEditorToolBarController";
|
import { QuestEditorToolBarController } from "../controllers/QuestEditorToolBarController";
|
||||||
import { QuestEditorToolBar } from "./QuestEditorToolBar";
|
import { QuestEditorToolBar } from "./QuestEditorToolBar";
|
||||||
import { GuiStore } from "../../core/stores/GuiStore";
|
import { GuiStore } from "../../core/stores/GuiStore";
|
||||||
|
@ -18,9 +18,9 @@ import { LogView } from "./LogView";
|
|||||||
import { QuestRunnerRendererView } from "./QuestRunnerRendererView";
|
import { QuestRunnerRendererView } from "./QuestRunnerRendererView";
|
||||||
import { QuestEditorStore } from "../stores/QuestEditorStore";
|
import { QuestEditorStore } from "../stores/QuestEditorStore";
|
||||||
import { QuestEditorUiPersister } from "../persistence/QuestEditorUiPersister";
|
import { QuestEditorUiPersister } from "../persistence/QuestEditorUiPersister";
|
||||||
import Logger = require("js-logger");
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/gui/QuestEditorView");
|
const logger = LogManager.get("quest_editor/gui/QuestEditorView");
|
||||||
|
|
||||||
const DEFAULT_LAYOUT_CONFIG = {
|
const DEFAULT_LAYOUT_CONFIG = {
|
||||||
settings: {
|
settings: {
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
/**
|
|
||||||
* @jest-environment jsdom
|
|
||||||
*/
|
|
||||||
import { QuestInfoController } from "../controllers/QuestInfoController";
|
import { QuestInfoController } from "../controllers/QuestInfoController";
|
||||||
import { undo_manager } from "../../core/undo/UndoManager";
|
import { undo_manager } from "../../core/undo/UndoManager";
|
||||||
import { QuestInfoView } from "./QuestInfoView";
|
import { QuestInfoView } from "./QuestInfoView";
|
||||||
|
@ -0,0 +1,181 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Renders correctly. 1`] = `
|
||||||
|
<div
|
||||||
|
class="core_ToolBar"
|
||||||
|
style="height: 33px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="core_DropDown"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="core_Button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="core_Button_inner"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="core_Button_left"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="fas fa-file-medical"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="core_Button_center"
|
||||||
|
>
|
||||||
|
New quest
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="core_Button_right"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="fas fa-caret-down"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="core_Menu"
|
||||||
|
hidden=""
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="core_Menu_inner"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-index="0"
|
||||||
|
>
|
||||||
|
Episode I
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
class="core_FileButton core_Button"
|
||||||
|
title="Open a quest file (Ctrl-O)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="core_FileButton_inner core_Button_inner"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="core_FileButton_left core_Button_left"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="fas fa-file"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="core_Button_center"
|
||||||
|
>
|
||||||
|
Open file...
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
accept=".qst"
|
||||||
|
class="core_FileButton_input core_Button_inner"
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
class="core_Button disabled"
|
||||||
|
disabled=""
|
||||||
|
title="Save this quest to new file (Ctrl-Shift-S)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="core_Button_inner"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="core_Button_left"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="fas fa-save"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="core_Button_center"
|
||||||
|
>
|
||||||
|
Save as...
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="core_Button disabled"
|
||||||
|
disabled=""
|
||||||
|
title="Nothing to undo (Ctrl-Z)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="core_Button_inner"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="core_Button_left"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="fas fa-undo"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="core_Button_center"
|
||||||
|
>
|
||||||
|
Undo
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="core_Button disabled"
|
||||||
|
disabled=""
|
||||||
|
title="Nothing to redo (Ctrl-Shift-Z)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="core_Button_inner"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="core_Button_left"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="fas fa-redo"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="core_Button_center"
|
||||||
|
>
|
||||||
|
Redo
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="core_Select disabled"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="core_Button disabled"
|
||||||
|
disabled=""
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="core_Button_inner"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="core_Button_center"
|
||||||
|
>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="core_Button_right"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="fas fa-caret-down"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="core_Menu"
|
||||||
|
hidden=""
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="core_Menu_inner"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
218
src/quest_editor/gui/__snapshots__/QuestInfoView.test.ts.snap
Normal file
218
src/quest_editor/gui/__snapshots__/QuestInfoView.test.ts.snap
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Renders correctly with a current quest.: should render property inputs 1`] = `
|
||||||
|
<div
|
||||||
|
class="quest_editor_QuestInfoView"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Episode:
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
I
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
ID:
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
class="core_NumberInput core_Input"
|
||||||
|
style="width: 54px;"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="core_NumberInput_inner core_Input_inner"
|
||||||
|
step="any"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Name:
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
class="core_TextInput core_Input"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="core_TextInput_inner core_Input_inner"
|
||||||
|
maxlength="32"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
colspan="2"
|
||||||
|
>
|
||||||
|
Short description:
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
colspan="2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="core_TextArea"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
class="core_TextArea_inner"
|
||||||
|
cols="25"
|
||||||
|
maxlength="128"
|
||||||
|
rows="5"
|
||||||
|
style="font-family: \\"Courier New\\", monospace;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
colspan="2"
|
||||||
|
>
|
||||||
|
Long description:
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
colspan="2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="core_TextArea"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
class="core_TextArea_inner"
|
||||||
|
cols="25"
|
||||||
|
maxlength="288"
|
||||||
|
rows="10"
|
||||||
|
style="font-family: \\"Courier New\\", monospace;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div
|
||||||
|
class="quest_editor_UnavailableView"
|
||||||
|
hidden=""
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="core_Label disabled"
|
||||||
|
>
|
||||||
|
No quest loaded.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Renders correctly without a current quest.: should render a "No quest loaded." view 1`] = `
|
||||||
|
<div
|
||||||
|
class="quest_editor_QuestInfoView"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
hidden=""
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Episode:
|
||||||
|
</th>
|
||||||
|
<td />
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
ID:
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
class="core_NumberInput core_Input"
|
||||||
|
style="width: 54px;"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="core_NumberInput_inner core_Input_inner"
|
||||||
|
step="any"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Name:
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
class="core_TextInput core_Input"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="core_TextInput_inner core_Input_inner"
|
||||||
|
maxlength="32"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
colspan="2"
|
||||||
|
>
|
||||||
|
Short description:
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
colspan="2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="core_TextArea"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
class="core_TextArea_inner"
|
||||||
|
cols="25"
|
||||||
|
maxlength="128"
|
||||||
|
rows="5"
|
||||||
|
style="font-family: \\"Courier New\\", monospace;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
colspan="2"
|
||||||
|
>
|
||||||
|
Long description:
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
colspan="2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="core_TextArea"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
class="core_TextArea_inner"
|
||||||
|
cols="25"
|
||||||
|
maxlength="288"
|
||||||
|
rows="10"
|
||||||
|
style="font-family: \\"Courier New\\", monospace;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div
|
||||||
|
class="quest_editor_UnavailableView"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="core_Label disabled"
|
||||||
|
>
|
||||||
|
No quest loaded.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -22,58 +22,75 @@ import { EventsView } from "./gui/EventsView";
|
|||||||
import { QuestRunnerRendererView } from "./gui/QuestRunnerRendererView";
|
import { QuestRunnerRendererView } from "./gui/QuestRunnerRendererView";
|
||||||
import { RegistersView } from "./gui/RegistersView";
|
import { RegistersView } from "./gui/RegistersView";
|
||||||
import { QuestInfoController } from "./controllers/QuestInfoController";
|
import { QuestInfoController } from "./controllers/QuestInfoController";
|
||||||
|
import { Disposer } from "../core/observable/Disposer";
|
||||||
|
import { Disposable } from "../core/observable/Disposable";
|
||||||
|
|
||||||
export function initialize_quest_editor(
|
export function initialize_quest_editor(
|
||||||
http_client: HttpClient,
|
http_client: HttpClient,
|
||||||
gui_store: GuiStore,
|
gui_store: GuiStore,
|
||||||
create_three_renderer: () => DisposableThreeRenderer,
|
create_three_renderer: () => DisposableThreeRenderer,
|
||||||
): QuestEditorView {
|
): { view: QuestEditorView } & Disposable {
|
||||||
|
const disposer = new Disposer();
|
||||||
|
|
||||||
// Asset Loaders
|
// Asset Loaders
|
||||||
const area_asset_loader = new AreaAssetLoader(http_client);
|
const area_asset_loader = disposer.add(new AreaAssetLoader(http_client));
|
||||||
const entity_asset_loader = new EntityAssetLoader(http_client);
|
const entity_asset_loader = disposer.add(new EntityAssetLoader(http_client));
|
||||||
|
|
||||||
// Stores
|
// Stores
|
||||||
const area_store = new AreaStore(area_asset_loader);
|
const area_store = disposer.add(new AreaStore(area_asset_loader));
|
||||||
const quest_editor_store = new QuestEditorStore(gui_store, area_store);
|
const quest_editor_store = disposer.add(new QuestEditorStore(gui_store, area_store));
|
||||||
const asm_editor_store = new AsmEditorStore(quest_editor_store);
|
const asm_editor_store = disposer.add(new AsmEditorStore(quest_editor_store));
|
||||||
|
|
||||||
// Persisters
|
// Persisters
|
||||||
const quest_editor_ui_persister = new QuestEditorUiPersister();
|
const quest_editor_ui_persister = new QuestEditorUiPersister();
|
||||||
|
|
||||||
// Entity Image Renderer
|
// Entity Image Renderer
|
||||||
const entity_image_renderer = new EntityImageRenderer(entity_asset_loader);
|
const entity_image_renderer = disposer.add(
|
||||||
|
new EntityImageRenderer(entity_asset_loader, create_three_renderer),
|
||||||
|
);
|
||||||
|
|
||||||
// View
|
// View
|
||||||
return new QuestEditorView(
|
const view = disposer.add(
|
||||||
gui_store,
|
new QuestEditorView(
|
||||||
quest_editor_store,
|
gui_store,
|
||||||
quest_editor_ui_persister,
|
quest_editor_store,
|
||||||
new QuestEditorToolBar(
|
quest_editor_ui_persister,
|
||||||
new QuestEditorToolBarController(gui_store, area_store, quest_editor_store),
|
disposer.add(
|
||||||
|
new QuestEditorToolBar(
|
||||||
|
new QuestEditorToolBarController(gui_store, area_store, quest_editor_store),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
() => new QuestInfoView(new QuestInfoController(quest_editor_store)),
|
||||||
|
() => new NpcCountsView(quest_editor_store),
|
||||||
|
() =>
|
||||||
|
new QuestEditorRendererView(
|
||||||
|
gui_store,
|
||||||
|
quest_editor_store,
|
||||||
|
area_asset_loader,
|
||||||
|
entity_asset_loader,
|
||||||
|
create_three_renderer(),
|
||||||
|
),
|
||||||
|
() => new AsmEditorView(gui_store, quest_editor_store.quest_runner, asm_editor_store),
|
||||||
|
() => new EntityInfoView(quest_editor_store),
|
||||||
|
() => new NpcListView(quest_editor_store, entity_image_renderer),
|
||||||
|
() => new ObjectListView(quest_editor_store, entity_image_renderer),
|
||||||
|
() => new EventsView(quest_editor_store),
|
||||||
|
() =>
|
||||||
|
new QuestRunnerRendererView(
|
||||||
|
gui_store,
|
||||||
|
quest_editor_store,
|
||||||
|
area_asset_loader,
|
||||||
|
entity_asset_loader,
|
||||||
|
create_three_renderer(),
|
||||||
|
),
|
||||||
|
() => new RegistersView(quest_editor_store.quest_runner),
|
||||||
),
|
),
|
||||||
() => new QuestInfoView(new QuestInfoController(quest_editor_store)),
|
|
||||||
() => new NpcCountsView(quest_editor_store),
|
|
||||||
() =>
|
|
||||||
new QuestEditorRendererView(
|
|
||||||
gui_store,
|
|
||||||
quest_editor_store,
|
|
||||||
area_asset_loader,
|
|
||||||
entity_asset_loader,
|
|
||||||
create_three_renderer(),
|
|
||||||
),
|
|
||||||
() => new AsmEditorView(gui_store, quest_editor_store.quest_runner, asm_editor_store),
|
|
||||||
() => new EntityInfoView(quest_editor_store),
|
|
||||||
() => new NpcListView(quest_editor_store, entity_image_renderer),
|
|
||||||
() => new ObjectListView(quest_editor_store, entity_image_renderer),
|
|
||||||
() => new EventsView(quest_editor_store),
|
|
||||||
() =>
|
|
||||||
new QuestRunnerRendererView(
|
|
||||||
gui_store,
|
|
||||||
quest_editor_store,
|
|
||||||
area_asset_loader,
|
|
||||||
entity_asset_loader,
|
|
||||||
create_three_renderer(),
|
|
||||||
),
|
|
||||||
() => new RegistersView(quest_editor_store.quest_runner),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
view,
|
||||||
|
dispose() {
|
||||||
|
disposer.dispose();
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -15,14 +15,21 @@ import {
|
|||||||
} from "../rendering/conversion/areas";
|
} from "../rendering/conversion/areas";
|
||||||
import { AreaVariantModel } from "../model/AreaVariantModel";
|
import { AreaVariantModel } from "../model/AreaVariantModel";
|
||||||
import { HttpClient } from "../../core/HttpClient";
|
import { HttpClient } from "../../core/HttpClient";
|
||||||
|
import { Disposable } from "../../core/observable/Disposable";
|
||||||
|
|
||||||
export class AreaAssetLoader {
|
export class AreaAssetLoader implements Disposable {
|
||||||
private readonly render_object_cache = new LoadingCache<string, Promise<RenderObject>>();
|
private readonly render_object_cache = new LoadingCache<string, Promise<RenderObject>>();
|
||||||
private readonly collision_object_cache = new LoadingCache<string, Promise<CollisionObject>>();
|
private readonly collision_object_cache = new LoadingCache<string, Promise<CollisionObject>>();
|
||||||
private readonly area_sections_cache = new LoadingCache<string, Promise<SectionModel[]>>();
|
private readonly area_sections_cache = new LoadingCache<string, Promise<SectionModel[]>>();
|
||||||
|
|
||||||
constructor(private readonly http_client: HttpClient) {}
|
constructor(private readonly http_client: HttpClient) {}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.render_object_cache.purge_all();
|
||||||
|
this.collision_object_cache.purge_all();
|
||||||
|
this.area_sections_cache.purge_all();
|
||||||
|
}
|
||||||
|
|
||||||
async load_sections(episode: Episode, area_variant: AreaVariantModel): Promise<SectionModel[]> {
|
async load_sections(episode: Episode, area_variant: AreaVariantModel): Promise<SectionModel[]> {
|
||||||
const key = `${episode}-${area_variant.area.id}-${area_variant.id}`;
|
const key = `${episode}-${area_variant.area.id}-${area_variant.id}`;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { BufferGeometry, CylinderBufferGeometry, Texture } from "three";
|
import { BufferGeometry, CylinderBufferGeometry, Texture } from "three";
|
||||||
import Logger from "js-logger";
|
|
||||||
import { LoadingCache } from "./LoadingCache";
|
import { LoadingCache } from "./LoadingCache";
|
||||||
import { Endianness } from "../../core/data_formats/Endianness";
|
import { Endianness } from "../../core/data_formats/Endianness";
|
||||||
import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCursor";
|
import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCursor";
|
||||||
@ -15,16 +14,47 @@ import {
|
|||||||
is_npc_type,
|
is_npc_type,
|
||||||
} from "../../core/data_formats/parsing/quest/entities";
|
} from "../../core/data_formats/parsing/quest/entities";
|
||||||
import { HttpClient } from "../../core/HttpClient";
|
import { HttpClient } from "../../core/HttpClient";
|
||||||
|
import { Disposable } from "../../core/observable/Disposable";
|
||||||
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/loading/EntityAssetLoader");
|
const logger = LogManager.get("quest_editor/loading/EntityAssetLoader");
|
||||||
|
|
||||||
export class EntityAssetLoader {
|
const DEFAULT_ENTITY = new CylinderBufferGeometry(3, 3, 20);
|
||||||
constructor(private readonly http_client: HttpClient) {}
|
DEFAULT_ENTITY.translate(0, 10, 0);
|
||||||
|
DEFAULT_ENTITY.computeBoundingBox();
|
||||||
|
DEFAULT_ENTITY.computeBoundingSphere();
|
||||||
|
|
||||||
|
const DEFAULT_ENTITY_PROMISE: Promise<BufferGeometry> = new Promise(resolve =>
|
||||||
|
resolve(DEFAULT_ENTITY),
|
||||||
|
);
|
||||||
|
|
||||||
|
const DEFAULT_ENTITY_TEX: Texture[] = [];
|
||||||
|
|
||||||
|
const DEFAULT_ENTITY_TEX_PROMISE: Promise<Texture[]> = new Promise(resolve =>
|
||||||
|
resolve(DEFAULT_ENTITY_TEX),
|
||||||
|
);
|
||||||
|
|
||||||
|
export class EntityAssetLoader implements Disposable {
|
||||||
|
private disposed = false;
|
||||||
|
private readonly geom_cache = new LoadingCache<EntityType, Promise<BufferGeometry>>();
|
||||||
|
private readonly tex_cache = new LoadingCache<EntityType, Promise<Texture[]>>();
|
||||||
|
|
||||||
|
constructor(private readonly http_client: HttpClient) {
|
||||||
|
this.warm_up_caches();
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.disposed = true;
|
||||||
|
this.geom_cache.purge_all();
|
||||||
|
this.tex_cache.purge_all();
|
||||||
|
}
|
||||||
|
|
||||||
async load_geometry(type: EntityType): Promise<BufferGeometry> {
|
async load_geometry(type: EntityType): Promise<BufferGeometry> {
|
||||||
return geom_cache.get_or_set(type, async () => {
|
return this.geom_cache.get_or_set(type, async () => {
|
||||||
try {
|
try {
|
||||||
const { url, data } = await this.load_data(type, AssetType.Geometry);
|
const { url, data } = await this.load_data(type, AssetType.Geometry);
|
||||||
|
if (this.disposed) return DEFAULT_ENTITY;
|
||||||
|
|
||||||
const cursor = new ArrayBufferCursor(data, Endianness.Little);
|
const cursor = new ArrayBufferCursor(data, Endianness.Little);
|
||||||
const nj_objects = url.endsWith(".nj") ? parse_nj(cursor) : parse_xj(cursor);
|
const nj_objects = url.endsWith(".nj") ? parse_nj(cursor) : parse_xj(cursor);
|
||||||
|
|
||||||
@ -42,9 +72,11 @@ export class EntityAssetLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load_textures(type: EntityType): Promise<Texture[]> {
|
async load_textures(type: EntityType): Promise<Texture[]> {
|
||||||
return tex_cache.get_or_set(type, async () => {
|
return this.tex_cache.get_or_set(type, async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await this.load_data(type, AssetType.Texture);
|
const { data } = await this.load_data(type, AssetType.Texture);
|
||||||
|
if (this.disposed) return DEFAULT_ENTITY_TEX;
|
||||||
|
|
||||||
const cursor = new ArrayBufferCursor(data, Endianness.Little);
|
const cursor = new ArrayBufferCursor(data, Endianness.Little);
|
||||||
const xvm = parse_xvm(cursor);
|
const xvm = parse_xvm(cursor);
|
||||||
return xvm_to_textures(xvm);
|
return xvm_to_textures(xvm);
|
||||||
@ -63,103 +95,89 @@ export class EntityAssetLoader {
|
|||||||
const data = await this.http_client.get(url).array_buffer();
|
const data = await this.http_client.get(url).array_buffer();
|
||||||
return { url, data };
|
return { url, data };
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_ENTITY = new CylinderBufferGeometry(3, 3, 20);
|
/**
|
||||||
DEFAULT_ENTITY.translate(0, 10, 0);
|
* Warms up the caches with default data for all entities without assets.
|
||||||
DEFAULT_ENTITY.computeBoundingBox();
|
*/
|
||||||
DEFAULT_ENTITY.computeBoundingSphere();
|
private warm_up_caches(): void {
|
||||||
|
for (const type of [
|
||||||
|
NpcType.Unknown,
|
||||||
|
NpcType.Migium,
|
||||||
|
NpcType.Hidoom,
|
||||||
|
NpcType.DeathGunner,
|
||||||
|
NpcType.StRappy,
|
||||||
|
NpcType.HalloRappy,
|
||||||
|
NpcType.EggRappy,
|
||||||
|
NpcType.Migium2,
|
||||||
|
NpcType.Hidoom2,
|
||||||
|
NpcType.Recon,
|
||||||
|
|
||||||
const DEFAULT_ENTITY_PROMISE: Promise<BufferGeometry> = new Promise(resolve =>
|
ObjectType.Unknown,
|
||||||
resolve(DEFAULT_ENTITY),
|
ObjectType.PlayerSet,
|
||||||
);
|
ObjectType.Particle,
|
||||||
|
ObjectType.LightCollision,
|
||||||
const DEFAULT_ENTITY_TEX: Texture[] = [];
|
ObjectType.EnvSound,
|
||||||
|
ObjectType.FogCollision,
|
||||||
const DEFAULT_ENTITY_TEX_PROMISE: Promise<Texture[]> = new Promise(resolve =>
|
ObjectType.EventCollision,
|
||||||
resolve(DEFAULT_ENTITY_TEX),
|
ObjectType.CharaCollision,
|
||||||
);
|
ObjectType.ObjRoomID,
|
||||||
|
ObjectType.LensFlare,
|
||||||
const geom_cache = new LoadingCache<EntityType, Promise<BufferGeometry>>();
|
ObjectType.ScriptCollision,
|
||||||
|
ObjectType.MapCollision,
|
||||||
const tex_cache = new LoadingCache<EntityType, Promise<Texture[]>>();
|
ObjectType.ScriptCollisionA,
|
||||||
|
ObjectType.ItemLight,
|
||||||
for (const type of [
|
ObjectType.RadarCollision,
|
||||||
NpcType.Unknown,
|
ObjectType.FogCollisionSW,
|
||||||
NpcType.Migium,
|
ObjectType.ImageBoard,
|
||||||
NpcType.Hidoom,
|
ObjectType.UnknownItem29,
|
||||||
NpcType.DeathGunner,
|
ObjectType.UnknownItem30,
|
||||||
NpcType.StRappy,
|
ObjectType.UnknownItem31,
|
||||||
NpcType.HalloRappy,
|
ObjectType.MenuActivation,
|
||||||
NpcType.EggRappy,
|
ObjectType.BoxDetectObject,
|
||||||
NpcType.Migium2,
|
ObjectType.SymbolChatObject,
|
||||||
NpcType.Hidoom2,
|
ObjectType.TouchPlateObject,
|
||||||
NpcType.Recon,
|
ObjectType.TargetableObject,
|
||||||
|
ObjectType.EffectObject,
|
||||||
ObjectType.Unknown,
|
ObjectType.CountDownObject,
|
||||||
ObjectType.PlayerSet,
|
ObjectType.UnknownItem38,
|
||||||
ObjectType.Particle,
|
ObjectType.UnknownItem39,
|
||||||
ObjectType.LightCollision,
|
ObjectType.UnknownItem40,
|
||||||
ObjectType.EnvSound,
|
ObjectType.UnknownItem41,
|
||||||
ObjectType.FogCollision,
|
ObjectType.TelepipeLocation,
|
||||||
ObjectType.EventCollision,
|
ObjectType.BGMCollision,
|
||||||
ObjectType.CharaCollision,
|
ObjectType.Pioneer2InvisibleTouchplate,
|
||||||
ObjectType.ObjRoomID,
|
ObjectType.TempleMapDetect,
|
||||||
ObjectType.LensFlare,
|
ObjectType.Firework,
|
||||||
ObjectType.ScriptCollision,
|
ObjectType.MainRagolTeleporterBattleInNextArea,
|
||||||
ObjectType.MapCollision,
|
ObjectType.Rainbow,
|
||||||
ObjectType.ScriptCollisionA,
|
ObjectType.FloatingBlueLight,
|
||||||
ObjectType.ItemLight,
|
ObjectType.PopupTrapNoTech,
|
||||||
ObjectType.RadarCollision,
|
ObjectType.Poison,
|
||||||
ObjectType.FogCollisionSW,
|
ObjectType.EnemyTypeBoxYellow,
|
||||||
ObjectType.ImageBoard,
|
ObjectType.EnemyTypeBoxBlue,
|
||||||
ObjectType.UnknownItem29,
|
ObjectType.EmptyTypeBoxBlue,
|
||||||
ObjectType.UnknownItem30,
|
ObjectType.FloatingRocks,
|
||||||
ObjectType.UnknownItem31,
|
ObjectType.FloatingSoul,
|
||||||
ObjectType.MenuActivation,
|
ObjectType.Butterfly,
|
||||||
ObjectType.BoxDetectObject,
|
ObjectType.UnknownItem400,
|
||||||
ObjectType.SymbolChatObject,
|
ObjectType.CCAAreaTeleporter,
|
||||||
ObjectType.TouchPlateObject,
|
ObjectType.UnknownItem523,
|
||||||
ObjectType.TargetableObject,
|
ObjectType.WhiteBird,
|
||||||
ObjectType.EffectObject,
|
ObjectType.OrangeBird,
|
||||||
ObjectType.CountDownObject,
|
ObjectType.UnknownItem529,
|
||||||
ObjectType.UnknownItem38,
|
ObjectType.UnknownItem530,
|
||||||
ObjectType.UnknownItem39,
|
ObjectType.Seagull,
|
||||||
ObjectType.UnknownItem40,
|
ObjectType.UnknownItem576,
|
||||||
ObjectType.UnknownItem41,
|
ObjectType.WarpInBarbaRayRoom,
|
||||||
ObjectType.TelepipeLocation,
|
ObjectType.UnknownItem672,
|
||||||
ObjectType.BGMCollision,
|
ObjectType.InstaWarp,
|
||||||
ObjectType.Pioneer2InvisibleTouchplate,
|
ObjectType.LabInvisibleObject,
|
||||||
ObjectType.TempleMapDetect,
|
ObjectType.UnknownItem700,
|
||||||
ObjectType.Firework,
|
]) {
|
||||||
ObjectType.MainRagolTeleporterBattleInNextArea,
|
this.geom_cache.set(type, DEFAULT_ENTITY_PROMISE);
|
||||||
ObjectType.Rainbow,
|
this.tex_cache.set(type, DEFAULT_ENTITY_TEX_PROMISE);
|
||||||
ObjectType.FloatingBlueLight,
|
}
|
||||||
ObjectType.PopupTrapNoTech,
|
}
|
||||||
ObjectType.Poison,
|
|
||||||
ObjectType.EnemyTypeBoxYellow,
|
|
||||||
ObjectType.EnemyTypeBoxBlue,
|
|
||||||
ObjectType.EmptyTypeBoxBlue,
|
|
||||||
ObjectType.FloatingRocks,
|
|
||||||
ObjectType.FloatingSoul,
|
|
||||||
ObjectType.Butterfly,
|
|
||||||
ObjectType.UnknownItem400,
|
|
||||||
ObjectType.CCAAreaTeleporter,
|
|
||||||
ObjectType.UnknownItem523,
|
|
||||||
ObjectType.WhiteBird,
|
|
||||||
ObjectType.OrangeBird,
|
|
||||||
ObjectType.UnknownItem529,
|
|
||||||
ObjectType.UnknownItem530,
|
|
||||||
ObjectType.Seagull,
|
|
||||||
ObjectType.UnknownItem576,
|
|
||||||
ObjectType.WarpInBarbaRayRoom,
|
|
||||||
ObjectType.UnknownItem672,
|
|
||||||
ObjectType.InstaWarp,
|
|
||||||
ObjectType.LabInvisibleObject,
|
|
||||||
ObjectType.UnknownItem700,
|
|
||||||
]) {
|
|
||||||
geom_cache.set(type, DEFAULT_ENTITY_PROMISE);
|
|
||||||
tex_cache.set(type, DEFAULT_ENTITY_TEX_PROMISE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AssetType {
|
enum AssetType {
|
||||||
|
@ -15,4 +15,8 @@ export class LoadingCache<K, V> {
|
|||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
purge_all(): void {
|
||||||
|
this.map.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import { QuestNpcModel } from "./QuestNpcModel";
|
|||||||
import { DatUnknown } from "../../core/data_formats/parsing/quest/dat";
|
import { DatUnknown } from "../../core/data_formats/parsing/quest/dat";
|
||||||
import { Segment } from "../scripting/instructions";
|
import { Segment } from "../scripting/instructions";
|
||||||
import { Property } from "../../core/observable/property/Property";
|
import { Property } from "../../core/observable/property/Property";
|
||||||
import Logger from "js-logger";
|
|
||||||
import { AreaVariantModel } from "./AreaVariantModel";
|
import { AreaVariantModel } from "./AreaVariantModel";
|
||||||
import { ListProperty } from "../../core/observable/property/list/ListProperty";
|
import { ListProperty } from "../../core/observable/property/list/ListProperty";
|
||||||
import { WritableListProperty } from "../../core/observable/property/list/WritableListProperty";
|
import { WritableListProperty } from "../../core/observable/property/list/WritableListProperty";
|
||||||
@ -15,8 +14,9 @@ import { entity_type_to_string } from "../../core/data_formats/parsing/quest/ent
|
|||||||
import { QuestEventDagModel } from "./QuestEventDagModel";
|
import { QuestEventDagModel } from "./QuestEventDagModel";
|
||||||
import { assert, defined, require_array } from "../../core/util";
|
import { assert, defined, require_array } from "../../core/util";
|
||||||
import { AreaStore } from "../stores/AreaStore";
|
import { AreaStore } from "../stores/AreaStore";
|
||||||
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/model/QuestModel");
|
const logger = LogManager.get("quest_editor/model/QuestModel");
|
||||||
|
|
||||||
export class QuestModel {
|
export class QuestModel {
|
||||||
private readonly _id: WritableProperty<number> = property(0);
|
private readonly _id: WritableProperty<number> = property(0);
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { HemisphereLight, PerspectiveCamera, Scene, Vector3, WebGLRenderer } from "three";
|
import { HemisphereLight, PerspectiveCamera, Scene, Vector3 } from "three";
|
||||||
import { EntityType } from "../../core/data_formats/parsing/quest/entities";
|
import { EntityType } from "../../core/data_formats/parsing/quest/entities";
|
||||||
import { create_entity_type_mesh } from "./conversion/entities";
|
import { create_entity_type_mesh } from "./conversion/entities";
|
||||||
import { sequential } from "../../core/sequential";
|
import { sequential } from "../../core/sequential";
|
||||||
import { EntityAssetLoader } from "../loading/EntityAssetLoader";
|
import { EntityAssetLoader } from "../loading/EntityAssetLoader";
|
||||||
|
import { Disposable } from "../../core/observable/Disposable";
|
||||||
|
import { DisposableThreeRenderer } from "../../core/rendering/Renderer";
|
||||||
|
|
||||||
const light = new HemisphereLight(0xffffff, 0x505050, 1.2);
|
const light = new HemisphereLight(0xffffff, 0x505050, 1.2);
|
||||||
const scene = new Scene();
|
const scene = new Scene();
|
||||||
@ -11,14 +13,25 @@ const camera = new PerspectiveCamera(30, 1, 10, 1000);
|
|||||||
const camera_position = new Vector3(1, 1, 2).normalize();
|
const camera_position = new Vector3(1, 1, 2).normalize();
|
||||||
const camera_dist_factor = 1.3 / Math.tan(((camera.fov / 180) * Math.PI) / 2);
|
const camera_dist_factor = 1.3 / Math.tan(((camera.fov / 180) * Math.PI) / 2);
|
||||||
|
|
||||||
export class EntityImageRenderer {
|
export class EntityImageRenderer implements Disposable {
|
||||||
private renderer = new WebGLRenderer({ alpha: true, antialias: true });
|
private renderer: DisposableThreeRenderer;
|
||||||
private readonly cache: Map<EntityType, Promise<string>> = new Map();
|
private readonly cache: Map<EntityType, Promise<string>> = new Map();
|
||||||
|
private disposed = false;
|
||||||
|
|
||||||
constructor(private readonly entity_asset_loader: EntityAssetLoader) {
|
constructor(
|
||||||
|
private readonly entity_asset_loader: EntityAssetLoader,
|
||||||
|
create_three_renderer: () => DisposableThreeRenderer,
|
||||||
|
) {
|
||||||
|
this.renderer = create_three_renderer();
|
||||||
this.renderer.setSize(100, 100);
|
this.renderer.setSize(100, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.disposed = true;
|
||||||
|
this.renderer.dispose();
|
||||||
|
this.cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
async render(entity: EntityType): Promise<string> {
|
async render(entity: EntityType): Promise<string> {
|
||||||
let url = this.cache.get(entity);
|
let url = this.cache.get(entity);
|
||||||
|
|
||||||
@ -32,8 +45,13 @@ export class EntityImageRenderer {
|
|||||||
|
|
||||||
private render_to_image = sequential(
|
private render_to_image = sequential(
|
||||||
async (entity: EntityType): Promise<string> => {
|
async (entity: EntityType): Promise<string> => {
|
||||||
|
if (this.disposed) return "";
|
||||||
|
|
||||||
const geometry = await this.entity_asset_loader.load_geometry(entity);
|
const geometry = await this.entity_asset_loader.load_geometry(entity);
|
||||||
|
if (this.disposed) return "";
|
||||||
|
|
||||||
const textures = await this.entity_asset_loader.load_textures(entity);
|
const textures = await this.entity_asset_loader.load_textures(entity);
|
||||||
|
if (this.disposed) return "";
|
||||||
|
|
||||||
scene.remove(...scene.children);
|
scene.remove(...scene.children);
|
||||||
scene.add(light);
|
scene.add(light);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { Intersection, Mesh, Object3D, Raycaster, Vector3 } from "three";
|
import { Intersection, Mesh, Object3D, Raycaster, Vector3 } from "three";
|
||||||
import { QuestRenderer } from "./QuestRenderer";
|
import { QuestRenderer } from "./QuestRenderer";
|
||||||
import { QuestEntityModel } from "../model/QuestEntityModel";
|
import { QuestEntityModel } from "../model/QuestEntityModel";
|
||||||
@ -18,8 +17,9 @@ import { Episode } from "../../core/data_formats/parsing/quest/Episode";
|
|||||||
import { AreaVariantModel } from "../model/AreaVariantModel";
|
import { AreaVariantModel } from "../model/AreaVariantModel";
|
||||||
import { EntityAssetLoader } from "../loading/EntityAssetLoader";
|
import { EntityAssetLoader } from "../loading/EntityAssetLoader";
|
||||||
import { AreaAssetLoader } from "../loading/AreaAssetLoader";
|
import { AreaAssetLoader } from "../loading/AreaAssetLoader";
|
||||||
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/rendering/QuestModelManager");
|
const logger = LogManager.get("quest_editor/rendering/QuestModelManager");
|
||||||
|
|
||||||
const CAMERA_POSITION = Object.freeze(new Vector3(0, 800, 700));
|
const CAMERA_POSITION = Object.freeze(new Vector3(0, 800, 700));
|
||||||
const CAMERA_LOOK_AT = Object.freeze(new Vector3(0, 0, 0));
|
const CAMERA_LOOK_AT = Object.freeze(new Vector3(0, 0, 0));
|
||||||
@ -55,6 +55,9 @@ export abstract class QuestModelManager implements Disposable {
|
|||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
this.disposer.dispose();
|
this.disposer.dispose();
|
||||||
|
this.npc_model_manager.remove_all();
|
||||||
|
this.object_model_manager.remove_all();
|
||||||
|
this.renderer.reset_entity_models();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -266,13 +269,13 @@ class EntityModelManager {
|
|||||||
|
|
||||||
private async load(entity: QuestEntityModel): Promise<void> {
|
private async load(entity: QuestEntityModel): Promise<void> {
|
||||||
const geom = await this.entity_asset_loader.load_geometry(entity.type);
|
const geom = await this.entity_asset_loader.load_geometry(entity.type);
|
||||||
const tex = await this.entity_asset_loader.load_textures(entity.type);
|
if (!this.queue.includes(entity)) return; // Could be cancelled by now.
|
||||||
const model = create_entity_mesh(entity, geom, tex);
|
|
||||||
|
|
||||||
// The model load might be cancelled by now.
|
const tex = await this.entity_asset_loader.load_textures(entity.type);
|
||||||
if (this.queue.includes(entity)) {
|
if (!this.queue.includes(entity)) return; // Could be cancelled by now.
|
||||||
this.update_entity_geometry(entity, model);
|
|
||||||
}
|
const model = create_entity_mesh(entity, geom, tex);
|
||||||
|
this.update_entity_geometry(entity, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
private update_entity_geometry(entity: QuestEntityModel, model: Mesh): void {
|
private update_entity_geometry(entity: QuestEntityModel, model: Mesh): void {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { reinterpret_f32_as_i32 } from "../../core/primitive_conversion";
|
import { reinterpret_f32_as_i32 } from "../../core/primitive_conversion";
|
||||||
import {
|
import {
|
||||||
AssemblyLexer,
|
AssemblyLexer,
|
||||||
@ -34,8 +33,9 @@ import {
|
|||||||
Param,
|
Param,
|
||||||
StackInteraction,
|
StackInteraction,
|
||||||
} from "./opcodes";
|
} from "./opcodes";
|
||||||
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/scripting/assembly");
|
const logger = LogManager.get("quest_editor/scripting/assembly");
|
||||||
|
|
||||||
export type AssemblyWarning = {
|
export type AssemblyWarning = {
|
||||||
line_no: number;
|
line_no: number;
|
||||||
|
@ -11,15 +11,10 @@ import {
|
|||||||
SignatureHelpOutput,
|
SignatureHelpOutput,
|
||||||
} from "./assembly_worker_messages";
|
} from "./assembly_worker_messages";
|
||||||
import { assemble, AssemblySettings } from "./assembly";
|
import { assemble, AssemblySettings } from "./assembly";
|
||||||
import Logger from "js-logger";
|
|
||||||
import { AsmToken, Segment, SegmentType } from "./instructions";
|
import { AsmToken, Segment, SegmentType } from "./instructions";
|
||||||
import { Kind, OP_BB_MAP_DESIGNATE, Opcode, OPCODES_BY_MNEMONIC } from "./opcodes";
|
import { Kind, OP_BB_MAP_DESIGNATE, Opcode, OPCODES_BY_MNEMONIC } from "./opcodes";
|
||||||
import { AssemblyLexer, IdentToken, TokenType } from "./AssemblyLexer";
|
import { AssemblyLexer, IdentToken, TokenType } from "./AssemblyLexer";
|
||||||
|
|
||||||
Logger.useDefaults({
|
|
||||||
defaultLevel: (Logger as any)[process.env["LOG_LEVEL"] || "INFO"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const ctx: Worker = self as any;
|
const ctx: Worker = self as any;
|
||||||
|
|
||||||
let lines: string[] = [];
|
let lines: string[] = [];
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { Instruction } from "../instructions";
|
import { Instruction } from "../instructions";
|
||||||
import {
|
import {
|
||||||
Kind,
|
Kind,
|
||||||
@ -26,8 +25,9 @@ import {
|
|||||||
} from "../opcodes";
|
} from "../opcodes";
|
||||||
import { BasicBlock, ControlFlowGraph } from "./ControlFlowGraph";
|
import { BasicBlock, ControlFlowGraph } from "./ControlFlowGraph";
|
||||||
import { ValueSet } from "./ValueSet";
|
import { ValueSet } from "./ValueSet";
|
||||||
|
import { LogManager } from "../../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/scripting/data_flow_analysis/register_value");
|
const logger = LogManager.get("quest_editor/scripting/data_flow_analysis/register_value");
|
||||||
|
|
||||||
export const MIN_REGISTER_VALUE = MIN_SIGNED_DWORD_VALUE;
|
export const MIN_REGISTER_VALUE = MIN_SIGNED_DWORD_VALUE;
|
||||||
export const MAX_REGISTER_VALUE = MAX_SIGNED_DWORD_VALUE;
|
export const MAX_REGISTER_VALUE = MAX_SIGNED_DWORD_VALUE;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import Logger from "js-logger";
|
|
||||||
import { Instruction } from "../instructions";
|
import { Instruction } from "../instructions";
|
||||||
import {
|
import {
|
||||||
MAX_SIGNED_DWORD_VALUE,
|
MAX_SIGNED_DWORD_VALUE,
|
||||||
@ -15,8 +14,9 @@ import {
|
|||||||
import { BasicBlock, ControlFlowGraph } from "./ControlFlowGraph";
|
import { BasicBlock, ControlFlowGraph } from "./ControlFlowGraph";
|
||||||
import { ValueSet } from "./ValueSet";
|
import { ValueSet } from "./ValueSet";
|
||||||
import { register_value } from "./register_value";
|
import { register_value } from "./register_value";
|
||||||
|
import { LogManager } from "../../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/scripting/data_flow_analysis/stack_value");
|
const logger = LogManager.get("quest_editor/scripting/data_flow_analysis/stack_value");
|
||||||
|
|
||||||
export const MIN_STACK_VALUE = MIN_SIGNED_DWORD_VALUE;
|
export const MIN_STACK_VALUE = MIN_SIGNED_DWORD_VALUE;
|
||||||
export const MAX_STACK_VALUE = MAX_SIGNED_DWORD_VALUE;
|
export const MAX_STACK_VALUE = MAX_SIGNED_DWORD_VALUE;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { reinterpret_i32_as_f32 } from "../../core/primitive_conversion";
|
import { reinterpret_i32_as_f32 } from "../../core/primitive_conversion";
|
||||||
import { Arg, Segment, SegmentType } from "./instructions";
|
import { Arg, Segment, SegmentType } from "./instructions";
|
||||||
import { AnyType, Kind, OP_VA_END, OP_VA_START, Param, StackInteraction } from "./opcodes";
|
import { AnyType, Kind, OP_VA_END, OP_VA_START, Param, StackInteraction } from "./opcodes";
|
||||||
import Logger from "js-logger";
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/scripting/disassembly");
|
const logger = LogManager.get("quest_editor/scripting/disassembly");
|
||||||
|
|
||||||
type ArgWithType = Arg & {
|
type ArgWithType = Arg & {
|
||||||
/**
|
/**
|
||||||
|
@ -101,9 +101,9 @@ import { Episode } from "../../../core/data_formats/parsing/quest/Episode";
|
|||||||
import { Endianness } from "../../../core/data_formats/Endianness";
|
import { Endianness } from "../../../core/data_formats/Endianness";
|
||||||
import { Random } from "./Random";
|
import { Random } from "./Random";
|
||||||
import { Memory } from "./Memory";
|
import { Memory } from "./Memory";
|
||||||
import Logger from "js-logger";
|
|
||||||
import { InstructionPointer } from "./InstructionPointer";
|
import { InstructionPointer } from "./InstructionPointer";
|
||||||
import { StackFrame, StepMode, Thread } from "./Thread";
|
import { StackFrame, StepMode, Thread } from "./Thread";
|
||||||
|
import { LogManager } from "../../../core/Logger";
|
||||||
|
|
||||||
export const REGISTER_COUNT = 256;
|
export const REGISTER_COUNT = 256;
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ const STRING_ARG_STORE_SIZE = 1024; // TODO: verify this value
|
|||||||
const ENTRY_SEGMENT = 0;
|
const ENTRY_SEGMENT = 0;
|
||||||
const LIST_ITEM_DELIMITER = "\n";
|
const LIST_ITEM_DELIMITER = "\n";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/scripting/vm/VirtualMachine");
|
const logger = LogManager.get("quest_editor/scripting/vm/VirtualMachine");
|
||||||
|
|
||||||
export enum ExecutionResult {
|
export enum ExecutionResult {
|
||||||
/**
|
/**
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { AsmToken } from "../instructions";
|
import { AsmToken } from "../instructions";
|
||||||
import Logger from "js-logger";
|
|
||||||
import { InstructionPointer } from "./InstructionPointer";
|
import { InstructionPointer } from "./InstructionPointer";
|
||||||
|
import { LogManager } from "../../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/scripting/vm/io");
|
const logger = LogManager.get("quest_editor/scripting/vm/io");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The virtual machine calls these methods when it requires input.
|
* The virtual machine calls these methods when it requires input.
|
||||||
|
@ -4,11 +4,14 @@ import { Episode, EPISODES } from "../../core/data_formats/parsing/quest/Episode
|
|||||||
import { SectionModel } from "../model/SectionModel";
|
import { SectionModel } from "../model/SectionModel";
|
||||||
import { get_areas_for_episode } from "../../core/data_formats/parsing/quest/areas";
|
import { get_areas_for_episode } from "../../core/data_formats/parsing/quest/areas";
|
||||||
import { AreaAssetLoader } from "../loading/AreaAssetLoader";
|
import { AreaAssetLoader } from "../loading/AreaAssetLoader";
|
||||||
|
import { Store } from "../../core/stores/Store";
|
||||||
|
|
||||||
export class AreaStore {
|
export class AreaStore extends Store {
|
||||||
private readonly areas: AreaModel[][] = [];
|
private readonly areas: AreaModel[][] = [];
|
||||||
|
|
||||||
constructor(private readonly area_asset_loader: AreaAssetLoader) {
|
constructor(private readonly area_asset_loader: AreaAssetLoader) {
|
||||||
|
super();
|
||||||
|
|
||||||
for (const episode of EPISODES) {
|
for (const episode of EPISODES) {
|
||||||
this.areas[episode] = get_areas_for_episode(episode).map(area => {
|
this.areas[episode] = get_areas_for_episode(episode).map(area => {
|
||||||
const observable_area = new AreaModel(area.id, area.name, area.order, []);
|
const observable_area = new AreaModel(area.id, area.name, area.order, []);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { editor, languages, MarkerSeverity, MarkerTag, Position } from "monaco-editor";
|
import { editor, languages, MarkerSeverity, MarkerTag, Position } from "monaco-editor";
|
||||||
import { AssemblyAnalyser } from "../scripting/AssemblyAnalyser";
|
import { AssemblyAnalyser } from "../scripting/AssemblyAnalyser";
|
||||||
import { Disposable } from "../../core/observable/Disposable";
|
|
||||||
import { Disposer } from "../../core/observable/Disposer";
|
import { Disposer } from "../../core/observable/Disposer";
|
||||||
import { SimpleUndo } from "../../core/undo/SimpleUndo";
|
import { SimpleUndo } from "../../core/undo/SimpleUndo";
|
||||||
import { ASM_SYNTAX } from "./asm_syntax";
|
import { ASM_SYNTAX } from "./asm_syntax";
|
||||||
@ -10,15 +9,16 @@ import { emitter, property } from "../../core/observable";
|
|||||||
import { WritableProperty } from "../../core/observable/property/WritableProperty";
|
import { WritableProperty } from "../../core/observable/property/WritableProperty";
|
||||||
import { Property } from "../../core/observable/property/Property";
|
import { Property } from "../../core/observable/property/Property";
|
||||||
import { ListProperty } from "../../core/observable/property/list/ListProperty";
|
import { ListProperty } from "../../core/observable/property/list/ListProperty";
|
||||||
|
import { Breakpoint } from "../scripting/vm/Debugger";
|
||||||
|
import { QuestEditorStore } from "./QuestEditorStore";
|
||||||
|
import { disposable_listener } from "../../core/gui/dom";
|
||||||
|
import { Store } from "../../core/stores/Store";
|
||||||
import ITextModel = editor.ITextModel;
|
import ITextModel = editor.ITextModel;
|
||||||
import CompletionList = languages.CompletionList;
|
import CompletionList = languages.CompletionList;
|
||||||
import IMarkerData = editor.IMarkerData;
|
import IMarkerData = editor.IMarkerData;
|
||||||
import SignatureHelpResult = languages.SignatureHelpResult;
|
import SignatureHelpResult = languages.SignatureHelpResult;
|
||||||
import LocationLink = languages.LocationLink;
|
import LocationLink = languages.LocationLink;
|
||||||
import IModelContentChange = editor.IModelContentChange;
|
import IModelContentChange = editor.IModelContentChange;
|
||||||
import { Breakpoint } from "../scripting/vm/Debugger";
|
|
||||||
import { QuestEditorStore } from "./QuestEditorStore";
|
|
||||||
import { disposable_listener } from "../../core/gui/dom";
|
|
||||||
|
|
||||||
const assembly_analyser = new AssemblyAnalyser();
|
const assembly_analyser = new AssemblyAnalyser();
|
||||||
|
|
||||||
@ -85,9 +85,8 @@ languages.registerDefinitionProvider("psoasm", {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export class AsmEditorStore implements Disposable {
|
export class AsmEditorStore extends Store {
|
||||||
private readonly disposer = new Disposer();
|
private readonly model_disposer = this.disposable(new Disposer());
|
||||||
private readonly model_disposer = this.disposer.add(new Disposer());
|
|
||||||
private readonly _model: WritableProperty<ITextModel | undefined> = property(undefined);
|
private readonly _model: WritableProperty<ITextModel | undefined> = property(undefined);
|
||||||
private readonly _did_undo = emitter<string>();
|
private readonly _did_undo = emitter<string>();
|
||||||
private readonly _did_redo = emitter<string>();
|
private readonly _did_redo = emitter<string>();
|
||||||
@ -109,10 +108,12 @@ export class AsmEditorStore implements Disposable {
|
|||||||
readonly pause_location: Property<number | undefined>;
|
readonly pause_location: Property<number | undefined>;
|
||||||
|
|
||||||
constructor(private readonly quest_editor_store: QuestEditorStore) {
|
constructor(private readonly quest_editor_store: QuestEditorStore) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.breakpoints = quest_editor_store.quest_runner.breakpoints;
|
this.breakpoints = quest_editor_store.quest_runner.breakpoints;
|
||||||
this.pause_location = quest_editor_store.quest_runner.pause_location;
|
this.pause_location = quest_editor_store.quest_runner.pause_location;
|
||||||
|
|
||||||
this.disposer.add_all(
|
this.disposables(
|
||||||
quest_editor_store.current_quest.observe(this.quest_changed, {
|
quest_editor_store.current_quest.observe(this.quest_changed, {
|
||||||
call_now: true,
|
call_now: true,
|
||||||
}),
|
}),
|
||||||
@ -130,10 +131,6 @@ export class AsmEditorStore implements Disposable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
|
||||||
this.disposer.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
set_inline_args_mode = (inline_args_mode: boolean): void => {
|
set_inline_args_mode = (inline_args_mode: boolean): void => {
|
||||||
// don't allow changing inline args mode if there are issues
|
// don't allow changing inline args mode if there are issues
|
||||||
if (!this.has_issues.val) {
|
if (!this.has_issues.val) {
|
||||||
|
@ -3,108 +3,69 @@ import { Property } from "../../core/observable/property/Property";
|
|||||||
import { list_property, property } from "../../core/observable";
|
import { list_property, property } from "../../core/observable";
|
||||||
import { Disposable } from "../../core/observable/Disposable";
|
import { Disposable } from "../../core/observable/Disposable";
|
||||||
import { Disposer } from "../../core/observable/Disposer";
|
import { Disposer } from "../../core/observable/Disposer";
|
||||||
import Logger from "js-logger";
|
import { LogEntry, Logger, LogHandler, LogLevel, LogManager } from "../../core/Logger";
|
||||||
import { enum_values } from "../../core/enums";
|
|
||||||
|
|
||||||
export type QuestLogger = {
|
const logger = LogManager.get("quest_editor/stroes/LogStore");
|
||||||
readonly debug: (message: string) => void;
|
|
||||||
readonly info: (message: string) => void;
|
|
||||||
readonly warning: (message: string) => void;
|
|
||||||
readonly error: (message: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Log level names in order of importance.
|
|
||||||
export enum LogLevel {
|
|
||||||
Debug,
|
|
||||||
Info,
|
|
||||||
Warning,
|
|
||||||
Error,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LogLevels = enum_values<LogLevel>(LogLevel);
|
|
||||||
|
|
||||||
export type LogMessage = {
|
|
||||||
readonly time: Date;
|
|
||||||
readonly message: string;
|
|
||||||
readonly level: LogLevel;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class LogStore implements Disposable {
|
export class LogStore implements Disposable {
|
||||||
private readonly disposer = new Disposer();
|
private readonly disposer = new Disposer();
|
||||||
private readonly default_log_level = LogLevel.Info;
|
private readonly default_log_level = LogLevel.Info;
|
||||||
|
|
||||||
private readonly message_buffer: LogMessage[] = [];
|
private readonly log_buffer: LogEntry[] = [];
|
||||||
private readonly logger_name_buffer: string[] = [];
|
private readonly logger_name_buffer: string[] = [];
|
||||||
|
|
||||||
private readonly _log_messages = list_property<LogMessage>();
|
private readonly _level = property<LogLevel>(this.default_log_level);
|
||||||
private readonly _log_level = property<LogLevel>(this.default_log_level);
|
private readonly _log = list_property<LogEntry>();
|
||||||
|
|
||||||
readonly log_messages: ListProperty<LogMessage>;
|
private readonly handler: LogHandler = (entry: LogEntry, logger_name: string): void => {
|
||||||
readonly log_level: Property<LogLevel> = this._log_level;
|
this.buffer_log_entry(entry, logger_name);
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
readonly level: Property<LogLevel> = this._level;
|
||||||
this.log_messages = this._log_messages.filtered(
|
readonly log: ListProperty<LogEntry> = this._log.filtered(
|
||||||
this.log_level.map(level => message => message.level >= level),
|
this.level.map(level => message => message.level >= level),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
get_logger(name: string): Logger {
|
||||||
|
const logger = LogManager.get(name);
|
||||||
|
logger.handler = this.handler;
|
||||||
|
return logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
this.disposer.dispose();
|
this.disposer.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
set_log_level(log_level: LogLevel): void {
|
set_level(log_level: LogLevel): void {
|
||||||
this._log_level.val = log_level;
|
this._level.val = log_level;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private buffer_log_entry(entry: LogEntry, logger_name: string): void {
|
||||||
* @param name - js-logger logger name
|
this.log_buffer.push(entry);
|
||||||
*/
|
|
||||||
get_logger(name: string): QuestLogger {
|
|
||||||
return {
|
|
||||||
debug: (message: string): void => {
|
|
||||||
this.buffer_log_message(message, LogLevel.Debug, name);
|
|
||||||
},
|
|
||||||
info: (message: string): void => {
|
|
||||||
this.buffer_log_message(message, LogLevel.Info, name);
|
|
||||||
},
|
|
||||||
warning: (message: string): void => {
|
|
||||||
this.buffer_log_message(message, LogLevel.Warning, name);
|
|
||||||
},
|
|
||||||
error: (message: string): void => {
|
|
||||||
this.buffer_log_message(message, LogLevel.Error, name);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private buffer_log_message(message: string, level: LogLevel, logger_name: string): void {
|
|
||||||
this.message_buffer.push({
|
|
||||||
time: new Date(),
|
|
||||||
message,
|
|
||||||
level,
|
|
||||||
});
|
|
||||||
this.logger_name_buffer.push(logger_name);
|
this.logger_name_buffer.push(logger_name);
|
||||||
|
|
||||||
this.add_buffered_log_messages();
|
this.add_buffered_log_entries();
|
||||||
}
|
}
|
||||||
|
|
||||||
private adding_log_messages?: number;
|
private adding_log_entries?: number;
|
||||||
|
|
||||||
private add_buffered_log_messages(): void {
|
private add_buffered_log_entries(): void {
|
||||||
if (this.adding_log_messages != undefined) return;
|
if (this.adding_log_entries != undefined) return;
|
||||||
|
|
||||||
this.adding_log_messages = requestAnimationFrame(() => {
|
this.adding_log_entries = requestAnimationFrame(() => {
|
||||||
const DROP_THRESHOLD = 500;
|
const DROP_THRESHOLD = 500;
|
||||||
const DROP_THRESHOLD_HALF = DROP_THRESHOLD / 2;
|
const DROP_THRESHOLD_HALF = DROP_THRESHOLD / 2;
|
||||||
const BATCH_SIZE = 200;
|
const BATCH_SIZE = 200;
|
||||||
|
|
||||||
// Drop messages if there are too many.
|
// Drop log entries if there are too many.
|
||||||
if (this.message_buffer.length > DROP_THRESHOLD) {
|
if (this.log_buffer.length > DROP_THRESHOLD) {
|
||||||
const drop_len = this.message_buffer.length - DROP_THRESHOLD;
|
const drop_len = this.log_buffer.length - DROP_THRESHOLD;
|
||||||
|
|
||||||
this.message_buffer.splice(DROP_THRESHOLD_HALF, drop_len, {
|
this.log_buffer.splice(DROP_THRESHOLD_HALF, drop_len, {
|
||||||
time: new Date(),
|
time: new Date(),
|
||||||
message: `...dropped ${drop_len} messages...`,
|
message: `...dropped ${drop_len} messages...`,
|
||||||
level: LogLevel.Warning,
|
level: LogLevel.Warn,
|
||||||
|
logger,
|
||||||
});
|
});
|
||||||
this.logger_name_buffer.splice(
|
this.logger_name_buffer.splice(
|
||||||
DROP_THRESHOLD_HALF,
|
DROP_THRESHOLD_HALF,
|
||||||
@ -113,42 +74,28 @@ export class LogStore implements Disposable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const len = Math.min(BATCH_SIZE, this.message_buffer.length);
|
const len = Math.min(BATCH_SIZE, this.log_buffer.length);
|
||||||
|
|
||||||
const buffered_messages = this.message_buffer.splice(0, len);
|
const buffered_entries = this.log_buffer.splice(0, len);
|
||||||
const buffered_logger_names = this.logger_name_buffer.splice(0, len);
|
const buffered_logger_names = this.logger_name_buffer.splice(0, len);
|
||||||
|
|
||||||
this._log_messages.push(...buffered_messages);
|
this._log.push(...buffered_entries);
|
||||||
|
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
const { level, message } = buffered_messages[i];
|
const entry = buffered_entries[i];
|
||||||
const logger_name = buffered_logger_names[i];
|
const logger_name = buffered_logger_names[i];
|
||||||
|
LogManager.default_handler(entry, logger_name);
|
||||||
switch (level) {
|
|
||||||
case LogLevel.Debug:
|
|
||||||
Logger.get(logger_name).debug(message);
|
|
||||||
break;
|
|
||||||
case LogLevel.Info:
|
|
||||||
Logger.get(logger_name).info(message);
|
|
||||||
break;
|
|
||||||
case LogLevel.Warning:
|
|
||||||
Logger.get(logger_name).warn(message);
|
|
||||||
break;
|
|
||||||
case LogLevel.Error:
|
|
||||||
Logger.get(logger_name).error(message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Occasionally clean up old log messages if there are too many.
|
// Occasionally clean up old log messages if there are too many.
|
||||||
if (this._log_messages.length.val > 2000) {
|
if (this._log.length.val > 2000) {
|
||||||
this._log_messages.splice(0, 1000);
|
this._log.splice(0, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.adding_log_messages = undefined;
|
this.adding_log_entries = undefined;
|
||||||
|
|
||||||
if (this.message_buffer.length) {
|
if (this.log_buffer.length) {
|
||||||
this.add_buffered_log_messages();
|
this.add_buffered_log_entries();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@ import { QuestNpcModel } from "../model/QuestNpcModel";
|
|||||||
import { AreaModel } from "../model/AreaModel";
|
import { AreaModel } from "../model/AreaModel";
|
||||||
import { SectionModel } from "../model/SectionModel";
|
import { SectionModel } from "../model/SectionModel";
|
||||||
import { QuestEntityModel } from "../model/QuestEntityModel";
|
import { QuestEntityModel } from "../model/QuestEntityModel";
|
||||||
import { Disposable } from "../../core/observable/Disposable";
|
|
||||||
import { Disposer } from "../../core/observable/Disposer";
|
|
||||||
import { GuiStore, GuiTool } from "../../core/stores/GuiStore";
|
import { GuiStore, GuiTool } from "../../core/stores/GuiStore";
|
||||||
import { UndoStack } from "../../core/undo/UndoStack";
|
import { UndoStack } from "../../core/undo/UndoStack";
|
||||||
import { TranslateEntityAction } from "../actions/TranslateEntityAction";
|
import { TranslateEntityAction } from "../actions/TranslateEntityAction";
|
||||||
@ -22,12 +20,12 @@ import { disposable_listener } from "../../core/gui/dom";
|
|||||||
import { QuestEventModel } from "../model/QuestEventModel";
|
import { QuestEventModel } from "../model/QuestEventModel";
|
||||||
import { EditEventSectionIdAction } from "../actions/EditEventSectionIdAction";
|
import { EditEventSectionIdAction } from "../actions/EditEventSectionIdAction";
|
||||||
import { EditEventDelayAction } from "../actions/EditEventDelayAction";
|
import { EditEventDelayAction } from "../actions/EditEventDelayAction";
|
||||||
import Logger = require("js-logger");
|
import { Store } from "../../core/stores/Store";
|
||||||
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/gui/QuestEditorStore");
|
const logger = LogManager.get("quest_editor/gui/QuestEditorStore");
|
||||||
|
|
||||||
export class QuestEditorStore implements Disposable {
|
export class QuestEditorStore extends Store {
|
||||||
private readonly disposer = new Disposer();
|
|
||||||
private readonly _current_quest = property<QuestModel | undefined>(undefined);
|
private readonly _current_quest = property<QuestModel | undefined>(undefined);
|
||||||
private readonly _current_area = property<AreaModel | undefined>(undefined);
|
private readonly _current_area = property<AreaModel | undefined>(undefined);
|
||||||
private readonly _selected_entity = property<QuestEntityModel | undefined>(undefined);
|
private readonly _selected_entity = property<QuestEntityModel | undefined>(undefined);
|
||||||
@ -40,9 +38,11 @@ export class QuestEditorStore implements Disposable {
|
|||||||
readonly selected_entity: Property<QuestEntityModel | undefined> = this._selected_entity;
|
readonly selected_entity: Property<QuestEntityModel | undefined> = this._selected_entity;
|
||||||
|
|
||||||
constructor(gui_store: GuiStore, private readonly area_store: AreaStore) {
|
constructor(gui_store: GuiStore, private readonly area_store: AreaStore) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.quest_runner = new QuestRunner(area_store);
|
this.quest_runner = new QuestRunner(area_store);
|
||||||
|
|
||||||
this.disposer.add_all(
|
this.disposables(
|
||||||
gui_store.tool.observe(
|
gui_store.tool.observe(
|
||||||
({ value: tool }) => {
|
({ value: tool }) => {
|
||||||
if (tool === GuiTool.QuestEditor) {
|
if (tool === GuiTool.QuestEditor) {
|
||||||
@ -85,7 +85,7 @@ export class QuestEditorStore implements Disposable {
|
|||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
this.quest_runner.stop();
|
this.quest_runner.stop();
|
||||||
this.disposer.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
set_current_area = (area?: AreaModel): void => {
|
set_current_area = (area?: AreaModel): void => {
|
||||||
|
@ -17,11 +17,11 @@ import {
|
|||||||
} from "../model/QuestEventActionModel";
|
} from "../model/QuestEventActionModel";
|
||||||
import { QuestEventDagModel, QuestEventDagModelMeta } from "../model/QuestEventDagModel";
|
import { QuestEventDagModel, QuestEventDagModelMeta } from "../model/QuestEventDagModel";
|
||||||
import { QuestEvent } from "../../core/data_formats/parsing/quest/entities";
|
import { QuestEvent } from "../../core/data_formats/parsing/quest/entities";
|
||||||
import Logger from "js-logger";
|
|
||||||
import { clone_segment } from "../scripting/instructions";
|
import { clone_segment } from "../scripting/instructions";
|
||||||
import { AreaStore } from "./AreaStore";
|
import { AreaStore } from "./AreaStore";
|
||||||
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("quest_editor/stores/model_conversion");
|
const logger = LogManager.get("quest_editor/stores/model_conversion");
|
||||||
|
|
||||||
export function convert_quest_to_model(area_store: AreaStore, quest: Quest): QuestModel {
|
export function convert_quest_to_model(area_store: AreaStore, quest: Quest): QuestModel {
|
||||||
// Create quest model.
|
// Create quest model.
|
||||||
|
@ -2,30 +2,53 @@ import { ViewerView } from "./gui/ViewerView";
|
|||||||
import { GuiStore } from "../core/stores/GuiStore";
|
import { GuiStore } from "../core/stores/GuiStore";
|
||||||
import { HttpClient } from "../core/HttpClient";
|
import { HttpClient } from "../core/HttpClient";
|
||||||
import { DisposableThreeRenderer } from "../core/rendering/Renderer";
|
import { DisposableThreeRenderer } from "../core/rendering/Renderer";
|
||||||
|
import { Disposable } from "../core/observable/Disposable";
|
||||||
|
import { Disposer } from "../core/observable/Disposer";
|
||||||
|
|
||||||
export function initialize_viewer(
|
export function initialize_viewer(
|
||||||
http_client: HttpClient,
|
http_client: HttpClient,
|
||||||
gui_store: GuiStore,
|
gui_store: GuiStore,
|
||||||
create_three_renderer: () => DisposableThreeRenderer,
|
create_three_renderer: () => DisposableThreeRenderer,
|
||||||
): ViewerView {
|
): { view: ViewerView } & Disposable {
|
||||||
return new ViewerView(
|
const disposer = new Disposer();
|
||||||
|
|
||||||
|
const view = new ViewerView(
|
||||||
async () => {
|
async () => {
|
||||||
const { Model3DStore } = await import("./stores/Model3DStore");
|
const { Model3DStore } = await import("./stores/Model3DStore");
|
||||||
const { Model3DView } = await import("./gui/model_3d/Model3DView");
|
const { Model3DView } = await import("./gui/model_3d/Model3DView");
|
||||||
const { CharacterClassAssetLoader } = await import(
|
const { CharacterClassAssetLoader } = await import(
|
||||||
"./loading/CharacterClassAssetLoader"
|
"./loading/CharacterClassAssetLoader"
|
||||||
);
|
);
|
||||||
return new Model3DView(
|
const store = new Model3DStore(new CharacterClassAssetLoader(http_client));
|
||||||
gui_store,
|
|
||||||
new Model3DStore(new CharacterClassAssetLoader(http_client)),
|
if (disposer.disposed) {
|
||||||
create_three_renderer(),
|
store.dispose();
|
||||||
);
|
} else {
|
||||||
|
disposer.add(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Model3DView(gui_store, store, create_three_renderer());
|
||||||
},
|
},
|
||||||
|
|
||||||
async () => {
|
async () => {
|
||||||
const { TextureStore } = await import("./stores/TextureStore");
|
const { TextureStore } = await import("./stores/TextureStore");
|
||||||
const { TextureView } = await import("./gui/TextureView");
|
const { TextureView } = await import("./gui/TextureView");
|
||||||
return new TextureView(gui_store, new TextureStore(), create_three_renderer());
|
const store = new TextureStore();
|
||||||
|
|
||||||
|
if (disposer.disposed) {
|
||||||
|
store.dispose();
|
||||||
|
} else {
|
||||||
|
disposer.add(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TextureView(gui_store, store, create_three_renderer());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
view,
|
||||||
|
dispose(): void {
|
||||||
|
disposer.dispose();
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,9 @@ import { Disposer } from "../../core/observable/Disposer";
|
|||||||
import { Xvm } from "../../core/data_formats/parsing/ninja/texture";
|
import { Xvm } from "../../core/data_formats/parsing/ninja/texture";
|
||||||
import { xvm_texture_to_texture } from "../../core/rendering/conversion/ninja_textures";
|
import { xvm_texture_to_texture } from "../../core/rendering/conversion/ninja_textures";
|
||||||
import { TextureStore } from "../stores/TextureStore";
|
import { TextureStore } from "../stores/TextureStore";
|
||||||
import Logger = require("js-logger");
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("viewer/rendering/TextureRenderer");
|
const logger = LogManager.get("viewer/rendering/TextureRenderer");
|
||||||
|
|
||||||
export class TextureRenderer extends Renderer implements Disposable {
|
export class TextureRenderer extends Renderer implements Disposable {
|
||||||
private readonly disposer = new Disposer();
|
private readonly disposer = new Disposer();
|
||||||
|
@ -5,16 +5,16 @@ import { NjObject, parse_nj, parse_xj } from "../../core/data_formats/parsing/ni
|
|||||||
import { CharacterClassModel } from "../model/CharacterClassModel";
|
import { CharacterClassModel } from "../model/CharacterClassModel";
|
||||||
import { CharacterClassAnimationModel } from "../model/CharacterClassAnimationModel";
|
import { CharacterClassAnimationModel } from "../model/CharacterClassAnimationModel";
|
||||||
import { WritableProperty } from "../../core/observable/property/WritableProperty";
|
import { WritableProperty } from "../../core/observable/property/WritableProperty";
|
||||||
import { Disposable } from "../../core/observable/Disposable";
|
|
||||||
import { read_file } from "../../core/read_file";
|
import { read_file } from "../../core/read_file";
|
||||||
import { property } from "../../core/observable";
|
import { property } from "../../core/observable";
|
||||||
import { Property } from "../../core/observable/property/Property";
|
import { Property } from "../../core/observable/property/Property";
|
||||||
import { PSO_FRAME_RATE } from "../../core/rendering/conversion/ninja_animation";
|
import { PSO_FRAME_RATE } from "../../core/rendering/conversion/ninja_animation";
|
||||||
import { parse_xvm, Xvm } from "../../core/data_formats/parsing/ninja/texture";
|
import { parse_xvm, Xvm } from "../../core/data_formats/parsing/ninja/texture";
|
||||||
import Logger = require("js-logger");
|
|
||||||
import { CharacterClassAssetLoader } from "../loading/CharacterClassAssetLoader";
|
import { CharacterClassAssetLoader } from "../loading/CharacterClassAssetLoader";
|
||||||
|
import { Store } from "../../core/stores/Store";
|
||||||
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("viewer/stores/ModelStore");
|
const logger = LogManager.get("viewer/stores/ModelStore");
|
||||||
const nj_object_cache: Map<string, Promise<NjObject>> = new Map();
|
const nj_object_cache: Map<string, Promise<NjObject>> = new Map();
|
||||||
const nj_motion_cache: Map<number, Promise<NjMotion>> = new Map();
|
const nj_motion_cache: Map<number, Promise<NjMotion>> = new Map();
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ export type NjData = {
|
|||||||
has_skeleton: boolean;
|
has_skeleton: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Model3DStore implements Disposable {
|
export class Model3DStore extends Store {
|
||||||
private readonly _current_model: WritableProperty<CharacterClassModel | undefined> = property(
|
private readonly _current_model: WritableProperty<CharacterClassModel | undefined> = property(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
@ -38,7 +38,6 @@ export class Model3DStore implements Disposable {
|
|||||||
private readonly _animation_playing: WritableProperty<boolean> = property(true);
|
private readonly _animation_playing: WritableProperty<boolean> = property(true);
|
||||||
private readonly _animation_frame_rate: WritableProperty<number> = property(PSO_FRAME_RATE);
|
private readonly _animation_frame_rate: WritableProperty<number> = property(PSO_FRAME_RATE);
|
||||||
private readonly _animation_frame: WritableProperty<number> = property(0);
|
private readonly _animation_frame: WritableProperty<number> = property(0);
|
||||||
private readonly disposables: Disposable[] = [];
|
|
||||||
|
|
||||||
readonly models: readonly CharacterClassModel[] = [
|
readonly models: readonly CharacterClassModel[] = [
|
||||||
new CharacterClassModel("HUmar", 1, 10, new Set([6])),
|
new CharacterClassModel("HUmar", 1, 10, new Set([6])),
|
||||||
@ -74,16 +73,14 @@ export class Model3DStore implements Disposable {
|
|||||||
);
|
);
|
||||||
|
|
||||||
constructor(private readonly asset_loader: CharacterClassAssetLoader) {
|
constructor(private readonly asset_loader: CharacterClassAssetLoader) {
|
||||||
this.disposables.push(
|
super();
|
||||||
|
|
||||||
|
this.disposables(
|
||||||
this.current_model.observe(({ value }) => this.load_model(value)),
|
this.current_model.observe(({ value }) => this.load_model(value)),
|
||||||
this.current_animation.observe(({ value }) => this.load_animation(value)),
|
this.current_animation.observe(({ value }) => this.load_animation(value)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
|
||||||
this.disposables.forEach(d => d.dispose());
|
|
||||||
}
|
|
||||||
|
|
||||||
set_current_model = (current_model: CharacterClassModel): void => {
|
set_current_model = (current_model: CharacterClassModel): void => {
|
||||||
this._current_model.val = current_model;
|
this._current_model.val = current_model;
|
||||||
};
|
};
|
||||||
|
@ -4,11 +4,12 @@ import { Property } from "../../core/observable/property/Property";
|
|||||||
import { read_file } from "../../core/read_file";
|
import { read_file } from "../../core/read_file";
|
||||||
import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCursor";
|
import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCursor";
|
||||||
import { Endianness } from "../../core/data_formats/Endianness";
|
import { Endianness } from "../../core/data_formats/Endianness";
|
||||||
import Logger = require("js-logger");
|
import { Store } from "../../core/stores/Store";
|
||||||
|
import { LogManager } from "../../core/Logger";
|
||||||
|
|
||||||
const logger = Logger.get("viewer/stores/TextureStore");
|
const logger = LogManager.get("viewer/stores/TextureStore");
|
||||||
|
|
||||||
export class TextureStore {
|
export class TextureStore extends Store {
|
||||||
private readonly _current_xvm = property<Xvm | undefined>(undefined);
|
private readonly _current_xvm = property<Xvm | undefined>(undefined);
|
||||||
readonly current_xvm: Property<Xvm | undefined> = this._current_xvm;
|
readonly current_xvm: Property<Xvm | undefined> = this._current_xvm;
|
||||||
|
|
||||||
|
@ -5,9 +5,8 @@ export class FileSystemHttpClient implements HttpClient {
|
|||||||
get(url: string): HttpResponse {
|
get(url: string): HttpResponse {
|
||||||
return {
|
return {
|
||||||
async json<T>(): Promise<T> {
|
async json<T>(): Promise<T> {
|
||||||
throw new Error(
|
const buf = await fs.promises.readFile(`./assets${url}`);
|
||||||
`FileSystemHttpClient's json method invoked for get request to "${url}".`,
|
return JSON.parse(buf.toString());
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async array_buffer(): Promise<ArrayBuffer> {
|
async array_buffer(): Promise<ArrayBuffer> {
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
const Logger = require("js-logger");
|
require("dotenv").config({ path: ".env.test" });
|
||||||
require('dotenv').config({ path: ".env.test" })
|
|
||||||
|
|
||||||
const log_level = process.env["LOG_LEVEL"] || "WARN";
|
// For GoldenLayout.
|
||||||
|
window.$ = require("jquery");
|
||||||
Logger.useDefaults({
|
|
||||||
defaultLevel: Logger[log_level],
|
|
||||||
});
|
|
||||||
|
@ -2,6 +2,16 @@ import * as fs from "fs";
|
|||||||
import { InstructionSegment, SegmentType } from "../../src/quest_editor/scripting/instructions";
|
import { InstructionSegment, SegmentType } from "../../src/quest_editor/scripting/instructions";
|
||||||
import { assemble } from "../../src/quest_editor/scripting/assembly";
|
import { assemble } from "../../src/quest_editor/scripting/assembly";
|
||||||
|
|
||||||
|
export async function timeout(millis: number): Promise<void> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => resolve(), millis);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function next_animation_frame(): Promise<void> {
|
||||||
|
return new Promise(resolve => requestAnimationFrame(() => resolve()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies f to all QST files in a directory.
|
* Applies f to all QST files in a directory.
|
||||||
* f is called with the path to the file, the file name and the content of the file.
|
* f is called with the path to the file, the file name and the content of the file.
|
||||||
|
@ -1,25 +1,18 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"module": "commonjs",
|
||||||
"lib": [
|
"target": "es6",
|
||||||
"dom",
|
"lib": ["es6", "dom", "dom.iterable"],
|
||||||
"dom.iterable",
|
"allowJs": true,
|
||||||
"esnext"
|
"skipLibCheck": true,
|
||||||
],
|
"esModuleInterop": true,
|
||||||
"allowJs": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"skipLibCheck": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"resolveJsonModule": true,
|
||||||
"strict": true,
|
"moduleResolution": "node",
|
||||||
"forceConsistentCasingInFileNames": true,
|
"noEmit": true,
|
||||||
"module": "commonjs",
|
"experimentalDecorators": true
|
||||||
"moduleResolution": "node",
|
},
|
||||||
"resolveJsonModule": true,
|
"include": ["static_generation"]
|
||||||
"noEmit": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"downlevelIteration": true
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"static_generation"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist/",
|
"outDir": "./dist/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"module": "commonjs",
|
"module": "esnext",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"lib": ["es6", "dom", "dom.iterable"],
|
"lib": ["es6", "dom", "dom.iterable"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
@ -9,7 +9,20 @@ module.exports = {
|
|||||||
path: path.resolve(__dirname, "dist"),
|
path: path.resolve(__dirname, "dist"),
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".js", ".ts", ".tsx"],
|
extensions: [".js", ".ts"],
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /^worker-loader!/,
|
||||||
|
loader: "worker-loader",
|
||||||
|
options: { name: "worker.[hash].js" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(gif|jpg|png|svg|ttf)$/,
|
||||||
|
loader: "file-loader",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
|
@ -8,19 +8,18 @@ const Dotenv = require("dotenv-webpack");
|
|||||||
module.exports = merge(common, {
|
module.exports = merge(common, {
|
||||||
mode: "development",
|
mode: "development",
|
||||||
devtool: "eval-source-map",
|
devtool: "eval-source-map",
|
||||||
|
devServer: {
|
||||||
|
port: 1623,
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
use: [
|
loader: "ts-loader",
|
||||||
{
|
options: {
|
||||||
loader: "ts-loader",
|
// fork-ts-checker-webpack-plugin does the type checking in a separate process.
|
||||||
options: {
|
transpileOnly: true,
|
||||||
// fork-ts-checker-webpack-plugin does the type checking in a separate process.
|
},
|
||||||
transpileOnly: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
include: path.resolve(__dirname, "src"),
|
include: path.resolve(__dirname, "src"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -30,6 +29,7 @@ module.exports = merge(common, {
|
|||||||
{
|
{
|
||||||
loader: MiniCssExtractPlugin.loader,
|
loader: MiniCssExtractPlugin.loader,
|
||||||
options: {
|
options: {
|
||||||
|
esModule: true,
|
||||||
hmr: true,
|
hmr: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -54,10 +54,6 @@ module.exports = merge(common, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
test: /\.(gif|jpg|png|svg|ttf)$/,
|
|
||||||
use: ["file-loader"],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
@ -65,6 +61,8 @@ module.exports = merge(common, {
|
|||||||
path: "./.env.dev",
|
path: "./.env.dev",
|
||||||
}),
|
}),
|
||||||
new ForkTsCheckerWebpackPlugin(),
|
new ForkTsCheckerWebpackPlugin(),
|
||||||
new MiniCssExtractPlugin(),
|
new MiniCssExtractPlugin({
|
||||||
|
ignoreOrder: true,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -34,17 +34,13 @@ module.exports = merge(common, {
|
|||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
use: "ts-loader",
|
loader: "ts-loader",
|
||||||
include: path.resolve(__dirname, "src"),
|
include: path.resolve(__dirname, "src"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
use: [MiniCssExtractPlugin.loader, "css-loader"],
|
use: [MiniCssExtractPlugin.loader, "css-loader"],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
test: /\.(gif|jpg|png|svg|ttf)$/,
|
|
||||||
use: ["file-loader"],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
@ -53,6 +49,7 @@ module.exports = merge(common, {
|
|||||||
path: "./.env.prod",
|
path: "./.env.prod",
|
||||||
}),
|
}),
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
|
ignoreOrder: true,
|
||||||
filename: "[name].[contenthash].css",
|
filename: "[name].[contenthash].css",
|
||||||
}),
|
}),
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin([
|
||||||
|
@ -4388,11 +4388,6 @@ jquery@*:
|
|||||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
|
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
|
||||||
integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==
|
integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==
|
||||||
|
|
||||||
js-logger@^1.6.0:
|
|
||||||
version "1.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/js-logger/-/js-logger-1.6.0.tgz#7abae5cfaf208c965f3ef20754533bb9e79c7aef"
|
|
||||||
integrity sha512-K4kt2AdD0jUYINbe00BPPpsL65u/rdYOgfaBBVWm/mid+ANk7qxDnoXgKI5ilm49Sjmach2Dzlc+5VxKdRA3tw==
|
|
||||||
|
|
||||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
|
Loading…
Reference in New Issue
Block a user