From 793b2e723497840db05704e54a633ffc92bcf40f Mon Sep 17 00:00:00 2001 From: Allen Kinzalow Date: Wed, 18 Dec 2024 06:47:44 -0500 Subject: [PATCH] [#78] Update cache2 (#79) --- .changeset/great-cycles-notice.md | 5 + src/utils/cache2/Cache.ts | 2 +- src/utils/cache2/Reader.ts | 24 ++- src/utils/cache2/loaders/Animation.ts | 207 ++++++++++++++++++++++++++ src/utils/cache2/loaders/HealthBar.ts | 65 ++++++++ src/utils/cache2/loaders/Item.ts | 2 +- src/utils/cache2/loaders/Obj.ts | 4 + src/utils/cache2/loaders/index.ts | 2 + src/utils/cache2/types.ts | 30 ++++ 9 files changed, 337 insertions(+), 4 deletions(-) create mode 100644 .changeset/great-cycles-notice.md create mode 100644 src/utils/cache2/loaders/Animation.ts create mode 100644 src/utils/cache2/loaders/HealthBar.ts diff --git a/.changeset/great-cycles-notice.md b/.changeset/great-cycles-notice.md new file mode 100644 index 0000000..a1d604b --- /dev/null +++ b/.changeset/great-cycles-notice.md @@ -0,0 +1,5 @@ +--- +"@osrs-wiki/cache-mediawiki": minor +--- + +Update cache2 from latest abextm/cache2 changes diff --git a/src/utils/cache2/Cache.ts b/src/utils/cache2/Cache.ts index 086a39e..64f39bc 100644 --- a/src/utils/cache2/Cache.ts +++ b/src/utils/cache2/Cache.ts @@ -110,7 +110,7 @@ export class ArchiveData { const numChunks = dv.getUint8(dv.byteLength - 1); let off = dv.byteLength - 1 - numChunks * fileCount * 4; - let doff = data.byteOffset; + let doff = 0; if (numChunks == 1) { let size = 0; diff --git a/src/utils/cache2/Reader.ts b/src/utils/cache2/Reader.ts index aa764ed..575634c 100644 --- a/src/utils/cache2/Reader.ts +++ b/src/utils/cache2/Reader.ts @@ -1,5 +1,5 @@ import { CacheVersion } from "./Cache"; -import { ParamID, Params } from "./types"; +import { ItemID, KitID, KitOrItem, ParamID, Params } from "./types"; export const cp1252CharMap: string[] = (() => { const ext = "€?‚ƒ„…†‡ˆ‰Š‹Œ?Ž??‘’“”•–—˜™š›œ?žŸ"; @@ -177,12 +177,32 @@ export class Reader { return out; } + public kit(): KitOrItem { + const 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(); + return this.i32() & (-1 >>> 1); } else { + return this.u16(); + } + } + public u32o16n(): number { + // rl BigSmart2 + if (this.view.getUint8(this.offset) & 0x80) { return this.i32() & (-1 >>> 1); + } else { + return this.u16n(); } } public leVarInt(): number { diff --git a/src/utils/cache2/loaders/Animation.ts b/src/utils/cache2/loaders/Animation.ts new file mode 100644 index 0000000..e6eca7d --- /dev/null +++ b/src/utils/cache2/loaders/Animation.ts @@ -0,0 +1,207 @@ +import { PerFileLoadable } from "../Loadable"; +import { Reader } from "../Reader"; +import { Typed } from "../reflect"; +import { + AnimationID, + AnimMayaID, + AnimRestartMode, + KitOrItem, + PoseID, + PostAnimMoveMode, + PreAnimMoveMode, + SkeletonID, + SoundEffectID, +} from "../types"; +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(); + } + public declare [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!: PreAnimMoveMode; + public postAnimMove!: PostAnimMoveMode; + public restartMode = AnimRestartMode.ResetLoops; + public sounds: Map = new Map(); + public static decode(r: Reader, id: AnimationID): Animation { + const v = new Animation(id); + const [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: { + const 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: { + const 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 PreAnimMoveMode; + break; + case 10: + v.postAnimMove = r.u8() as PostAnimMoveMode; + break; + case 11: + v.restartMode = r.u8() as AnimRestartMode; + break; + case 12: { + const 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: { + const 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: { + const len = r.u16(); + for (let i = 0; i < len; i++) { + const 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); + const len = r.u8(); + for (let i = 0; i < len; i++) { + v.masks[r.u8()] = true; + } + break; + } + default: + throw new Error(`unknown animation opcode ${opcode}`); + } + } + const defaultAnimMode = + v.interleaveLeave === undefined && v.masks == undefined + ? PreAnimMoveMode.DelayMove + : PreAnimMoveMode.Merge; + v.preAnimMove ??= defaultAnimMode; + v.postAnimMove ??= defaultAnimMode as any as PostAnimMoveMode; + return v; + } +} +function readFrameSound(v: Animation, r: Reader, frame: number): void { + let sound: FrameSound; + if (r.isAfter({ era: "osrs", indexRevision: 4106 })) { + const id = r.u16(); + const weight = r.isAfter({ era: "osrs", indexRevision: 4470 }) + ? r.u8() + : -1; + const loops = r.u8(); + const location = r.u8(); + const retain = r.u8(); + sound = new FrameSound( + id as SoundEffectID, + weight, + loops, + location, + retain + ); + } else { + const 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/src/utils/cache2/loaders/HealthBar.ts b/src/utils/cache2/loaders/HealthBar.ts new file mode 100644 index 0000000..3e75c5c --- /dev/null +++ b/src/utils/cache2/loaders/HealthBar.ts @@ -0,0 +1,65 @@ +import { PerFileLoadable } from "../Loadable"; +import { Reader } from "../Reader"; +import { Typed } from "../reflect"; +import { HealthBarID, SpriteID } from "../types"; +export class HealthBar extends PerFileLoadable { + constructor(public id: HealthBarID) { + super(); + } + public declare [Typed.type]: Typed.Any; + public static readonly index = 2; + public static readonly archive = 33; + public unused1: number | undefined = undefined; + public sortOrder = 255; + public despawnPriority = 255; + public fadeOutAt = -1; + public duration = 70; + public unused2: number | undefined = undefined; + public filledSprite = -1 as SpriteID; + public emptySprite = -1 as SpriteID; + public denominator = 30; + public borderSize = 0; + public static decode(r: Reader, id: HealthBarID): HealthBar { + const v = new HealthBar(id); + for (let opcode: number; (opcode = r.u8()) != 0; ) { + switch (opcode) { + case 1: + v.unused1 = r.u16(); + break; + case 2: + v.sortOrder = r.u8(); + break; + case 3: + v.despawnPriority = r.u8(); + break; + case 4: + v.fadeOutAt = 0; + break; + case 5: + v.duration = r.u16(); + break; + case 6: + v.unused2 = r.u8(); + break; + case 7: + v.filledSprite = r.u32o16n() as SpriteID; + break; + case 8: + v.emptySprite = r.u32o16n() as SpriteID; + break; + case 11: + v.fadeOutAt = r.u16(); + break; + case 14: + v.denominator = r.u8(); + break; + case 15: + v.borderSize = r.u8(); + break; + default: + throw new Error(`unknown opcode ${opcode}`); + } + } + return v; + } +} diff --git a/src/utils/cache2/loaders/Item.ts b/src/utils/cache2/loaders/Item.ts index c12e2b3..a85bce7 100644 --- a/src/utils/cache2/loaders/Item.ts +++ b/src/utils/cache2/loaders/Item.ts @@ -168,7 +168,7 @@ export class Item extends PerFileLoadable { break; } case 42: - v.shiftClickIndex = r.u8(); + v.shiftClickIndex = r.i8(); break; case 65: v.isGrandExchangable = true; diff --git a/src/utils/cache2/loaders/Obj.ts b/src/utils/cache2/loaders/Obj.ts index ce3f7e6..f56e04b 100644 --- a/src/utils/cache2/loaders/Obj.ts +++ b/src/utils/cache2/loaders/Obj.ts @@ -72,6 +72,7 @@ export class Obj extends PerFileLoadable { public ambientSoundChangeTicksMax = 0; public randomizeAnimationStart = true; public blockingMask: undefined | number = undefined; + public deferAnimChange = false; public params = new Params(); public static decode(r: Reader, id: ObjID): Obj { @@ -242,6 +243,9 @@ export class Obj extends PerFileLoadable { case 89: v.randomizeAnimationStart = false; break; + case 90: + v.deferAnimChange = true; + break; case 92: { v.varbit = r.u16n(); v.varp = r.u16n(); diff --git a/src/utils/cache2/loaders/index.ts b/src/utils/cache2/loaders/index.ts index a9f1e0f..046142a 100644 --- a/src/utils/cache2/loaders/index.ts +++ b/src/utils/cache2/loaders/index.ts @@ -1,6 +1,8 @@ +export * from "./Animation"; export * from "./Area"; export * from "./DBRow"; export * from "./Enum"; +export * from "./HealthBar"; export * from "./Hitsplat"; export * from "./Item"; export * from "./NPC"; diff --git a/src/utils/cache2/types.ts b/src/utils/cache2/types.ts index c6f5d0a..6e5b9e5 100644 --- a/src/utils/cache2/types.ts +++ b/src/utils/cache2/types.ts @@ -43,14 +43,18 @@ export type DBTableID = NewType; export type DBColumnID = NewType; export type EnumID = NewType; 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; @@ -65,8 +69,15 @@ export type RGB = AliasType; export type WorldPoint = NewType; export type ObjType = NewType; +export type PreAnimMoveMode = NewType; +export type PostAnimMoveMode = 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 @@ -132,6 +143,25 @@ export namespace ObjType { export const byID = makeByID(); } +export namespace PreAnimMoveMode { + export const DelayMove = 0 as PreAnimMoveMode; + export const DelayAnim = 1 as PreAnimMoveMode; + export const Merge = 2 as PreAnimMoveMode; + export const byID = makeByID(); +} +export namespace PostAnimMoveMode { + export const DelayMove = 0 as PostAnimMoveMode; + export const AbortAnim = 1 as PostAnimMoveMode; + export const Merge = 2 as PostAnimMoveMode; + 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,