mirror of
https://github.com/skillbert/rsmv.git
synced 2025-12-23 21:47:48 -05:00
add skeletal anims back to viewer
This commit is contained in:
@@ -2,7 +2,7 @@ import { Stream, packedHSL2HSL, HSL2RGB } from "./utils";
|
||||
import { cacheMajors } from "../constants";
|
||||
import { CacheFileSource, SubFile } from "../cache";
|
||||
import { parseFrames, parseFramemaps, parseSequences, parseSkeletalAnim } from "../opdecoder";
|
||||
import { AnimationClip, Bone, Euler, KeyframeTrack, Matrix3, Matrix4, Object3D, Quaternion, QuaternionKeyframeTrack, Skeleton, Vector3, VectorKeyframeTrack } from "three";
|
||||
import { AnimationClip, Bone, Euler, KeyframeTrack, Matrix3, Matrix4, Object3D, Quaternion, QuaternionKeyframeTrack, Skeleton, SkeletonHelper, SkinnedMesh, Vector3, VectorKeyframeTrack } from "three";
|
||||
import { skeletalanim } from "../../generated/skeletalanim";
|
||||
import { framemaps } from "../../generated/framemaps";
|
||||
import { ThreejsSceneCache } from "./ob3tothree";
|
||||
@@ -21,22 +21,14 @@ import { MountableAnimation } from "./animationframes";
|
||||
//28895 elder egg, currently bugged
|
||||
|
||||
|
||||
export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: number): Promise<MountableAnimation> {
|
||||
let anim = parseSkeletalAnim.read(await cache.getFileById(cacheMajors.skeletalAnims, animid));
|
||||
let base = parseFramemaps.read(await cache.getFileById(cacheMajors.framemaps, anim.framebase));
|
||||
export async function mountSkeletalSkeleton(rootnode: Object3D, cache: ThreejsSceneCache, framebaseid: number) {
|
||||
let base = parseFramemaps.read(await cache.getFileById(cacheMajors.framemaps, framebaseid));
|
||||
if (!base.skeleton) {
|
||||
throw new Error("framebase does not have skeleton");
|
||||
}
|
||||
|
||||
let convertedtracks: KeyframeTrack[] = [];
|
||||
|
||||
let animtracks = anim.tracks.sort((a, b) => {
|
||||
if (a.boneid != b.boneid) { return a.boneid - b.boneid; }
|
||||
return a.type_0to9 - b.type_0to9;
|
||||
});
|
||||
|
||||
let bones: Bone[] = [];
|
||||
let binds: Matrix4[] = [];
|
||||
// let binds: Matrix4[] = [];
|
||||
let rootbones: Bone[] = [];
|
||||
let tmp = new Matrix4();
|
||||
let prematrix = new Matrix4().makeScale(1, 1, -1);
|
||||
@@ -59,12 +51,35 @@ export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: n
|
||||
// "", +bone.scale.x.toFixed(2), +bone.scale.y.toFixed(2), +bone.scale.z.toFixed(2));
|
||||
bone.updateMatrixWorld();
|
||||
bones[id] = bone;
|
||||
binds[id] = matrix;
|
||||
// binds[id] = matrix;
|
||||
}
|
||||
prematrix.invert();
|
||||
binds.forEach(q => q.multiply(prematrix));
|
||||
// prematrix.invert();
|
||||
// binds.forEach(q => q.multiply(prematrix));
|
||||
|
||||
let skeleton = new Skeleton(bones);
|
||||
|
||||
if (rootbones.length != 0) { rootnode.add(...rootbones); }
|
||||
rootnode.updateMatrixWorld(true);
|
||||
let childbind = new Matrix4().copy(rootnode.matrixWorld);
|
||||
//TODO find out whats wrong with my own inverses
|
||||
skeleton.calculateInverses();
|
||||
rootnode.traverse(node => {
|
||||
if (node instanceof SkinnedMesh) {
|
||||
node.bind(skeleton, childbind);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: number) {
|
||||
let anim = parseSkeletalAnim.read(await cache.getFileById(cacheMajors.skeletalAnims, animid));
|
||||
|
||||
let convertedtracks: KeyframeTrack[] = [];
|
||||
|
||||
let animtracks = anim.tracks.sort((a, b) => {
|
||||
if (a.boneid != b.boneid) { return a.boneid - b.boneid; }
|
||||
return a.type_0to9 - b.type_0to9;
|
||||
});
|
||||
|
||||
let actiontypemap: { t: "unknown" | "rotate" | "translate" | "scale", a: number }[] = [
|
||||
{ t: "unknown", a: 0 },
|
||||
|
||||
@@ -80,16 +95,16 @@ export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: n
|
||||
{ t: "scale", a: 2 },
|
||||
|
||||
//10-16 unknown
|
||||
{ t: "unknown", a: 0 },
|
||||
{ t: "unknown", a: 0 },
|
||||
{ t: "unknown", a: 0 },
|
||||
{ t: "unknown", a: 0 },
|
||||
{ t: "unknown", a: 0 },
|
||||
{ t: "unknown", a: 0 },
|
||||
{ t: "unknown", a: 0 },
|
||||
{ t: "unknown", a: 0 },//109 hits, -1 3x, 0 103x, 1 3x
|
||||
{ t: "unknown", a: 0 },//109 hits, -1 6x, 0 103x
|
||||
{ t: "unknown", a: 0 },//109 hits, -1 4x, 0 94x, 1 15x
|
||||
{ t: "unknown", a: 0 },//4k hits, 0x 3400, 42 600x
|
||||
{ t: "unknown", a: 0 },//4k hits, sort of spread between 0-0.015, most at bounderies
|
||||
{ t: "unknown", a: 0 },//4k hits, spread between -0.3-0.1, most at bounderies
|
||||
{ t: "unknown", a: 0 },//2k hits, spread between -1-1, most at bounderies or 0
|
||||
]
|
||||
|
||||
|
||||
console.log(animtracks);
|
||||
for (let index = 0; index < animtracks.length;) {
|
||||
let track = animtracks[index];
|
||||
|
||||
@@ -110,13 +125,9 @@ export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: n
|
||||
if (t2.a == 2) { zvalues = track2.chunks; }
|
||||
index++;
|
||||
}
|
||||
|
||||
let bone = bones[boneid];
|
||||
if (!bone) {
|
||||
console.log("animation track without bone", boneid, track.boneid);
|
||||
continue;
|
||||
}
|
||||
let bonename = bone.name;
|
||||
// if (track.bonetype_01or3 == 3) { continue; }
|
||||
// if (boneid >= 6 && boneid <= 8) { continue; }
|
||||
let bonename = "bone_" + boneid;
|
||||
|
||||
let defaultvalue = (tracktype.t == "scale" ? 1 : 9);
|
||||
let intp = (v: { time: number, value: number[] }[] | null, i: number, t: number) => {
|
||||
@@ -131,6 +142,9 @@ export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: n
|
||||
let data: number[] = [];
|
||||
let euler = new Euler();
|
||||
let quat = new Quaternion();
|
||||
// if (tracktype.t == "scale") {
|
||||
// console.log(xvalues, yvalues, zvalues);
|
||||
// }
|
||||
// let time = new Float32Array(timearray.map(q => q * 0.020));
|
||||
for (let ix = 0, iy = 0, iz = 0, idata = 0; ;) {
|
||||
let tx = xvalues?.[ix]?.time ?? Infinity;
|
||||
@@ -177,5 +191,5 @@ export async function parseSkeletalAnimation(cache: ThreejsSceneCache, animid: n
|
||||
let clip = new AnimationClip("anim_" + (Math.random() * 1000 | 0), undefined, convertedtracks);
|
||||
|
||||
|
||||
return { skeleton, clip, rootbones };
|
||||
return { clip, framebaseid: anim.framebase };
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ export function parseOb3Model(modelfile: Buffer) {
|
||||
let boneid = rawbuf[dataindex++] | (rawbuf[dataindex++] << 8);//manual 16bit building since it might not be alligned
|
||||
let actualweight = (weight != 0 ? weight : remainder);
|
||||
remainder -= weight;
|
||||
skinIdBuffer[i * 4 + j] = (boneid == 65535 ? 0 : boneid);
|
||||
skinIdBuffer[i * 4 + j] = (boneid == 65535 ? 0 : boneid);//TODO this should be boneid+1since we're shifting in -1 to 0?
|
||||
skinWeightBuffer[i * 4 + j] = actualweight;
|
||||
if (boneid >= bonecount) {
|
||||
bonecount = boneid + 2;//we are adding a root bone at 0, and count is max+1
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
let filehandle = null;
|
||||
|
||||
setInterval(() => {
|
||||
filehandle?.requestPermission().then(q => console.log(q));
|
||||
filehandle?.requestPermission();
|
||||
}, 1000 * 60);
|
||||
|
||||
onmessage = (e) => {
|
||||
|
||||
@@ -4,10 +4,16 @@
|
||||
["endtime","uint"],
|
||||
["unk_always0","ubyte"],
|
||||
["tracks",["array","ushort",["struct",
|
||||
//seems to correlate to action type
|
||||
//1=standard bone(1-9) 2=unknown(7x 3, 14x 7, 14x 8), 3=unknown(10-15), 3=unknown(16)
|
||||
["unk_1to4","ubyte"],
|
||||
//boneid+0x40, or boneid+0x4040 if 2 byte
|
||||
["boneid","varushort"],
|
||||
//animation type 1-3=rotatexyz, 4-6=translatexyz 7-9=scalexyz
|
||||
["type_0to9","ubyte"],
|
||||
["$packetlength","ushort"],
|
||||
//interpolation mode? 0=euler, 1=linear, 3lin/log???
|
||||
//0=rotation (1,2,3), 1=translation (4,5,6,13,14,15,some 16), 3=scale (7,8,9,10,11,12,some 16,some 3)
|
||||
["bonetype_01or3","ubyte"],
|
||||
["always0","ushort"],
|
||||
["flag2","bool"],
|
||||
@@ -81,7 +81,7 @@ export const parseAnimgroupConfigs = new FileParser<import("../generated/animgro
|
||||
export const parseModels = new FileParser<import("../generated/models").models>(require("./opcodes/models.json"));
|
||||
export const parseSpotAnims = new FileParser<import("../generated/spotanims").spotanims>(require("./opcodes/spotanims.json"));
|
||||
export const parseRootCacheIndex = new FileParser<import("../generated/rootcacheindex").rootcacheindex>(require("./opcodes/rootcacheindex.json"));
|
||||
export const parseSkeletalAnim = new FileParser<import("../generated/skeletalanim").skeletalanim>(require("./opcodes/skeletalanim.json"));
|
||||
export const parseSkeletalAnim = new FileParser<import("../generated/skeletalanim").skeletalanim>(require("./opcodes/skeletalanim.jsonc"));
|
||||
export const parseMaterials = new FileParser<import("../generated/materials").materials>(require("./opcodes/materials.jsonc"));
|
||||
export const parseQuickchatCategories = new FileParser<import("../generated/quickchatcategories").quickchatcategories>(require("./opcodes/quickchatcategories.jsonc"));
|
||||
export const parseQuickchatLines = new FileParser<import("../generated/quickchatlines").quickchatlines>(require("./opcodes/quickchatlines.jsonc"));
|
||||
|
||||
@@ -94,4 +94,53 @@ async function start() {
|
||||
}
|
||||
}
|
||||
}
|
||||
start();
|
||||
start();
|
||||
|
||||
async function loadSkeletons() {
|
||||
let source = new GameCacheLoader();
|
||||
|
||||
let skelindex = await source.getIndexFile(cacheMajors.skeletalAnims);
|
||||
|
||||
let files: Buffer[] = [];
|
||||
for (let index of skelindex) {
|
||||
if (!index) { continue; }
|
||||
if (files.length % 50 == 0) { console.log(files.length); }
|
||||
files.push(await source.getFile(index.major, index.minor, index.crc));
|
||||
}
|
||||
|
||||
return function* () {
|
||||
for (let file of files) {
|
||||
yield parseSkeletalAnim.read(file);
|
||||
}
|
||||
}
|
||||
};
|
||||
globalThis.loadSkeletons = loadSkeletons;
|
||||
|
||||
async function render() {
|
||||
let skelfiles = await loadSkeletons()
|
||||
let points: number[] = [];
|
||||
for (let skel of skelfiles()) {
|
||||
for (let track of skel.tracks) {
|
||||
if (track.type_0to9 >= 1 && track.type_0to9 <= 3) {
|
||||
points.push(...track.chunks.flatMap(q => q.value[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
let min = Infinity, max = -Infinity;
|
||||
for (let p of points) { min = Math.min(min, p); max = Math.max(max, p) }
|
||||
let n = 21;
|
||||
let start = -10;
|
||||
let end = 10;
|
||||
let size = (end - start) / (n - 1);
|
||||
let buckets = new Array(n).fill(0);
|
||||
let misses = 0;
|
||||
for (let p of points) {
|
||||
let i = Math.floor((p - start) / size);
|
||||
if (i >= 0 && i < n) { buckets[i]++; }
|
||||
else { misses++; }
|
||||
}
|
||||
console.log("min", min);
|
||||
console.log("max", max);
|
||||
console.log("misses", misses);
|
||||
buckets.forEach((n, i) => console.log((i * size + start).toFixed(1), n))
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
import { parseAnimgroupConfigs, parseEnvironments, parseItem, parseNpc, parseObject } from "../opdecoder";
|
||||
import { parseAnimgroupConfigs, parseEnvironments, parseItem, parseNpc, parseObject, parseSkeletalAnim } from "../opdecoder";
|
||||
import { ThreeJsRenderer } from "./threejsrender";
|
||||
import { cacheConfigPages, cacheMajors } from "../constants";
|
||||
import * as React from "react";
|
||||
@@ -19,6 +19,8 @@ import { appearanceUrl, avatarStringToBytes, avatarToModel } from "../3d/avatar"
|
||||
import { ModelBrowser } from "./scenenodes";
|
||||
import "./fsapi";
|
||||
|
||||
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept(["../3d/ob3togltf", "../3d/ob3tothree"]);
|
||||
}
|
||||
@@ -158,6 +160,25 @@ class App extends React.Component<{}, { renderer: ThreeJsRenderer | null, cache:
|
||||
requestFiles() {
|
||||
ensureCachePermission().then(engine => {
|
||||
this.setState({ cache: new ThreejsSceneCache(engine) });
|
||||
//TODO remove
|
||||
let source = engine.source;
|
||||
globalThis.loadSkeletons = async function run() {
|
||||
|
||||
let skelindex = await source.getIndexFile(cacheMajors.skeletalAnims);
|
||||
|
||||
let files: Buffer[] = [];
|
||||
for (let index of skelindex) {
|
||||
if (!index) { continue; }
|
||||
if (files.length % 50 == 0) { console.log(files.length); }
|
||||
files.push(await source.getFile(index.major, index.minor, index.crc));
|
||||
}
|
||||
|
||||
return function* () {
|
||||
for (let file of files) {
|
||||
yield parseSkeletalAnim.read(file);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ParsedTexture } from "../3d/textures";
|
||||
import { appearanceUrl, avatarStringToBytes, avatarToModel } from "../3d/avatar";
|
||||
import { ThreeJsRenderer } from "./threejsrender";
|
||||
import { ModelData, parseOb3Model } from "../3d/ob3togltf";
|
||||
import { parseSkeletalAnimation } from "../3d/animationskeletal";
|
||||
import { mountSkeletalSkeleton, parseSkeletalAnimation } from "../3d/animationskeletal";
|
||||
import { TypedEmitter } from "../3d/utils";
|
||||
import { objects } from "../../generated/objects";
|
||||
import { items } from "../../generated/items";
|
||||
@@ -78,23 +78,27 @@ export class ModelBrowser extends React.Component<{ render: ThreeJsRenderer, cac
|
||||
// // }));
|
||||
// }
|
||||
|
||||
render() {
|
||||
setMode(mode: LookupMode) {
|
||||
localStorage.rsmv_lastmode = mode;
|
||||
this.setState({ mode, search: "" });
|
||||
}
|
||||
|
||||
render() {
|
||||
let ModeComp = LookupModeComponentMap[this.state.mode];
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="sidebar-browser-tab-strip">
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "item" })} onClick={() => this.setState({ mode: "item" })}>Items IDs</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "npc" })} onClick={() => this.setState({ mode: "npc" })}>NPCs IDs</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "object" })} onClick={() => this.setState({ mode: "object" })}>Obj/Locs IDs</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "avatar" })} onClick={() => this.setState({ mode: "avatar" })}>Avatar</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "model" })} onClick={() => this.setState({ mode: "model" })}>Model IDs</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "map" })} onClick={() => this.setState({ mode: "map" })}>Map</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "material" })} onClick={() => this.setState({ mode: "material" })}>Material IDs</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "spotanim" })} onClick={() => this.setState({ mode: "spotanim" })}>Spotanims</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "scenario" })} onClick={() => this.setState({ mode: "scenario" })}>Scenario</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "item" })} onClick={() => this.setMode("item")}>Items IDs</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "npc" })} onClick={() => this.setMode("npc")}>NPCs IDs</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "object" })} onClick={() => this.setMode("object")}>Obj/Locs IDs</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "avatar" })} onClick={() => this.setMode("avatar")}>Avatar</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "model" })} onClick={() => this.setMode("model")}>Model IDs</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "map" })} onClick={() => this.setMode("map")}>Map</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "material" })} onClick={() => this.setMode("material")}>Material IDs</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "spotanim" })} onClick={() => this.setMode("spotanim")}>Spotanims</div>
|
||||
<div className={classNames("rsmv-icon-button", { active: this.state.mode == "scenario" })} onClick={() => this.setMode("scenario")}>Scenario</div>
|
||||
</div>
|
||||
<ModeComp cache={this.props.cache} scene={this.props.render} />
|
||||
<ModeComp cache={this.props.cache} scene={this.props.render} initialId={this.state.search} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -143,20 +147,24 @@ export class RSModel extends TypedEmitter<{ loaded: undefined }>{
|
||||
renderscene: ThreeJsRenderer | null = null;
|
||||
targetAnimId = -1;
|
||||
skeletontype: "none" | "baked" | "full" = "none";
|
||||
skeletonHelper: SkeletonHelper | null = null;
|
||||
|
||||
|
||||
cleanup() {
|
||||
this.listeners = {};
|
||||
this.rootnode.removeFromParent();
|
||||
this.skeletonHelper?.removeFromParent();
|
||||
if (this.renderscene) {
|
||||
this.renderscene.animationMixers.delete(this.mixer);
|
||||
}
|
||||
this.renderscene = null;
|
||||
}
|
||||
|
||||
addToScene(scene: ThreeJsRenderer, node = scene.modelnode) {
|
||||
this.renderscene = scene;
|
||||
scene.animationMixers.add(this.mixer);
|
||||
node.add(this.rootnode);
|
||||
if (this.skeletonHelper) { node.add(this.skeletonHelper); }
|
||||
scene.forceFrame();
|
||||
}
|
||||
|
||||
@@ -208,8 +216,8 @@ export class RSModel extends TypedEmitter<{ loaded: undefined }>{
|
||||
this.mixer.stopAllAction();
|
||||
let action = this.mixer.clipAction(clip, mesh);
|
||||
action.play();
|
||||
// let skelhelper = new SkeletonHelper(mesh);
|
||||
// this.rootnode.add(skelhelper);
|
||||
this.skeletonHelper = new SkeletonHelper(mesh);
|
||||
this.renderscene?.scene.add(this.skeletonHelper);
|
||||
this.mountedanim = clip;
|
||||
}
|
||||
|
||||
@@ -224,8 +232,14 @@ export class RSModel extends TypedEmitter<{ loaded: undefined }>{
|
||||
|
||||
let clip: AnimationClip;
|
||||
if (seq.skeletal_animation) {
|
||||
throw new Error("todo");//TODO
|
||||
// mount = await parseSkeletalAnimation(this.cache, seq.skeletal_animation);
|
||||
let anim = await parseSkeletalAnimation(this.cache, seq.skeletal_animation);
|
||||
clip = anim.clip;
|
||||
let loaded = this.loaded ?? await this.model;
|
||||
if (this.skeletontype != "full") {
|
||||
if (this.skeletontype != "none") { throw new Error("wrong skeleton type already mounted to model"); }
|
||||
await mountSkeletalSkeleton(loaded.mesh, this.cache, anim.framebaseid);
|
||||
this.skeletontype = "full";
|
||||
}
|
||||
} else if (seq.frames) {
|
||||
let frameanim = await parseAnimationSequence4(this.cache, seq.frames);
|
||||
let loaded = this.loaded ?? await this.model;
|
||||
@@ -262,7 +276,7 @@ type ScenarioComponent = {
|
||||
}
|
||||
|
||||
|
||||
class InputCommitted2 extends React.Component<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>>{
|
||||
class InputCommitted extends React.Component<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>>{
|
||||
el: HTMLInputElement | null = null;
|
||||
@boundMethod
|
||||
onChange(e: Event) {
|
||||
@@ -286,22 +300,11 @@ class InputCommitted2 extends React.Component<React.DetailedHTMLProps<React.Inpu
|
||||
}
|
||||
}
|
||||
|
||||
function InputCommitted(p: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>) {
|
||||
let ref = React.useCallback((e: HTMLInputElement | null) => {
|
||||
if (e && p.onChange) {
|
||||
e.addEventListener("change", p.onChange as any);
|
||||
return () => e.removeEventListener("change", p.onChange as any);
|
||||
}
|
||||
}, [p.onChange]);
|
||||
let newp = { ...p, onChange: undefined, value: undefined, defaultValue: undefined };
|
||||
return <input ref={ref} {...newp} />;
|
||||
}
|
||||
|
||||
function ScenarioAnimControl(p: { anim: ScenarioComponent["anims"][number], onChange: (v: ScenarioComponent["anims"][number] | null) => void }) {
|
||||
return (
|
||||
<form>
|
||||
<InputCommitted2 type="number" value={p.anim.animid} onChange={e => p.onChange({ ...p.anim, animid: +e.currentTarget.value })} />
|
||||
<InputCommitted2 type="number" value={p.anim.startTime} onChange={e => p.onChange({ ...p.anim, startTime: +e.currentTarget.value })} />
|
||||
<InputCommitted type="number" value={p.anim.animid} onChange={e => p.onChange({ ...p.anim, animid: +e.currentTarget.value })} />
|
||||
<InputCommitted type="number" value={p.anim.startTime} onChange={e => p.onChange({ ...p.anim, startTime: +e.currentTarget.value })} />
|
||||
<div onClick={() => p.onChange(null)}>delete</div>
|
||||
</form>
|
||||
);
|
||||
@@ -320,12 +323,13 @@ function ScenarioControl(p: { comp: ScenarioComponent, onChange: (v: ScenarioCom
|
||||
};
|
||||
return <ScenarioAnimControl key={i} anim={anim} onChange={onchange} />
|
||||
})}
|
||||
<div onClick={e => p.onChange({ ...p.comp, anims: [...p.comp.anims, { animid: -1, startTime: 0 }] })}>add anim</div>
|
||||
<div onClick={e => p.onChange(null)}>delete</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export class SceneScenario extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { components: ScenarioComponent[], addType: keyof typeof primitiveModelInits }>{
|
||||
export class SceneScenario extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { components: ScenarioComponent[], addType: keyof typeof primitiveModelInits }>{
|
||||
|
||||
models = new Map<ScenarioComponent, RSModel>();
|
||||
|
||||
@@ -377,7 +381,7 @@ export class SceneScenario extends React.Component<{ scene: ThreeJsRenderer, cac
|
||||
this.models.delete(oldcomp);
|
||||
if (model && newcomp) {
|
||||
this.models.set(newcomp, model);
|
||||
model.setAnimation(newcomp.anims[0].animid);
|
||||
if (newcomp.anims.length != 0) { model.setAnimation(newcomp.anims[0].animid); }
|
||||
}
|
||||
if (newcomp) { components[index] = newcomp; }
|
||||
else { components.splice(index, 1); }
|
||||
@@ -476,7 +480,7 @@ async function locToModel(cache: ThreejsSceneCache, id: number) {
|
||||
return { models, animids, loc: obj };
|
||||
}
|
||||
|
||||
export class ScenePlayer extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { avaitems: items[] | null, animset: animgroupconfigs | null }> {
|
||||
export class ScenePlayer extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { avaitems: items[] | null, animset: animgroupconfigs | null }> {
|
||||
model: RSModel | null = null;
|
||||
modelid = "";
|
||||
|
||||
@@ -494,6 +498,7 @@ export class ScenePlayer extends React.Component<{ scene: ThreeJsRenderer, cache
|
||||
|
||||
@boundMethod
|
||||
async setModel(modelid: string) {
|
||||
localStorage.rsmv_lastsearch = modelid;
|
||||
if (this.model && this.modelid != modelid) {
|
||||
this.model.cleanup();
|
||||
}
|
||||
@@ -516,7 +521,7 @@ export class ScenePlayer extends React.Component<{ scene: ThreeJsRenderer, cache
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<StringInput onChange={this.setModel} />
|
||||
<StringInput onChange={this.setModel} initialid={this.props.initialId} />
|
||||
{this.state.avaitems?.map((item, index) => {
|
||||
return <div key={index}>{item.name ?? "no name"}</div>
|
||||
})}
|
||||
@@ -530,7 +535,7 @@ function JsonDisplay(p: { obj: any }) {
|
||||
return (<pre className="json-block">{prettyJson(p.obj)}</pre>);
|
||||
}
|
||||
|
||||
export class SceneRawModel extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }> {
|
||||
export class SceneRawModel extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }> {
|
||||
model: RSModel | null = null;
|
||||
modelid: number = -1;
|
||||
|
||||
@@ -540,6 +545,7 @@ export class SceneRawModel extends React.Component<{ scene: ThreeJsRenderer, cac
|
||||
|
||||
@boundMethod
|
||||
setModel(modelid: number) {
|
||||
localStorage.rsmv_lastsearch = modelid;
|
||||
if (this.model && this.modelid != modelid) {
|
||||
this.model.cleanup();
|
||||
}
|
||||
@@ -555,13 +561,13 @@ export class SceneRawModel extends React.Component<{ scene: ThreeJsRenderer, cac
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<IdInput onChange={this.setModel} />
|
||||
<IdInput onChange={this.setModel} initialid={+this.props.initialId} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class SceneMaterial extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { matdata: materials | null }> {
|
||||
export class SceneMaterial extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { matdata: materials | null }> {
|
||||
model: RSModel | null = null;
|
||||
modelid: number = -1;
|
||||
|
||||
@@ -578,6 +584,7 @@ export class SceneMaterial extends React.Component<{ scene: ThreeJsRenderer, cac
|
||||
|
||||
@boundMethod
|
||||
async setModel(modelid: number) {
|
||||
localStorage.rsmv_lastsearch = modelid;
|
||||
if (this.model && this.modelid != modelid) {
|
||||
this.model.cleanup();
|
||||
}
|
||||
@@ -617,14 +624,14 @@ export class SceneMaterial extends React.Component<{ scene: ThreeJsRenderer, cac
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<IdInput onChange={this.setModel} />
|
||||
<IdInput onChange={this.setModel} initialid={+this.props.initialId} />
|
||||
<JsonDisplay obj={this.state.matdata} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class SceneLocation extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { locdata: objects | null }> {
|
||||
export class SceneLocation extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { locdata: objects | null }> {
|
||||
model: RSModel | null = null;
|
||||
modelid: number = -1;
|
||||
|
||||
@@ -641,6 +648,7 @@ export class SceneLocation extends React.Component<{ scene: ThreeJsRenderer, cac
|
||||
|
||||
@boundMethod
|
||||
async setModel(modelid: number) {
|
||||
localStorage.rsmv_lastsearch = modelid;
|
||||
if (this.model && this.modelid != modelid) {
|
||||
this.model.cleanup();
|
||||
}
|
||||
@@ -661,14 +669,14 @@ export class SceneLocation extends React.Component<{ scene: ThreeJsRenderer, cac
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<IdInput onChange={this.setModel} />
|
||||
<IdInput onChange={this.setModel} initialid={+this.props.initialId} />
|
||||
<JsonDisplay obj={this.state.locdata} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class SceneItem extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { itemdata: items | null }> {
|
||||
export class SceneItem extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { itemdata: items | null }> {
|
||||
model: RSModel | null = null;
|
||||
modelid: number = -1;
|
||||
|
||||
@@ -685,6 +693,7 @@ export class SceneItem extends React.Component<{ scene: ThreeJsRenderer, cache:
|
||||
|
||||
@boundMethod
|
||||
async setModel(modelid: number) {
|
||||
localStorage.rsmv_lastsearch = modelid;
|
||||
if (this.model && this.modelid != modelid) {
|
||||
this.model.cleanup();
|
||||
}
|
||||
@@ -710,13 +719,13 @@ export class SceneItem extends React.Component<{ scene: ThreeJsRenderer, cache:
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<IdInput onChange={this.setModel} />
|
||||
<IdInput onChange={this.setModel} initialid={+this.props.initialId} />
|
||||
<JsonDisplay obj={this.state.itemdata} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
export class SceneNpc extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { npcdata: npcs | null }> {
|
||||
export class SceneNpc extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { npcdata: npcs | null }> {
|
||||
model: RSModel | null = null;
|
||||
modelid: number = -1;
|
||||
|
||||
@@ -733,6 +742,7 @@ export class SceneNpc extends React.Component<{ scene: ThreeJsRenderer, cache: T
|
||||
|
||||
@boundMethod
|
||||
async setModel(modelid: number) {
|
||||
localStorage.rsmv_lastsearch = modelid;
|
||||
if (this.model && this.modelid != modelid) {
|
||||
this.model.cleanup();
|
||||
}
|
||||
@@ -753,14 +763,14 @@ export class SceneNpc extends React.Component<{ scene: ThreeJsRenderer, cache: T
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<IdInput onChange={this.setModel} />
|
||||
<IdInput onChange={this.setModel} initialid={+this.props.initialId} />
|
||||
<JsonDisplay obj={this.state.npcdata} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class SceneSpotAnim extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, { animdata: spotanims | null }> {
|
||||
export class SceneSpotAnim extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, { animdata: spotanims | null }> {
|
||||
model: RSModel | null = null;
|
||||
modelid: number = -1;
|
||||
|
||||
@@ -777,6 +787,7 @@ export class SceneSpotAnim extends React.Component<{ scene: ThreeJsRenderer, cac
|
||||
|
||||
@boundMethod
|
||||
async setModel(modelid: number) {
|
||||
localStorage.rsmv_lastsearch = modelid;
|
||||
if (this.model && this.modelid != modelid) {
|
||||
this.model.cleanup();
|
||||
}
|
||||
@@ -797,7 +808,7 @@ export class SceneSpotAnim extends React.Component<{ scene: ThreeJsRenderer, cac
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<IdInput onChange={this.setModel} />
|
||||
<IdInput onChange={this.setModel} initialid={+this.props.initialId} />
|
||||
<JsonDisplay obj={this.state.animdata} />
|
||||
</React.Fragment>
|
||||
)
|
||||
@@ -808,7 +819,7 @@ type SceneMapState = {
|
||||
center: { x: number, z: number },
|
||||
toggles: Record<string, boolean>
|
||||
};
|
||||
export class SceneMapModel extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, SceneMapState> {
|
||||
export class SceneMapModel extends React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, SceneMapState> {
|
||||
oldautoframe: boolean;
|
||||
constructor(p) {
|
||||
super(p);
|
||||
@@ -984,7 +995,7 @@ export class SceneMapModel extends React.Component<{ scene: ThreeJsRenderer, cac
|
||||
}
|
||||
|
||||
|
||||
const LookupModeComponentMap: Record<LookupMode, { new(p: any): React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache }, any> }> = {
|
||||
const LookupModeComponentMap: Record<LookupMode, { new(p: any): React.Component<{ scene: ThreeJsRenderer, cache: ThreejsSceneCache, initialId: string }, any> }> = {
|
||||
model: SceneRawModel,
|
||||
item: SceneItem,
|
||||
avatar: ScenePlayer,
|
||||
|
||||
@@ -19,7 +19,7 @@ module.exports = {
|
||||
diff: "./src/scripts/cachediff.ts",
|
||||
deps: "./src/scripts/dependencies.ts",
|
||||
quickchatlookup: "./src/scripts/quickchatlookup.ts",
|
||||
scrapeavatars: "./src/scripts/scrapeavatars.ts",
|
||||
scrapeavatars: "./src/scripts/scrapeavatars.ts"
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
|
||||
Reference in New Issue
Block a user