From 7e733c697df1852cb93d6aae116b2948948ccf4d Mon Sep 17 00:00:00 2001 From: Max Weber Date: Wed, 23 Oct 2024 07:03:29 -0600 Subject: [PATCH] add animation config support still need some work to make the reflect stuff work for KitOrItems --- cache2-ts/src/Reader.ts | 14 +- cache2-ts/src/loaders/Animation.ts | 206 +++++++++++++++++++++++++++ cache2-ts/src/loaders/index.ts | 1 + cache2-ts/src/types.ts | 21 +++ viewer/src/common/Runner.ts | 2 + viewer/src/main/viewer/Viewer.svelte | 9 +- viewer/src/runner/Runner.ts | 1 + 7 files changed, 249 insertions(+), 5 deletions(-) create mode 100644 cache2-ts/src/loaders/Animation.ts diff --git a/cache2-ts/src/Reader.ts b/cache2-ts/src/Reader.ts index 49e8f4d..7b3b92a 100644 --- a/cache2-ts/src/Reader.ts +++ b/cache2-ts/src/Reader.ts @@ -1,5 +1,5 @@ import { CacheVersion } from "./Cache.js"; -import { ParamID, Params } from "./types.js"; +import { ItemID, KitID, KitOrItem, ParamID, Params } from "./types.js"; export const cp1252CharMap: string[] = (() => { const ext = "€?‚ƒ„…†‡ˆ‰Š‹Œ?Ž??‘’“”•–—˜™š›œ?žŸ"; @@ -164,6 +164,18 @@ export class Reader { return out; } + public kit(): KitOrItem { + let id = this.u16(); + if (id === 0) { + return undefined; + } else if (id >= 512) { + return { item: (id - 512) as ItemID }; + } else if (id >= 256) { + return { kit: (id - 256) as KitID }; + } else { + throw new Error(`invalid KitOrItem ${id}`); + } + } public u32o16(): number { // rl BigSmart if (this.view.getUint8(this.offset) & 0x80) { return this.u16(); diff --git a/cache2-ts/src/loaders/Animation.ts b/cache2-ts/src/loaders/Animation.ts new file mode 100644 index 0000000..e42fa23 --- /dev/null +++ b/cache2-ts/src/loaders/Animation.ts @@ -0,0 +1,206 @@ +import { PerFileLoadable } from "../Loadable.js"; +import { Reader } from "../Reader.js"; +import { Typed } from "../reflect.js"; +import { + AnimationID, + AnimMayaID, + AnimMoveMode, + AnimRestartMode, + KitOrItem, + PoseID, + SkeletonID, + SoundEffectID, +} from "../types.js"; + +export class FrameSound { + constructor( + public id: SoundEffectID, + public weight: number, + public loops: number, + location: number, + public retain: number, + ) { + this.offsetX = (location >> 16) & 0xFF; + this.offsetY = (location >> 8) & 0xFF; + this.maxDistance = location & 0xFF; + this.isAreaSound = location === 0; + } + + public offsetX: number; + public offsetY: number; + public isAreaSound: boolean; + public maxDistance: number; +} + +export class Animation extends PerFileLoadable { + constructor(public id: AnimationID) { + super(); + } + + declare public [Typed.type]: Typed.Any; + + public static readonly index = 2; + public static readonly archive = 12; + + public frameLengths?: number[] = undefined; + public frameIDs?: [SkeletonID, PoseID][] = undefined; + public chatheadFrameIDs?: [SkeletonID, PoseID][] = undefined; + + public animMayaID?: AnimMayaID = undefined; + public animMayaStart = 0; + public animMayaEnd = 0; + public masks?: boolean[] = undefined; + + public frameStep = -1; + public interleaveLeave?: number[] = undefined; + public stretches = true; + public priority = 5; + public leftHandItem?: KitOrItem = undefined; + public rightHandItem?: KitOrItem = undefined; + public maxLoops?: number = undefined; + public preAnimMove!: AnimMoveMode; + public postAnimMove!: AnimMoveMode; + public restartMode = AnimRestartMode.ResetLoops; + public sounds: Map = new Map(); + + public static decode(r: Reader, id: AnimationID): Animation { + const v = new Animation(id); + let [legacyFrameSounds, animMayaID, frameSounds, animMayaBounds] = r.isAfter({ era: "osrs", indexRevision: 4470 }) + ? [-1, 13, 14, 15] + : [13, 14, 15, 16]; + for (let opcode: number; (opcode = r.u8()) != 0;) { + switch (opcode) { + case 1: { + let len = r.u16(); + v.frameLengths = new Array(len); + v.frameIDs = new Array(len); + for (let i = 0; i < len; i++) { + v.frameLengths[i] = r.u16(); + } + for (let i = 0; i < len; i++) { + v.frameIDs[i] = [0 as SkeletonID, r.u16() as PoseID]; + } + for (let i = 0; i < len; i++) { + v.frameIDs[i][0] = r.u16() as SkeletonID; + } + break; + } + case 2: + v.frameStep = r.u16(); + break; + case 3: { + let len = r.u8(); + v.interleaveLeave = new Array(len + 1); + for (let i = 0; i < len; i++) { + v.interleaveLeave[i] = r.u8(); + } + v.interleaveLeave[len] = 9999999; + break; + } + case 4: + v.stretches = true; + break; + case 5: + v.priority = r.u8(); + break; + case 6: + v.leftHandItem = r.kit(); + break; + case 7: + v.rightHandItem = r.kit(); + break; + case 8: + v.maxLoops = r.u8(); + break; + case 9: + v.preAnimMove = r.u8() as AnimMoveMode; + break; + case 10: + v.postAnimMove = r.u8() as AnimMoveMode; + break; + case 11: + v.restartMode = r.u8() as AnimRestartMode; + break; + case 12: { + let len = r.u8(); + v.chatheadFrameIDs = new Array(len); + for (let i = 0; i < len; i++) { + v.chatheadFrameIDs[i] = [0 as SkeletonID, r.u16() as PoseID]; + } + for (let i = 0; i < len; i++) { + v.chatheadFrameIDs[i][0] = r.u16() as SkeletonID; + } + break; + } + case legacyFrameSounds: { + let len = r.u8(); + for (let i = 0; i < len; i++) { + readFrameSound(v, r, i); + } + break; + } + case animMayaID: + v.animMayaID = r.i32() as AnimMayaID; + break; + case frameSounds: { + let len = r.u16(); + for (let i = 0; i < len; i++) { + let frame = r.u16(); + readFrameSound(v, r, frame); + } + break; + } + case animMayaBounds: + v.animMayaStart = r.u16(); + v.animMayaEnd = r.u16(); + break; + case 17: { + v.masks = new Array(256); + v.masks.fill(false); + let len = r.u8(); + for (let i = 0; i < len; i++) { + v.masks[r.u8()] = true; + } + break; + } + default: + throw new Error(`unknown animation opcode ${opcode}`); + } + } + + let defaultAnimMode = v.interleaveLeave === undefined && v.masks == undefined + ? 0 as AnimMoveMode + : 2 as AnimMoveMode; + + v.preAnimMove ??= defaultAnimMode; + v.postAnimMove ??= defaultAnimMode; + + return v; + } +} + +function readFrameSound(v: Animation, r: Reader, frame: number): void { + let sound: FrameSound; + if (r.isAfter({ era: "osrs", indexRevision: 4106 })) { + let id = r.u16(); + let weight = r.isAfter({ era: "osrs", indexRevision: 4470 }) + ? r.u8() + : -1; + let loops = r.u8(); + let location = r.u8(); + let retain = r.u8(); + sound = new FrameSound(id as SoundEffectID, weight, loops, location, retain); + } else { + let bits = r.u24(); + sound = new FrameSound((bits >> 8) as SoundEffectID, -1, (bits >> 4) & 7, bits & 15, 0); + } + + if (sound.id >= 1 && sound.loops >= 1) { + let list = v.sounds.get(frame); + if (!list) { + list = []; + v.sounds.set(frame, list); + } + list.push(sound); + } +} diff --git a/cache2-ts/src/loaders/index.ts b/cache2-ts/src/loaders/index.ts index 4840902..b286720 100644 --- a/cache2-ts/src/loaders/index.ts +++ b/cache2-ts/src/loaders/index.ts @@ -1,3 +1,4 @@ +export * from "./Animation.js"; export * from "./DBRow.js"; export * from "./Enum.js"; export * from "./HealthBar.js"; diff --git a/cache2-ts/src/types.ts b/cache2-ts/src/types.ts index 9486e02..55d46fa 100644 --- a/cache2-ts/src/types.ts +++ b/cache2-ts/src/types.ts @@ -48,12 +48,15 @@ export type FontID = NewType; export type HealthBarID = NewType; export type HitsplatID = NewType; export type ItemID = NewType; +export type KitID = NewType; export type MapElementID = NewType; export type MapSceneIconID = NewType; export type ModelID = NewType; export type NPCID = NewType; export type ObjID = NewType; export type ParamID = NewType; +export type PoseID = NewType; +export type SkeletonID = NewType; export type SoundEffectID = NewType; export type SpriteID = NewType; export type StructID = NewType; @@ -68,9 +71,15 @@ export type RGB = AliasType; export type WorldPoint = NewType; export type ObjType = NewType; +export type AnimMoveMode = NewType; +export type AnimRestartMode = NewType; +export type AnimMayaID = NewType; + export class Params extends Map { } +export type KitOrItem = { kit: KitID; } | { item: ItemID; } | undefined; + function makeByID(): (this: object, id: T) => string | undefined { let byID: string[] | undefined; return function(id: T) { @@ -133,6 +142,18 @@ export namespace ObjType { export const byID = makeByID(); } +export namespace AnimMoveMode { + export const byID = makeByID(); +} + +export namespace AnimRestartMode { + export const Continue = 0 as AnimRestartMode; + export const Restart = 1 as AnimRestartMode; + export const ResetLoops = 2 as AnimRestartMode; + + export const byID = makeByID(); +} + export namespace DBColumnID { export function pack(table: DBTableID, column: number, tupleIndex: number = 0): DBColumnID { return ((table << 12) | ((column & 0xFF) << 4) | (tupleIndex & 0xF)) as DBColumnID; diff --git a/viewer/src/common/Runner.ts b/viewer/src/common/Runner.ts index 605d86e..bdb9733 100644 --- a/viewer/src/common/Runner.ts +++ b/viewer/src/common/Runner.ts @@ -7,6 +7,7 @@ import { ServiceClient } from "./ServiceClient"; import { UIData } from "./uiobject"; export const lookupTypes = { + AnimationID: "animation", DBRowID: "dbrow", DBTableID: "dbtable", EnumID: "enum", @@ -21,6 +22,7 @@ export const lookupTypes = { UnderlayID: "underlay", } as const; export type LookupType = + | "animation" | "dbrow" | "dbtable" | "enum" diff --git a/viewer/src/main/viewer/Viewer.svelte b/viewer/src/main/viewer/Viewer.svelte index 81dbd40..2c3d286 100644 --- a/viewer/src/main/viewer/Viewer.svelte +++ b/viewer/src/main/viewer/Viewer.svelte @@ -35,17 +35,18 @@
All Indexes
- - + + + + - - +
Sprites
diff --git a/viewer/src/runner/Runner.ts b/viewer/src/runner/Runner.ts index d1ee3a4..d4257cb 100644 --- a/viewer/src/runner/Runner.ts +++ b/viewer/src/runner/Runner.ts @@ -251,6 +251,7 @@ interface Filterable { let types: Record> = { index, + animation: c2.Animation, dbrow: c2.DBRow, dbtable: c2.DBTable, enum: c2.Enum,