diff --git a/app/src/chart/component/notefield/Notefield.ts b/app/src/chart/component/notefield/Notefield.ts index 008b272d..c411cd22 100644 --- a/app/src/chart/component/notefield/Notefield.ts +++ b/app/src/chart/component/notefield/Notefield.ts @@ -60,17 +60,29 @@ export class NoteWrapper extends Container { this.addChild(object, this.icon) - object.nf.noteskin?.onUpdate(this, cr => { + if (object.nf.noteskin === undefined) { + EventHandler.on("noteskinLoaded", () => this.loadEventHandler()) + } else { + this.loadEventHandler() + } + } + + loadEventHandler() { + this.object.nf.noteskin!.onUpdate(this, cr => { if (!Options.chart.drawIcons) { this.icon.visible = false return } - if (object.nf.noteskinOptions?.hideIcons?.includes(object.note.type)) { + if ( + this.object.nf.noteskinOptions?.hideIcons?.includes( + this.object.note.type + ) + ) { this.icon.visible = false return } this.icon.visible = true - if (object.note.type == "Fake") { + if (this.object.note.type == "Fake") { this.icon.visible = cr.chartManager.getMode() != EditMode.Play } }) @@ -86,6 +98,17 @@ export class NoteObject extends Container { super() this.note = note this.nf = notefield + + if (this.nf.noteskin === undefined) { + EventHandler.on("noteskinLoaded", () => { + this.loadElement(note) + }) + } else { + this.loadElement(note) + } + } + + loadElement(note: TapNotedataEntry) { const element = this.nf.noteskin!.getElement( { element: note.type, @@ -132,6 +155,7 @@ export class HoldObject extends Container { private readonly metrics private readonly ns readonly nf + private loaded = false constructor(notefield: Notefield, note: HoldNotedataEntry) { super() @@ -142,6 +166,25 @@ export class HoldObject extends Container { this.ns = notefield.noteskin! this.nf = notefield this.metrics = this.ns.metrics + + active.visible = false + + this.active = active + this.inactive = inactive + + this.addChild(inactive, active) + + if (notefield.noteskin === undefined) { + EventHandler.on("noteskinLoaded", () => { + this.loadElements() + }) + } else { + this.loadElements() + } + } + + loadElements() { + if (this.loaded) return ;(this.elements as any) = {} for (const state of ["Active", "Inactive"] as const) { ;(this.elements[state] as any) = {} @@ -162,18 +205,12 @@ export class HoldObject extends Container { } else { this.elements[state][part] = element } - ;(state == "Active" ? active : inactive).addChild( + ;(state == "Active" ? this.active : this.inactive).addChild( this.elements[state][part] ) } } - - active.visible = false - - this.active = active - this.inactive = inactive - - this.addChild(inactive, active) + this.loaded = true } getNoteskinElement(element: string) { @@ -196,6 +233,7 @@ export class HoldObject extends Container { } setBrightness(brightness: number) { + if (!this.loaded) return const states = ["Active", "Inactive"] as const const items = ["Body", "TopCap", "BottomCap"] as const for (const state of states) { @@ -212,6 +250,7 @@ export class HoldObject extends Container { } setLength(length: number) { + if (!this.loaded) return const states = ["Active", "Inactive"] as const const sign = Math.sign(length) const absLength = Math.abs(length) @@ -242,10 +281,13 @@ export class HoldObject extends Container { } this.elements[state].TopCap.y = - absLength + this.metrics[`${this.note.type}BodyTopOffset`] + this.metrics[`${this.note.type}BodyTopOffset`] const bottomCapScale = Math.abs(this.elements[state].BottomCap.scale.y) this.elements[state].BottomCap.scale.y = length < 0 ? -bottomCapScale : bottomCapScale + const topCapScale = Math.abs(this.elements[state].TopCap.scale.y) + this.elements[state].TopCap.scale.y = + length < 0 ? -topCapScale : topCapScale this.elements[state].Body.height *= sign this.elements[state].Body.y *= sign @@ -502,23 +544,6 @@ export class Notefield extends Container implements ChartRendererComponent { } createNote(note: NotedataEntry): NoteWrapper { - if (this.noteskin === undefined) { - const a = new Container() as NoteObject - a.type = "note" - a.note = { - beat: 0, - type: "Tap", - col: 0, - warped: false, - fake: false, - second: 0, - quant: 4, - } - return new NoteWrapper(a) - } - const ns = this.noteskin - const col = this.getColumnName(note.col) - const opts = { note, columnName: col, columnNumber: note.col } switch (note.type) { case "Tap": case "Lift": diff --git a/app/src/chart/gameTypes/noteskin/NoteskinRegistry.ts b/app/src/chart/gameTypes/noteskin/NoteskinRegistry.ts index e5b2d5bf..2e023bf6 100644 --- a/app/src/chart/gameTypes/noteskin/NoteskinRegistry.ts +++ b/app/src/chart/gameTypes/noteskin/NoteskinRegistry.ts @@ -210,15 +210,7 @@ NoteskinRegistry.register({ }) NoteskinRegistry.register({ id: "starlight-vivid", - gameTypes: [ - "dance-single", - "dance-double", - "dance-couple", - "dance-solo", - "dance-solodouble", - "dance-threepanel", - "dance-threedouble", - ], + gameTypes: ["dance-single", "dance-double", "dance-couple"], path: "./dance/starlight-vivid", title: "SLNEXXT-vivid", subtitle: "from STARLiGHT-NEXXT", diff --git a/app/src/chart/gameTypes/noteskin/_template/HoldBody.ts b/app/src/chart/gameTypes/noteskin/_template/HoldBody.ts index 5eca5bd6..9b0b3d58 100644 --- a/app/src/chart/gameTypes/noteskin/_template/HoldBody.ts +++ b/app/src/chart/gameTypes/noteskin/_template/HoldBody.ts @@ -88,15 +88,16 @@ export class AnimatedHoldBody extends HoldBody { updateTexture() { const currentFrame = this.currentFrame - this._previousFrame !== currentFrame && - ((this._previousFrame = currentFrame), - (this._texture = this._textures[currentFrame]), - (this._textureID = -1), - (this._textureTrimmedID = -1), - (this._cachedTint = 16777215), - (this.uvs = this._texture._uvs.uvsFloat32), - this.updateAnchor && this._anchor.copyFrom(this._texture.defaultAnchor), - this.onFrameChange?.(this.currentFrame)) + if (this._previousFrame !== currentFrame) { + this._previousFrame = currentFrame + this.texture = this._textures[currentFrame] + this._textureID = -1 + this._textureTrimmedID = -1 + this._cachedTint = 16777215 + this.uvs = this._texture._uvs.uvsFloat32 + if (this.updateAnchor) this._anchor.copyFrom(this._texture.defaultAnchor) + this.onFrameChange?.(this.currentFrame) + } } destroy(options?: IDestroyOptions | boolean) { diff --git a/app/src/chart/gameTypes/noteskin/_template/HoldTail.ts b/app/src/chart/gameTypes/noteskin/_template/HoldTail.ts index a8a02983..6d3c030e 100644 --- a/app/src/chart/gameTypes/noteskin/_template/HoldTail.ts +++ b/app/src/chart/gameTypes/noteskin/_template/HoldTail.ts @@ -1,6 +1,5 @@ import { IDestroyOptions, Texture, Ticker, UPDATE_PRIORITY } from "pixi.js" import { VertCropSprite } from "../../../../util/VertCropSprite" -import { HoldBody } from "./HoldBody" export class HoldTail extends VertCropSprite { constructor(texture: Texture, holdWidth = 64) { @@ -17,7 +16,7 @@ export class HoldTail extends VertCropSprite { } } -export class AnimatedHoldBody extends HoldBody { +export class AnimatedHoldTail extends HoldTail { private _playing = false private _autoUpdate = false private _isConnectedToTicker = false @@ -85,15 +84,11 @@ export class AnimatedHoldBody extends HoldBody { updateTexture() { const currentFrame = this.currentFrame - this._previousFrame !== currentFrame && - ((this._previousFrame = currentFrame), - (this._texture = this._textures[currentFrame]), - (this._textureID = -1), - (this._textureTrimmedID = -1), - (this._cachedTint = 16777215), - (this.uvs = this._texture._uvs.uvsFloat32), - this.updateAnchor && this._anchor.copyFrom(this._texture.defaultAnchor), - this.onFrameChange?.(this.currentFrame)) + if (this._previousFrame !== currentFrame) { + this._previousFrame = currentFrame + this.texture = this._textures[currentFrame] + this.onFrameChange?.(this.currentFrame) + } } destroy(options?: IDestroyOptions | boolean) { diff --git a/app/src/chart/gameTypes/noteskin/_template/HoldTopCap.ts b/app/src/chart/gameTypes/noteskin/_template/HoldTopCap.ts new file mode 100644 index 00000000..cedf881b --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/_template/HoldTopCap.ts @@ -0,0 +1,158 @@ +import { + IDestroyOptions, + Sprite, + Texture, + Ticker, + UPDATE_PRIORITY, +} from "pixi.js" + +export class HoldTopCap extends Sprite { + constructor(texture: Texture, holdWidth = 64, reverse = false) { + super(texture) + this.scale.set(holdWidth / this.texture.width) + this.anchor.x = 0.5 + if (reverse) { + this.rotation = Math.PI + } + this.texture.on("update", () => { + this.scale.set(holdWidth / this.texture.width) + }) + } +} + +export class AnimatedHoldTopCap extends HoldTopCap { + private _playing = false + private _autoUpdate = false + private _isConnectedToTicker = false + private _tickerUpdate = this.update.bind(this) + private _currentTime = 0 + private _textures!: Texture[] + private _previousFrame: number | null = null + + onComplete: (() => void) | null = null + onLoop: (() => void) | null = null + onFrameChange: ((frame: number) => void) | null = null + animationSpeed = 1 + loop = false + updateAnchor = false + constructor(textures: Texture[], holdWidth: number) { + super(textures[0], holdWidth) + this.textures = textures + } + + stop() { + if (this._playing) { + this._playing = false + if (this._autoUpdate && this._isConnectedToTicker) { + Ticker.shared.remove(this._tickerUpdate) + this._isConnectedToTicker = false + } + } + } + play() { + if (!this._playing) { + this._playing = true + if (this._autoUpdate && !this._isConnectedToTicker) { + Ticker.shared.add(this._tickerUpdate, this, UPDATE_PRIORITY.HIGH) + this._isConnectedToTicker = true + } + } + } + + gotoAndStop(frameNumber: number) { + this.stop() + this.currentFrame = frameNumber + } + gotoAndPlay(frameNumber: number) { + this.currentFrame = frameNumber + this.play() + } + + update(deltaTime: number) { + if (!this._playing) return + const elapsed = this.animationSpeed * deltaTime, + previousFrame = this.currentFrame + this._currentTime += elapsed + this._currentTime < 0 && !this.loop + ? (this.gotoAndStop(0), this.onComplete?.()) + : this._currentTime >= this._textures.length && !this.loop + ? (this.gotoAndStop(this._textures.length - 1), this.onComplete?.()) + : previousFrame !== this.currentFrame && + (this.loop && + this.onLoop && + ((this.animationSpeed > 0 && this.currentFrame < previousFrame) || + (this.animationSpeed < 0 && this.currentFrame > previousFrame)) && + this.onLoop(), + this.updateTexture()) + } + + updateTexture() { + const currentFrame = this.currentFrame + if (this._previousFrame !== currentFrame) { + this._previousFrame = currentFrame + this.texture = this._textures[currentFrame] + this.onFrameChange?.(this.currentFrame) + } + } + + destroy(options?: IDestroyOptions | boolean) { + this.stop(), + super.destroy(options), + (this.onComplete = null), + (this.onFrameChange = null), + (this.onLoop = null) + } + + get totalFrames() { + return this._textures?.length ?? 0 + } + + get textures() { + return this._textures ?? [] + } + + set textures(value: Texture[]) { + this._textures = value + this._previousFrame = null + this.gotoAndStop(0) + this.updateTexture() + } + + get currentFrame() { + let currentFrame = Math.floor(this._currentTime) % this._textures.length + return ( + currentFrame < 0 && (currentFrame += this._textures.length), currentFrame + ) + } + + set currentFrame(value) { + if (value < 0 || value > this.totalFrames - 1) + throw new Error( + `[AnimatedSprite]: Invalid frame index value ${value}, expected to be between 0 and totalFrames ${this.totalFrames}.` + ) + const previousFrame = this.currentFrame + ;(this._currentTime = value), + previousFrame !== this.currentFrame && this.updateTexture() + } + + get playing() { + return this._playing + } + + get autoUpdate() { + return this._autoUpdate + } + + set autoUpdate(value) { + value !== this._autoUpdate && + ((this._autoUpdate = value), + !this._autoUpdate && this._isConnectedToTicker + ? (Ticker.shared.remove(this._tickerUpdate), + (this._isConnectedToTicker = !1)) + : this._autoUpdate && + !this._isConnectedToTicker && + this._playing && + (Ticker.shared.add(this._tickerUpdate), + (this._isConnectedToTicker = !0))) + } +} diff --git a/app/src/chart/gameTypes/noteskin/pump/default/Noteskin.ts b/app/src/chart/gameTypes/noteskin/pump/default/Noteskin.ts index 22ec9e5e..77e46592 100644 --- a/app/src/chart/gameTypes/noteskin/pump/default/Noteskin.ts +++ b/app/src/chart/gameTypes/noteskin/pump/default/Noteskin.ts @@ -1,18 +1,14 @@ // FIESTA, from https://github.com/cesarmades/piunoteskins -import { - AnimatedSprite, - BLEND_MODES, - Container, - Sprite, - Texture, -} from "pixi.js" -import { Noteskin, NoteskinOptions } from "../../Noteskin" +import { BLEND_MODES, Container, Rectangle, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" import { NoteRenderer } from "./NoteRenderer" import receptorsUrl from "./receptors.png" import { splitTex } from "../../../../../util/Util" +import { AnimatedHoldBody } from "../../_template/HoldBody" +import { AnimatedHoldTail } from "../../_template/HoldTail" import { NoteFlashContainer } from "./NoteFlash" const receptorTex = splitTex(Texture.from(receptorsUrl), 5, 2, 96, 96) @@ -21,61 +17,28 @@ export const texOrder = ["DownLeft", "UpLeft", "Center", "UpRight", "DownRight"] const holdTex: Record = {} -for (const col of texOrder) { - const body = splitTex( - Texture.from(new URL(`./hold/${col}Body.png`, import.meta.url).href), - 6, - 1, - 96, - 128 - )[0] - const cap = splitTex( - Texture.from(new URL(`./hold/${col}Cap.png`, import.meta.url).href), - 6, - 1, - 96, - 120 - )[0] - holdTex[col] = { body, cap } -} - -class HoldWithTail extends Container { - body - cap - constructor(columnName: string, noteskin: Noteskin) { - super() - const body = new AnimatedSprite(holdTex[columnName].body) - body.width = 72 - body.anchor.y = 1 - body.x = -36 - - const cap = new AnimatedSprite(holdTex[columnName].cap) - cap.scale.set(72 / 96) - cap.anchor.y = 0.5 - cap.x = -36 - - this.body = body - this.cap = cap - this.addChild(body, cap) - - noteskin.onUpdate(this, cr => { - const time = cr.getVisualTime() - - const frame = Math.floor(((((time % 0.3) + 0.3) % 0.3) / 0.3) * 6) - - cap.currentFrame = frame - body.currentFrame = frame - }) - } +const tailHeight = 96 - set height(height: number) { - this.body.height = Math.abs(height) - this.body.anchor.y = height >= 0 ? 1 : 0 - - this.cap.scale.y = ((height >= 0 ? 1 : -1) * 72) / 96 - - return +for (const col of texOrder) { + // Split the hold into body and tail + const tex = Texture.from(new URL(`./hold/${col}.png`, import.meta.url).href) + const body: Texture[] = [] + const cap: Texture[] = [] + for (let i = 0; i < 6; i++) { + body.push( + new Texture( + tex.baseTexture, + new Rectangle(i * 96, 0, 96, 288 - tailHeight) + ) + ) + cap.push( + new Texture( + tex.baseTexture, + new Rectangle(i * 96, 288 - tailHeight, 96, tailHeight) + ) + ) } + holdTex[col] = { body, cap } } export default { @@ -123,12 +86,32 @@ export default { ), "Hold Active Head": { element: "Tap" }, "Hold Inactive Head": { element: "Tap" }, - "Hold Active Body": opt => new HoldWithTail(opt.columnName, opt.noteskin), + "Hold Active Body": opt => { + const body = new AnimatedHoldBody(holdTex[opt.columnName].body, 72) + opt.noteskin.onUpdate(body, cr => { + const time = cr.getVisualTime() + + const frame = Math.floor(((((time % 0.3) + 0.3) % 0.3) / 0.3) * 6) + + body.currentFrame = frame + }) + return body + }, "Hold Inactive Body": { element: "Hold Active Body" }, "Hold Active TopCap": () => new Sprite(Texture.EMPTY), "Hold Inactive TopCap": () => new Sprite(Texture.EMPTY), - "Hold Active BottomCap": () => new Sprite(Texture.EMPTY), - "Hold Inactive BottomCap": () => new Sprite(Texture.EMPTY), + "Hold Active BottomCap": opt => { + const cap = new AnimatedHoldTail(holdTex[opt.columnName].cap, 72) + opt.noteskin.onUpdate(cap, cr => { + const time = cr.getVisualTime() + + const frame = Math.floor(((((time % 0.3) + 0.3) % 0.3) / 0.3) * 6) + + cap.currentFrame = frame + }) + return cap + }, + "Hold Inactive BottomCap": { element: "Hold Active BottomCap" }, "Roll Active Head": options => { const spr = new Sprite(Texture.WHITE) @@ -143,8 +126,8 @@ export default { "Roll Inactive Body": { element: "Hold Active Body" }, "Roll Active TopCap": () => new Sprite(Texture.EMPTY), "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), - "Roll Active BottomCap": () => new Sprite(Texture.EMPTY), - "Roll Inactive BottomCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": { element: "Hold Active BottomCap" }, + "Roll Inactive BottomCap": { element: "Hold Active BottomCap" }, }, }, load: function (element, options) { @@ -160,4 +143,8 @@ export default { update(renderer) { NoteRenderer.setArrowTexTime(renderer.chartManager.app) }, + metrics: { + HoldBodyBottomOffset: -36, + RollBodyBottomOffset: -36, + }, } satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/Center.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/Center.png new file mode 100644 index 00000000..3c58cdb5 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/hold/Center.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/CenterBody.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/CenterBody.png deleted file mode 100644 index 17b6b408..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/default/hold/CenterBody.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/CenterCap.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/CenterCap.png deleted file mode 100644 index 1dfee9c5..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/default/hold/CenterCap.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/DownLeft.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/DownLeft.png new file mode 100644 index 00000000..7c46121f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/hold/DownLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/DownLeftBody.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/DownLeftBody.png deleted file mode 100644 index 4b5fbed4..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/default/hold/DownLeftBody.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/DownLeftCap.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/DownLeftCap.png deleted file mode 100644 index 82e25830..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/default/hold/DownLeftCap.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/DownRight.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/DownRight.png new file mode 100644 index 00000000..534a1087 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/hold/DownRight.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/DownRightBody.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/DownRightBody.png deleted file mode 100644 index d608fc3d..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/default/hold/DownRightBody.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/DownRightCap.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/DownRightCap.png deleted file mode 100644 index 91a7a684..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/default/hold/DownRightCap.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/UpLeft.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/UpLeft.png new file mode 100644 index 00000000..6be2d278 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/hold/UpLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/UpLeftBody.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/UpLeftBody.png deleted file mode 100644 index e6a8db1b..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/default/hold/UpLeftBody.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/UpLeftCap.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/UpLeftCap.png deleted file mode 100644 index ecfb1d39..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/default/hold/UpLeftCap.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/UpRight.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/UpRight.png new file mode 100644 index 00000000..c07adb19 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/hold/UpRight.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/UpRightBody.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/UpRightBody.png deleted file mode 100644 index 6071ea3a..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/default/hold/UpRightBody.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/hold/UpRightCap.png b/app/src/chart/gameTypes/noteskin/pump/default/hold/UpRightCap.png deleted file mode 100644 index 4bd59da3..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/default/hold/UpRightCap.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteSkin.ts b/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteSkin.ts index 5ad4396a..29fafccd 100644 --- a/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteSkin.ts +++ b/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteSkin.ts @@ -1,6 +1,6 @@ // SM4 Bold, bundled with SM4 -import { Sprite, Texture, TilingSprite } from "pixi.js" +import { Sprite, Texture } from "pixi.js" import { NoteskinOptions } from "../../Noteskin" import { NoteRenderer } from "./NoteRenderer" @@ -14,6 +14,9 @@ import holdCapInactiveUrl from "./hold/capInactive.png" import { BezierAnimator } from "../../../../../util/BezierEasing" import { rgbtoHex } from "../../../../../util/Color" +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" +import { HoldTopCap } from "../../_template/HoldTopCap" import { NoteFlashContainer } from "./NoteFlash" import rollBodyActiveUrl from "./roll/bodyActive.png" import rollBodyInactiveUrl from "./roll/bodyInactive.png" @@ -64,27 +67,6 @@ const toRotate = [ "Roll Active Head", ] -const holdBody = (tex: Texture) => { - const body = new TilingSprite(tex, 64, 0) - body.tileScale.x = 0.5 - body.tileScale.y = 0.5 - body.uvRespectAnchor = true - body.anchor.y = 1 - body.x = -32 - return body -} - -const holdCap = (tex: Texture, reverse = false) => { - const cap = new Sprite(tex) - cap.width = 64 - cap.height = 32 - cap.anchor.x = 0.5 - if (reverse) { - cap.height = -32 - } - return cap -} - export default { elements: { DownLeft: { @@ -140,21 +122,25 @@ export default { new NoteFlashContainer(options.noteskin, options.columnNumber), "Hold Active Head": { element: "Tap" }, "Hold Inactive Head": { element: "Tap" }, - "Hold Active Body": () => holdBody(holdTex.hold.active.body), - "Hold Inactive Body": () => holdBody(holdTex.hold.inactive.body), - "Hold Active TopCap": () => holdCap(holdTex.hold.active.cap, true), - "Hold Inactive TopCap": () => holdCap(holdTex.hold.inactive.cap, true), - "Hold Active BottomCap": () => holdCap(holdTex.hold.active.cap), - "Hold Inactive BottomCap": () => holdCap(holdTex.hold.inactive.cap), + "Hold Active Body": () => new HoldBody(holdTex.hold.active.body), + "Hold Inactive Body": () => new HoldBody(holdTex.hold.inactive.body), + "Hold Active TopCap": () => + new HoldTopCap(holdTex.hold.active.cap, 64, true), + "Hold Inactive TopCap": () => + new HoldTopCap(holdTex.hold.inactive.cap, 64, true), + "Hold Active BottomCap": () => new HoldTail(holdTex.hold.active.cap), + "Hold Inactive BottomCap": () => new HoldTail(holdTex.hold.inactive.cap), "Roll Active Head": { element: "Tap" }, "Roll Inactive Head": { element: "Tap" }, - "Roll Active Body": () => holdBody(holdTex.roll.active.body), - "Roll Inactive Body": () => holdBody(holdTex.roll.inactive.body), - "Roll Active TopCap": () => holdCap(holdTex.roll.active.cap, true), - "Roll Inactive TopCap": () => holdCap(holdTex.roll.inactive.cap, true), - "Roll Active BottomCap": () => holdCap(holdTex.roll.active.cap), - "Roll Inactive BottomCap": () => holdCap(holdTex.roll.inactive.cap), + "Roll Active Body": () => new HoldBody(holdTex.roll.active.body), + "Roll Inactive Body": () => new HoldBody(holdTex.roll.inactive.body), + "Roll Active TopCap": () => + new HoldTopCap(holdTex.roll.active.cap, 64, true), + "Roll Inactive TopCap": () => + new HoldTopCap(holdTex.roll.inactive.cap, 64, true), + "Roll Active BottomCap": () => new HoldTail(holdTex.roll.active.cap), + "Roll Inactive BottomCap": () => new HoldTail(holdTex.roll.inactive.cap), }, }, load: function (element, options) { diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/NoteSkin.ts b/app/src/chart/gameTypes/noteskin/pump/prime/NoteSkin.ts index 4060021e..840d300d 100644 --- a/app/src/chart/gameTypes/noteskin/pump/prime/NoteSkin.ts +++ b/app/src/chart/gameTypes/noteskin/pump/prime/NoteSkin.ts @@ -1,18 +1,14 @@ // PRIME, from https://github.com/cesarmades/piunoteskins -import { - AnimatedSprite, - BLEND_MODES, - Container, - Sprite, - Texture, -} from "pixi.js" -import { Noteskin, NoteskinOptions } from "../../Noteskin" +import { BLEND_MODES, Container, Rectangle, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" import { NoteRenderer } from "./NoteRenderer" import receptorsUrl from "./receptors.png" import { splitTex } from "../../../../../util/Util" +import { AnimatedHoldBody } from "../../_template/HoldBody" +import { AnimatedHoldTail } from "../../_template/HoldTail" import { NoteFlashContainer } from "./NoteFlash" const receptorTex = splitTex(Texture.from(receptorsUrl), 5, 2, 96, 96) @@ -21,61 +17,28 @@ export const texOrder = ["DownLeft", "UpLeft", "Center", "UpRight", "DownRight"] const holdTex: Record = {} -for (const col of texOrder) { - const body = splitTex( - Texture.from(new URL(`./hold/${col}Body.png`, import.meta.url).href), - 6, - 1, - 96, - 128 - )[0] - const cap = splitTex( - Texture.from(new URL(`./hold/${col}Cap.png`, import.meta.url).href), - 6, - 1, - 96, - 120 - )[0] - holdTex[col] = { body, cap } -} - -class HoldWithTail extends Container { - body - cap - constructor(columnName: string, noteskin: Noteskin) { - super() - const body = new AnimatedSprite(holdTex[columnName].body) - body.width = 72 - body.anchor.y = 1 - body.x = -36 - - const cap = new AnimatedSprite(holdTex[columnName].cap) - cap.scale.set(72 / 96) - cap.anchor.y = 0.5 - cap.x = -36 - - this.body = body - this.cap = cap - this.addChild(body, cap) - - noteskin.onUpdate(this, cr => { - const time = cr.getVisualTime() - - const frame = Math.floor(((((time % 0.3) + 0.3) % 0.3) / 0.3) * 6) - - cap.currentFrame = frame - body.currentFrame = frame - }) - } +const tailHeight = 96 - set height(height: number) { - this.body.height = Math.abs(height) - this.body.anchor.y = height >= 0 ? 1 : 0 - - this.cap.scale.y = ((height >= 0 ? 1 : -1) * 72) / 96 - - return +for (const col of texOrder) { + // Split the hold into body and tail + const tex = Texture.from(new URL(`./hold/${col}.png`, import.meta.url).href) + const body: Texture[] = [] + const cap: Texture[] = [] + for (let i = 0; i < 6; i++) { + body.push( + new Texture( + tex.baseTexture, + new Rectangle(i * 96, 0, 96, 288 - tailHeight) + ) + ) + cap.push( + new Texture( + tex.baseTexture, + new Rectangle(i * 96, 288 - tailHeight, 96, tailHeight) + ) + ) } + holdTex[col] = { body, cap } } export default { @@ -123,13 +86,32 @@ export default { ), "Hold Active Head": { element: "Tap" }, "Hold Inactive Head": { element: "Tap" }, - "Hold Active Body": opt => new HoldWithTail(opt.columnName, opt.noteskin), + "Hold Active Body": opt => { + const body = new AnimatedHoldBody(holdTex[opt.columnName].body, 72) + opt.noteskin.onUpdate(body, cr => { + const time = cr.getVisualTime() + + const frame = Math.floor(((((time % 0.3) + 0.3) % 0.3) / 0.3) * 6) + + body.currentFrame = frame + }) + return body + }, "Hold Inactive Body": { element: "Hold Active Body" }, "Hold Active TopCap": () => new Sprite(Texture.EMPTY), "Hold Inactive TopCap": () => new Sprite(Texture.EMPTY), - "Hold Active BottomCap": () => new Sprite(Texture.EMPTY), - "Hold Inactive BottomCap": () => new Sprite(Texture.EMPTY), + "Hold Active BottomCap": opt => { + const cap = new AnimatedHoldTail(holdTex[opt.columnName].cap, 72) + opt.noteskin.onUpdate(cap, cr => { + const time = cr.getVisualTime() + const frame = Math.floor(((((time % 0.3) + 0.3) % 0.3) / 0.3) * 6) + + cap.currentFrame = frame + }) + return cap + }, + "Hold Inactive BottomCap": { element: "Hold Active BottomCap" }, "Roll Active Head": options => { const spr = new Sprite(Texture.WHITE) NoteRenderer.setRollTex(spr, options.columnName) @@ -143,8 +125,8 @@ export default { "Roll Inactive Body": { element: "Hold Active Body" }, "Roll Active TopCap": () => new Sprite(Texture.EMPTY), "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), - "Roll Active BottomCap": () => new Sprite(Texture.EMPTY), - "Roll Inactive BottomCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": { element: "Hold Active BottomCap" }, + "Roll Inactive BottomCap": { element: "Hold Active BottomCap" }, }, }, load: function (element, options) { @@ -160,4 +142,8 @@ export default { update(renderer) { NoteRenderer.setArrowTexTime(renderer.chartManager.app) }, + metrics: { + HoldBodyBottomOffset: -36, + RollBodyBottomOffset: -36, + }, } satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/Center.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/Center.png new file mode 100644 index 00000000..6b5bc646 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/hold/Center.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/CenterBody.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/CenterBody.png deleted file mode 100644 index 5f37016b..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/prime/hold/CenterBody.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/CenterCap.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/CenterCap.png deleted file mode 100644 index 86783cb9..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/prime/hold/CenterCap.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownLeft.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownLeft.png new file mode 100644 index 00000000..4db5129b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownLeftBody.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownLeftBody.png deleted file mode 100644 index 11079df6..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownLeftBody.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownLeftCap.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownLeftCap.png deleted file mode 100644 index a45c0820..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownLeftCap.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownRight.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownRight.png new file mode 100644 index 00000000..f339237f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownRight.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownRightBody.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownRightBody.png deleted file mode 100644 index ae3f7ee9..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownRightBody.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownRightCap.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownRightCap.png deleted file mode 100644 index bf0b4716..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/prime/hold/DownRightCap.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpLeft.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpLeft.png new file mode 100644 index 00000000..d6f15755 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpLeftBody.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpLeftBody.png deleted file mode 100644 index 98ff7f92..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpLeftBody.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpLeftCap.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpLeftCap.png deleted file mode 100644 index df0eedbf..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpLeftCap.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpRight.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpRight.png new file mode 100644 index 00000000..7a09684b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpRight.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpRightBody.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpRightBody.png deleted file mode 100644 index d686987a..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpRightBody.png and /dev/null differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpRightCap.png b/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpRightCap.png deleted file mode 100644 index b3df02cd..00000000 Binary files a/app/src/chart/gameTypes/noteskin/pump/prime/hold/UpRightCap.png and /dev/null differ diff --git a/app/src/gui/widget/StatusWidget.ts b/app/src/gui/widget/StatusWidget.ts index ac64bc6c..f363b8b7 100644 --- a/app/src/gui/widget/StatusWidget.ts +++ b/app/src/gui/widget/StatusWidget.ts @@ -3,6 +3,11 @@ import { Sprite, Texture } from "pixi.js" import tippy from "tippy.js" import { EditMode, EditTimingMode } from "../../chart/ChartManager" import { NoteskinSprite } from "../../chart/gameTypes/noteskin/Noteskin" +import { + HOLD_NOTE_TYPES, + HoldNoteType, + TapNoteType, +} from "../../chart/sm/NoteTypes" import { BetterRoundedRect } from "../../util/BetterRoundedRect" import { EventHandler } from "../../util/EventHandler" import { Flags } from "../../util/Flags" @@ -481,10 +486,11 @@ export class StatusWidget extends Widget { if (!this.manager.chartManager.loadedChart) return for (const type of this.manager.chartManager.loadedChart.gameType .editNoteTypes) { + if (HOLD_NOTE_TYPES.includes(type as HoldNoteType)) continue const sprite = this.manager.chartManager .chartView!.getNotefield() .createNote({ - type, + type: type as TapNoteType, beat: 0, col: 0, quant: 4,