diff --git a/app/assets/icon/fake.png b/app/assets/icon/fake.png new file mode 100644 index 00000000..46e3d342 Binary files /dev/null and b/app/assets/icon/fake.png differ diff --git a/app/assets/icon/lift.png b/app/assets/icon/lift.png new file mode 100644 index 00000000..b5505b54 Binary files /dev/null and b/app/assets/icon/lift.png differ diff --git a/app/assets/noteskin/dance/default/hold_judgment.png b/app/assets/judgement/hold_judgement.png similarity index 100% rename from app/assets/noteskin/dance/default/hold_judgment.png rename to app/assets/judgement/hold_judgement.png diff --git a/app/assets/judgment/judgmentITG.png b/app/assets/judgement/judgementITG.png similarity index 100% rename from app/assets/judgment/judgmentITG.png rename to app/assets/judgement/judgementITG.png diff --git a/app/assets/judgment/judgmentWaterfall.png b/app/assets/judgement/judgementWaterfall.png similarity index 100% rename from app/assets/judgment/judgmentWaterfall.png rename to app/assets/judgement/judgementWaterfall.png diff --git a/app/assets/missing.png b/app/assets/missing.png new file mode 100644 index 00000000..ac43cafe Binary files /dev/null and b/app/assets/missing.png differ diff --git a/app/assets/noteskin/dance/default/icon/fake.png b/app/assets/noteskin/dance/default/icon/fake.png deleted file mode 100644 index 1d69e892..00000000 Binary files a/app/assets/noteskin/dance/default/icon/fake.png and /dev/null differ diff --git a/app/assets/noteskin/dance/default/icon/lift.png b/app/assets/noteskin/dance/default/icon/lift.png deleted file mode 100644 index d5e4d920..00000000 Binary files a/app/assets/noteskin/dance/default/icon/lift.png and /dev/null differ diff --git a/app/assets/noteskin/dance/default/tap/parts_old.png b/app/assets/noteskin/dance/default/tap/parts_old.png deleted file mode 100644 index fe77caf3..00000000 Binary files a/app/assets/noteskin/dance/default/tap/parts_old.png and /dev/null differ diff --git a/app/assets/preview.png b/app/assets/preview.png new file mode 100644 index 00000000..d9ea9719 Binary files /dev/null and b/app/assets/preview.png differ diff --git a/app/index.css b/app/index.css index 5c094c2c..988d1552 100644 --- a/app/index.css +++ b/app/index.css @@ -2743,3 +2743,79 @@ input[type="color"] { .animated .po-collapse img { transition: 0.15s cubic-bezier(0.61, 0.04, 0.38, 1.02); } + +.noteskin-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + width: 100%; + gap: 15px; + padding: 10px; + background: var(--bg-secondary); + margin-top: 10px; + border-radius: 5px; + overflow: auto; + height: 330px; +} + +.noteskin-cell { + height: 250px; + position: relative; + border-radius: 5px; +} + +.noteskin-cell img { + min-width: 0; + width: 100%; + height: 100%; + aspect-ratio: 9 / 21; + object-fit: cover; + border-radius: 5px; +} + +.noteskin-label { + position: absolute; + bottom: 0; + padding: 10px; + width: 100%; + padding-top: 100px; + background: linear-gradient( + to top, + rgba(0, 0, 0, 0.7), + rgba(0, 0, 0, 0.7) 30%, + rgba(0, 0, 0, 0) 100% + ); + border-radius: 5px; +} + +.noteskin-title { + font-size: 16px; + font-weight: bold; +} + +.noteskin-subtitle { + font-style: italic; + font-size: 12px; +} + +.noteskin-cell:hover { + transform: scale(1.02); + box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.6); +} + +.noteskin-cell:hover img { + filter: brightness(1.1); +} + +.noteskin-cell:active { + transform: scale(0.98); +} + +.noteskin-cell.selected img { + box-shadow: 0 0 5px 2px rgba(24, 147, 195, 0.6); + filter: brightness(1.2); +} + +.animated .noteskin-cell, +.animated .noteskin-cell img { + transition: 0.2s cubic-bezier(0.26, 0.64, 0.47, 1.6); +} diff --git a/app/src/App.ts b/app/src/App.ts index e3126269..a1b37b26 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -12,6 +12,8 @@ import "tippy.js/animations/scale-subtle.css" import "tippy.js/dist/tippy.css" import WebFont from "webfontloader" import { ChartManager } from "./chart/ChartManager" +import { GameTypeRegistry } from "./chart/gameTypes/GameTypeRegistry" +import { NoteskinRegistry } from "./chart/gameTypes/noteskin/NoteskinRegistry" import { Chart } from "./chart/sm/Chart" import { ContextMenuPopup } from "./gui/element/ContextMenu" import { MenubarManager } from "./gui/element/MenubarManager" @@ -37,6 +39,8 @@ declare global { interface Window { app: App Parity?: ParityGenerator + GameTypeRegistry: GameTypeRegistry + NoteskinRegistry: NoteskinRegistry } interface File { path?: string @@ -59,6 +63,7 @@ interface Version { } export class App { + options = Options renderer: Renderer ticker: Ticker stage: Container @@ -442,5 +447,7 @@ function init() { ` } else { window.app = new App() + window.GameTypeRegistry = GameTypeRegistry + window.NoteskinRegistry = NoteskinRegistry } } diff --git a/app/src/chart/ChartManager.ts b/app/src/chart/ChartManager.ts index 85e4a729..57f4a6e6 100644 --- a/app/src/chart/ChartManager.ts +++ b/app/src/chart/ChartManager.ts @@ -41,6 +41,7 @@ import { FileHandler } from "../util/file-handler/FileHandler" import { ChartRenderer } from "./ChartRenderer" import { ChartAudio } from "./audio/ChartAudio" import { GameTypeRegistry } from "./gameTypes/GameTypeRegistry" +import { NoteskinRegistry } from "./gameTypes/noteskin/NoteskinRegistry" import { GameplayStats } from "./play/GameplayStats" import { TIMING_WINDOW_AUTOPLAY } from "./play/StandardTimingWindow" import { Chart } from "./sm/Chart" @@ -425,7 +426,7 @@ export class ChartManager { ) ) { if (this.mode != EditMode.Play) - this.chartView.doJudgment( + this.chartView.doJudgement( notedata[this.noteIndex], 0, TIMING_WINDOW_AUTOPLAY @@ -776,6 +777,25 @@ export class ChartManager { Options.play.timingCollection = Options.play.defaultTimingCollection[chart.gameType.id] ?? "ITG" + // Check if the same noteskin is compatible with the current chart + const oldType = GameTypeRegistry.getGameType(Options.chart.noteskin.type) + const newNoteskin = { + type: chart.gameType.id, + name: Options.chart.lastNoteskins[chart.gameType.id] ?? "default", + } + if (oldType) { + const oldSkin = NoteskinRegistry.getNoteskinData( + oldType, + Options.chart.noteskin.name + ) + if (oldSkin?.gameTypes.includes(chart.gameType.id)) { + // Use the old note skin + newNoteskin.name = oldSkin.id + } + } + Options.chart.noteskin = newNoteskin + Options.chart.lastNoteskins[chart.gameType.id] = newNoteskin.name + this.getAssistTickIndex() this.chartView = new ChartRenderer(this) this.chartView.x = this.app.renderer.screen.width / 2 @@ -1417,16 +1437,18 @@ export class ChartManager { if (note.second < this.time) note.gameplay!.hasHit = true else break } - this.loadedChart.gameType.gameLogic.endPlay(this) + this.loadedChart.gameType.gameLogic.startPlay(this) this.gameStats = new GameplayStats(this) this.widgetManager.startPlay() this.chartAudio.seek(Math.max(0, this.time) - 1) this.chartAudio.play() + this.chartView.startPlay() } else if (this.mode == EditMode.Record) { this.chartAudio.seek(Math.max(0, this.time) - 1) this.chartAudio.play() } else { this.chartView.endPlay() + notedata.forEach(note => (note.gameplay = undefined)) } } diff --git a/app/src/chart/ChartRenderer.ts b/app/src/chart/ChartRenderer.ts index 6ebb862f..7bdafee0 100644 --- a/app/src/chart/ChartRenderer.ts +++ b/app/src/chart/ChartRenderer.ts @@ -20,7 +20,7 @@ import { Waveform } from "./component/edit/Waveform" import { Notefield } from "./component/notefield/Notefield" import { ComboNumber } from "./component/play/ComboNumber" import { ErrorBarContainer } from "./component/play/ErrorBarContainer" -import { JudgmentSprite } from "./component/play/JudgmentSprite" +import { JudgementSprite } from "./component/play/JudgementSprite" import { SelectionTimingEventContainer } from "./component/timing/SelectionTimingEventContainer" import { TimingAreaContainer } from "./component/timing/TimingAreaContainer" import { TimingTrackContainer } from "./component/timing/TimingTrackContainer" @@ -56,9 +56,9 @@ export class ChartRenderer extends Container { private readonly timingTracks: TimingTrackContainer private readonly selectedEvents: SelectionTimingEventContainer private readonly timingBar: ErrorBarContainer - private readonly notefield: Notefield + private notefield: Notefield private readonly snapDisplay: SnapContainer - private readonly judgment: JudgmentSprite + private readonly judgement: JudgementSprite private readonly combo: ComboNumber private readonly selectionBoundary: SelectionBoundary private readonly selectionArea: SelectionAreaContainer @@ -82,7 +82,7 @@ export class ChartRenderer extends Container { this.snapDisplay = new SnapContainer(this) this.previewArea = new PreviewAreaContainer(this) this.selectionArea = new SelectionAreaContainer(this) - this.judgment = new JudgmentSprite() + this.judgement = new JudgementSprite() this.combo = new ComboNumber(this) this.selectionBoundary = new SelectionBoundary(this) this.scrollDebug = new ScrollDebug(this) @@ -99,7 +99,7 @@ export class ChartRenderer extends Container { this.combo, this.notefield, this.snapDisplay, - this.judgment, + this.judgement, this.selectionBoundary, this.scrollDebug ) @@ -264,39 +264,40 @@ export class ChartRenderer extends Container { return !!this.selectionBounds } - doJudgment(note: NotedataEntry, error: number, judgment: TimingWindow) { + doJudgement( + note: NotedataEntry, + error: number | null, + judgement: TimingWindow + ) { if (this.chartManager.getMode() == EditMode.Play) { - this.judgment.doJudge(error, judgment) - this.timingBar.addBar(error, judgment) + this.judgement.doJudge(error, judgement) + this.timingBar.addBar(error, judgement) } - this.notefield.onJudgment(note.col, judgment) + this.notefield.onJudgement(note.col, judgement) } - activateHold(col: number) { - this.notefield.activateHold(col) - } - - keyDown(col: number) { - this.notefield.keyDown(col) - } - - keyUp(col: number) { - this.notefield.keyUp(col) + startPlay() { + this.notefield.startPlay() } endPlay() { this.notefield.endPlay() this.timingBar.reset() - this.judgment.reset() + this.judgement.reset() } update() { + if (this.destroyed) return + this.speedMult = Options.chart.doSpeedChanges ? this.getCurrentSpeedMult() : 1 - const firstBeat = this.getEarliestOnScreenBeat() - const lastBeat = this.getLastOnScreenBeat() + const topBeat = this.getTopOnScreenBeat() + const bottomBeat = this.getBottomOnScreenBeat() + + const firstBeat = Math.min(topBeat, bottomBeat) + const lastBeat = Math.max(topBeat, bottomBeat) this.scale.x = Options.chart.zoom this.scale.y = Options.chart.zoom @@ -528,6 +529,19 @@ export class ChartRenderer extends Container { return currentBeat + deltaBeat } + getColumnFromXPos(xp: number) { + const gt = this.chart.gameType + let lastDist = null + for (let i = 0; i < gt.numCols; i++) { + const dist = Math.abs(this.notefield.getColumnX(i) - xp) + if (lastDist !== null && dist > lastDist) { + return i - 1 + } + lastDist = dist + } + return gt.numCols - 1 + } + /** * Returns the y position of the receptors after zooming. * @@ -581,7 +595,7 @@ export class ChartRenderer extends Container { * @memberof ChartRenderer */ getUpperBound(): number { - return -this.y / Options.chart.zoom - 32 + return -this.y / Options.chart.zoom - 64 } /** @@ -594,7 +608,7 @@ export class ChartRenderer extends Container { return ( (this.chartManager.app.renderer.screen.height - this.y) / Options.chart.zoom + - 32 + 64 ) } @@ -677,7 +691,7 @@ export class ChartRenderer extends Container { return { beat: 0, value: 1, type: "SCROLLS" } } - getEarliestOnScreenBeat() { + getTopOnScreenBeat() { if ( Options.chart.waveform.speedChanges && !Options.chart.CMod && @@ -716,7 +730,7 @@ export class ChartRenderer extends Container { return this.getBeatFromYPos(this.getUpperBound()) } - getLastOnScreenBeat() { + getBottomOnScreenBeat() { if ( Options.chart.waveform.speedChanges && !Options.chart.CMod && @@ -1021,7 +1035,7 @@ export class ChartRenderer extends Container { * @return {*} {boolean} * @memberof ChartRenderer */ - selectionTest(object: Container): boolean { + selectionTest(object: DisplayObject): boolean { if (!this.selectionBounds) return false const ab = this.selectionBoundary.getBounds() const bb = object.getBounds() @@ -1072,7 +1086,7 @@ export class ChartRenderer extends Container { if (Math.abs(snapBeat - newBeat) > Math.abs(newBeat - note.beat)) { snapBeat = note.beat } - const col = Math.round((position.x + 96) / 64) + const col = this.getColumnFromXPos(position.x) this.chartManager.selection.shift ||= { columnShift: 0, beatShift: 0, @@ -1161,6 +1175,19 @@ export class ChartRenderer extends Container { return this.notefield } + swapNoteskin(name: string) { + Options.chart.noteskin.name = name + Options.chart.lastNoteskins[this.chart.gameType.id] = name + this.reloadNotefield() + } + + reloadNotefield() { + const newNotefield = new Notefield(this) + this.addChildAt(newNotefield, this.children.indexOf(this.notefield)) + this.notefield.destroy() + this.notefield = newNotefield + } + getSelectionBounds() { return this.selectionBounds } diff --git a/app/src/chart/component/edit/ScrollDebug.ts b/app/src/chart/component/edit/ScrollDebug.ts index 5ef05e97..cc242bea 100644 --- a/app/src/chart/component/edit/ScrollDebug.ts +++ b/app/src/chart/component/edit/ScrollDebug.ts @@ -219,17 +219,17 @@ export class ScrollDebug extends Container implements ChartRendererComponent { this.renderer.getVisualBeat() + Options.chart.maxDrawBeats ) this.topScreenBeat.y = this.renderer.getYPosFromBeat( - this.renderer.getEarliestOnScreenBeat() + this.renderer.getTopOnScreenBeat() ) this.bottomScreenBeat.y = this.renderer.getYPosFromBeat( - this.renderer.getLastOnScreenBeat() + this.renderer.getBottomOnScreenBeat() ) this.topScreenBeatText.y = this.topScreenBeat.y this.topScreenBeatText.text = - roundDigit(this.renderer.getEarliestOnScreenBeat(), 3) + "" + roundDigit(this.renderer.getTopOnScreenBeat(), 3) + "" this.bottomScreenBeatText.y = this.bottomScreenBeat.y this.bottomScreenBeatText.text = - roundDigit(this.renderer.getLastOnScreenBeat(), 3) + "" + roundDigit(this.renderer.getBottomOnScreenBeat(), 3) + "" } inBounds(y: number) { diff --git a/app/src/chart/component/edit/SelectionSprite.ts b/app/src/chart/component/edit/SelectionSprite.ts index 4a7aa5b0..6d0eb988 100644 --- a/app/src/chart/component/edit/SelectionSprite.ts +++ b/app/src/chart/component/edit/SelectionSprite.ts @@ -12,6 +12,7 @@ export class SelectionBoundary this.renderer = renderer this.visible = false this.alpha = 0.2 + this.eventMode = "none" } update() { const bounds = this.renderer.getSelectionBounds() diff --git a/app/src/chart/component/edit/Waveform.ts b/app/src/chart/component/edit/Waveform.ts index dbff1dbf..08de9dc4 100644 --- a/app/src/chart/component/edit/Waveform.ts +++ b/app/src/chart/component/edit/Waveform.ts @@ -8,7 +8,7 @@ import { import { EventHandler } from "../../../util/EventHandler" import { clamp } from "../../../util/Math" import { Options } from "../../../util/Options" -import { bsearch, destroyChildIf } from "../../../util/Util" +import { destroyChildIf } from "../../../util/Util" import { ChartRenderer, ChartRendererComponent } from "../../ChartRenderer" import { BeatTimingCache, ScrollTimingEvent } from "../../sm/TimingTypes" @@ -215,74 +215,71 @@ export class Waveform extends Sprite implements ChartRendererComponent { // XMod with speed changes const chartSpeed = Options.chart.speed - const speedMult = this.renderer.chart.timingData.getSpeedMult( - this.renderer.getVisualBeat(), - this.renderer.getVisualTime() - ) - const speedSign = speedMult >= 0 != Options.chart.reverse ? 1 : -1 + const speedMult = this.renderer.getCurrentSpeedMult() + + const topBeat = this.renderer.getTopOnScreenBeat() + const bottomBeat = this.renderer.getBottomOnScreenBeat() + const startBeat = Math.min(topBeat, bottomBeat) + const endBeat = Math.max(topBeat, bottomBeat) + + const startScroll = this.renderer.findFirstOnScreenScroll() + const endScroll = this.renderer.findLastOnScreenScroll() + + const offset = this.renderer.chart.timingData.getOffset() + const startBPM = + this.renderer.chart.timingData.getEventAtBeat("BPMS", 0)?.value ?? 120 - const maxDrawBeats = - this.renderer.getVisualBeat() + Options.chart.maxDrawBeats const scrolls: ScrollTimingEvent[] = [ ...this.renderer.chart.timingData.getTimingData("SCROLLS"), ] if (scrolls[0]?.beat != 0) scrolls.unshift({ type: "SCROLLS", beat: 0, value: 1 }) - const offset = this.renderer.chart.timingData.getOffset() - const startBPM = - this.renderer.chart.timingData.getEventAtBeat("BPMS", 0)?.value ?? 120 + + const startScrollIndex = scrolls.findIndex( + a => a.beat == startScroll.beat + ) + const endScrollIndex = scrolls.findIndex(a => a.beat == endScroll.beat) + const timingChanges = this.renderer.chart.timingData.getBeatTiming() const pixelsToEffectiveBeats = 100 / chartSpeed / Math.abs(speedMult) / 64 / Options.chart.zoom const screenHeight = this.renderer.chartManager.app.renderer.screen.height - let finishedDrawing = false - - // Get the first scroll index after curBeat - let scrollIndex = bsearch( - scrolls, - this.renderer.getVisualBeat() - Options.chart.maxDrawBeatsBack, - a => a.beat - ) - - let currentBeat = scrolls[scrollIndex]?.beat ?? 0 // start drawing from the start of the scroll section - if (currentBeat == 0) currentBeat = -Options.chart.maxDrawBeatsBack // Draw the waveform before beat 0 - let curSec = this.renderer.chart.getSecondsFromBeat(currentBeat) - + let currentBeat = startBeat let currentYPos = Math.round( this.renderer.getYPosFromBeat(currentBeat) * Options.chart.zoom + this.parent.y ) - while (currentBeat < maxDrawBeats && !finishedDrawing) { - const scroll = scrolls[scrollIndex] ?? { beat: 0, value: 1 } - const scrollEndBeat = scrolls[scrollIndex + 1]?.beat ?? maxDrawBeats - const scrollEndYPos = - this.renderer.getYPosFromBeat(scrollEndBeat) * Options.chart.zoom + - this.parent.y - - // Skip this scroll if - // a. value is 0, - // b. value is positive and ends off the top of the screen - // c. value is negative and ends off the bottom of the screen - if ( - scrolls[scrollIndex + 1] && - (scroll.value == 0 || - (scroll.value * speedSign < 0 && scrollEndYPos > screenHeight) || - (scroll.value * speedSign > 0 && scrollEndYPos < 0)) - ) { - scrollIndex++ - currentBeat = scrolls[scrollIndex]!.beat - currentYPos = Math.round(scrollEndYPos) - continue - } + let curSec = this.renderer.chart.getSecondsFromBeat(currentBeat) + for (const scroll of scrolls.slice( + startScrollIndex, + endScrollIndex + 1 + )) { + if (scroll.value == 0) continue const pixelsToBeats = pixelsToEffectiveBeats / Math.abs(scroll.value) - // Start drawing this scroll segment by stepping by 1 pixel - while (currentBeat < Math.min(scrollEndBeat, maxDrawBeats)) { + if (scroll != startScroll) { + currentBeat = scroll.beat + } else { + // fix flickering by rounding the current beat to land on a pixel + currentBeat = + Math.round((currentBeat - scroll.beat) / pixelsToBeats) * + pixelsToBeats + + scroll.beat + } + currentYPos = Math.round( + this.renderer.getYPosFromBeat(currentBeat) * Options.chart.zoom + + this.parent.y + ) + curSec = this.renderer.chart.getSecondsFromBeat(currentBeat) + const scrollDirection = this.renderer.getScrollDirection(scroll.value) + const scrollEndBeat = + scrolls[scrolls.indexOf(scroll) + 1]?.beat ?? Number.MAX_VALUE + while (currentBeat < Math.min(scrollEndBeat, endBeat)) { // Stop if the scroll is off the screen if (currentYPos < 0) { - // Skip the scroll if we step off the stop of the screen - if (scroll.value * speedSign < 0) { + // Skip the scroll if we step off the top of the screen + if (scroll.value * scrollDirection < 0) { currentBeat = scrollEndBeat break } @@ -292,9 +289,9 @@ export class Waveform extends Sprite implements ChartRendererComponent { } if (currentYPos > screenHeight) { - // Stop, waveform finished rendering - if (scroll.value * speedSign > 0) { - finishedDrawing = true + // Skip the scroll if we step off the bottom of the screen + if (scroll.value * scrollDirection > 0) { + currentBeat = scrollEndBeat break } // Skip to the bottom of the screen and keep stepping from there @@ -306,7 +303,7 @@ export class Waveform extends Sprite implements ChartRendererComponent { // Step by 1 or -1 pixels and get the current beat currentBeat += pixelsToBeats * Options.chart.waveform.lineHeight currentYPos += - (scroll.value * speedSign > 0 ? 1 : -1) * + (scroll.value * scrollDirection > 0 ? 1 : -1) * Options.chart.waveform.lineHeight curSec = this.calculateSecond( @@ -317,9 +314,6 @@ export class Waveform extends Sprite implements ChartRendererComponent { ) this.drawLine(curSec, currentYPos, hasFilters) } - scrollIndex++ - currentBeat = scrollEndBeat - currentYPos = scrollEndYPos } } else if (!Options.chart.CMod) { // XMod no speed changes diff --git a/app/src/chart/component/notefield/HoldJudgmentContainer.ts b/app/src/chart/component/notefield/HoldJudgementContainer.ts similarity index 69% rename from app/src/chart/component/notefield/HoldJudgmentContainer.ts rename to app/src/chart/component/notefield/HoldJudgementContainer.ts index 6a378373..1e380298 100644 --- a/app/src/chart/component/notefield/HoldJudgmentContainer.ts +++ b/app/src/chart/component/notefield/HoldJudgementContainer.ts @@ -1,5 +1,5 @@ import { Assets, Container, Rectangle, Sprite, Texture } from "pixi.js" -import holdJudgmentUrl from "../../../../assets/noteskin/dance/default/hold_judgment.png" +import holdJudgementUrl from "../../../../assets/judgement/hold_judgement.png" import { destroyChildIf } from "../../../util/Util" import { TimingWindow } from "../../play/TimingWindow" import { @@ -10,12 +10,12 @@ import { import { Options } from "../../../util/Options" import { Notefield } from "./Notefield" -interface HoldJudgmentObject extends Sprite { +interface HoldJudgementObject extends Sprite { createTime: number } -export class HoldJudgmentContainer extends Container { - children: HoldJudgmentObject[] = [] +export class HoldJudgementContainer extends Container { + children: HoldJudgementObject[] = [] private static held_tex?: Texture private static dropped_tex?: Texture @@ -24,19 +24,19 @@ export class HoldJudgmentContainer extends Container { constructor(notefield: Notefield) { super() - if (!HoldJudgmentContainer.held_tex) this.loadTex() + if (!HoldJudgementContainer.held_tex) this.loadTex() this.notefield = notefield } async loadTex() { - const judgeTex = await Assets.load(holdJudgmentUrl) + const judgeTex = await Assets.load(holdJudgementUrl) const tHeight = judgeTex.height const tWidth = judgeTex.width - HoldJudgmentContainer.held_tex = new Texture( + HoldJudgementContainer.held_tex = new Texture( judgeTex, new Rectangle(0, 0, tWidth, tHeight / 2) ) - HoldJudgmentContainer.dropped_tex = new Texture( + HoldJudgementContainer.dropped_tex = new Texture( judgeTex, new Rectangle(0, tHeight / 2, tWidth, tHeight / 2) ) @@ -60,14 +60,14 @@ export class HoldJudgmentContainer extends Container { destroyChildIf(this.children, child => Date.now() - child.createTime > 800) } - addJudge(col: number, judgment: TimingWindow) { - if (!isHoldDroppedTimingWindow(judgment) && !isHoldTimingWindow(judgment)) + addJudge(col: number, judgement: TimingWindow) { + if (!isHoldDroppedTimingWindow(judgement) && !isHoldTimingWindow(judgement)) return const judge = new Sprite( - isHoldDroppedTimingWindow(judgment) - ? HoldJudgmentContainer.dropped_tex - : HoldJudgmentContainer.held_tex - ) as HoldJudgmentObject + isHoldDroppedTimingWindow(judgement) + ? HoldJudgementContainer.dropped_tex + : HoldJudgementContainer.held_tex + ) as HoldJudgementObject judge.anchor.set(0.5) judge.x = this.notefield.getColumnX(col) judge.createTime = Date.now() diff --git a/app/src/chart/component/notefield/NoteContainer.ts b/app/src/chart/component/notefield/NoteContainer.ts index 38dc5c38..a8146733 100644 --- a/app/src/chart/component/notefield/NoteContainer.ts +++ b/app/src/chart/component/notefield/NoteContainer.ts @@ -1,18 +1,17 @@ import { Container, Sprite, Texture } from "pixi.js" -import { rgbtoHex } from "../../../util/Color" import { EventHandler } from "../../../util/EventHandler" import { Options } from "../../../util/Options" import { getNoteEnd } from "../../../util/Util" import { EditMode, EditTimingMode } from "../../ChartManager" -import { NoteObject } from "../../gameTypes/base/Noteskin" import { TimingWindowCollection } from "../../play/TimingWindowCollection" -import { NotedataEntry, isHoldNote } from "../../sm/NoteTypes" -import { Notefield } from "./Notefield" +import { isHoldNote, NotedataEntry } from "../../sm/NoteTypes" +import { HoldObject, Notefield, NoteWrapper } from "./Notefield" interface HighlightedNoteObject extends Container { selection: Sprite parity: Sprite - object: NoteObject + wrapper: NoteWrapper + lastActive: boolean } const parityColors: Record = { @@ -64,12 +63,11 @@ export class NoteContainer extends Container { if (!this.shouldDisplayNote(note, firstBeat, lastBeat)) continue if (!this.arrowMap.has(note)) { const container = new Container() as HighlightedNoteObject - const object = this.notefield.noteskin.createNote(note) + const object = this.notefield.createNote(note) Object.assign(container, { x: this.notefield.getColumnX(note.col), zIndex: note.beat, }) - object.note.rotation = this.notefield.getColumnRotation(note.col) const selection = new Sprite(Texture.WHITE) const objectBounds = object.getBounds() selection.x = objectBounds.x @@ -85,9 +83,10 @@ export class NoteContainer extends Container { parity.height = objectBounds.height parity.alpha = 0 this.notefield.renderer.registerDragNote(container, note) - container.object = object + container.wrapper = object container.selection = selection container.parity = parity + container.lastActive = false this.arrowMap.set(note, container) container.addChild(object, selection, parity) this.addChild(container) @@ -101,8 +100,6 @@ export class NoteContainer extends Container { continue } - container.object.update(this.notefield.renderer) - container.y = this.notefield.renderer.getActualReceptorYPos() if ( !isHoldNote(note) || @@ -119,7 +116,8 @@ export class NoteContainer extends Container { const holdLength = this.notefield.renderer.getYPosFromBeat(getNoteEnd(note)) - container.y - this.setHoldLength(container.object, holdLength) + const hold = container.wrapper.object as HoldObject + hold.setLength(holdLength) if (note.gameplay?.lastHoldActivation) { let t = (Date.now() - note.gameplay.lastHoldActivation) / @@ -127,9 +125,19 @@ export class NoteContainer extends Container { .getHeldJudgement(note) .getTimingWindowMS() t = Math.min(1.2, t) - this.setHoldBrightness(container.object, 1 - t * 0.7) + hold.setBrightness(1 - t * 0.7) + } else { + hold.setBrightness(1) + } + if (note.gameplay) { + hold.setActive( + !( + note.gameplay.lastHoldActivation === undefined || + note.gameplay.droppedHoldBeat !== undefined + ) + ) } else { - this.setHoldBrightness(container.object, 0.8) + hold.setActive(false) } } if ( @@ -142,8 +150,8 @@ export class NoteContainer extends Container { container.selection.alpha = inSelection ? Math.sin(Date.now() / 320) * 0.1 + 0.3 : 0 - if (inSelection && container.object.hold) { - const objectBounds = container.object.getLocalBounds() + if (inSelection && container.wrapper.object.type == "hold") { + const objectBounds = container.wrapper.getLocalBounds() container.selection.x = objectBounds.x container.selection.y = objectBounds.y container.selection.width = objectBounds.width @@ -152,7 +160,7 @@ export class NoteContainer extends Container { container.visible = !inSelection || !this.notefield.renderer.chartManager.selection.shift const inSelectionBounds = this.notefield.renderer.selectionTest( - container.object + container.wrapper ) if (!inSelection && inSelectionBounds) { this.notefield.renderer.chartManager.addNoteToDragSelection(note) @@ -180,26 +188,4 @@ export class NoteContainer extends Container { if (getNoteEnd(note) < firstBeat) return false return note.beat <= lastBeat } - - private setHoldLength(object: NoteObject, length: number) { - if (!object.hold) return - object.hold.holdBody.height = length - object.hold.holdBody.y = length - object.hold.holdCap.y = length - object.hold.holdCap.scale.y = length < 0 ? -0.5 : 0.5 - } - - private setHoldBrightness(object: NoteObject, brightness: number) { - if (!object.hold) return - object.hold.holdBody.tint = rgbtoHex( - brightness * 255, - brightness * 255, - brightness * 255 - ) - object.hold.holdCap.tint = rgbtoHex( - brightness * 255, - brightness * 255, - brightness * 255 - ) - } } diff --git a/app/src/chart/component/notefield/NoteFlashContainer.ts b/app/src/chart/component/notefield/NoteFlashContainer.ts index 4bdaa729..47010e1c 100644 --- a/app/src/chart/component/notefield/NoteFlashContainer.ts +++ b/app/src/chart/component/notefield/NoteFlashContainer.ts @@ -1,68 +1,29 @@ import { Container } from "pixi.js" -import { NoteFlash } from "../../gameTypes/base/Noteskin" -import { TimingWindow } from "../../play/TimingWindow" -import { - isHoldDroppedTimingWindow, - isHoldTimingWindow, -} from "../../play/TimingWindowCollection" -import { Notefield } from "./Notefield" -interface ColumnNoteFlashes extends Container { - flashes: Container - holdFlashes: Container -} +import { NoteskinSprite } from "../../gameTypes/noteskin/Noteskin" +import { Notefield } from "./Notefield" export class NoteFlashContainer extends Container { private readonly notefield - readonly children: ColumnNoteFlashes[] = [] + readonly children: NoteskinSprite[] = [] constructor(notefield: Notefield) { super() this.notefield = notefield + this.eventMode = "none" for (let colNum = 0; colNum < this.notefield.gameType.numCols; colNum++) { - const container: ColumnNoteFlashes = new Container() as ColumnNoteFlashes - container.rotation = this.notefield.getColumnRotation(colNum) - container.x = this.notefield.getColumnX(colNum) - - const flashes = new Container() - const holdFlashes = new Container() - container.addChild(flashes, holdFlashes) - container.flashes = flashes - container.holdFlashes = holdFlashes - this.addChild(container) + const noteflash = this.notefield.getElement({ + element: "NoteFlash", + columnName: this.notefield.getColumnName(colNum), + columnNumber: colNum, + }) + noteflash.x = this.notefield.getColumnX(colNum) + this.addChild(noteflash) } } update(): void { this.y = this.notefield.renderer.getActualReceptorYPos() - this.children.forEach(column => { - column.flashes.children.forEach(flash => - flash.update(this.notefield.renderer) - ) - column.holdFlashes.children.forEach(flash => - flash.update(this.notefield.renderer) - ) - }) - } - createNoteFlash(col: number, judge: TimingWindow): void { - if (isHoldTimingWindow(judge) || isHoldDroppedTimingWindow(judge)) { - this.children[col].holdFlashes.removeChildren() - } - const holdJudgment = this.notefield.noteskin.createNoteFlash(judge) - if (!holdJudgment) return - this.children[col].flashes.addChild(holdJudgment) - } - createHoldNoteFlash(col: number): void { - const holdJudgment = this.notefield.noteskin.createHoldNoteFlash() - if (!holdJudgment) return - this.children[col].holdFlashes.addChild(holdJudgment) - } - - reset() { - this.children.forEach(column => { - column.flashes.removeChildren() - column.holdFlashes.removeChildren() - }) } } diff --git a/app/src/chart/component/notefield/Notefield.ts b/app/src/chart/component/notefield/Notefield.ts index 1f85f150..46d4fa56 100644 --- a/app/src/chart/component/notefield/Notefield.ts +++ b/app/src/chart/component/notefield/Notefield.ts @@ -1,33 +1,312 @@ -import { Container } from "pixi.js" +import { Container, Sprite, Texture } from "pixi.js" import { WaterfallManager } from "../../../gui/element/WaterfallManager" +import { rgbtoHex } from "../../../util/Color" +import { EventHandler } from "../../../util/EventHandler" import { Options } from "../../../util/Options" import { EditMode, EditTimingMode } from "../../ChartManager" import { ChartRenderer, ChartRendererComponent } from "../../ChartRenderer" -import { NoteSkinRegistry } from "../../gameTypes/NoteSkinRegistry" import { - NoteObject, - NoteSkin, - NoteSkinOptions, -} from "../../gameTypes/base/Noteskin" + MISSING_TEX, + Noteskin, + NoteskinElementCreationOptions, + NoteskinElementOptions, + NoteskinElements, + NoteskinHoldTail, + NoteskinOptions, + NoteskinSprite, +} from "../../gameTypes/noteskin/Noteskin" +import { NoteskinRegistry } from "../../gameTypes/noteskin/NoteskinRegistry" import { TimingWindow } from "../../play/TimingWindow" -import { NotedataEntry } from "../../sm/NoteTypes" -import { HoldJudgmentContainer } from "./HoldJudgmentContainer" +import { + isHoldDroppedTimingWindow, + isHoldTimingWindow, + isMineTimingWindow, + isStandardMissTimingWindow, + isStandardTimingWindow, +} from "../../play/TimingWindowCollection" +import { + HoldNotedataEntry, + HoldNoteType, + isHoldNote, + NotedataEntry, + TapNotedataEntry, + TapNoteType, +} from "../../sm/NoteTypes" +import { HoldJudgementContainer } from "./HoldJudgementContainer" import { NoteContainer } from "./NoteContainer" import { NoteFlashContainer } from "./NoteFlashContainer" import { ReceptorContainer } from "./ReceptorContainer" import { SelectionNoteContainer } from "./SelectionNoteContainer" +import fakeIcon from "../../../../assets/icon/fake.png" +import liftIcon from "../../../../assets/icon/lift.png" +import { HoldTail } from "../../gameTypes/noteskin/_template/HoldTail" + +const ICONS: Record = { + Fake: Texture.from(fakeIcon), + Lift: Texture.from(liftIcon), +} + +export type NotefieldObject = NoteObject | HoldObject + +export class NoteWrapper extends Container { + object: NotefieldObject + icon + constructor(object: NotefieldObject) { + super() + this.object = object + + this.icon = new Sprite(ICONS[object.note.type]) + this.icon.anchor.set(0.5) + this.icon.scale.set(0.3) + this.icon.alpha = 0.8 + + this.addChild(object, this.icon) + + 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 ( + this.object.nf.noteskinOptions?.hideIcons?.includes( + this.object.note.type + ) + ) { + this.icon.visible = false + return + } + this.icon.visible = true + if (this.object.note.type == "Fake") { + this.icon.visible = cr.chartManager.getMode() != EditMode.Play + } + }) + } +} + +export class NoteObject extends Container { + type = "note" + note: NotedataEntry + readonly nf: Notefield + + constructor(notefield: Notefield, note: TapNotedataEntry) { + 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 as TapNoteType, + columnName: this.nf.getColumnName(note.col), + columnNumber: note.col, + }, + { note } + ) + this.addChild(element) + } +} + +interface HoldElements { + Active: { + Body: NoteskinSprite + TopCap: NoteskinSprite + BottomCap: NoteskinHoldTail + Head: NoteskinSprite + } + Inactive: { + Body: NoteskinSprite + TopCap: NoteskinSprite + BottomCap: NoteskinHoldTail + Head: NoteskinSprite + } +} + +function isHoldTail(tail: NoteskinSprite): tail is NoteskinHoldTail { + return (tail as any).cropTop !== undefined +} + +export class HoldObject extends Container { + type = "hold" + + note: HoldNotedataEntry + + private active + private inactive + + private wasActive = false + + private elements!: HoldElements + + private readonly metrics + private readonly ns + readonly nf + private loaded = false + + constructor(notefield: Notefield, note: HoldNotedataEntry) { + super() + const active = new Container() + const inactive = new Container() + + this.note = note + 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) = {} + for (const part of ["BottomCap", "Body", "TopCap", "Head"] as const) { + const element = this.getNoteskinElement(`${state} ${part}`) + if (part == "BottomCap") { + if (isHoldTail(element)) { + this.elements[state][part] = element + } else { + if (Options.debug.showNoteskinErrors) { + WaterfallManager.createFormatted( + `Noteskin Error: invalid tail found for ${state} ${part}!`, + "error" + ) + } + this.elements[state][part] = new HoldTail(MISSING_TEX, 64) + } + } else { + this.elements[state][part] = element + } + ;(state == "Active" ? this.active : this.inactive).addChild( + this.elements[state][part] + ) + } + } + this.loaded = true + } + + getNoteskinElement(element: string) { + return this.ns.getElement( + { + element: `${this.note.type} ${element}` as keyof NoteskinElements, + columnName: this.nf.getColumnName(this.note.col), + columnNumber: this.note.col, + }, + { note: this.note } + ) + } + + setActive(active: boolean) { + if (this.wasActive != active) { + this.wasActive = active + this.active.visible = active + this.inactive.visible = !active + } + } + + 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) { + for (const item of items) { + if ("tint" in this.elements[state][item]) { + ;(this.elements[state][item] as Sprite).tint = rgbtoHex( + brightness * 255, + brightness * 255, + brightness * 255 + ) + } + } + } + } + + setLength(length: number) { + if (!this.loaded) return + const bbO = + this.metrics[`${this.note.type as HoldNoteType}BodyBottomOffset`] + const btO = this.metrics[`${this.note.type as HoldNoteType}BodyTopOffset`] + + const states = ["Active", "Inactive"] as const + const sign = Math.sign(length) + const absLength = Math.abs(length) + for (const state of states) { + this.elements[state].Body.height = Math.max(0, absLength + bbO - btO) + this.elements[state].Body.y = absLength + bbO + + this.elements[state].BottomCap.y = absLength + bbO + + if (this.elements[state].BottomCap.y < 0) { + this.elements[state].BottomCap.cropTop( + -this.elements[state].BottomCap.y + ) + if (length < 0) { + this.elements[state].BottomCap.y -= + this.elements[state].BottomCap.y / + Math.abs(this.elements[state].BottomCap.scale.y) + } + } else { + this.elements[state].BottomCap.cropTop(0) + } + + this.elements[state].TopCap.y = btO + 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 + this.elements[state].BottomCap.y *= sign + this.elements[state].TopCap.y *= sign + } + } +} + export class Notefield extends Container implements ChartRendererComponent { - readonly noteskinOptions!: NoteSkinOptions - readonly noteskin!: NoteSkin + noteskinOptions?: NoteskinOptions + noteskin?: Noteskin readonly gameType readonly renderer - private readonly receptors!: ReceptorContainer - private readonly notes!: NoteContainer - private readonly selectionNotes!: SelectionNoteContainer - private readonly flashes!: NoteFlashContainer - private readonly holdJudges!: HoldJudgmentContainer - private ghostNote?: NoteObject + private receptors?: ReceptorContainer + private notes?: NoteContainer + private selectionNotes?: SelectionNoteContainer + private flashes?: NoteFlashContainer + private holdJudges?: HoldJudgementContainer + private ghostNote?: NoteWrapper private ghostNoteEntry?: NotedataEntry private readonly columnX: number[] = [] @@ -37,46 +316,48 @@ export class Notefield extends Container implements ChartRendererComponent { this.renderer = renderer this.gameType = renderer.chart.gameType - const noteskinOptions = NoteSkinRegistry.getNoteSkin( + NoteskinRegistry.getNoteskin( this.gameType, - Options.chart.noteskin[renderer.chart.gameType.id] - ) + Options.chart.noteskin.name + ).then(noteskinOptions => { + if (!noteskinOptions) { + WaterfallManager.createFormatted( + "Couldn't find an available noteskin!", + "error" + ) + return + } - if (!noteskinOptions) { - WaterfallManager.createFormatted( - "Couldn't find an available noteskin!", - "error" - ) - return - } + // Calculate column x positions + let accumulatedWidth = 0 + + for (let colNum = 0; colNum < this.gameType.numCols; colNum++) { + const colWidth = this.gameType.columnWidths[colNum] + this.columnX.push( + accumulatedWidth - this.gameType.notefieldWidth / 2 + colWidth / 2 + ) + accumulatedWidth += colWidth + } - // Calculate column x positions - let accumulatedWidth = 0 + this.noteskinOptions = noteskinOptions + this.noteskin = new Noteskin(this.renderer, noteskinOptions) - for (let colNum = 0; colNum < this.gameType.numCols; colNum++) { - const colWidth = this.gameType.columnWidths[colNum] - this.columnX.push( - accumulatedWidth - this.gameType.notefieldWidth / 2 + colWidth / 2 + this.receptors = new ReceptorContainer(this) + this.flashes = new NoteFlashContainer(this) + this.notes = new NoteContainer(this) + this.selectionNotes = new SelectionNoteContainer(this) + + this.holdJudges = new HoldJudgementContainer(this) + this.addChild( + this.receptors, + this.notes, + this.selectionNotes, + this.flashes, + this.holdJudges ) - accumulatedWidth += colWidth - } - this.noteskinOptions = noteskinOptions - this.noteskin = new this.noteskinOptions.object(renderer) - - this.receptors = new ReceptorContainer(this) - this.flashes = new NoteFlashContainer(this) - this.notes = new NoteContainer(this) - this.selectionNotes = new SelectionNoteContainer(this) - - this.holdJudges = new HoldJudgmentContainer(this) - this.addChild( - this.receptors, - this.notes, - this.selectionNotes, - this.flashes, - this.holdJudges - ) + EventHandler.emit("noteskinLoaded") + }) } setGhostNote(note?: NotedataEntry): void { @@ -84,26 +365,28 @@ export class Notefield extends Container implements ChartRendererComponent { this.ghostNote = undefined this.ghostNoteEntry = note if (!note) return - this.ghostNote = this.noteskin.createNote(note) + this.ghostNote = this.createNote(note) this.addChildAt(this.ghostNote, 1) this.ghostNote.alpha = 0.4 this.ghostNote.x = this.getColumnX(note.col) - this.ghostNote.rotation = this.getColumnRotation(note.col) this.ghostNote.y = this.renderer.getYPosFromBeat(note.beat) } - getNoteSprite(note: NotedataEntry): Container { - const spr = this.noteskin.createNote(note) - return spr + getElement( + element: NoteskinElementOptions, + options: Partial = {} + ): NoteskinSprite { + return this.noteskin!.getElement(element, options) } update(firstBeat: number, lastBeat: number): void { - this.noteskin.update() - this.receptors.update(this.renderer.getVisualBeat()) - this.flashes.update() - this.notes.update(firstBeat, lastBeat) - this.selectionNotes.update(firstBeat, lastBeat) - this.holdJudges.update() + if (this.noteskin === undefined) return + this.noteskin.update(this.renderer) + this.receptors!.update() + this.flashes!.update() + this.notes!.update(firstBeat, lastBeat) + this.selectionNotes!.update(firstBeat, lastBeat) + this.holdJudges!.update() if (this.ghostNote) { this.ghostNote.y = this.renderer.getYPosFromBeat( @@ -119,34 +402,151 @@ export class Notefield extends Container implements ChartRendererComponent { } } - onJudgment(col: number, judge: TimingWindow): void { - this.flashes.createNoteFlash(col, judge) - this.holdJudges.addJudge(col, judge) + onJudgement(col: number, judge: TimingWindow): void { + if (this.noteskin === undefined) return + this.holdJudges!.addJudge(col, judge) + if (isStandardTimingWindow(judge)) { + this.noteskin.broadcast({ + type: "hit", + judgement: judge, + columnName: this.getColumnName(col), + columnNumber: col, + }) + } + if (isHoldTimingWindow(judge)) { + this.noteskin.broadcast({ + type: "held", + columnName: this.getColumnName(col), + columnNumber: col, + }) + } + if (isHoldDroppedTimingWindow(judge)) { + this.noteskin.broadcast({ + type: "letgo", + columnName: this.getColumnName(col), + columnNumber: col, + }) + } + if (isStandardMissTimingWindow(judge)) { + this.noteskin.broadcast({ + type: "miss", + judgement: judge, + columnName: this.getColumnName(col), + columnNumber: col, + }) + } + if (isMineTimingWindow(judge)) { + this.noteskin.broadcast({ + type: "hitmine", + columnName: this.getColumnName(col), + columnNumber: col, + }) + } } + startPlay(): void {} + endPlay(): void { - this.flashes.reset() + if (this.noteskin === undefined) return + for (let i = 0; i < this.gameType.numCols; i++) { + this.noteskin.broadcast({ + type: "holdoff", + columnName: this.getColumnName(i), + columnNumber: i, + }) + this.noteskin.broadcast({ + type: "rolloff", + columnName: this.getColumnName(i), + columnNumber: i, + }) + this.noteskin.broadcast({ + type: "lift", + columnName: this.getColumnName(i), + columnNumber: i, + }) + } + } + + press(col: number): void { + if (this.noteskin === undefined) return + this.noteskin.broadcast({ + type: "press", + columnName: this.getColumnName(col), + columnNumber: col, + }) } - keyDown(col: number): void { - this.receptors.keyDown(col) + lift(col: number): void { + if (this.noteskin === undefined) return + this.noteskin.broadcast({ + type: "lift", + columnName: this.getColumnName(col), + columnNumber: col, + }) } - keyUp(col: number): void { - this.receptors.keyUp(col) + ghostTap(col: number): void { + if (this.noteskin === undefined) return + this.noteskin.broadcast({ + type: "ghosttap", + columnName: this.getColumnName(col), + columnNumber: col, + }) } activateHold(col: number): void { - this.flashes.createHoldNoteFlash(col) + if (this.noteskin === undefined) return + this.noteskin.broadcast({ + type: "holdon", + columnName: this.getColumnName(col), + columnNumber: col, + }) + } + + releaseHold(col: number): void { + if (this.noteskin === undefined) return + this.noteskin.broadcast({ + type: "holdoff", + columnName: this.getColumnName(col), + columnNumber: col, + }) + } + + activateRoll(col: number): void { + if (this.noteskin === undefined) return + this.noteskin.broadcast({ + type: "rollon", + columnName: this.getColumnName(col), + columnNumber: col, + }) + } + + releaseRoll(col: number): void { + if (this.noteskin === undefined) return + this.noteskin.broadcast({ + type: "rolloff", + columnName: this.getColumnName(col), + columnNumber: col, + }) } getColumnX(col: number) { return this.columnX[col] ?? 0 } - getColumnRotation(col: number) { - return this.noteskinOptions.rotateColumns - ? (this.gameType.columnRotations[col] / 180) * Math.PI - : 0 + getColumnWidth(col: number) { + return this.gameType.columnWidths[col] + } + + getColumnName(col: number) { + return this.gameType.columnNames[col] + } + + createNote(note: NotedataEntry): NoteWrapper { + if (isHoldNote(note)) { + return new NoteWrapper(new HoldObject(this, note)) + } else { + return new NoteWrapper(new NoteObject(this, note)) + } } } diff --git a/app/src/chart/component/notefield/ReceptorContainer.ts b/app/src/chart/component/notefield/ReceptorContainer.ts index 5b330360..300e18ab 100644 --- a/app/src/chart/component/notefield/ReceptorContainer.ts +++ b/app/src/chart/component/notefield/ReceptorContainer.ts @@ -1,35 +1,28 @@ import { Container } from "pixi.js" -import { Receptor } from "../../gameTypes/base/Noteskin" + +import { NoteskinSprite } from "../../gameTypes/noteskin/Noteskin" import { Notefield } from "./Notefield" export class ReceptorContainer extends Container { private readonly notefield - readonly children: Receptor[] = [] + readonly children: NoteskinSprite[] = [] constructor(notefield: Notefield) { super() this.notefield = notefield for (let colNum = 0; colNum < this.notefield.gameType.numCols; colNum++) { - const receptor = this.notefield.noteskin.createReceptor(colNum) - receptor.rotation = this.notefield.getColumnRotation(colNum) + const receptor = this.notefield.getElement({ + element: "Receptor", + columnName: this.notefield.getColumnName(colNum), + columnNumber: colNum, + }) receptor.x = this.notefield.getColumnX(colNum) this.addChild(receptor) } } - update(beat: number): void { + update(): void { this.y = this.notefield.renderer.getActualReceptorYPos() - this.children.forEach(receptor => - receptor.update(this.notefield.renderer, beat) - ) - } - - keyDown(col: number): void { - this.children[col].keyDown() - } - - keyUp(col: number): void { - this.children[col].keyUp() } } diff --git a/app/src/chart/component/notefield/SelectionNoteContainer.ts b/app/src/chart/component/notefield/SelectionNoteContainer.ts index 03c49652..51c6eba6 100644 --- a/app/src/chart/component/notefield/SelectionNoteContainer.ts +++ b/app/src/chart/component/notefield/SelectionNoteContainer.ts @@ -1,12 +1,10 @@ import { Container, Sprite, Texture } from "pixi.js" -import { rgbtoHex } from "../../../util/Color" -import { NoteObject } from "../../gameTypes/base/Noteskin" -import { NotedataEntry, isHoldNote } from "../../sm/NoteTypes" -import { Notefield } from "./Notefield" +import { isHoldNote, NotedataEntry } from "../../sm/NoteTypes" +import { HoldObject, Notefield, NoteWrapper } from "./Notefield" interface HighlightedNoteObject extends Container { selection: Sprite - object: NoteObject + wrapper: NoteWrapper } export class SelectionNoteContainer extends Container { @@ -47,18 +45,15 @@ export class SelectionNoteContainer extends Container { }) container.x = this.notefield.getColumnX(newNote.col) - container.object.destroy() - container.object.alpha = 0.4 - container.object = this.notefield.noteskin.createNote(newNote) - container.object.note.rotation = this.notefield.getColumnRotation( - newNote.col - ) - const objectBounds = container.object.getBounds() + container.wrapper.destroy() + container.wrapper.alpha = 0.4 + container.wrapper = this.notefield.createNote(newNote) + const objectBounds = container.wrapper.getBounds() container.selection.x = objectBounds.x container.selection.y = objectBounds.y container.selection.width = objectBounds.width container.selection.height = objectBounds.height - container.addChild(container.object, container.selection) + container.addChild(container.wrapper, container.selection) } } @@ -77,13 +72,12 @@ export class SelectionNoteContainer extends Container { col: note.col + columnShift, } const container = new Container() as HighlightedNoteObject - const object = this.notefield.noteskin.createNote(newNote) + const object = this.notefield.createNote(newNote) Object.assign(container, { x: this.notefield.getColumnX(newNote.col), zIndex: newNote.beat, alpha: 0.4, }) - object.note.rotation = this.notefield.getColumnRotation(newNote.col) const selection = new Sprite(Texture.WHITE) const objectBounds = object.getBounds() selection.x = objectBounds.x @@ -92,7 +86,7 @@ export class SelectionNoteContainer extends Container { selection.height = objectBounds.height selection.alpha = 0 this.notefield.renderer.registerDragNote(object, newNote) - container.object = object + container.wrapper = object container.selection = selection this.arrowMap.set(note, container) container.addChild(object, selection) @@ -118,9 +112,10 @@ export class SelectionNoteContainer extends Container { this.notefield.renderer.getYPosFromBeat( newBeat + (isHoldNote(note) ? note.hold : 0) ) - container.y - this.setHoldLength(container.object, holdLength) - this.setHoldBrightness(container.object, 0.8) - const objectBounds = container.object.getLocalBounds() + const hold = container.wrapper.object as HoldObject + hold.setLength(holdLength) + hold.setBrightness(1) + const objectBounds = container.wrapper.getLocalBounds() container.selection.x = objectBounds.x container.selection.y = objectBounds.y container.selection.width = objectBounds.width @@ -128,26 +123,4 @@ export class SelectionNoteContainer extends Container { } } } - - private setHoldLength(object: NoteObject, length: number) { - if (!object.hold) return - object.hold.holdBody.height = length - object.hold.holdBody.y = length - object.hold.holdCap.y = length - object.hold.holdCap.scale.y = length < 0 ? -0.5 : 0.5 - } - - private setHoldBrightness(object: NoteObject, brightness: number) { - if (!object.hold) return - object.hold.holdBody.tint = rgbtoHex( - brightness * 255, - brightness * 255, - brightness * 255 - ) - object.hold.holdCap.tint = rgbtoHex( - brightness * 255, - brightness * 255, - brightness * 255 - ) - } } diff --git a/app/src/chart/component/play/ComboNumber.ts b/app/src/chart/component/play/ComboNumber.ts index 90ca83bf..c24253d5 100644 --- a/app/src/chart/component/play/ComboNumber.ts +++ b/app/src/chart/component/play/ComboNumber.ts @@ -31,7 +31,7 @@ export class ComboNumber extends BitmapText implements ChartRendererComponent { if (gameStats.getCombo() == 0) { this.tint = TimingWindowCollection.getCollection( Options.play.timingCollection - ).getMissJudgment().color + ).getMissJudgement().color } else if (gameStats.getBestJudge()) { this.tint = lighten( gameStats.getBestJudge()!.color, diff --git a/app/src/chart/component/play/ErrorBarContainer.ts b/app/src/chart/component/play/ErrorBarContainer.ts index 67207a80..be6ab598 100644 --- a/app/src/chart/component/play/ErrorBarContainer.ts +++ b/app/src/chart/component/play/ErrorBarContainer.ts @@ -103,7 +103,8 @@ export class ErrorBarContainer else this.currentMedian.x = this.target } - addBar(error: number, judge: TimingWindow) { + addBar(error: number | null, judge: TimingWindow) { + if (error == null) return if (!isStandardMissTimingWindow(judge) && !isStandardTimingWindow(judge)) return const bar = new Sprite(Texture.WHITE) as ErrorBar diff --git a/app/src/chart/component/play/JudgmentSprite.ts b/app/src/chart/component/play/JudgementSprite.ts similarity index 87% rename from app/src/chart/component/play/JudgmentSprite.ts rename to app/src/chart/component/play/JudgementSprite.ts index 0c1140c3..3d3bd256 100644 --- a/app/src/chart/component/play/JudgmentSprite.ts +++ b/app/src/chart/component/play/JudgementSprite.ts @@ -13,7 +13,7 @@ import { isStandardTimingWindow, } from "../../play/TimingWindowCollection" -export class JudgmentSprite extends Sprite implements ChartRendererComponent { +export class JudgementSprite extends Sprite implements ChartRendererComponent { private createTime = -1 private active = false private type: StandardTimingWindow = TIMING_WINDOW_AUTOPLAY @@ -51,20 +51,21 @@ export class JudgmentSprite extends Sprite implements ChartRendererComponent { } } - doJudge(error: number, judgment: TimingWindow) { + doJudge(error: number | null, judgment: TimingWindow) { + if (error == null) error = 0 if ( !isStandardTimingWindow(judgment) && !isStandardMissTimingWindow(judgment) ) return - const tex = judgment.judgmentTexture.getTexture(error, judgment) + const tex = judgment.judgementTexture.getTexture(error, judgment) if (!tex) return this.texture = tex this.texture.updateUvs() this.active = true this.type = judgment this.createTime = Date.now() - if (Options.play.judgmentTilt) + if (Options.play.judgementTilt) this.rotation = ((clamp(error, -0.05, 0.05) * 300) / 180) * Math.PI else this.rotation = 0 } diff --git a/app/src/chart/gameTypes/GameTypeRegistry.ts b/app/src/chart/gameTypes/GameTypeRegistry.ts index b155a50d..811699ad 100644 --- a/app/src/chart/gameTypes/GameTypeRegistry.ts +++ b/app/src/chart/gameTypes/GameTypeRegistry.ts @@ -4,13 +4,14 @@ import { NotedataParser } from "./base/NotedataParser" import { BasicGameLogic } from "./common/BasicGameLogic" import { BasicNotedataParser } from "./common/BasicNotedataParser" +import { PumpGameLogic } from "./pump/PumpGameLogic" export interface GameType { id: string numCols: number columnWidths: number[] notefieldWidth: number - columnRotations: number[] + columnNames: string[] gameLogic: GameLogic parser: NotedataParser editNoteTypes: NoteType[] @@ -50,7 +51,7 @@ GameTypeRegistry.register({ id: "dance-single", numCols: 4, columnWidths: [64, 64, 64, 64], - columnRotations: [0, -90, 90, 180], + columnNames: ["Left", "Down", "Up", "Right"], gameLogic: new BasicGameLogic(), parser: new BasicNotedataParser(), editNoteTypes: ["Tap", "Mine", "Fake", "Lift"], @@ -64,7 +65,7 @@ GameTypeRegistry.register({ id: "dance-double", numCols: 8, columnWidths: [64, 64, 64, 64, 64, 64, 64, 64], - columnRotations: [0, -90, 90, 180, 0, -90, 90, 180], + columnNames: ["Left", "Down", "Up", "Right", "Left", "Down", "Up", "Right"], gameLogic: new BasicGameLogic(), parser: new BasicNotedataParser(), editNoteTypes: ["Tap", "Mine", "Fake", "Lift"], @@ -78,7 +79,7 @@ GameTypeRegistry.register({ id: "dance-couple", numCols: 8, columnWidths: [64, 64, 64, 64, 64, 64, 64, 64], - columnRotations: [0, -90, 90, 180, 0, -90, 90, 180], + columnNames: ["Left", "Down", "Up", "Right", "Left", "Down", "Up", "Right"], gameLogic: new BasicGameLogic(), parser: new BasicNotedataParser(), editNoteTypes: ["Tap", "Mine", "Fake", "Lift"], @@ -92,7 +93,7 @@ GameTypeRegistry.register({ id: "dance-solo", numCols: 6, columnWidths: [64, 64, 64, 64, 64, 64], - columnRotations: [0, 45, -90, 90, 135, 180], + columnNames: ["Left", "UpLeft", "Down", "Up", "UpRight", "Right"], gameLogic: new BasicGameLogic(), parser: new BasicNotedataParser(), editNoteTypes: ["Tap", "Mine", "Fake", "Lift"], @@ -106,7 +107,20 @@ GameTypeRegistry.register({ id: "dance-solodouble", numCols: 12, columnWidths: [64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64], - columnRotations: [0, 45, -90, 90, 135, 180, 0, 45, -90, 90, 135, 180], + columnNames: [ + "Left", + "UpLeft", + "Down", + "Up", + "UpRight", + "Right", + "Left", + "UpLeft", + "Down", + "Up", + "UpRight", + "Right", + ], gameLogic: new BasicGameLogic(), parser: new BasicNotedataParser(), editNoteTypes: ["Tap", "Mine", "Fake", "Lift"], @@ -120,7 +134,7 @@ GameTypeRegistry.register({ id: "dance-threepanel", numCols: 3, columnWidths: [64, 64, 64], - columnRotations: [45, -90, 135], + columnNames: ["UpLeft", "Down", "UpRight"], gameLogic: new BasicGameLogic(), parser: new BasicNotedataParser(), editNoteTypes: ["Tap", "Mine", "Fake", "Lift"], @@ -134,7 +148,7 @@ GameTypeRegistry.register({ id: "dance-threedouble", numCols: 6, columnWidths: [64, 64, 64, 64, 64, 64], - columnRotations: [45, -90, 135, 45, -90, 135], + columnNames: ["UpLeft", "Down", "UpRight", "UpLeft", "Down", "UpRight"], gameLogic: new BasicGameLogic(), parser: new BasicNotedataParser(), editNoteTypes: ["Tap", "Mine", "Fake", "Lift"], @@ -143,3 +157,113 @@ GameTypeRegistry.register({ vertical: [0, 1, 2, 3, 4, 5], }, }) + +GameTypeRegistry.register({ + id: "pump-single", + numCols: 5, + columnWidths: [58, 58, 58, 58, 58], + columnNames: ["DownLeft", "UpLeft", "Center", "UpRight", "DownRight"], + gameLogic: new PumpGameLogic(), + parser: new BasicNotedataParser(), + editNoteTypes: ["Tap", "Mine", "Fake", "Lift"], + flipColumns: { + horizontal: [4, 3, 2, 1, 0], + vertical: [1, 0, 2, 4, 3], + }, +}) + +GameTypeRegistry.register({ + id: "pump-double", + numCols: 10, + columnWidths: [58, 58, 58, 58, 58, 58, 58, 58, 58, 58], + columnNames: [ + "DownLeft", + "UpLeft", + "Center", + "UpRight", + "DownRight", + "DownLeft", + "UpLeft", + "Center", + "UpRight", + "DownRight", + ], + gameLogic: new PumpGameLogic(), + parser: new BasicNotedataParser(), + editNoteTypes: ["Tap", "Mine", "Fake", "Lift"], + flipColumns: { + horizontal: [4, 3, 2, 1, 0, 9, 8, 7, 6, 5], + vertical: [1, 0, 2, 4, 3, 6, 5, 7, 9, 8], + }, +}) + +GameTypeRegistry.register({ + id: "pump-versus", + numCols: 10, + columnWidths: [58, 58, 58, 58, 58, 58, 58, 58, 58, 58], + columnNames: [ + "DownLeft", + "UpLeft", + "Center", + "UpRight", + "DownRight", + "DownLeft", + "UpLeft", + "Center", + "UpRight", + "DownRight", + ], + gameLogic: new PumpGameLogic(), + parser: new BasicNotedataParser(), + editNoteTypes: ["Tap", "Mine", "Fake", "Lift"], + flipColumns: { + horizontal: [4, 3, 2, 1, 0, 9, 8, 7, 6, 5], + vertical: [1, 0, 2, 4, 3, 6, 5, 7, 9, 8], + }, +}) + +GameTypeRegistry.register({ + id: "pump-couple", + numCols: 10, + columnWidths: [58, 58, 58, 58, 58, 58, 58, 58, 58, 58], + columnNames: [ + "DownLeft", + "UpLeft", + "Center", + "UpRight", + "DownRight", + "DownLeft", + "UpLeft", + "Center", + "UpRight", + "DownRight", + ], + gameLogic: new PumpGameLogic(), + parser: new BasicNotedataParser(), + editNoteTypes: ["Tap", "Mine", "Fake", "Lift"], + flipColumns: { + horizontal: [4, 3, 2, 1, 0, 9, 8, 7, 6, 5], + vertical: [1, 0, 2, 4, 3, 6, 5, 7, 9, 8], + }, +}) + +GameTypeRegistry.register({ + id: "pump-halfdouble", + numCols: 6, + columnWidths: [58, 58, 58, 58, 58, 58], + columnNames: [ + "Center", + "UpRight", + "DownRight", + "DownLeft", + "UpLeft", + "Center", + ], + gameLogic: new PumpGameLogic(), + parser: new BasicNotedataParser(), + editNoteTypes: ["Tap", "Mine", "Fake", "Lift"], + flipColumns: { + horizontal: [5, 1, 2, 3, 4, 0], + vertical: [0, 2, 1, 4, 3, 5], + }, +}) diff --git a/app/src/chart/gameTypes/NoteSkinRegistry.ts b/app/src/chart/gameTypes/NoteSkinRegistry.ts deleted file mode 100644 index 889cb0f6..00000000 --- a/app/src/chart/gameTypes/NoteSkinRegistry.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { GameType } from "./GameTypeRegistry" -import { NoteSkinOptions } from "./base/Noteskin" -import { DanceDefaultNoteskin } from "./dance/default/DanceDefaultNoteskin" - -export class NoteSkinRegistry { - private static noteskins = new Map>() - - static register(options: NoteSkinOptions) { - for (const gameType of new Set(options.gameTypes)) { - if (!NoteSkinRegistry.noteskins.has(gameType)) { - NoteSkinRegistry.noteskins.set(gameType, new Map()) - } - NoteSkinRegistry.noteskins.get(gameType)!.set(options.name, options) - } - } - - static getNoteSkin( - gameType: GameType, - name: string - ): NoteSkinOptions | undefined { - const gameTypeNoteskins = this.noteskins.get(gameType.id) - if (!gameTypeNoteskins || gameTypeNoteskins.size == 0) return - return gameTypeNoteskins.get(name) ?? [...gameTypeNoteskins.values()][0] - } - - static getNoteSkins() { - return this.noteskins - } -} - -NoteSkinRegistry.register(DanceDefaultNoteskin) diff --git a/app/src/chart/gameTypes/base/GameLogic.ts b/app/src/chart/gameTypes/base/GameLogic.ts index 1dfdfc2c..1267e366 100644 --- a/app/src/chart/gameTypes/base/GameLogic.ts +++ b/app/src/chart/gameTypes/base/GameLogic.ts @@ -1,5 +1,6 @@ import { ChartManager } from "../../ChartManager" -import { NotedataEntry } from "../../sm/NoteTypes" +import { Notedata, NotedataEntry } from "../../sm/NoteTypes" +import { TimingData } from "../../sm/TimingData" export abstract class GameLogic { /** @@ -12,13 +13,13 @@ export abstract class GameLogic { abstract update(chartManager: ChartManager): void /** - * Called when play mode is exited. + * Called when play mode is started. * * @abstract * @param {ChartManager} chartManager * @memberof GameLogic */ - abstract endPlay(chartManager: ChartManager): void + abstract startPlay(chartManager: ChartManager): void /** * Called when a column is pressed down. @@ -49,4 +50,8 @@ export abstract class GameLogic { * @memberof GameLogic */ abstract shouldAssistTick(note: NotedataEntry): boolean + + abstract calculateMaxDP(notedata: Notedata, timingData: TimingData): number + + abstract usesHoldTicks: boolean } diff --git a/app/src/chart/gameTypes/base/Noteskin.ts b/app/src/chart/gameTypes/base/Noteskin.ts deleted file mode 100644 index 370f7957..00000000 --- a/app/src/chart/gameTypes/base/Noteskin.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Container, DisplayObject, Sprite, TilingSprite } from "pixi.js" -import { ChartRenderer } from "../../ChartRenderer" -import { TimingWindow } from "../../play/TimingWindow" -import { NotedataEntry } from "../../sm/NoteTypes" - -export abstract class NoteSkin { - protected readonly renderer - - constructor(renderer: ChartRenderer) { - this.renderer = renderer - } - - /** - * Creates a receptor sprite given a column number. - * - * @abstract - * @param {number} columnNumber - * @return {*} {Container} - * @memberof Notefield - */ - abstract createReceptor(columnNumber: number): Receptor - - /** - * Creates a note flash sprite given a judgement. - * - * @abstract - * @param {TimingWindow} judgement - * @return {*} {NoteFlash | undefined} - * @memberof Notefield - */ - abstract createNoteFlash(judgement: TimingWindow): NoteFlash | undefined - - /** - * Creates a note flash sprite when a hold is pressed. - * - * @abstract - * @return {*} {NoteFlash | undefined} - * @memberof Notefield - */ - abstract createHoldNoteFlash(): NoteFlash - - /** - * Called every frame to update the noteskin. - * - * @abstract - * @memberof Notefield - */ - abstract update(): void - - /** - * Creates a note sprite given a note entry. - * - * @abstract - * @param {NotedataEntry} note - * @return {*} {NoteObject} - * @memberof Notefield - */ - abstract createNote(note: NotedataEntry): NoteObject -} - -export interface Receptor extends DisplayObject { - update(renderer: ChartRenderer, beat: number): void - keyDown(): void - keyUp(): void -} - -export interface NoteFlash extends DisplayObject { - update(renderer: ChartRenderer): void -} - -export interface NoteObject extends Container { - hold?: NoteObjectHold - note: Container - update(renderer: ChartRenderer): void -} - -export interface NoteObjectHold extends Container { - holdBody: TilingSprite - holdCap: Sprite -} - -type NoteSkinConstructor = new (renderer: ChartRenderer) => NoteSkin - -export interface NoteSkinOptions { - name: string - object: NoteSkinConstructor - gameTypes: string[] - rotateColumns: boolean -} diff --git a/app/src/chart/gameTypes/common/BasicGameLogic.ts b/app/src/chart/gameTypes/common/BasicGameLogic.ts index 4b799911..cf3c93fa 100644 --- a/app/src/chart/gameTypes/common/BasicGameLogic.ts +++ b/app/src/chart/gameTypes/common/BasicGameLogic.ts @@ -10,15 +10,17 @@ import { Notedata, NotedataEntry, } from "../../sm/NoteTypes" +import { TimingData } from "../../sm/TimingData" import { GameLogic } from "../base/GameLogic" export class BasicGameLogic extends GameLogic { - private chordCohesion: Map = new Map() - private missNoteIndex = 0 - private holdProgress: HoldNotedataEntry[] = [] - private heldCols: ColHeldTracker = new ColHeldTracker() - private collection: TimingWindowCollection = + protected chordCohesion: Map = new Map() + protected missNoteIndex = 0 + protected holdProgress: HoldNotedataEntry[] = [] + protected heldCols: ColHeldTracker = new ColHeldTracker() + protected collection: TimingWindowCollection = TimingWindowCollection.getCollection("ITG") + usesHoldTicks = false update(chartManager: ChartManager): void { if (!chartManager.loadedChart || !chartManager.chartView) return @@ -41,16 +43,16 @@ export class BasicGameLogic extends GameLogic { !note.gameplay!.hasHit ) { lastChord = note.beat - chartManager.chartView.doJudgment( + chartManager.chartView.doJudgement( note, - 0, - this.collection.getMissJudgment() + null, + this.collection.getMissJudgement() ) const chord = this.chordCohesion.get(note.beat)! chartManager.gameStats?.addDataPoint( chord, - this.collection.getMissJudgment(), - 0 + this.collection.getMissJudgement(), + null ) } this.missNoteIndex++ @@ -62,17 +64,22 @@ export class BasicGameLogic extends GameLogic { if (this.heldCols.isPressed(hold.col) && hold.type == "Hold") hold.gameplay!.lastHoldActivation = Date.now() if (this.shouldDropHold(hold, Date.now())) { - chartManager.chartView.doJudgment( + chartManager.chartView.doJudgement( hold, - 0, - this.collection.getDroppedJudgment() + null, + this.collection.getDroppedJudgement() ) hold.gameplay!.droppedHoldBeat = chartManager.chartView.getBeatWithOffset() this.holdProgress.splice(this.holdProgress.indexOf(hold), 1) + if (hold.type == "Roll") { + chartManager.chartView.getNotefield().releaseRoll(hold.col) + } else { + chartManager.chartView.getNotefield().releaseHold(hold.col) + } chartManager.gameStats?.addHoldDataPoint( hold, - this.collection.getDroppedJudgment() + this.collection.getDroppedJudgement() ) continue } @@ -81,11 +88,12 @@ export class BasicGameLogic extends GameLogic { chartManager.chartView.chart.getSecondsFromBeat(hold.beat + hold.hold) ) { hold.gameplay!.hideNote = true - chartManager.chartView.doJudgment( + chartManager.chartView.doJudgement( hold, - 0, + null, this.collection.getHeldJudgement(hold) ) + chartManager.chartView.getNotefield().releaseHold(hold.col) this.holdProgress.splice(this.holdProgress.indexOf(hold), 1) chartManager.gameStats?.addHoldDataPoint( hold, @@ -99,30 +107,30 @@ export class BasicGameLogic extends GameLogic { const mine = this.getClosestNote( chartManager.loadedChart.getNotedata(), chartManager.chartView.getTimeWithOffset() - - this.collection.getMineJudgment().getTimingWindowMS() / 2000, + this.collection.getMineJudgement().getTimingWindowMS() / 2000, col, ["Mine"], - this.collection.getMineJudgment().getTimingWindowMS() / 2 + this.collection.getMineJudgement().getTimingWindowMS() / 2 ) if (mine) { mine.gameplay!.hasHit = true mine.gameplay!.hideNote = true - chartManager.chartView.doJudgment( + chartManager.chartView.doJudgement( mine, - 0, - this.collection.getMineJudgment() + null, + this.collection.getMineJudgement() ) chartManager.gameStats?.addDataPoint( [mine], - this.collection.getMineJudgment(), - 0 + this.collection.getMineJudgement(), + null ) chartManager.mine.play() } } } - endPlay(chartManager: ChartManager): void { + startPlay(chartManager: ChartManager): void { if (!chartManager.loadedChart || !chartManager.chartView) return this.collection = TimingWindowCollection.getCollection( Options.play.timingCollection @@ -164,12 +172,13 @@ export class BasicGameLogic extends GameLogic { ["Tap", "Hold", "Roll"] ) this.heldCols.keyDown(col) + chartManager.chartView.getNotefield().press(col) for (const hold of this.holdProgress) { if (hold.type == "Roll" && hold.col == col) hold.gameplay!.lastHoldActivation = Date.now() } if (closestNote) this.hitNote(chartManager, closestNote, hitTime) - else chartManager.chartView.keyDown(col) + else chartManager.chartView.getNotefield().ghostTap(col) } keyUp(chartManager: ChartManager, col: number): void { @@ -182,7 +191,7 @@ export class BasicGameLogic extends GameLogic { ["Lift"] ) this.heldCols.keyUp(col) - chartManager.chartView.keyUp(col) + chartManager.chartView.getNotefield().lift(col) if (closestNote) this.hitNote(chartManager, closestNote, hitTime) } @@ -190,7 +199,7 @@ export class BasicGameLogic extends GameLogic { return !note.fake && !note.warped && note.type != "Mine" } - private hitNote( + protected hitNote( chartManager: ChartManager, note: NotedataEntry, hitTime: number @@ -198,7 +207,11 @@ export class BasicGameLogic extends GameLogic { note.gameplay!.hasHit = true if (isHoldNote(note)) { note.gameplay!.lastHoldActivation = Date.now() - chartManager.chartView!.activateHold(note.col) + if (note.type == "Roll") { + chartManager.chartView!.getNotefield().activateRoll(note.col) + } else { + chartManager.chartView!.getNotefield().activateHold(note.col) + } this.holdProgress.push(note) } const chord = this.chordCohesion.get(note.beat)! @@ -208,7 +221,7 @@ export class BasicGameLogic extends GameLogic { ) const hideNote = this.collection.shouldHideNote(judge) chord.forEach(note => { - chartManager.chartView!.doJudgment( + chartManager.chartView!.doJudgement( note, (hitTime - note.second) / Options.audio.rate, judge @@ -223,7 +236,7 @@ export class BasicGameLogic extends GameLogic { } } - private getClosestNote( + protected getClosestNote( notedata: Notedata, hitTime: number, col: number, @@ -267,10 +280,42 @@ export class BasicGameLogic extends GameLogic { return closestNote } - private shouldDropHold(note: HoldNotedataEntry, time: number): boolean { + protected shouldDropHold(note: HoldNotedataEntry, time: number): boolean { if (!note.gameplay?.lastHoldActivation) return false const window = this.collection.getHeldJudgement(note) if (!window) return false return time - note.gameplay.lastHoldActivation >= window.getTimingWindowMS() } + + calculateMaxDP(notedata: Notedata, _: TimingData) { + const chordCohesion: Map = new Map() + const numHoldsMap: Map = new Map() + for (const note of notedata) { + if (note.type == "Mine" || note.fake || note.warped) continue + if (isHoldNote(note)) { + if (!numHoldsMap.has(note.type)) numHoldsMap.set(note.type, 0) + numHoldsMap.set(note.type, numHoldsMap.get(note.type)! + 1) + } + if (!chordCohesion.has(note.beat)) chordCohesion.set(note.beat, []) + chordCohesion.get(note.beat)!.push(note) + } + let maxDancePoints = + chordCohesion.size * + TimingWindowCollection.getCollection( + Options.play.timingCollection + ).getMaxDancePoints() + maxDancePoints += Array.from(numHoldsMap.entries()).reduce( + (totalDP, entry) => { + return ( + totalDP + + entry[1] * + TimingWindowCollection.getCollection( + Options.play.timingCollection + ).getMaxHoldDancePoints(entry[0]) + ) + }, + 0 + ) + return maxDancePoints + } } diff --git a/app/src/chart/gameTypes/dance/default/DanceDefaultNoteFlashContainer.ts b/app/src/chart/gameTypes/dance/default/DanceDefaultNoteFlashContainer.ts deleted file mode 100644 index 35a0e3a6..00000000 --- a/app/src/chart/gameTypes/dance/default/DanceDefaultNoteFlashContainer.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { BLEND_MODES, Sprite, Texture } from "pixi.js" -import { TimingWindow } from "../../../play/TimingWindow" -import { - isHoldTimingWindow, - isMineTimingWindow, - isStandardTimingWindow, -} from "../../../play/TimingWindowCollection" - -import flashW4Url from "../../../../../assets/noteskin/dance/default/flash/decent.png" -import flashW2Url from "../../../../../assets/noteskin/dance/default/flash/excellent.png" -import flashW0Url from "../../../../../assets/noteskin/dance/default/flash/fantastic.png" -import flashW3Url from "../../../../../assets/noteskin/dance/default/flash/great.png" -import holdFlashUrl from "../../../../../assets/noteskin/dance/default/flash/hold.png" -import flashMineUrl from "../../../../../assets/noteskin/dance/default/flash/mine.png" -import flashW5Url from "../../../../../assets/noteskin/dance/default/flash/way_off.png" -import flashW1Url from "../../../../../assets/noteskin/dance/default/flash/white_fantastic.png" -import { NoteFlash } from "../../base/Noteskin" - -const HOLD_TEX = Texture.from(holdFlashUrl) - -const FLASH_TEX_MAP: Record = { - w0: Texture.from(flashW0Url), - w1: Texture.from(flashW1Url), - w2: Texture.from(flashW2Url), - w3: Texture.from(flashW3Url), - w4: Texture.from(flashW4Url), - w5: Texture.from(flashW5Url), - mine: Texture.from(flashMineUrl), -} - -export class DanceDefaultNoteFlash extends Sprite implements NoteFlash { - private createTime = 0 - private type = "flash" - - constructor(tex: Texture) { - super(tex) - this.anchor.set(0.5) - this.createTime = Date.now() - } - - update() { - switch (this.type) { - case "flash": { - const t = (Date.now() - this.createTime) / 150 - this.scale.set(1.1 - t * 0.1) - this.alpha = 1.2 - t * 1.2 - break - } - case "mine": { - const t2 = (Date.now() - this.createTime) / 600 - this.rotation = t2 * -Math.PI - this.alpha = Math.min(1, 2 - 2 * t2) - break - } - case "hold": - this.alpha = Math.sin(Date.now()) * 0.2 + 1.2 - } - if (Date.now() - this.createTime > 150 && this.type == "flash") { - this.destroy() - } - } - - static createJudgment(judgment: TimingWindow) { - let tex: Texture | undefined - if (isStandardTimingWindow(judgment)) tex = FLASH_TEX_MAP[judgment.id] - if (isMineTimingWindow(judgment)) tex = FLASH_TEX_MAP["mine"] - if (isHoldTimingWindow(judgment)) tex = FLASH_TEX_MAP["w2"] - if (!tex) return - const flash = new DanceDefaultNoteFlash(tex) - flash.type = "flash" - if (isMineTimingWindow(judgment)) { - flash.type = "mine" - flash.blendMode = BLEND_MODES.ADD - } - return flash - } - - static createHoldJudgment() { - const flash = new DanceDefaultNoteFlash(HOLD_TEX) - flash.type = "hold" - return flash - } -} diff --git a/app/src/chart/gameTypes/dance/default/DanceDefaultNoteRenderer.ts b/app/src/chart/gameTypes/dance/default/DanceDefaultNoteRenderer.ts deleted file mode 100644 index b04eb727..00000000 --- a/app/src/chart/gameTypes/dance/default/DanceDefaultNoteRenderer.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Container, Sprite, Texture, TilingSprite } from "pixi.js" -import { rgbtoHex } from "../../../../util/Color" -import { NotedataEntry } from "../../../sm/NoteTypes" -import { DanceDefaultNoteTexture } from "./DanceDefaultNoteTexture" - -import holdBodyUrl from "../../../../../assets/noteskin/dance/default/hold/body.png" -import holdCapUrl from "../../../../../assets/noteskin/dance/default/hold/cap.png" -import fakeIconUrl from "../../../../../assets/noteskin/dance/default/icon/fake.png" -import rollBodyUrl from "../../../../../assets/noteskin/dance/default/roll/body.png" -import rollCapUrl from "../../../../../assets/noteskin/dance/default/roll/cap.png" -import { EditMode } from "../../../ChartManager" -import { ChartRenderer } from "../../../ChartRenderer" -import { NoteObject, NoteObjectHold } from "../../base/Noteskin" - -const holdBodyTex = Texture.from(holdBodyUrl) -const holdCapTex = Texture.from(holdCapUrl) - -const rollBodyTex = Texture.from(rollBodyUrl) -const rollCapTex = Texture.from(rollCapUrl) - -const ICONS: Record = { - Fake: Texture.from(fakeIconUrl), -} - -class NoteItem extends Container { - note: Sprite - icon?: Sprite - constructor() { - super() - this.note = new Sprite() - this.note.anchor.set(0.5) - this.addChild(this.note) - } -} - -class DanceDefaultNoteHold extends Container implements NoteObjectHold { - holdBody: TilingSprite - holdCap: Sprite - constructor(holdBodyTex: Texture, holdCapTex: Texture) { - super() - const holdBody = new TilingSprite(holdBodyTex, 64, 0) - holdBody.tileScale.x = 0.5 - holdBody.tileScale.y = 0.5 - holdBody.uvRespectAnchor = true - holdBody.anchor.y = 1 - - holdBody.x = -32 - const holdCap = new Sprite(holdCapTex) - holdCap.width = 64 - holdCap.height = 32 - holdCap.anchor.x = 0.5 - - this.holdBody = holdBody - this.holdCap = holdCap - this.addChild(holdBody) - this.addChild(holdCap) - } -} - -export class DanceDefaultNoteObject extends Container implements NoteObject { - type = "" - hold?: DanceDefaultNoteHold - note = new NoteItem() - constructor(note: NotedataEntry) { - super() - this.eventMode = "static" - const type = note.type - - DanceDefaultNoteTexture.setNoteTex(this.note.note, note) - - // Create hold - if (type == "Hold" || type == "Roll") { - this.hold = new DanceDefaultNoteHold( - type == "Hold" ? holdBodyTex : rollBodyTex, - type == "Hold" ? holdCapTex : rollCapTex - ) - this.hold.holdBody.tint = rgbtoHex(0.8 * 255, 0.8 * 255, 0.8 * 255) - this.hold.holdCap.tint = rgbtoHex(0.8 * 255, 0.8 * 255, 0.8 * 255) - this.addChild(this.hold) - } - - // Create icon - if (ICONS[type]) { - const icon = new Sprite(ICONS[type]) - icon.width = 32 - icon.height = 32 - icon.anchor.set(0.5) - icon.alpha = 0.9 - this.note.icon = icon - this.note.addChild(icon) - } - - this.addChild(this.note) - this.type = note.type - } - update(renderer: ChartRenderer) { - if (this.type == "Fake") { - this.note.icon!.visible = renderer.chartManager.getMode() != EditMode.Play - } - } -} diff --git a/app/src/chart/gameTypes/dance/default/DanceDefaultNoteTexture.ts b/app/src/chart/gameTypes/dance/default/DanceDefaultNoteTexture.ts deleted file mode 100644 index 2a55965f..00000000 --- a/app/src/chart/gameTypes/dance/default/DanceDefaultNoteTexture.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { - BaseTexture, - Container, - Geometry, - Mesh, - MIPMAP_MODES, - Rectangle, - RenderTexture, - Shader, - Sprite, - Texture, -} from "pixi.js" -import { Options } from "../../../../util/Options" -import { NotedataEntry } from "../../../sm/NoteTypes" - -import liftPartsUrl from "../../../../../assets/noteskin/dance/default/lift/parts.png" -import mineFrameUrl from "../../../../../assets/noteskin/dance/default/mine/frame.png" -import minePartsUrl from "../../../../../assets/noteskin/dance/default/mine/parts.png" -import tapPartsUrl from "../../../../../assets/noteskin/dance/default/tap/parts.png" - -import arrowGradientFrag from "../../../../../assets/noteskin/dance/default/shader/arrow_gradient.frag?raw" -import liftGradientFrag from "../../../../../assets/noteskin/dance/default/shader/lift_gradient.frag?raw" -import mineGradientFrag from "../../../../../assets/noteskin/dance/default/shader/mine_gradient.frag?raw" -import noopFrag from "../../../../../assets/noteskin/dance/default/shader/noop.frag?raw" -import noopVert from "../../../../../assets/noteskin/dance/default/shader/noop.vert?raw" - -import arrowBodyGeomText from "../../../../../assets/noteskin/dance/default/tap/body.txt?raw" -import arrowFrameGeomText from "../../../../../assets/noteskin/dance/default/tap/frame.txt?raw" - -import liftBodyGeomText from "../../../../../assets/noteskin/dance/default/lift/body.txt?raw" -import mineBodyGeomText from "../../../../../assets/noteskin/dance/default/mine/body.txt?raw" -import { App } from "../../../../App" - -const mine_frame_texture = Texture.from(mineFrameUrl) - -export class DanceDefaultNoteTexture { - static arrowPartsTex = BaseTexture.from(tapPartsUrl, { - mipmap: MIPMAP_MODES.OFF, - }) - static minePartsTex = BaseTexture.from(minePartsUrl, { - mipmap: MIPMAP_MODES.OFF, - }) - static liftPartsTex = BaseTexture.from(liftPartsUrl, { - mipmap: MIPMAP_MODES.OFF, - }) - - static arrowBodyGeom: Geometry - static arrowFrameGeom: Geometry - static liftBodyGeom: Geometry - static mineBodyGeom: Geometry - - static arrowFrameTex: RenderTexture - static arrowFrame: Mesh - static arrowTex: RenderTexture - static arrowContainer = new Container() - static liftTex: RenderTexture - static liftContainer = new Container() - static mineTex: RenderTexture - static mineConainer = new Container() - - private static loaded = false - - static async initArrowTex() { - if (this.loaded) return - - // Initialize rendertextures in here so we can read options - DanceDefaultNoteTexture.arrowFrameTex = RenderTexture.create({ - width: 64, - height: 64, - resolution: Options.performance.resolution, - }) - DanceDefaultNoteTexture.arrowTex = RenderTexture.create({ - width: 256, - height: 320, - resolution: Options.performance.resolution, - }) - DanceDefaultNoteTexture.liftTex = RenderTexture.create({ - width: 256, - height: 320, - resolution: Options.performance.resolution, - }) - DanceDefaultNoteTexture.mineTex = RenderTexture.create({ - width: 64, - height: 64, - resolution: Options.performance.resolution, - }) - - this.arrowBodyGeom = await this.loadGeometry(arrowBodyGeomText) - this.arrowFrameGeom = await this.loadGeometry(arrowFrameGeomText) - this.mineBodyGeom = await this.loadGeometry(mineBodyGeomText) - this.liftBodyGeom = await this.loadGeometry(liftBodyGeomText) - - { - const shader_frame = Shader.from(noopVert, noopFrag, { - sampler0: this.arrowPartsTex, - }) - const arrow_frame = new Mesh( - DanceDefaultNoteTexture.arrowFrameGeom, - shader_frame - ) - arrow_frame.x = 32 - arrow_frame.y = 32 - arrow_frame.rotation = -Math.PI / 2 - this.arrowFrame = arrow_frame - } - { - for (let i = 0; i < 10; i++) { - const shader_body = Shader.from(noopVert, arrowGradientFrag, { - sampler0: this.arrowPartsTex, - time: 0, - quant: i, - }) - const arrow_frame = new Sprite(DanceDefaultNoteTexture.arrowFrameTex) - arrow_frame.x = (i % 3) * 64 - arrow_frame.y = Math.floor(i / 3) * 64 - const arrow_body = new Mesh( - DanceDefaultNoteTexture.arrowBodyGeom, - shader_body - ) - arrow_body.x = (i % 3) * 64 + 32 - arrow_body.y = Math.floor(i / 3) * 64 + 32 - arrow_body.rotation = -Math.PI / 2 - arrow_body.name = "body" + i - DanceDefaultNoteTexture.arrowContainer.addChild(arrow_frame) - DanceDefaultNoteTexture.arrowContainer.addChild(arrow_body) - } - } - { - for (let i = 0; i < 10; i++) { - const shader_body = Shader.from(noopVert, liftGradientFrag, { - sampler0: this.liftPartsTex, - time: 0, - quant: i, - }) - const arrow_body = new Mesh( - DanceDefaultNoteTexture.liftBodyGeom, - shader_body - ) - arrow_body.x = (i % 3) * 64 + 32 - arrow_body.y = Math.floor(i / 3) * 64 + 32 - arrow_body.rotation = -Math.PI / 2 - arrow_body.name = "body" + i - DanceDefaultNoteTexture.liftContainer.addChild(arrow_body) - } - } - { - const shader_body = Shader.from(noopVert, mineGradientFrag, { - sampler0: this.minePartsTex, - time: 0, - }) - const mine_body = new Mesh( - DanceDefaultNoteTexture.mineBodyGeom, - shader_body - ) - const mine_frame = new Sprite(mine_frame_texture) - mine_frame.width = 64 - mine_frame.height = 64 - mine_frame.anchor.set(0.5) - mine_frame.pivot.y = 3 // epic recenter - DanceDefaultNoteTexture.mineConainer.position.set(32) - DanceDefaultNoteTexture.mineConainer.addChild(mine_body) - DanceDefaultNoteTexture.mineConainer.addChild(mine_frame) - } - - this.loaded = true - } - - private static async loadGeometry(data: string): Promise { - const lines = data.split("\n") - const numVertices = parseInt(lines[0]) - const numTriangles = parseInt(lines[numVertices + 1]) - const vPos = [] - const vUvs = [] - const vIndex = [] - for (let i = 0; i < numVertices; i++) { - const match = - /(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)/.exec( - lines[i + 1] - ) - if (match) { - vPos.push(parseFloat(match[1])) - vPos.push(parseFloat(match[2])) - vUvs.push(parseFloat(match[3])) - vUvs.push(parseFloat(match[4])) - } else { - console.error("Failed to load vertex " + lines[i + 1]) - return new Geometry() - } - } - for (let i = 0; i < numTriangles; i++) { - const match = /(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)/.exec( - lines[i + 2 + numVertices] - ) - if (match) { - vIndex.push(parseFloat(match[1])) - vIndex.push(parseFloat(match[2])) - vIndex.push(parseFloat(match[3])) - } else { - console.error("Failed to load triangle " + lines[i + 2 + numVertices]) - return new Geometry() - } - } - return new Geometry() - .addAttribute("aVertexPosition", vPos, 2) - .addAttribute("aUvs", vUvs, 2) - .addIndex(vIndex) - } - - static setArrowTexTime(app: App, beat: number, second: number) { - if (!this.loaded) return - for (let i = 0; i < 10; i++) { - const tapShader: Mesh = - DanceDefaultNoteTexture.arrowContainer.getChildByName("body" + i)! - tapShader.shader.uniforms.time = beat - const liftShader: Mesh = - DanceDefaultNoteTexture.liftContainer.getChildByName("body" + i)! - liftShader.shader.uniforms.time = beat - } - ;(( - DanceDefaultNoteTexture.mineConainer.children[0] - )).shader.uniforms.time = second - DanceDefaultNoteTexture.mineConainer.rotation = (second % 1) * Math.PI * 2 - - app.renderer.render(DanceDefaultNoteTexture.arrowFrame, { - renderTexture: DanceDefaultNoteTexture.arrowFrameTex, - }) - app.renderer.render(DanceDefaultNoteTexture.arrowContainer, { - renderTexture: DanceDefaultNoteTexture.arrowTex, - }) - app.renderer.render(DanceDefaultNoteTexture.mineConainer, { - renderTexture: DanceDefaultNoteTexture.mineTex, - }) - app.renderer.render(DanceDefaultNoteTexture.liftContainer, { - renderTexture: DanceDefaultNoteTexture.liftTex, - }) - } - - static setNoteTex(arrow: Sprite, note: NotedataEntry) { - if (note.type == "Mine") { - arrow.texture = DanceDefaultNoteTexture.mineTex - } else { - const i = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf(note.quant) - arrow.texture = new Texture( - note.type == "Lift" - ? DanceDefaultNoteTexture.liftTex.baseTexture - : DanceDefaultNoteTexture.arrowTex.baseTexture, - new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) - ) - } - } -} diff --git a/app/src/chart/gameTypes/dance/default/DanceDefaultNoteskin.ts b/app/src/chart/gameTypes/dance/default/DanceDefaultNoteskin.ts deleted file mode 100644 index 0c4db6db..00000000 --- a/app/src/chart/gameTypes/dance/default/DanceDefaultNoteskin.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ChartRenderer } from "../../../ChartRenderer" -import { TimingWindow } from "../../../play/TimingWindow" -import { NotedataEntry } from "../../../sm/NoteTypes" -import { NoteSkin, NoteSkinOptions } from "../../base/Noteskin" -import { DanceDefaultNoteFlash } from "./DanceDefaultNoteFlashContainer" -import { DanceDefaultNoteObject } from "./DanceDefaultNoteRenderer" -import { DanceDefaultNoteTexture } from "./DanceDefaultNoteTexture" -import { DanceDefaultReceptor } from "./DanceDefaultReceptor" - -class DanceDefaultNoteskinObject extends NoteSkin { - constructor(renderer: ChartRenderer) { - super(renderer) - - DanceDefaultNoteTexture.initArrowTex() - } - - createReceptor(_: number) { - return new DanceDefaultReceptor() - } - - createNote(note: NotedataEntry) { - return new DanceDefaultNoteObject(note) - } - - createNoteFlash(judgement: TimingWindow) { - return DanceDefaultNoteFlash.createJudgment(judgement) - } - createHoldNoteFlash() { - return DanceDefaultNoteFlash.createHoldJudgment() - } - - update(): void { - DanceDefaultNoteTexture.setArrowTexTime( - this.renderer.chartManager.app, - this.renderer.getVisualBeat(), - this.renderer.getVisualTime() - ) - } -} - -export const DanceDefaultNoteskin: NoteSkinOptions = { - name: "default", - object: DanceDefaultNoteskinObject, - gameTypes: [ - "dance-single", - "dance-double", - "dance-couple", - "dance-solo", - "dance-solodouble", - "dance-threepanel", - "dance-threedouble", - ], - rotateColumns: true, -} diff --git a/app/src/chart/gameTypes/dance/default/DanceDefaultReceptor.ts b/app/src/chart/gameTypes/dance/default/DanceDefaultReceptor.ts deleted file mode 100644 index 9858e965..00000000 --- a/app/src/chart/gameTypes/dance/default/DanceDefaultReceptor.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Sprite, Texture } from "pixi.js" - -import receptorUrl from "../../../../../assets/noteskin/dance/default/receptor.png" -import { rgbtoHex } from "../../../../util/Color" -import { ChartRenderer } from "../../../ChartRenderer" -import { Receptor } from "../../base/Noteskin" - -const receptorTex = Texture.from(receptorUrl) - -export class DanceDefaultReceptor extends Sprite implements Receptor { - private lastPressedTime = -1 - private pressedKeys = 0 - - constructor() { - super(receptorTex) - this.width = 69 - this.height = 69 - this.anchor.set(0.5) - } - - update(_renderer: ChartRenderer, beat: number) { - const col = - Math.min( - 1, - Math.max(1 - ((beat + 100000) % 1), 0.5 + (this.pressedKeys ? 0.2 : 0)) - ) * 255 - let scale = 1 - if (Date.now() - this.lastPressedTime < 110) - scale = ((Date.now() - this.lastPressedTime) / 110) * 0.25 + 0.75 - this.scale.set(scale * 0.5) - this.tint = rgbtoHex(col, col, col) - } - - keyDown() { - const wasPressed = this.pressedKeys++ - if (!wasPressed) this.lastPressedTime = Date.now() - } - - keyUp() { - this.pressedKeys = Math.max(this.pressedKeys - 1, 0) - } -} diff --git a/app/src/chart/gameTypes/noteskin/Noteskin.ts b/app/src/chart/gameTypes/noteskin/Noteskin.ts new file mode 100644 index 00000000..b1cf4903 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/Noteskin.ts @@ -0,0 +1,350 @@ +import { Container, Sprite, Texture } from "pixi.js" +import { ChartRenderer } from "../../ChartRenderer" +import { NotedataEntry } from "../../sm/NoteTypes" + +import missingTexUrl from "../../../../assets/missing.png" +import { WaterfallManager } from "../../../gui/element/WaterfallManager" +import { Options } from "../../../util/Options" +import { VertCropSprite } from "../../../util/VertCropSprite" +import { StandardMissTimingWindow } from "../../play/StandardMissTimingWindow" +import { StandardTimingWindow } from "../../play/StandardTimingWindow" + +export const MISSING_TEX = Texture.from(missingTexUrl) + +export interface NoteskinElementOptions { + columnName: string + columnNumber: number + element: keyof NoteskinElements +} + +export interface NoteskinElementCreationOptions { + note: NotedataEntry +} + +export type NoteskinElementCreationData = + Partial & { + noteskin: Noteskin + columnName: string + columnNumber: number + } + +export type Generator = (data: NoteskinElementCreationData) => Element + +export type NoteskinElements = { + Tap: NoteskinSprite + Fake: NoteskinSprite + Lift: NoteskinSprite + Mine: NoteskinSprite + "Hold Inactive Head": NoteskinSprite + "Hold Inactive TopCap": NoteskinSprite + "Hold Inactive Body": NoteskinSprite + "Hold Inactive BottomCap": NoteskinHoldTail + "Hold Active Head": NoteskinSprite + "Hold Active TopCap": NoteskinSprite + "Hold Active Body": NoteskinSprite + "Hold Active BottomCap": NoteskinHoldTail + "Roll Inactive Head": NoteskinSprite + "Roll Inactive TopCap": NoteskinSprite + "Roll Inactive Body": NoteskinSprite + "Roll Inactive BottomCap": NoteskinHoldTail + "Roll Active Head": NoteskinSprite + "Roll Active TopCap": NoteskinSprite + "Roll Active Body": NoteskinSprite + "Roll Active BottomCap": NoteskinHoldTail + Receptor: NoteskinSprite + NoteFlash: NoteskinSprite +} + +export type NoteskinElementRedirect = { + element: keyof NoteskinElements + columnName?: string + columnNumber?: number +} + +export type NoteskinElementGenerators = { + [K in keyof NoteskinElements]: + | Generator + | NoteskinElementRedirect +} + +export type NoteskinMetrics = typeof DEFAULT_METRICS + +export interface NoteskinOptions { + elements: Record> + metrics?: Partial + load?: ( + this: Noteskin, + element: NoteskinElementOptions, + data: NoteskinElementCreationData + ) => NoteskinSprite + init?: (renderer: ChartRenderer) => void + update?: (renderer: ChartRenderer) => void + hideIcons?: string[] +} + +export interface NoteskinElement {} + +export type NoteskinSprite = NoteskinElement & Container + +export type NoteskinHoldTail = NoteskinElement & VertCropSprite + +export type NoteskinEvent = + | PressEvent + | LiftEvent + | GhostTapEvent + | HitEvent + | AvoidMineEvent + | HitMineEvent + | MissEvent + | HoldOffEvent + | HoldOnEvent + | RollOffEvent + | RollOnEvent + | HoldLetGoEvent + | HoldHeldEvent + +export type NoteskinEventNames = NoteskinEvent["type"] + +// Button presses + +type PressEvent = { + type: "press" + columnName: string + columnNumber: number +} + +type LiftEvent = { + type: "lift" + columnName: string + columnNumber: number +} + +type GhostTapEvent = { + type: "ghosttap" + columnName: string + columnNumber: number +} + +// Judgements + +type HitEvent = { + type: "hit" + judgement: StandardTimingWindow + columnName: string + columnNumber: number +} + +type AvoidMineEvent = { + type: "avoidmine" + columnName: string + columnNumber: number +} + +type HitMineEvent = { + type: "hitmine" + columnName: string + columnNumber: number +} + +type MissEvent = { + type: "miss" + judgement: StandardMissTimingWindow + columnName: string + columnNumber: number +} + +// Holding + +type HoldOnEvent = { + type: "holdon" + columnName: string + columnNumber: number +} + +type HoldOffEvent = { + type: "holdoff" + columnName: string + columnNumber: number +} + +type RollOnEvent = { + type: "rollon" + columnName: string + columnNumber: number +} + +type RollOffEvent = { + type: "rolloff" + columnName: string + columnNumber: number +} + +type HoldLetGoEvent = { + type: "letgo" + columnName: string + columnNumber: number +} + +type HoldHeldEvent = { + type: "held" + columnName: string + columnNumber: number +} + +const DEFAULT_METRICS = { + HoldBodyTopOffset: 0, + HoldBodyBottomOffset: 0, + RollBodyTopOffset: 0, + RollBodyBottomOffset: 0, +} + +export class Noteskin { + protected readonly renderer + protected readonly options + + protected objects: NoteskinSprite[] = [] + + protected readonly updateHooks: Set<{ + item: NoteskinSprite + cb: (renderer: ChartRenderer) => void + }> = new Set() + + protected readonly hooks: { + [Name in NoteskinEventNames]?: Set<{ + item: NoteskinSprite + cb: (event: Extract) => void + }> + } = {} + + readonly metrics: NoteskinMetrics + + constructor(renderer: ChartRenderer, options: NoteskinOptions) { + this.renderer = renderer + this.options = options + this.options.init?.(renderer) + this.metrics = { + ...DEFAULT_METRICS, + ...this.options.metrics, + } + } + + update(renderer: ChartRenderer) { + this.options.update?.(renderer) + this.updateHooks.forEach(({ item, cb }) => { + if (item.destroyed) return + cb(renderer) + }) + } + + getPlaceholderSprite(): NoteskinSprite { + const spr = new Sprite(MISSING_TEX) + spr.anchor.set(0.5) + return spr + } + + getBlankSprite(): NoteskinSprite { + const spr = new Sprite(Texture.EMPTY) + return spr + } + + getElement( + element: NoteskinElementOptions, + options: Partial = {} + ): NoteskinSprite { + try { + if (this.options.load) { + const spr = this.options.load.bind(this)(element, { + noteskin: this, + columnName: element.columnName, + columnNumber: element.columnNumber, + ...options, + }) + return spr ?? this.getPlaceholderSprite() + } + return this.loadElement(element, options) ?? this.getPlaceholderSprite() + } catch (e: any) { + console.error(e) + if (Options.debug.showNoteskinErrors) { + WaterfallManager.createFormatted("Noteskin Error: " + e, "error") + } + return this.getPlaceholderSprite() + } + } + + loadElement( + element: NoteskinElementOptions, + options: Partial = {} + ) { + const func = this.followRedirs(element) + if (func === undefined) { + if (Options.debug.showNoteskinErrors) + WaterfallManager.createFormatted( + `Noteskin element ${element.columnName} ${element.element} failed to load for noteskin: Redirect loop`, + "error" + ) + return this.getPlaceholderSprite() + } + return func({ + noteskin: this, + columnName: element.columnName, + columnNumber: element.columnNumber, + ...options, + }) + } + + followRedirs(element: NoteskinElementOptions) { + const visited = [element] + let current = element + while (true) { + const next = this.options.elements[current.columnName]?.[current.element] + if (next === undefined) return undefined + if (typeof next == "function") { + return next + } else { + current = { + columnName: next.columnName ?? current.columnName, + columnNumber: next.columnNumber ?? current.columnNumber, + element: next.element, + } + if ( + visited.some( + seen => + current.columnName == seen.columnName && + current.element == seen.element + ) + ) { + return undefined + } + visited.push(current) + } + } + } + + on( + item: NoteskinSprite, + event: Event, + cb: (event: Extract) => void + ) { + if (this.hooks[event] === undefined) this.hooks[event] = new Set() + const c = { item, cb } + this.hooks[event]!.add(c) + item.once("destroyed", () => this.hooks[event]!.delete(c)) + } + + onUpdate(item: NoteskinSprite, cb: (renderer: ChartRenderer) => void) { + const c = { item, cb } + this.updateHooks.add(c) + item.once("destroyed", () => this.updateHooks.delete(c)) + } + + broadcast( + event: Extract + ) { + if (this.hooks[event.type] === undefined) return + const hooks = this.hooks[event.type]! + hooks.forEach(({ item, cb }) => { + if (item.destroyed) return + ;(cb as (event: Extract) => void)(event) + }) + } +} diff --git a/app/src/chart/gameTypes/noteskin/NoteskinRegistry.ts b/app/src/chart/gameTypes/noteskin/NoteskinRegistry.ts new file mode 100644 index 00000000..45e2213c --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/NoteskinRegistry.ts @@ -0,0 +1,270 @@ +import { WaterfallManager } from "../../../gui/element/WaterfallManager" +import { GameType } from "../GameTypeRegistry" +import { NoteskinOptions } from "./Noteskin" + +interface RegistryData { + id: string + gameTypes: string[] + path: string + title?: string + subtitle?: string +} + +export class NoteskinRegistry { + private static noteskins = new Map>() + + static register(options: RegistryData) { + for (const gameType of new Set(options.gameTypes)) { + if (!NoteskinRegistry.noteskins.has(gameType)) { + NoteskinRegistry.noteskins.set(gameType, new Map()) + } + NoteskinRegistry.noteskins.get(gameType)!.set(options.id, options) + } + } + + static async getNoteskin( + gameType: GameType, + id: string + ): Promise { + const gameTypeNoteskins = this.noteskins.get(gameType.id) + if (!gameTypeNoteskins || gameTypeNoteskins.size == 0) return + const skin = gameTypeNoteskins.get(id) ?? [...gameTypeNoteskins.values()][0] + if (!gameTypeNoteskins.get(id)) + WaterfallManager.createFormatted( + `Couldn't find the noteskin ${id}!`, + "warn" + ) + return (await ( + await import(`${skin.path}/Noteskin`) + ).default) as NoteskinOptions + } + + static getNoteskinData(gameType: GameType, id: string) { + const gameTypeNoteskins = this.noteskins.get(gameType.id) + if (!gameTypeNoteskins || gameTypeNoteskins.size == 0) return + const skin = gameTypeNoteskins.get(id) ?? [...gameTypeNoteskins.values()][0] + return skin + } + + static getNoteskins() { + return this.noteskins + } + + static getPreviewUrl(gameType: GameType, id: string) { + return new URL( + `${this.getNoteskinData(gameType, id)?.path}/preview.png`, + import.meta.url + ).href + } +} + +NoteskinRegistry.register({ + id: "default", + gameTypes: [ + "dance-single", + "dance-double", + "dance-couple", + "dance-solo", + "dance-solodouble", + "dance-threepanel", + "dance-threedouble", + ], + path: "./dance/default", + title: "Scalable", + subtitle: "Pete-Lawrence", +}) +NoteskinRegistry.register({ + id: "cf-chrome", + gameTypes: [ + "dance-single", + "dance-double", + "dance-couple", + "dance-solo", + "dance-solodouble", + "dance-threepanel", + "dance-threedouble", + ], + path: "./dance/cf-chrome", + title: "CF_CHROME", + subtitle: "Pete-Lawrence", +}) +NoteskinRegistry.register({ + id: "ddr-note", + gameTypes: [ + "dance-single", + "dance-double", + "dance-couple", + "dance-solo", + "dance-solodouble", + "dance-threepanel", + "dance-threedouble", + ], + path: "./dance/ddr-note", + title: "DDR-Note", + subtitle: "Pete-Lawrence", +}) +NoteskinRegistry.register({ + id: "ddr-note-itg", + gameTypes: [ + "dance-single", + "dance-double", + "dance-couple", + "dance-solo", + "dance-solodouble", + "dance-threepanel", + "dance-threedouble", + ], + path: "./dance/ddr-note-itg", + title: "DDR-Note (ITG quants)", + subtitle: "Pete-Lawrence", +}) +NoteskinRegistry.register({ + id: "ddr-rainbow", + gameTypes: ["dance-single", "dance-double", "dance-couple"], + path: "./dance/ddr-rainbow", + title: "DDR-Rainbow", + subtitle: "LemmaEOF", +}) +NoteskinRegistry.register({ + id: "ddr-rainbow-itg", + gameTypes: ["dance-single", "dance-double", "dance-couple"], + path: "./dance/ddr-rainbow-itg", + title: "DDR-Rainbow (ITG quants)", + subtitle: "LemmaEOF", +}) +NoteskinRegistry.register({ + id: "metal", + gameTypes: [ + "dance-single", + "dance-double", + "dance-couple", + "dance-solo", + "dance-solodouble", + "dance-threepanel", + "dance-threedouble", + ], + path: "./dance/metal", + title: "Metal", + subtitle: "Pete-Lawrence", +}) +NoteskinRegistry.register({ + id: "pastel", + gameTypes: [ + "dance-single", + "dance-double", + "dance-couple", + "dance-solo", + "dance-solodouble", + "dance-threepanel", + "dance-threedouble", + ], + path: "./dance/pastel", + title: "Pastel", + subtitle: "halcyoniix", +}) +NoteskinRegistry.register({ + id: "dividebyzero", + gameTypes: [ + "dance-single", + "dance-double", + "dance-couple", + "dance-solo", + "dance-solodouble", + "dance-threepanel", + "dance-threedouble", + ], + path: "./dance/dividebyzero", + title: "DivideByZero", + subtitle: "MinaciousGrace", +}) +NoteskinRegistry.register({ + id: "subtractbyzero", + gameTypes: [ + "dance-single", + "dance-double", + "dance-couple", + "dance-solo", + "dance-solodouble", + "dance-threepanel", + "dance-threedouble", + ], + path: "./dance/subtractbyzero", + title: "SubtractByZero", + subtitle: "qwertyzoro/Vague", +}) +NoteskinRegistry.register({ + id: "sm4", + gameTypes: [ + "dance-single", + "dance-double", + "dance-couple", + "dance-solo", + "dance-solodouble", + "dance-threepanel", + "dance-threedouble", + ], + path: "./dance/sm4", + title: "SM4", + subtitle: "from SM4", +}) +NoteskinRegistry.register({ + id: "sm4-bold", + gameTypes: [ + "dance-single", + "dance-double", + "dance-couple", + "dance-solo", + "dance-solodouble", + "dance-threepanel", + "dance-threedouble", + ], + path: "./dance/sm4-bold", + title: "SM4 Bold", + subtitle: "from SM4", +}) +NoteskinRegistry.register({ + id: "starlight-vivid", + gameTypes: ["dance-single", "dance-double", "dance-couple"], + path: "./dance/starlight-vivid", + title: "SLNEXXT-vivid", + subtitle: "from STARLiGHT-NEXXT", +}) + +NoteskinRegistry.register({ + id: "default", + gameTypes: [ + "pump-single", + "pump-double", + "pump-couple", + "pump-versus", + "pump-halfdouble", + ], + path: "./pump/default", + title: "Fiesta", +}) +NoteskinRegistry.register({ + id: "fourv2", + gameTypes: [ + "pump-single", + "pump-double", + "pump-couple", + "pump-versus", + "pump-halfdouble", + ], + path: "./pump/fourv2", + title: "FourV2", + subtitle: "Jousway", +}) + +NoteskinRegistry.register({ + id: "prime", + gameTypes: [ + "pump-single", + "pump-double", + "pump-couple", + "pump-versus", + "pump-halfdouble", + ], + path: "./pump/prime", + title: "Prime", +}) diff --git a/app/src/chart/gameTypes/noteskin/README.md b/app/src/chart/gameTypes/noteskin/README.md new file mode 100644 index 00000000..166a9f73 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/README.md @@ -0,0 +1,7 @@ +All noteskins sources are in their respective Noteskin.ts files. If you are the owner of the noteskin and want it to be removed, please +contact me on GitHub. + +Some best practices: +- Render note types using a RenderTexture for better performance (especially those with models) +- Use redirects as much as possible (less work for you!) +- Some scripts are provided in _scripts \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/_scripts/convertMS3DASCII.js b/app/src/chart/gameTypes/noteskin/_scripts/convertMS3DASCII.js new file mode 100644 index 00000000..15093b93 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/_scripts/convertMS3DASCII.js @@ -0,0 +1,66 @@ +// Converts MS3DASCII into model txt files that SMEditor uses + +function convert(model_text) { + const lines = model_text + .replaceAll(/\/\/.+/g, "") + .split("\n") + .map(a => a.trim()) + .filter(a => a != "") + let idx = lines.findIndex(a => a.includes("Meshes: ")) + let mesh_match = /Meshes: (\d+)/.exec(lines[idx]) + if (mesh_match == null) { + throw "Invalid mesh count" + } + let n_meshes = mesh_match[1] + idx++ + + for (let i = 0; i < n_meshes; i++) { + idx++ + + let mesh = "" + + let n_vertex = parseInt(lines[idx]) + if (n_vertex <= 0) { + throw "Invalid vertex count" + } + + mesh += n_vertex + "\n" + idx++ + + for (let j = 0; j < n_vertex; j++) { + let vert = /(-?[.\d]+) (-?[.\d]+) (-?[.\d]+) (-?[.\d]+) (-?[.\d]+) (-?[.\d]+) (-?[.\d]+)/.exec(lines[idx]) + if (vert == null) { + throw "Invalid vertex " + j + ": " + lines[idx] + } + mesh += `${vert[2]} ${vert[3]} ${vert[5]} ${vert[6]}\n` + idx++ + } + + let n_normal = parseInt(lines[idx]) + if (n_normal == 0) { + throw "Invalid normal count: " + lines[idx] + } + idx += n_normal + 1 + + let n_tris = parseInt(lines[idx]) + if (n_tris <= 0) { + throw "Invalid triangle count: " + lines[idx] + } + + mesh += n_tris + "\n" + idx++ + + for (let j = 0; j < n_tris; j++) { + let tri = /(\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+)/.exec(lines[idx]) + if (tri == null) { + throw "Invalid triangle " + j + ": " + lines[idx] + } + mesh += `${tri[2]} ${tri[3]} ${tri[4]}\n` + idx++ + } + + console.log(mesh) + + } + +} diff --git a/app/src/chart/gameTypes/noteskin/_scripts/generateNoteskinPreview.js b/app/src/chart/gameTypes/noteskin/_scripts/generateNoteskinPreview.js new file mode 100644 index 00000000..a057bbea --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/_scripts/generateNoteskinPreview.js @@ -0,0 +1,189 @@ +const previewNotes = { + "dance-single": `ArrowVortex:notes:!"=J[(]]*q$ip>F!?_Fh""8[L8-/fK!]^?DJOi<6Jk/E8!`, + "pump-single": `ArrowVortex:notes:!"=J[(]]*q$ip>F!?_Fh""8gP8-/cJ!BC3BJOi<6Jk/E8!`, +} + +const viewOptions = { + "chart.speed": 400, + "chart.CMod": false, + "chart.reverse": false, + "chart.waveform.enabled": false, + "chart.zoom": 1.3, + "chart.receptorYPos": -250, + "chart.maxDrawBeats": 20, + "chart.maxDrawBeatsBack": 10, + "chart.drawNoteFlash": true, + "chart.drawIcons": true, + "chart.noteLayout.enabled": false, + "chart.npsGraph.enabled": false, + "debug.showFPS": false, + "debug.showTimers": false, + "debug.showScroll": false, + "play.offset": 0, + "play.visualOffset": 0, +} + +const imageOptions = { + xPadding: 10, + aspectRatio: 9/21, + exportWidth: 450, +} + +async function generateNoteskinPreview(gameType, skinName, notes) { + if (!gameType || !skinName) { + throw "No gameType/skinName provided!" + } + + if (notes == undefined) notes = previewNotes[gameType] ?? previewNotes["dance-single"] + + const cm = window.app.chartManager + const sm = cm.loadedSM + if (!sm) { + throw "Please load an SM file first" + } + const chart = cm.loadedChart + if (!sm) { + throw "Please load a chart first" + } + + const oldSkin = app.options.chart.noteskin + const oldLastSkin = app.options.chart.lastNoteskins[gameType] + + // Create new chart + const newChart = new chart.constructor(sm) + newChart.gameType = window.GameTypeRegistry.getGameType(gameType) + if (newChart.gameType === undefined) { + throw "Invalid game type" + } + sm.addChart(newChart) + await cm.loadChart(newChart) + const playing = app.chartManager.getAudio().isPlaying() + if (playing) { + cm.playPause() + } + cm.setBeat(0) + + Object.keys(newChart.sm.timingData.columns).forEach(key => { + newChart.timingData.columns[key] = {type: key, events: []} + }) + newChart.timingData.reloadCache() + + // Paste notes + cm.pasteNotes(notes) + cm.clearSelections() + + // Cache the options + + const optionsCache = Object.keys(viewOptions).map(key => { + return [key, window.app.options.getOption(key)] + }) + + // Apply options + + const cv = cm.chartView + cv.swapNoteskin(skinName) + + Object.entries(viewOptions).map(opt => { + window.app.options.applyOption(opt) + }) + + cv.barlines.alpha = 0 + cv.previewArea.alpha = 0 + cv.snapDisplay.alpha = 0 + + cm.mode = 'View Mode' + document.getElementById("status-widget").style.visibility = "hidden" + document.getElementById("waterfall").style.visibility = "hidden" + document.body.classList.remove("animated") + + // Wait for the noteskin to load + await new Promise(resolve => { + setTimeout(() => resolve(), 400) + }) + + // Render the image + window.app.renderer.render(window.app.stage) + const blob = await new Promise(resolve => window.app.view.toBlob(resolve)); + + // Load the image + const url = URL.createObjectURL(blob) + + const image = document.createElement("img") + image.src = url + const canvas = document.createElement("canvas") + const ctx = canvas.getContext("2d") + + const notefieldWidth = newChart.gameType.notefieldWidth + + const imgSourceWidth = (notefieldWidth + imageOptions.xPadding * 2) * 1.3 * window.app.options.performance.resolution + const imgSourceHeight = imgSourceWidth / imageOptions.aspectRatio + + const cw = app.view.width / window.app.options.performance.resolution + const ch = app.view.height / window.app.options.performance.resolution + + canvas.width = imageOptions.exportWidth + canvas.height = imageOptions.exportWidth / imageOptions.aspectRatio + + document.body.appendChild(image) + document.body.appendChild(canvas) + + await new Promise(resolve => { + image.onload = () => resolve() + }) + + ctx.fillStyle = "#18191c" + + ctx.fillRect(0, 0, canvas.width, canvas.height) + + ctx.drawImage(image, + cw - imgSourceWidth / 2, ch - imgSourceHeight / 2, + imgSourceWidth, imgSourceHeight, 0, 0, canvas.width, canvas.height + ) + + const croppedBlob = await new Promise(resolve => canvas.toBlob(resolve)); + + const a = document.createElement("a") + document.body.appendChild(a) + const croppedUrl = URL.createObjectURL(croppedBlob) + a.href = croppedUrl + a.download = `${skinName}.png` + a.click() + a.remove() + + URL.revokeObjectURL(url) + URL.revokeObjectURL(croppedUrl) + + canvas.remove() + image.remove() + + + // Revert options + + cv.barlines.alpha = 1 + cv.previewArea.alpha = 1 + cv.snapDisplay.alpha = 1 + + cm.mode = 'Edit Mode' + document.getElementById("status-widget").style.visibility = "" + document.getElementById("waterfall").style.visibility = "" + optionsCache.forEach(opt => { + window.app.options.applyOption(opt) + }) + + document.body.classList.toggle("animated", window.app.options.general.smoothAnimations) + + sm.removeChart(newChart) + cm.loadChart(chart) + window.app.chartManager.chartView.swapNoteskin(oldSkin.name) + app.options.chart.lastNoteskins[gameType] = oldLastSkin + if (playing) { + cm.playPause() + } +} + +async function generateAllPreviews(gameType, notes) { + const noteskins = window.NoteskinRegistry.getNoteskins().get(gameType).keys() + for (const skin of noteskins) { + await generateNoteskinPreview(gameType, skin, notes) + } +} \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/_template/HoldBody.ts b/app/src/chart/gameTypes/noteskin/_template/HoldBody.ts new file mode 100644 index 00000000..9b0b3d58 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/_template/HoldBody.ts @@ -0,0 +1,163 @@ +import { + IDestroyOptions, + Texture, + Ticker, + TilingSprite, + UPDATE_PRIORITY, +} from "pixi.js" + +export class HoldBody extends TilingSprite { + constructor(texture: Texture, holdWidth = 64) { + super(texture) + this.tileScale.set(holdWidth / this.texture.width) + this.texture.on("update", () => { + this.tileScale.set(holdWidth / this.texture.width) + }) + this.uvRespectAnchor = true + this.anchor.y = 1 + this.x = -holdWidth / 2 + this.width = holdWidth + } +} + +export class AnimatedHoldBody extends HoldBody { + 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._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) { + 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/_template/HoldTail.ts b/app/src/chart/gameTypes/noteskin/_template/HoldTail.ts new file mode 100644 index 00000000..6d3c030e --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/_template/HoldTail.ts @@ -0,0 +1,154 @@ +import { IDestroyOptions, Texture, Ticker, UPDATE_PRIORITY } from "pixi.js" +import { VertCropSprite } from "../../../../util/VertCropSprite" + +export class HoldTail extends VertCropSprite { + constructor(texture: Texture, holdWidth = 64) { + super(texture) + this.scale.set(holdWidth / this.texture.width) + this.pivot.x = holdWidth / 2 / this.scale.x + this.texture.on("update", () => { + this.width = this.texture.width + this.height = this.texture.height + this.scale.set(holdWidth / this.texture.width) + this.pivot.x = holdWidth / 2 / this.scale.x + this.refresh() + }) + } +} + +export class AnimatedHoldTail extends HoldTail { + 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/_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/dance/cf-chrome/ModelRenderer.ts b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/ModelRenderer.ts new file mode 100644 index 00000000..325e1a08 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/ModelRenderer.ts @@ -0,0 +1,254 @@ +import { + BaseTexture, + Container, + Geometry, + Mesh, + MIPMAP_MODES, + Rectangle, + RenderTexture, + Shader, + Sprite, + Texture, +} from "pixi.js" + +import { App } from "../../../../../App" +import { Options } from "../../../../../util/Options" +import { loadGeometry } from "../../../../../util/Util" +import { NotedataEntry } from "../../../../sm/NoteTypes" + +import mineFrameUrl from "./mine/frame.png" +import minePartsUrl from "./mine/parts.png" + +import tapBodyUrl from "./tap/body.png" +import tapFrameBlackUrl from "./tap/frameBlack.png" +import tapFrameChromeUrl from "./tap/frameChrome.png" + +import liftFrameBlackUrl from "./lift/frameBlack.png" +import liftFrameChromeUrl from "./lift/frameChrome.png" + +import arrowGradientFrag from "./shader/arrow_gradient.frag?raw" +import mineGradientFrag from "./shader/mine_gradient.frag?raw" +import noopFrag from "./shader/noop.frag?raw" +import noopVert from "./shader/noop.vert?raw" + +import arrowBodyGeomText from "./tap/body.txt?raw" +import arrowFrameBlackGeomText from "./tap/frameBlack.txt?raw" +import arrowFrameChromeGeomText from "./tap/frameChrome.txt?raw" + +import liftFrameBlackGeomText from "./lift/frameBlack.txt?raw" +import liftFrameChromeGeomText from "./lift/frameChrome.txt?raw" + +import mineBodyGeomText from "./mine/body.txt?raw" + +const mine_frame_texture = Texture.from(mineFrameUrl) + +export class ModelRenderer { + static arrowBodyTex = BaseTexture.from(tapBodyUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static arrowFrameChromeTex = BaseTexture.from(tapFrameChromeUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static arrowFrameBlackTex = BaseTexture.from(tapFrameBlackUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static minePartsTex = BaseTexture.from(minePartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static liftFrameChromeTex = BaseTexture.from(liftFrameChromeUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static liftFrameBlackTex = BaseTexture.from(liftFrameBlackUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + + static arrowBodyGeom: Geometry + static arrowFrameBlackGeom: Geometry + static arrowFrameChromeGeom: Geometry + + static liftFrameBlackGeom: Geometry + static liftFrameChromeGeom: Geometry + + static mineBodyGeom: Geometry + + static arrowFrameTex: RenderTexture + static arrowChrome: Mesh + static arrowBlack: Mesh + static arrowFrameContainer = new Container() + + static arrowTex: RenderTexture + static arrowContainer = new Container() + + static liftTex: RenderTexture + static liftChrome: Mesh + static liftBlack: Mesh + static liftContainer = new Container() + + static mineTex: RenderTexture + static mineContainer = new Container() + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + ModelRenderer.arrowFrameTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + ModelRenderer.arrowTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.liftTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + ModelRenderer.mineTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + + this.arrowBodyGeom = await loadGeometry(arrowBodyGeomText) + this.arrowFrameChromeGeom = await loadGeometry(arrowFrameChromeGeomText) + this.arrowFrameBlackGeom = await loadGeometry(arrowFrameBlackGeomText) + this.mineBodyGeom = await loadGeometry(mineBodyGeomText) + this.liftFrameChromeGeom = await loadGeometry(liftFrameChromeGeomText) + this.liftFrameBlackGeom = await loadGeometry(liftFrameBlackGeomText) + + { + const chrome_shader = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.arrowFrameChromeTex, + time: 0, + quant: 0, + }) + const chrome = new Mesh(ModelRenderer.arrowFrameChromeGeom, chrome_shader) + chrome.x = 32 + chrome.y = 32 + chrome.rotation = -Math.PI / 2 + this.arrowChrome = chrome + + const black_shader = Shader.from(noopVert, noopFrag, { + sampler0: this.arrowFrameBlackTex, + }) + const black = new Mesh(ModelRenderer.arrowFrameBlackGeom, black_shader) + black.x = 32 + black.y = 32 + black.rotation = -Math.PI / 2 + this.arrowBlack = black + + this.arrowFrameContainer.addChild(chrome, black) + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.arrowBodyTex, + time: 0, + quant: i, + }) + const arrow_frame = new Sprite(ModelRenderer.arrowFrameTex) + arrow_frame.x = (i % 3) * 64 + arrow_frame.y = Math.floor(i / 3) * 64 + const arrow_body = new Mesh(ModelRenderer.arrowBodyGeom, shader_body) + arrow_body.x = (i % 3) * 64 + 32 + arrow_body.y = Math.floor(i / 3) * 64 + 32 + arrow_body.rotation = -Math.PI / 2 + arrow_body.name = "body" + i + ModelRenderer.arrowContainer.addChild(arrow_frame) + ModelRenderer.arrowContainer.addChild(arrow_body) + } + } + { + const chrome_shader = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.liftFrameChromeTex, + time: 0, + quant: 0, + }) + const chrome = new Mesh(ModelRenderer.liftFrameChromeGeom, chrome_shader) + chrome.x = 32 + chrome.y = 32 + chrome.rotation = -Math.PI / 2 + this.liftChrome = chrome + + const black_shader = Shader.from(noopVert, noopFrag, { + sampler0: this.liftFrameBlackTex, + }) + const black = new Mesh(ModelRenderer.liftFrameBlackGeom, black_shader) + black.x = 32 + black.y = 32 + black.rotation = -Math.PI / 2 + this.liftBlack = black + + this.liftContainer.addChild(chrome, black) + } + { + const shader_body = Shader.from(noopVert, mineGradientFrag, { + sampler0: this.minePartsTex, + time: 0, + }) + const mine_body = new Mesh(ModelRenderer.mineBodyGeom, shader_body) + const mine_frame = new Sprite(mine_frame_texture) + mine_frame.width = 64 + mine_frame.height = 64 + mine_frame.anchor.set(0.5) + mine_frame.pivot.y = 3 // epic recenter + ModelRenderer.mineContainer.position.set(32) + ModelRenderer.mineContainer.addChild(mine_body) + ModelRenderer.mineContainer.addChild(mine_frame) + } + + this.loaded = true + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const beat = app.chartManager.chartView!.getVisualBeat() + const second = app.chartManager.chartView!.getVisualTime() + for (let i = 0; i < 10; i++) { + const tapShader: Mesh = + ModelRenderer.arrowContainer.getChildByName("body" + i)! + tapShader.shader.uniforms.time = beat + } + this.arrowChrome.shader.uniforms.time = beat + this.liftChrome.shader.uniforms.time = beat + ;(ModelRenderer.mineContainer.children[0]).shader.uniforms.time = + second + ModelRenderer.mineContainer.rotation = (second % 1) * Math.PI * 2 + + app.renderer.render(ModelRenderer.arrowFrameContainer, { + renderTexture: ModelRenderer.arrowFrameTex, + }) + app.renderer.render(ModelRenderer.arrowContainer, { + renderTexture: ModelRenderer.arrowTex, + }) + app.renderer.render(ModelRenderer.mineContainer, { + renderTexture: ModelRenderer.mineTex, + }) + app.renderer.render(ModelRenderer.liftContainer, { + renderTexture: ModelRenderer.liftTex, + }) + } + + static setNoteTex(arrow: Sprite, note: NotedataEntry | undefined) { + if (note !== undefined && note.type == "Mine") { + arrow.texture = ModelRenderer.mineTex + } else if (note !== undefined && note.type == "Lift") { + arrow.texture = ModelRenderer.liftTex + } else { + const i = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + note?.quant ?? 4 + ) + arrow.texture = new Texture( + note?.type == "Lift" ?? "Tap" + ? ModelRenderer.liftTex.baseTexture + : ModelRenderer.arrowTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/NoteFlash.ts new file mode 100644 index 00000000..460cf967 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/NoteFlash.ts @@ -0,0 +1,165 @@ +import { BLEND_MODES, Container, Sprite, Texture } from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import bezier from "bezier-easing" +import { BezierAnimator } from "../../../../../util/BezierEasing" +import w4Url from "./flash/decent.png" +import w2Url from "./flash/excellent.png" +import w0Url from "./flash/fantastic.png" +import w3Url from "./flash/great.png" +import holdFlashUrl from "./flash/hold.png" +import mineUrl from "./flash/mine.png" +import w5Url from "./flash/way_off.png" +import w1Url from "./flash/white_fantastic.png" + +const flashTex = { + hold: Texture.from(holdFlashUrl), + w0: Texture.from(w0Url), + w1: Texture.from(w1Url), + w2: Texture.from(w2Url), + w3: Texture.from(w3Url), + w4: Texture.from(w4Url), + w5: Texture.from(w5Url), + mine: Texture.from(mineUrl), +} + +export class NoteFlashContainer extends Container { + holdExplosion = new Sprite(flashTex.hold) + standard: Record = { + w0: new Sprite(flashTex.w0), + w1: new Sprite(flashTex.w1), + w2: new Sprite(flashTex.w2), + w3: new Sprite(flashTex.w3), + w4: new Sprite(flashTex.w4), + w5: new Sprite(flashTex.w5), + } + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + this.scale.set(0.5) + + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + if (this.standard[event.judgement.id]) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + const obj = this.standard[event.judgement.id] + this.anims.add( + BezierAnimator.animate( + obj, + { + "0": { + alpha: 1.2, + "scale.x": 1.1, + "scale.y": 1.1, + }, + "1": { + alpha: 0, + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.15, + bezier(0.11, 0, 0.5, 0) + ) + ) + } + } + }) + + noteskin.on(this, "held", event => { + if (col == event.columnNumber) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + const obj = this.standard.w2 + BezierAnimator.animate( + obj, + { + "0": { + alpha: 1.2, + "scale.x": 1.1, + "scale.y": 1.1, + }, + "1": { + alpha: 0, + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.09, + bezier(0.11, 0, 0.5, 0) + ) + } + }) + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(flashTex.mine) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + noteskin.onUpdate(this, () => { + this.holdExplosion.alpha = + Math.sin((Date.now() / 1000) * Math.PI * 2 * 20) * 0.1 + 1 + }) + + this.holdExplosion.visible = false + this.holdExplosion.anchor.set(0.5) + this.addChild(this.holdExplosion) + for (const item of Object.values(this.standard)) { + item.alpha = 0 + item.anchor.set(0.5) + this.addChild(item) + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/Noteskin.ts new file mode 100644 index 00000000..823edd7a --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/Noteskin.ts @@ -0,0 +1,182 @@ +// CF-CHROME by Pete-Lawrence https://github.com/Pete-Lawrence/Peters-Noteskins/ +// Color modifications by tillvit + +import { BLEND_MODES, Container, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" + +import { ModelRenderer } from "./ModelRenderer" +import { NoteFlashContainer } from "./NoteFlash" +import receptorUrl from "./receptor.png" + +import holdBodyActiveUrl from "./hold/bodyActive.png" +import holdBodyInactiveUrl from "./hold/bodyInactive.png" +import holdBottomCapActiveUrl from "./hold/bottomCapActive.png" +import holdBottomCapInactiveUrl from "./hold/bottomCapInactive.png" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { rgbtoHex } from "../../../../../util/Color" +import { clamp } from "../../../../../util/Math" +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" +import rollBodyActiveUrl from "./roll/bodyActive.png" +import rollBodyInactiveUrl from "./roll/bodyInactive.png" +import rollBottomCapActiveUrl from "./roll/bottomCapActive.png" +import rollBottomCapInactiveUrl from "./roll/bottomCapInactive.png" + +const receptorTex = Texture.from(receptorUrl) + +const holdTex = { + hold: { + active: { + body: Texture.from(holdBodyActiveUrl), + bottomCap: Texture.from(holdBottomCapActiveUrl), + }, + inactive: { + body: Texture.from(holdBodyInactiveUrl), + bottomCap: Texture.from(holdBottomCapInactiveUrl), + }, + }, + roll: { + active: { + body: Texture.from(rollBodyActiveUrl), + bottomCap: Texture.from(rollBottomCapActiveUrl), + }, + inactive: { + body: Texture.from(rollBodyInactiveUrl), + bottomCap: Texture.from(rollBottomCapInactiveUrl), + }, + }, +} + +const rotationMap: Record = { + Left: 0, + Down: -90, + Up: 90, + Right: 180, + UpLeft: 45, + UpRight: 135, + DownRight: -135, + DownLeft: -45, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", + "NoteFlash", +] + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + const container = new Container() + const spr = new Sprite(receptorTex) + spr.width = 64 + spr.height = 64 + spr.anchor.set(0.5) + const overlay = new Sprite(receptorTex) + overlay.width = 64 + overlay.height = 64 + overlay.alpha = 0 + overlay.blendMode = BLEND_MODES.ADD + overlay.anchor.set(0.5) + container.addChild(spr, overlay) + options.noteskin.on( + container, + "press", + opt => + opt.columnNumber == options.columnNumber && (overlay.alpha = 0.2) + ) + options.noteskin.on( + container, + "lift", + opt => opt.columnNumber == options.columnNumber && (overlay.alpha = 0) + ) + options.noteskin.on(container, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + container, + { + "0": { + "scale.x": 0.75, + "scale.y": 0.75, + }, + "1": { + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.11 + ) + } + }) + options.noteskin.onUpdate(container, r => { + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + const col = clamp(1 - partialBeat, 0.5, 1) * 255 + spr.tint = rgbtoHex(col, col, col) + }) + return container + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + ModelRenderer.setNoteTex(spr, options.note) + spr.anchor.set(0.5) + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer(options.noteskin, options.columnNumber) + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: { element: "Tap" }, + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "Hold Active Body": () => new HoldBody(holdTex.hold.active.body), + "Hold Inactive Body": () => new HoldBody(holdTex.hold.inactive.body), + "Hold Active TopCap": () => new Sprite(Texture.EMPTY), + "Hold Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Hold Active BottomCap": () => + new HoldTail(holdTex.hold.active.bottomCap), + "Hold Inactive BottomCap": () => + new HoldTail(holdTex.hold.inactive.bottomCap), + + "Roll Active Head": { element: "Tap" }, + "Roll Inactive Head": { element: "Tap" }, + "Roll Active Body": () => new HoldBody(holdTex.roll.active.body), + "Roll Inactive Body": () => new HoldBody(holdTex.roll.inactive.body), + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": () => + new HoldTail(holdTex.roll.active.bottomCap), + "Roll Inactive BottomCap": () => + new HoldTail(holdTex.roll.inactive.bottomCap), + }, + }, + load: function (element, options) { + const col = element.columnName + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + init() { + ModelRenderer.initArrowTex() + }, + update(renderer) { + ModelRenderer.setArrowTexTime(renderer.chartManager.app) + }, + hideIcons: ["Lift"], +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/decent.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/decent.png new file mode 100644 index 00000000..8bd8928c Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/decent.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/excellent.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/excellent.png new file mode 100644 index 00000000..5335c5ac Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/excellent.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/fantastic.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/fantastic.png new file mode 100644 index 00000000..dcb6ea4d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/fantastic.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/great.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/great.png new file mode 100644 index 00000000..3fbb491d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/great.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/hold.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/hold.png new file mode 100644 index 00000000..cdcf31d4 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/hold.png differ diff --git a/app/assets/noteskin/dance/default/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/mine.png similarity index 100% rename from app/assets/noteskin/dance/default/flash/mine.png rename to app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/mine.png diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/way_off.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/way_off.png new file mode 100644 index 00000000..3eded961 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/way_off.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/white_fantastic.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/white_fantastic.png new file mode 100644 index 00000000..5e2e13db Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/flash/white_fantastic.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/hold/bodyActive.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/hold/bodyActive.png new file mode 100644 index 00000000..c2430064 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/hold/bodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/hold/bodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/hold/bodyInactive.png new file mode 100644 index 00000000..633a12f0 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/hold/bodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/hold/bottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/hold/bottomCapActive.png new file mode 100644 index 00000000..18646440 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/hold/bottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/hold/bottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/hold/bottomCapInactive.png new file mode 100644 index 00000000..94592b90 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/hold/bottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/lift/frameBlack.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/lift/frameBlack.png new file mode 100644 index 00000000..42147846 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/lift/frameBlack.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/lift/frameBlack.txt b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/lift/frameBlack.txt new file mode 100644 index 00000000..28cfb810 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/lift/frameBlack.txt @@ -0,0 +1,50 @@ +24 +-20.000000 5.000000 0.953125 0.500000 +-20.000000 7.000003 0.953125 0.500000 +-27.000000 0.000005 0.953125 0.500000 +-25.000000 -0.000000 0.953125 0.500000 +0.000002 27.000000 0.953125 0.500000 +-6.499996 20.500000 0.953125 0.500000 +-5.000000 20.000000 0.953125 0.500000 +0.000000 25.000000 0.953125 0.500000 +-0.000002 -27.000000 0.953125 0.500000 +0.000000 -25.000000 0.953125 0.500000 +0.000000 -15.000000 0.953125 0.500000 +-0.000001 -13.000000 0.953125 0.500000 +-5.000000 1.500000 0.953125 0.500000 +-6.500000 1.000001 0.953125 0.500000 +-0.000000 -5.500000 0.953125 0.500000 +-0.000000 -3.500000 0.953125 0.500000 +20.000000 5.000000 0.953125 0.500000 +25.000000 -0.000000 0.953125 0.500000 +27.000000 -0.000000 0.953125 0.500000 +20.000000 7.000000 0.953125 0.500000 +5.000000 20.000000 0.953125 0.500000 +6.500000 20.500000 0.953125 0.500000 +5.000000 1.499999 0.953125 0.500000 +6.500000 1.000000 0.953125 0.500000 +24 +0 1 2 +0 2 3 +4 5 6 +4 6 7 +2 8 9 +2 9 3 +0 10 11 +0 11 1 +12 6 5 +12 5 13 +13 14 15 +13 15 12 +16 17 18 +16 18 19 +4 7 20 +4 20 21 +18 17 9 +18 9 8 +16 19 11 +16 11 10 +22 23 21 +22 21 20 +23 22 15 +23 15 14 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/lift/frameChrome.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/lift/frameChrome.png new file mode 100644 index 00000000..2e314c75 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/lift/frameChrome.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/lift/frameChrome.txt b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/lift/frameChrome.txt new file mode 100644 index 00000000..fc0bff91 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/lift/frameChrome.txt @@ -0,0 +1,142 @@ +92 +-0.000000 -32.000000 0.898438 0.000000 +-0.000000 -30.000000 0.601562 0.000000 +-30.000000 -0.000000 0.601562 1.000000 +-32.000000 -0.000000 0.898438 1.000000 +-32.000000 -0.000000 0.898438 0.000000 +-30.000000 -0.000000 0.601562 0.000000 +-20.000000 10.000000 0.601562 1.000000 +-20.000000 12.000000 0.898438 1.000000 +-10.000000 22.000000 0.898438 1.000000 +-10.000000 2.000002 0.898438 0.000000 +-8.500000 0.500001 0.601562 0.000000 +-8.500000 21.500000 0.601562 1.000000 +-20.000000 10.000000 0.398438 1.000000 +-20.000000 7.000003 0.101562 1.000000 +-0.000001 -13.000000 0.101562 0.000000 +-0.000001 -10.000000 0.398438 0.000000 +0.000000 30.000000 0.398438 0.000000 +-8.500000 21.500000 0.398438 1.000000 +-6.499996 20.500000 0.101562 1.000000 +0.000002 27.000000 0.101562 0.000000 +-30.000000 -0.000000 0.398438 0.000000 +-0.000000 -30.000000 0.398438 1.000000 +-0.000002 -27.000000 0.101562 1.000000 +-27.000000 0.000005 0.101562 0.000000 +-6.500000 1.000001 0.101562 1.000000 +-6.499996 20.500000 0.101562 0.000000 +-8.500000 21.500000 0.398438 0.000000 +-8.500000 0.500001 0.398438 1.000000 +-27.000000 0.000005 0.101562 1.000000 +-20.000000 7.000003 0.101562 0.000000 +-20.000000 10.000000 0.398438 0.000000 +-30.000000 -0.000000 0.398438 1.000000 +-6.500000 1.000001 0.101562 0.000000 +-8.500000 0.500001 0.398438 0.000000 +-0.000001 -8.000000 0.398438 1.000000 +-0.000000 -5.500000 0.101562 1.000000 +-8.500000 0.500001 0.898438 0.500000 +-10.000000 2.000002 0.898438 0.500000 +-20.000000 12.000000 0.898438 0.000000 +-20.000000 10.000000 0.601562 0.000000 +-0.000001 -10.000000 0.601562 1.000000 +-0.000001 -8.000000 0.898438 1.000000 +-8.500000 21.500000 0.601562 0.000000 +0.000000 30.000000 0.601562 1.000000 +0.000000 32.000000 0.898438 1.000000 +-10.000000 22.000000 0.898438 0.000000 +-0.000000 -32.000000 0.898438 1.000000 +32.000000 0.000000 0.898438 0.000000 +30.000000 0.000000 0.601562 0.000000 +-0.000000 -30.000000 0.601562 1.000000 +32.000000 0.000000 0.898438 1.000000 +20.000000 12.000000 0.898438 0.000000 +20.000000 10.000000 0.601562 0.000000 +30.000000 0.000000 0.601562 1.000000 +10.000000 22.000000 0.898438 0.000000 +8.500000 21.500000 0.601562 0.000000 +8.500000 0.500000 0.601562 1.000000 +10.000000 2.000000 0.898438 1.000000 +20.000000 10.000000 0.398438 0.000000 +-0.000001 -10.000000 0.398438 1.000000 +-0.000001 -13.000000 0.101562 1.000000 +20.000000 7.000000 0.101562 0.000000 +0.000000 30.000000 0.398438 1.000000 +0.000002 27.000000 0.101562 1.000000 +6.500000 20.500000 0.101562 0.000000 +8.500000 21.500000 0.398438 0.000000 +30.000000 0.000000 0.398438 1.000000 +27.000000 -0.000000 0.101562 1.000000 +-0.000002 -27.000000 0.101562 0.000000 +-0.000000 -30.000000 0.398438 0.000000 +6.500000 1.000000 0.101562 0.000000 +8.500000 0.500000 0.398438 0.000000 +8.500000 21.500000 0.398438 1.000000 +6.500000 20.500000 0.101562 1.000000 +27.000000 -0.000000 0.101562 0.000000 +30.000000 0.000000 0.398438 0.000000 +20.000000 10.000000 0.398438 1.000000 +20.000000 7.000000 0.101562 1.000000 +6.500000 1.000000 0.101562 1.000000 +-0.000000 -5.500000 0.101562 0.000000 +-0.000001 -8.000000 0.398438 0.000000 +8.500000 0.500000 0.398438 1.000000 +8.500000 21.500000 0.601562 1.000000 +10.000000 22.000000 0.898438 1.000000 +0.000000 32.000000 0.898438 0.000000 +0.000000 30.000000 0.601562 0.000000 +-0.000001 -10.000000 0.601562 0.000000 +20.000000 10.000000 0.601562 1.000000 +20.000000 12.000000 0.898438 1.000000 +10.000000 2.000000 0.898438 0.500000 +8.500000 0.500000 0.898438 0.375000 +-0.000001 -8.000000 0.898438 0.000000 +48 +0 1 2 +0 2 3 +4 5 6 +4 6 7 +8 9 10 +8 10 11 +12 13 14 +12 14 15 +16 17 18 +16 18 19 +20 21 22 +20 22 23 +24 25 26 +24 26 27 +28 29 30 +28 30 31 +32 33 34 +32 34 35 +36 37 38 +36 38 39 +36 39 40 +36 40 41 +42 43 44 +42 44 45 +46 47 48 +46 48 49 +50 51 52 +50 52 53 +54 55 56 +54 56 57 +58 59 60 +58 60 61 +62 63 64 +62 64 65 +66 67 68 +66 68 69 +70 71 72 +70 72 73 +74 75 76 +74 76 77 +78 79 80 +78 80 81 +82 83 84 +82 84 85 +86 87 88 +86 88 89 +86 89 90 +86 90 91 \ No newline at end of file diff --git a/app/assets/noteskin/dance/default/mine/body.txt b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/mine/body.txt similarity index 100% rename from app/assets/noteskin/dance/default/mine/body.txt rename to app/src/chart/gameTypes/noteskin/dance/cf-chrome/mine/body.txt diff --git a/app/assets/noteskin/dance/default/mine/frame.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/mine/frame.png similarity index 100% rename from app/assets/noteskin/dance/default/mine/frame.png rename to app/src/chart/gameTypes/noteskin/dance/cf-chrome/mine/frame.png diff --git a/app/assets/noteskin/dance/default/mine/parts.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/mine/parts.png similarity index 100% rename from app/assets/noteskin/dance/default/mine/parts.png rename to app/src/chart/gameTypes/noteskin/dance/cf-chrome/mine/parts.png diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/preview.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/preview.png new file mode 100644 index 00000000..9c6bb7e1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/receptor.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/receptor.png new file mode 100644 index 00000000..e364f20f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/receptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/roll/bodyActive.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/roll/bodyActive.png new file mode 100644 index 00000000..a328afec Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/roll/bodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/roll/bodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/roll/bodyInactive.png new file mode 100644 index 00000000..6e73730f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/roll/bodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/roll/bottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/roll/bottomCapActive.png new file mode 100644 index 00000000..42a7a342 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/roll/bottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/roll/bottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/roll/bottomCapInactive.png new file mode 100644 index 00000000..26d25b43 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/roll/bottomCapInactive.png differ diff --git a/app/assets/noteskin/dance/default/shader/arrow_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/shader/arrow_gradient.frag similarity index 100% rename from app/assets/noteskin/dance/default/shader/arrow_gradient.frag rename to app/src/chart/gameTypes/noteskin/dance/cf-chrome/shader/arrow_gradient.frag diff --git a/app/assets/noteskin/dance/default/shader/lift_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/shader/lift_gradient.frag similarity index 100% rename from app/assets/noteskin/dance/default/shader/lift_gradient.frag rename to app/src/chart/gameTypes/noteskin/dance/cf-chrome/shader/lift_gradient.frag diff --git a/app/assets/noteskin/dance/default/shader/mine_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/shader/mine_gradient.frag similarity index 100% rename from app/assets/noteskin/dance/default/shader/mine_gradient.frag rename to app/src/chart/gameTypes/noteskin/dance/cf-chrome/shader/mine_gradient.frag diff --git a/app/assets/noteskin/dance/default/shader/noop.frag b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/shader/noop.frag similarity index 100% rename from app/assets/noteskin/dance/default/shader/noop.frag rename to app/src/chart/gameTypes/noteskin/dance/cf-chrome/shader/noop.frag diff --git a/app/assets/noteskin/dance/default/shader/noop.vert b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/shader/noop.vert similarity index 100% rename from app/assets/noteskin/dance/default/shader/noop.vert rename to app/src/chart/gameTypes/noteskin/dance/cf-chrome/shader/noop.vert diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/body.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/body.png new file mode 100644 index 00000000..42336a73 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/body.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/body.txt b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/body.txt new file mode 100755 index 00000000..c44876a7 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/body.txt @@ -0,0 +1,28 @@ +14 +0.000000 1.500000 0.046875 0.187500 +-5.000000 1.500000 0.015625 0.125000 +-0.000000 -3.500000 0.015625 0.250000 +-5.000000 20.000000 0.015625 -0.187500 +0.000000 20.000000 0.046875 -0.125000 +0.000000 25.000000 0.046875 -0.187500 +-25.000000 -0.000000 0.046875 -0.187500 +0.000000 -25.000000 0.015625 -0.187500 +0.000000 -15.000000 0.015625 -0.062500 +-20.000000 5.000000 0.046875 -0.062500 +5.000000 1.499999 0.015625 0.125000 +5.000000 20.000000 0.015625 -0.187500 +25.000000 -0.000000 0.046875 -0.187500 +20.000000 5.000000 0.046875 -0.062500 +12 +0 1 2 +3 1 0 +3 0 4 +4 5 3 +6 7 8 +6 8 9 +0 2 10 +11 4 0 +11 0 10 +4 11 5 +12 13 8 +12 8 7 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/frameBlack.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/frameBlack.png new file mode 100644 index 00000000..42147846 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/frameBlack.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/frameBlack.txt b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/frameBlack.txt new file mode 100755 index 00000000..9ef588c0 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/frameBlack.txt @@ -0,0 +1,50 @@ +24 +-20.000000 5.000000 0.953125 0.500000 +-20.000000 7.000003 0.953125 0.500000 +-27.000000 0.000005 0.953125 0.500000 +-25.000000 -0.000000 0.953125 0.500000 +0.000002 27.000000 0.953125 0.500000 +-6.499996 20.500000 0.953125 0.500000 +-5.000000 20.000000 0.953125 0.500000 +0.000000 25.000000 0.953125 0.500000 +-0.000002 -27.000000 0.953125 0.500000 +0.000000 -25.000000 0.953125 0.500000 +0.000000 -15.000000 0.953125 0.500000 +-0.000001 -13.000000 0.953125 0.500000 +-5.000000 1.500000 0.953125 0.500000 +-6.500000 1.000001 0.953125 0.500000 +-0.000000 -5.500000 0.953125 0.500000 +-0.000000 -3.500000 0.953125 0.500000 +20.000000 5.000000 0.953125 0.500000 +25.000000 -0.000000 0.953125 0.500000 +27.000000 -0.000000 0.953125 0.500000 +20.000000 7.000000 0.953125 0.500000 +5.000000 20.000000 0.953125 0.500000 +6.500000 20.500000 0.953125 0.500000 +5.000000 1.499999 0.953125 0.500000 +6.500000 1.000000 0.953125 0.500000 +24 +0 1 2 +0 2 3 +4 5 6 +4 6 7 +2 8 9 +2 9 3 +0 10 11 +0 11 1 +12 6 5 +12 5 13 +13 14 15 +13 15 12 +16 17 18 +16 18 19 +4 7 20 +4 20 21 +18 17 9 +18 9 8 +16 19 11 +16 11 10 +22 23 21 +22 21 20 +23 22 15 +23 15 14 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/frameChrome.png b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/frameChrome.png new file mode 100644 index 00000000..2e314c75 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/frameChrome.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/frameChrome.txt b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/frameChrome.txt new file mode 100755 index 00000000..400fb0b4 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/cf-chrome/tap/frameChrome.txt @@ -0,0 +1,142 @@ +92 +-0.000000 -32.000000 0.898438 0.000000 +-0.000000 -30.000000 0.601562 0.000000 +-30.000000 -0.000000 0.601562 1.000000 +-32.000000 -0.000000 0.898438 1.000000 +-32.000000 -0.000000 0.898438 0.000000 +-30.000000 -0.000000 0.601562 0.000000 +-20.000000 10.000000 0.601562 1.000000 +-20.000000 12.000000 0.898438 1.000000 +-10.000000 22.000000 0.898438 1.000000 +-10.000000 2.000002 0.898438 0.000000 +-8.500000 0.500001 0.601562 0.000000 +-8.500000 21.500000 0.601562 1.000000 +-20.000000 10.000000 0.398438 1.000000 +-20.000000 7.000003 0.101562 1.000000 +-0.000001 -13.000000 0.101562 0.000000 +-0.000001 -10.000000 0.398438 0.000000 +0.000000 30.000000 0.398438 0.000000 +-8.500000 21.500000 0.398438 1.000000 +-6.499996 20.500000 0.101562 1.000000 +0.000002 27.000000 0.101562 0.000000 +-30.000000 -0.000000 0.398438 0.000000 +-0.000000 -30.000000 0.398438 1.000000 +-0.000002 -27.000000 0.101562 1.000000 +-27.000000 0.000005 0.101562 0.000000 +-6.500000 1.000001 0.101562 1.000000 +-6.499996 20.500000 0.101562 0.000000 +-8.500000 21.500000 0.398438 0.000000 +-8.500000 0.500001 0.398438 1.000000 +-27.000000 0.000005 0.101562 1.000000 +-20.000000 7.000003 0.101562 0.000000 +-20.000000 10.000000 0.398438 0.000000 +-30.000000 -0.000000 0.398438 1.000000 +-6.500000 1.000001 0.101562 0.000000 +-8.500000 0.500001 0.398438 0.000000 +-0.000001 -8.000000 0.398438 1.000000 +-0.000000 -5.500000 0.101562 1.000000 +-8.500000 0.500001 0.898438 0.500000 +-10.000000 2.000002 0.898438 0.500000 +-20.000000 12.000000 0.898438 0.000000 +-20.000000 10.000000 0.601562 0.000000 +-0.000001 -10.000000 0.601562 1.000000 +-0.000001 -8.000000 0.898438 1.000000 +-8.500000 21.500000 0.601562 0.000000 +0.000000 30.000000 0.601562 1.000000 +0.000000 32.000000 0.898438 1.000000 +-10.000000 22.000000 0.898438 0.000000 +-0.000000 -32.000000 0.898438 1.000000 +32.000000 0.000000 0.898438 0.000000 +30.000000 0.000000 0.601562 0.000000 +-0.000000 -30.000000 0.601562 1.000000 +32.000000 0.000000 0.898438 1.000000 +20.000000 12.000000 0.898438 0.000000 +20.000000 10.000000 0.601562 0.000000 +30.000000 0.000000 0.601562 1.000000 +10.000000 22.000000 0.898438 0.000000 +8.500000 21.500000 0.601562 0.000000 +8.500000 0.500000 0.601562 1.000000 +10.000000 2.000000 0.898438 1.000000 +20.000000 10.000000 0.398438 0.000000 +-0.000001 -10.000000 0.398438 1.000000 +-0.000001 -13.000000 0.101562 1.000000 +20.000000 7.000000 0.101562 0.000000 +0.000000 30.000000 0.398438 1.000000 +0.000002 27.000000 0.101562 1.000000 +6.500000 20.500000 0.101562 0.000000 +8.500000 21.500000 0.398438 0.000000 +30.000000 0.000000 0.398438 1.000000 +27.000000 -0.000000 0.101562 1.000000 +-0.000002 -27.000000 0.101562 0.000000 +-0.000000 -30.000000 0.398438 0.000000 +6.500000 1.000000 0.101562 0.000000 +8.500000 0.500000 0.398438 0.000000 +8.500000 21.500000 0.398438 1.000000 +6.500000 20.500000 0.101562 1.000000 +27.000000 -0.000000 0.101562 0.000000 +30.000000 0.000000 0.398438 0.000000 +20.000000 10.000000 0.398438 1.000000 +20.000000 7.000000 0.101562 1.000000 +6.500000 1.000000 0.101562 1.000000 +-0.000000 -5.500000 0.101562 0.000000 +-0.000001 -8.000000 0.398438 0.000000 +8.500000 0.500000 0.398438 1.000000 +8.500000 21.500000 0.601562 1.000000 +10.000000 22.000000 0.898438 1.000000 +0.000000 32.000000 0.898438 0.000000 +0.000000 30.000000 0.601562 0.000000 +-0.000001 -10.000000 0.601562 0.000000 +20.000000 10.000000 0.601562 1.000000 +20.000000 12.000000 0.898438 1.000000 +10.000000 2.000000 0.898438 0.500000 +8.500000 0.500000 0.898438 0.375000 +-0.000001 -8.000000 0.898438 0.000000 +48 +0 1 2 +0 2 3 +4 5 6 +4 6 7 +8 9 10 +8 10 11 +12 13 14 +12 14 15 +16 17 18 +16 18 19 +20 21 22 +20 22 23 +24 25 26 +24 26 27 +28 29 30 +28 30 31 +32 33 34 +32 34 35 +36 37 38 +36 38 39 +36 39 40 +36 40 41 +42 43 44 +42 44 45 +46 47 48 +46 48 49 +50 51 52 +50 52 53 +54 55 56 +54 56 57 +58 59 60 +58 60 61 +62 63 64 +62 64 65 +66 67 68 +66 68 69 +70 71 72 +70 72 73 +74 75 76 +74 76 77 +78 79 80 +78 80 81 +82 83 84 +82 84 85 +86 87 88 +86 88 89 +86 89 90 +86 90 91 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/ModelRenderer.ts b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/ModelRenderer.ts new file mode 100644 index 00000000..2fca0853 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/ModelRenderer.ts @@ -0,0 +1,231 @@ +import { + BaseTexture, + Container, + Geometry, + Mesh, + MIPMAP_MODES, + Rectangle, + RenderTexture, + Shader, + Sprite, + Texture, +} from "pixi.js" + +import { App } from "../../../../../App" +import { Options } from "../../../../../util/Options" +import { NotedataEntry } from "../../../../sm/NoteTypes" + +import liftPartsUrl from "./lift/parts.png" +import liftQuantsUrl from "./lift/quants.png" +import mineFrameUrl from "./mine/frame.png" +import minePartsUrl from "./mine/parts.png" +import tapPartsUrl from "./tap/parts.png" + +import arrowGradientFrag from "./shader/arrow_gradient.frag?raw" +import liftGradientFrag from "./shader/lift_gradient.frag?raw" +import mineGradientFrag from "./shader/mine_gradient.frag?raw" +import noopFrag from "./shader/noop.frag?raw" +import noopVert from "./shader/noop.vert?raw" + +import arrowBodyGeomText from "./tap/body.txt?raw" +import arrowFrameGeomText from "./tap/frame.txt?raw" + +import { loadGeometry } from "../../../../../util/Util" +import liftBodyGeomText from "./lift/body.txt?raw" +import liftFrameGeomText from "./lift/frame.txt?raw" +import mineBodyGeomText from "./mine/body.txt?raw" + +const mine_frame_texture = Texture.from(mineFrameUrl) + +export class ModelRenderer { + static arrowPartsTex = BaseTexture.from(tapPartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static minePartsTex = BaseTexture.from(minePartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static liftPartsTex = BaseTexture.from(liftPartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static liftQuantsTex = BaseTexture.from(liftQuantsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + + static arrowBodyGeom: Geometry + static arrowFrameGeom: Geometry + static liftBodyGeom: Geometry + static liftFrameGeom: Geometry + static mineBodyGeom: Geometry + + static arrowFrameTex: RenderTexture + static arrowFrame: Mesh + static arrowTex: RenderTexture + static arrowContainer = new Container() + static liftTex: RenderTexture + static liftContainer = new Container() + static mineTex: RenderTexture + static mineContainer = new Container() + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + ModelRenderer.arrowFrameTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + ModelRenderer.arrowTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.liftTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.mineTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + + this.arrowBodyGeom = await loadGeometry(arrowBodyGeomText) + this.arrowFrameGeom = await loadGeometry(arrowFrameGeomText) + this.mineBodyGeom = await loadGeometry(mineBodyGeomText) + this.liftBodyGeom = await loadGeometry(liftBodyGeomText) + this.liftFrameGeom = await loadGeometry(liftFrameGeomText) + + { + const shader_frame = Shader.from(noopVert, noopFrag, { + sampler0: this.arrowPartsTex, + }) + const arrow_frame = new Mesh(ModelRenderer.arrowFrameGeom, shader_frame) + arrow_frame.x = 32 + arrow_frame.y = 32 + arrow_frame.rotation = Math.PI + this.arrowFrame = arrow_frame + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.arrowPartsTex, + time: 0, + quant: Math.min(i, 8), + }) + const arrow_frame = new Sprite(ModelRenderer.arrowFrameTex) + arrow_frame.x = (i % 3) * 64 + arrow_frame.y = Math.floor(i / 3) * 64 + const arrow_body = new Mesh(ModelRenderer.arrowBodyGeom, shader_body) + arrow_body.x = (i % 3) * 64 + 32 + arrow_body.y = Math.floor(i / 3) * 64 + 32 + arrow_body.rotation = Math.PI + arrow_body.name = "body" + i + ModelRenderer.arrowContainer.addChild(arrow_frame) + ModelRenderer.arrowContainer.addChild(arrow_body) + } + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.liftQuantsTex, + time: 0, + quant: Math.min(i, 8), + }) + const shader_frame = Shader.from(noopVert, liftGradientFrag, { + sampler0: this.liftPartsTex, + }) + const lift_body = new Mesh(ModelRenderer.liftBodyGeom, shader_body) + lift_body.x = (i % 3) * 64 + 32 + lift_body.y = Math.floor(i / 3) * 64 + 32 + lift_body.rotation = Math.PI + lift_body.name = "body" + i + ModelRenderer.liftContainer.addChild(lift_body) + + const lift_frame = new Mesh(ModelRenderer.liftFrameGeom, shader_frame) + lift_frame.x = (i % 3) * 64 + 32 + lift_frame.y = Math.floor(i / 3) * 64 + 32 + lift_frame.rotation = Math.PI + lift_frame.name = "frame" + i + ModelRenderer.liftContainer.addChild(lift_frame) + } + } + { + const shader_body = Shader.from(noopVert, mineGradientFrag, { + sampler0: this.minePartsTex, + time: 0, + }) + const mine_body = new Mesh(ModelRenderer.mineBodyGeom, shader_body) + const mine_frame = new Sprite(mine_frame_texture) + mine_frame.width = 64 + mine_frame.height = 64 + mine_frame.anchor.set(0.5) + mine_frame.pivot.y = 3 // epic recenter + ModelRenderer.mineContainer.position.set(32) + ModelRenderer.mineContainer.addChild(mine_body) + ModelRenderer.mineContainer.addChild(mine_frame) + } + + this.loaded = true + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const beat = app.chartManager.chartView!.getVisualBeat() + const second = app.chartManager.chartView!.getVisualTime() + for (let i = 0; i < 10; i++) { + const tapShader: Mesh = + ModelRenderer.arrowContainer.getChildByName("body" + i)! + tapShader.shader.uniforms.time = beat / 8 + + const liftShader: Mesh = + ModelRenderer.liftContainer.getChildByName("body" + i)! + liftShader.shader.uniforms.time = beat / 8 + const liftFrameShader: Mesh = + ModelRenderer.liftContainer.getChildByName("frame" + i)! + liftFrameShader.shader.uniforms.time = beat / 8 + } + + ;(ModelRenderer.mineContainer.children[0]).shader.uniforms.time = + second + ModelRenderer.mineContainer.rotation = (second % 1) * Math.PI * 2 + + app.renderer.render(ModelRenderer.arrowFrame, { + renderTexture: ModelRenderer.arrowFrameTex, + }) + app.renderer.render(ModelRenderer.arrowContainer, { + renderTexture: ModelRenderer.arrowTex, + }) + app.renderer.render(ModelRenderer.mineContainer, { + renderTexture: ModelRenderer.mineTex, + }) + app.renderer.render(ModelRenderer.liftContainer, { + renderTexture: ModelRenderer.liftTex, + }) + } + + static setNoteTex(arrow: Sprite, note: NotedataEntry | undefined) { + if (note !== undefined && note.type == "Mine") { + arrow.texture = ModelRenderer.mineTex + } else if (note !== undefined && note.type == "Lift") { + const i = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + note?.quant ?? 4 + ) + arrow.texture = new Texture( + ModelRenderer.liftTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } else { + const i = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + note?.quant ?? 4 + ) + arrow.texture = new Texture( + ModelRenderer.arrowTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/NoteFlash.ts new file mode 100644 index 00000000..6299ec83 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/NoteFlash.ts @@ -0,0 +1,183 @@ +import { + AnimatedSprite, + BLEND_MODES, + Container, + Sprite, + Texture, +} from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import { + BezierAnimator, + BezierKeyFrames, +} from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" + +import brightUrl from "./flash/flashBright.png" +import dimUrl from "./flash/flashDim.png" +import holdFlashUrl from "./flash/hold.png" +import mineUrl from "./flash/mine.png" + +const brightTex = Texture.from(brightUrl) +const dimTex = Texture.from(dimUrl) +const holdTex = Texture.from(holdFlashUrl) +const mineTex = Texture.from(mineUrl) + +const bright: BezierKeyFrames = { + "0": { + tint: 0xffffff, + alpha: 1, + "scale.x": 0.8, + "scale.y": 0.8, + }, + "0.5": { + tint: 0xffffff, + alpha: 1, + "scale.x": 1, + "scale.y": 1, + }, + "1": { + tint: 0xffffff, + alpha: 0, + "scale.x": 1, + "scale.y": 1, + }, +} + +const dim = (tint: number) => { + return { + "0": { + tint, + alpha: 1, + "scale.x": 0.4, + "scale.y": 0.4, + }, + "0.5": { + tint, + alpha: 1, + "scale.x": 0.5, + "scale.y": 0.5, + }, + "1": { + tint, + alpha: 0, + "scale.x": 0.5, + "scale.y": 0.5, + }, + } +} + +const animations: Record = { + w0: bright, + w1: bright, + w2: dim(0xffff4c), + w3: dim(0x00ff66), + held: bright, +} + +export class NoteFlashContainer extends Container { + holdExplosion = new AnimatedSprite(splitTex(holdTex, 4, 1, 160, 160)[0]) + bright = new Sprite(brightTex) + dim = new Sprite(dimTex) + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + let target = this.dim + if (event.judgement.id == "w0") { + target = this.bright + } + if (animations[event.judgement.id] === undefined) return + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate(target, animations[event.judgement.id], 0.12) + ) + } + }) + + noteskin.on(this, "held", event => { + if (col == event.columnNumber) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate(this.bright, animations.held, 0.12) + ) + } + }) + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(mineTex) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + this.holdExplosion.visible = false + this.holdExplosion.anchor.set(0.5) + this.holdExplosion.scale.set(0.5) + this.holdExplosion.play() + this.addChild(this.holdExplosion) + + this.dim.scale.set(0.5) + this.dim.anchor.set(0.5) + this.dim.alpha = 0 + this.addChild(this.dim) + + this.bright.scale.set(0.5) + this.bright.anchor.set(0.5) + this.bright.alpha = 0 + this.addChild(this.bright) + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/Noteskin.ts new file mode 100644 index 00000000..9a34bd06 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/Noteskin.ts @@ -0,0 +1,163 @@ +// Peters-DDR-Note by Pete-Lawrence https://github.com/Pete-Lawrence/Peters-Noteskins/ +// Quantized Lifts by LemmaEOF https://github.com/lemmaeof/lemmas-ddrainbow +// Color modifications by tillvit + +import { AnimatedSprite, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" + +import { ModelRenderer } from "./ModelRenderer" +import { NoteFlashContainer } from "./NoteFlash" +import receptorUrl from "./receptor.png" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" + +const receptorTex = Texture.from(receptorUrl) + +const holdTex: Record>> = {} +const rollTex: Record> = {} + +for (const dir of ["Left", "Down", "Up", "Right", "UpLeft", "UpRight"]) { + for (const asset of ["Body", "BottomCap"]) { + for (const state of ["Active", "Inactive"]) { + if (holdTex[dir] === undefined) holdTex[dir] = {} + if (holdTex[dir][state] === undefined) holdTex[dir][state] = {} + holdTex[dir][state][asset] = Texture.from( + new URL( + `./hold/${dir.toLowerCase()}${asset}${state}.png`, + import.meta.url + ).href + ) + } + } +} + +for (const asset of ["body", "bottomCap"]) { + for (const state of ["Active", "Inactive"]) { + if (rollTex[state] === undefined) rollTex[state] = {} + rollTex[state][asset] = Texture.from( + new URL(`./roll/${asset}${state}.png`, import.meta.url).href + ) + } +} + +const rotationMap: Record = { + Left: 90, + Down: 0, + Up: 180, + Right: -90, + UpLeft: 135, + UpRight: -135, + DownRight: -45, + DownLeft: 45, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", + "NoteFlash", +] + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + const spr = new AnimatedSprite(splitTex(receptorTex, 2, 1, 256, 256)[0]) + spr.width = 64 + spr.height = 64 + spr.anchor.set(0.5) + options.noteskin.on(spr, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + spr, + { + "0": { + alpha: 1.2, + width: 48, + height: 48, + }, + "1": { + alpha: 1, + width: 64, + height: 64, + }, + }, + 0.06 + ) + } + }) + options.noteskin.onUpdate(spr, r => { + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + spr.currentFrame = partialBeat < 0.2 ? 0 : 1 + }) + return spr + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + ModelRenderer.setNoteTex(spr, options.note) + spr.anchor.set(0.5) + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer(options.noteskin, options.columnNumber) + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: { element: "Tap" }, + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "Hold Active Body": opt => + new HoldBody(holdTex[opt.columnName].Active.Body), + "Hold Inactive Body": opt => + new HoldBody(holdTex[opt.columnName].Inactive.Body), + "Hold Active TopCap": () => new Sprite(Texture.EMPTY), + "Hold Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Hold Active BottomCap": opt => + new HoldTail(holdTex[opt.columnName].Active.BottomCap), + "Hold Inactive BottomCap": opt => + new HoldTail(holdTex[opt.columnName].Inactive.BottomCap), + + "Roll Active Head": { element: "Tap" }, + "Roll Inactive Head": { element: "Tap" }, + "Roll Active Body": () => new HoldBody(rollTex.Active.body), + "Roll Inactive Body": () => new HoldBody(rollTex.Inactive.body), + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": () => new HoldTail(rollTex.Active.bottomCap), + "Roll Inactive BottomCap": () => new HoldTail(rollTex.Inactive.bottomCap), + }, + }, + load: function (element, options) { + const col = element.columnName + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + init() { + ModelRenderer.initArrowTex() + }, + update(renderer) { + ModelRenderer.setArrowTexTime(renderer.chartManager.app) + }, + hideIcons: ["Lift"], + metrics: { + HoldBodyBottomOffset: -32, + RollBodyBottomOffset: -32, + }, +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/flash/flashBright.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/flash/flashBright.png new file mode 100644 index 00000000..3f3a41cc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/flash/flashBright.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/flash/flashDim.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/flash/flashDim.png new file mode 100644 index 00000000..2bcf76a4 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/flash/flashDim.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/flash/hold.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/flash/hold.png new file mode 100644 index 00000000..7d925fcc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/flash/hold.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/downBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/downBodyActive.png new file mode 100644 index 00000000..c551f969 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/downBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/downBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/downBodyInactive.png new file mode 100644 index 00000000..4a06cdb3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/downBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/downBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/downBottomCapActive.png new file mode 100644 index 00000000..b270cedb Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/downBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/downBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/downBottomCapInactive.png new file mode 100644 index 00000000..3e8b53ee Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/downBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/leftBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/leftBodyActive.png new file mode 100644 index 00000000..c7c5b29e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/leftBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/leftBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/leftBodyInactive.png new file mode 100644 index 00000000..48f5af8b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/leftBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/leftBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/leftBottomCapActive.png new file mode 100644 index 00000000..996cc870 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/leftBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/leftBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/leftBottomCapInactive.png new file mode 100644 index 00000000..0070c317 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/leftBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/rightBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/rightBodyActive.png new file mode 100644 index 00000000..6c8e4201 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/rightBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/rightBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/rightBodyInactive.png new file mode 100644 index 00000000..71d0b446 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/rightBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/rightBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/rightBottomCapActive.png new file mode 100644 index 00000000..87b69bd1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/rightBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/rightBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/rightBottomCapInactive.png new file mode 100644 index 00000000..bca5fd0c Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/rightBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upBodyActive.png new file mode 100644 index 00000000..5f4be8de Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upBodyInactive.png new file mode 100644 index 00000000..3460297e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upBottomCapActive.png new file mode 100644 index 00000000..072d9b37 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upBottomCapInactive.png new file mode 100644 index 00000000..788e62cf Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upleftBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upleftBodyActive.png new file mode 100644 index 00000000..00363322 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upleftBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upleftBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upleftBodyInactive.png new file mode 100644 index 00000000..4190bb41 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upleftBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upleftBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upleftBottomCapActive.png new file mode 100644 index 00000000..84395f44 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upleftBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upleftBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upleftBottomCapInactive.png new file mode 100644 index 00000000..c96d2bf1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/upleftBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/uprightBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/uprightBodyActive.png new file mode 100644 index 00000000..983ab3d9 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/uprightBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/uprightBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/uprightBodyInactive.png new file mode 100644 index 00000000..495cdd47 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/uprightBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/uprightBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/uprightBottomCapActive.png new file mode 100644 index 00000000..ddb39594 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/uprightBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/uprightBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/uprightBottomCapInactive.png new file mode 100644 index 00000000..4c0f1ea1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/hold/uprightBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/lift/body.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/lift/body.txt new file mode 100644 index 00000000..20c86fe5 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/lift/body.txt @@ -0,0 +1,54 @@ +29 +1.500000 21.000000 0.440771 0.001033 +-1.500000 21.000000 0.440771 0.001033 +-4.000000 18.500000 0.440771 0.001033 +4.000000 18.500000 0.440771 0.001033 +-4.000000 9.000000 0.440771 0.001033 +4.000000 9.000000 0.440771 0.001033 +0.000000 5.000000 0.440771 0.001033 +1.500000 4.000000 0.448399 0.001033 +4.000000 -5.000000 0.448399 0.001033 +4.000000 6.500000 0.448399 0.001033 +0.000000 -9.000000 0.448399 0.001033 +-1.500000 4.000000 0.448399 0.001033 +-4.000000 6.500000 0.448399 0.001033 +-4.000000 -5.000000 0.448399 0.001033 +-1.500000 -10.000000 0.456533 0.001033 +0.000000 -21.500000 0.456533 0.001033 +1.500000 -10.000000 0.456533 0.001033 +8.500000 -6.000000 0.456533 0.001033 +-8.500000 -6.000000 0.456533 0.001033 +23.000000 4.000000 0.458630 0.001033 +22.000000 5.000000 0.458630 0.001033 +19.500000 5.000000 0.458630 0.001033 +23.000000 1.500000 0.458630 0.001033 +-19.500000 5.000000 0.454991 0.001033 +-22.000000 5.000000 0.454991 0.001033 +-23.000000 4.000000 0.454991 0.001033 +-23.000000 1.500000 0.454991 0.001033 +5.500000 -6.000000 0.456533 0.001033 +-5.500000 -6.000000 0.456533 0.001033 +23 +0 1 2 +2 3 0 +4 5 3 +4 3 2 +4 6 5 +7 8 9 +10 8 7 +10 7 11 +10 11 12 +12 13 10 +14 15 16 +15 17 16 +15 14 18 +19 20 21 +21 22 19 +23 24 25 +23 25 26 +15 18 23 +23 26 15 +21 17 15 +15 22 21 +16 17 27 +14 28 18 diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/lift/frame.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/lift/frame.txt new file mode 100644 index 00000000..894064c6 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/lift/frame.txt @@ -0,0 +1,68 @@ +36 +1.500000 28.000000 0.065523 0.068416 +2.000000 30.000000 0.063171 0.069380 +-2.000000 30.000000 0.087725 0.071097 +-1.500000 28.000000 0.085475 0.069820 +8.000000 21.500000 0.032200 0.128497 +10.000000 22.000000 0.028876 0.147933 +10.000000 0.000000 0.011447 0.497174 +18.500000 10.000000 0.015941 0.343234 +18.000000 12.000000 0.016983 0.314690 +10.000000 4.000000 0.014775 0.378867 +23.500000 10.000000 0.014987 0.372102 +24.000000 12.000000 0.015605 0.353105 +-18.500000 10.000000 0.131996 0.348850 +-10.000000 4.000000 0.133143 0.384499 +-18.000000 12.000000 0.130975 0.320288 +-10.000000 0.000000 0.136447 0.502826 +-8.000000 21.500000 0.116488 0.133426 +-10.000000 22.000000 0.119596 0.153069 +28.000000 5.500000 0.013081 0.436815 +30.000000 6.000000 0.013111 0.435746 +30.000000 0.000000 0.011447 0.497174 +28.000000 0.500000 0.011596 0.491616 +0.000000 -27.500000 0.072472 0.932979 +0.000000 -30.000000 0.072472 0.932979 +30.000000 0.000000 0.011625 0.497174 +28.000000 0.500000 0.011477 0.491616 +-28.000000 0.500000 0.136299 0.497268 +-30.000000 0.000000 0.136447 0.502826 +-28.000000 5.500000 0.134819 0.442462 +-30.000000 6.000000 0.134789 0.441393 +-23.500000 10.000000 0.132934 0.377733 +-24.000000 12.000000 0.132325 0.358726 +8.000000 0.000000 0.011447 0.497174 +-8.000000 0.000000 0.136447 0.502826 +-10.000000 1.500000 0.135203 0.456484 +10.000000 1.500000 0.012695 0.450835 +30 +0 1 2 +3 0 2 +4 5 1 +1 0 4 +5 4 6 +7 8 9 +10 11 8 +8 7 10 +12 13 14 +15 16 17 +16 3 2 +16 2 17 +18 19 11 +11 10 18 +20 19 18 +18 21 20 +22 23 24 +24 25 22 +22 26 27 +27 23 22 +28 29 27 +27 26 28 +30 31 29 +29 28 30 +12 14 31 +31 30 12 +4 32 6 +15 33 16 +34 13 12 +7 9 35 diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/lift/parts.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/lift/parts.png new file mode 100644 index 00000000..eb65fcf3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/lift/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/lift/quants.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/lift/quants.png new file mode 100644 index 00000000..1eb8434d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/lift/quants.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/mine/body.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/mine/body.txt new file mode 100755 index 00000000..0bf678f4 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/mine/body.txt @@ -0,0 +1,103 @@ +45 +-16.0 0.0 0 0.75 +-11.31 -11.31 0 0.75 + 0.0 -16.0 0 0.75 + 11.31 -11.31 0 0.75 + 16.0 0.0 0 0.75 + 11.31 11.31 0 0.75 + -0.0 16.0 0 0.75 +-11.31 11.31 0 0.75 +29.422066 0.009519 -0.999500 0.25 +25.401829 14.852878 -0.931249 0.1240015 +14.843359 25.411350 -0.751997 0.0343755 +-0.000001 29.431583 -0.500000 0.00025 +-14.843361 25.411348 -0.248003 0.0343755 +-25.401833 14.852878 -0.068751 0.1240015 +-29.422066 0.009516 -0.000500 0.25 +-25.401827 -14.833844 -0.068751 0.3759985 +-14.843357 -25.392317 -0.248003 0.4656245 +0.000004 -29.412546 -0.500000 0.49975 +14.843363 -25.392307 -0.751997 0.4656245 +25.401833 -14.833840 -0.931249 0.3759985 +19.921448 0.009518 0.838208 0.25 +17.189407 10.096646 0.791826 0.164375 +10.087127 17.198929 0.671250 0.104087 +-0.000000 19.930965 0.500000 0.080896 +-10.087129 17.198925 0.328750 0.104087 +-17.189411 10.096646 0.208174 0.164375 +-19.921448 0.009518 0.161792 0.25 +-17.189405 -10.077612 0.208174 0.335625 +-10.087127 -17.179895 0.328750 0.395913 +0.000001 -19.911928 0.500000 0.419104 +10.087130 -17.179886 0.671250 0.395913 +17.189411 -10.077609 0.791826 0.335625 +10.420829 0.009516 -0.676915 0.25 +8.976986 5.340415 -0.652403 0.2047485 +5.330894 8.986506 -0.590503 0.1737985 +0.000001 10.430347 -0.500000 0.1615425 +-5.330897 8.986505 -0.409497 0.1737985 +-8.976989 5.340414 -0.347597 0.2047485 +-10.420829 0.009520 -0.323085 0.25 +-8.976985 -5.321381 -0.347597 0.2952515 +-5.330895 -8.967473 -0.409497 0.3262015 +-0.000003 -10.411310 -0.500000 0.3384575 +5.330898 -8.967464 -0.590503 0.3262015 +8.976988 -5.321376 -0.652403 0.2952515 +0 0 0.5 0.75 +56 +44 0 1 +44 1 2 +44 2 3 +44 3 4 +44 4 5 +44 5 6 +44 6 7 +44 7 0 +8 9 20 +9 21 20 +9 10 21 +10 22 21 +10 11 22 +11 23 22 +11 12 23 +12 24 23 +12 13 24 +13 25 24 +13 14 25 +14 26 25 +14 15 26 +15 27 26 +15 16 27 +16 28 27 +16 17 28 +17 29 28 +17 18 29 +18 30 29 +18 19 30 +19 31 30 +19 8 31 +8 20 31 +20 21 32 +21 33 32 +21 22 33 +22 34 33 +22 23 34 +23 35 34 +23 24 35 +24 36 35 +24 25 36 +25 37 36 +25 26 37 +26 38 37 +26 27 38 +27 39 38 +27 28 39 +28 40 39 +28 29 40 +29 41 40 +29 30 41 +30 42 41 +30 31 42 +31 43 42 +31 20 43 +20 32 43 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/mine/frame.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/mine/frame.png new file mode 100644 index 00000000..388e7814 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/mine/frame.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/mine/parts.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/mine/parts.png new file mode 100755 index 00000000..744ba8dc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/mine/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/preview.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/preview.png new file mode 100644 index 00000000..56592b1a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/receptor.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/receptor.png new file mode 100644 index 00000000..aed822d4 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/receptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/roll/bodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/roll/bodyActive.png new file mode 100644 index 00000000..606c01bc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/roll/bodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/roll/bodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/roll/bodyInactive.png new file mode 100644 index 00000000..af1bbcee Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/roll/bodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/roll/bottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/roll/bottomCapActive.png new file mode 100644 index 00000000..adcb70d0 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/roll/bottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/roll/bottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/roll/bottomCapInactive.png new file mode 100644 index 00000000..f4d93aae Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/roll/bottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/arrow_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/arrow_gradient.frag new file mode 100644 index 00000000..7d068f93 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/arrow_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform float time; +uniform float quant; + +void main() { + vec4 col = texture2D( + sampler0, + vec2(vUvs.x + 0.0625 * quant, mod(vUvs.y - time, 2.0) / 2.0) + ); + gl_FragColor = col; +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/lift_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/lift_gradient.frag new file mode 100644 index 00000000..5ee858ec --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/lift_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform float time; +uniform float quant; + +void main() { + vec4 col = texture2D( + sampler0, + vec2(mod(vUvs.x - time, 2.0) / 2.0, vUvs.y) + ); + gl_FragColor = col; +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/mine_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/mine_gradient.frag new file mode 100644 index 00000000..f1d32938 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/mine_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform sampler2D sampler1; +uniform float time; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + if (col.a < 1.0) { + discard; + } + gl_FragColor = texture2D(sampler1, vec2(mod(col.r - time, 1.0), 0.625)); +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/noop.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/noop.frag new file mode 100644 index 00000000..3a329fdc --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/noop.frag @@ -0,0 +1,12 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + + gl_FragColor = col; + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/noop.vert b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/noop.vert new file mode 100644 index 00000000..002c3782 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/shader/noop.vert @@ -0,0 +1,24 @@ +precision mediump float; + +attribute vec2 aVertexPosition; +attribute vec2 aUvs; + +uniform mat3 translationMatrix; +uniform mat3 projectionMatrix; +uniform float time; + +varying vec2 vUvs; + +float rand(vec2 co) { + return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); +} + +void main() { + vUvs = aUvs; + gl_Position = vec4( + (projectionMatrix * translationMatrix * vec3(aVertexPosition.xy, 1.0)).xy, + 0.0, + 1.0 + ); + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/tap/body.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/tap/body.txt new file mode 100755 index 00000000..8fe4fd77 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/tap/body.txt @@ -0,0 +1,152 @@ +78 +1.500000 21.000000 0.482234 0.059122 +1.500000 28.000000 0.482234 0.000000 +-1.500000 28.000000 0.481498 0.000000 +-1.500000 21.000000 0.481498 0.059122 +-8.000000 21.500000 0.479904 0.054899 +-4.000000 18.500000 0.480885 0.080236 +-1.500000 4.000000 0.481498 0.202703 +0.000000 5.000000 0.481866 0.194257 +-4.000000 9.000000 0.480885 0.160473 +-4.000000 6.500000 0.480885 0.181588 +1.500000 4.000000 0.482234 0.202703 +4.000000 6.500000 0.482847 0.181588 +4.000000 9.000000 0.482847 0.160473 +4.000000 18.500000 0.482847 0.080236 +8.000000 21.500000 0.483828 0.054899 +4.000000 -5.000000 0.482847 0.278716 +19.500000 5.000000 0.486648 0.194257 +18.500000 10.000000 0.486403 0.152027 +10.000000 0.000000 0.484319 0.236486 +8.500000 -6.000000 0.483951 0.287162 +5.500000 -6.000000 0.483215 0.287162 +0.000000 -9.000000 0.481866 0.312500 +1.500000 -10.000000 0.482234 0.320946 +-1.500000 -10.000000 0.481498 0.320946 +-4.000000 -5.000000 0.480885 0.278716 +-5.500000 -6.000000 0.480517 0.287162 +-10.000000 0.000000 0.479414 0.236486 +-8.500000 -6.000000 0.479782 0.287162 +-18.500000 10.000000 0.477329 0.152027 +-19.500000 5.000000 0.477084 0.194257 +-23.500000 10.000000 0.476103 0.152027 +-22.000000 5.000000 0.476471 0.194257 +-28.000000 5.500000 0.475000 0.190034 +-23.000000 4.000000 0.476226 0.202703 +-23.000000 1.500000 0.476226 0.223818 +-28.000000 0.500000 0.475000 0.232264 +0.000000 -27.500000 0.481866 0.468750 +0.000000 -21.500000 0.481866 0.418074 +28.000000 0.500000 0.488733 0.232264 +23.000000 1.500000 0.487507 0.223818 +28.000000 5.500000 0.488733 0.190034 +23.000000 4.000000 0.487507 0.202703 +23.500000 10.000000 0.487629 0.152027 +22.000000 5.000000 0.487261 0.194257 +1.500000 21.000000 0.440771 0.001033 +-1.500000 21.000000 0.440514 0.001033 +-4.000000 18.500000 0.440300 0.001033 +4.000000 18.500000 0.440985 0.001033 +-4.000000 9.000000 0.440300 0.001033 +4.000000 9.000000 0.440985 0.001033 +0.000000 5.000000 0.440771 0.001033 +1.500000 4.000000 0.448399 0.001033 +4.000000 -5.000000 0.448613 0.001033 +4.000000 6.500000 0.448613 0.001033 +0.000000 -9.000000 0.448399 0.001033 +-1.500000 4.000000 0.448142 0.001033 +0.000000 -9.000000 0.448142 0.001033 +-4.000000 6.500000 0.447928 0.001033 +-4.000000 -5.000000 0.447928 0.001033 +-1.500000 -10.000000 0.456533 0.001033 +0.000000 -21.500000 0.456661 0.001033 +1.500000 -10.000000 0.456789 0.001033 +8.500000 -6.000000 0.457389 0.001033 +-8.500000 -6.000000 0.455933 0.001033 +23.000000 4.000000 0.458630 0.001033 +22.000000 5.000000 0.458545 0.001033 +19.500000 5.000000 0.458331 0.001033 +23.000000 1.500000 0.458630 0.001033 +-19.500000 5.000000 0.454991 0.001033 +-22.000000 5.000000 0.454777 0.001033 +-23.000000 4.000000 0.454691 0.001033 +-23.000000 1.500000 0.454691 0.001033 +5.500000 -6.000000 0.457132 0.001033 +-5.500000 -6.000000 0.456190 0.001033 +8.000000 0.000000 0.483828 0.236486 +-8.000000 0.000000 0.479904 0.236486 +-10.000000 1.500000 0.479414 0.223818 +10.000000 1.500000 0.484319 0.223818 +72 +0 1 2 +2 3 0 +3 2 4 +3 4 5 +6 7 8 +8 9 6 +10 7 7 +6 10 7 +11 12 7 +10 11 7 +13 14 1 +13 1 0 +14 13 15 +16 17 18 +19 16 18 +20 19 18 +15 20 18 +21 22 20 +15 21 20 +23 22 21 +24 25 23 +24 23 21 +26 25 24 +26 27 25 +28 29 27 +27 26 28 +28 30 29 +30 31 29 +32 33 31 +31 30 32 +32 34 33 +32 35 34 +36 37 34 +34 35 36 +36 38 39 +36 39 37 +38 40 39 +40 41 39 +42 43 41 +42 41 40 +16 43 42 +16 42 17 +44 45 46 +46 47 44 +48 49 47 +48 47 46 +48 50 49 +51 52 53 +54 52 51 +54 51 55 +56 55 57 +57 58 56 +59 60 61 +60 62 61 +60 59 63 +64 65 66 +66 67 64 +68 69 70 +68 70 71 +60 63 68 +68 71 60 +66 62 60 +60 67 66 +61 62 72 +59 73 63 +15 18 74 +14 15 74 +26 24 75 +75 24 5 +5 4 75 +26 76 28 +17 77 18 diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/tap/frame.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/tap/frame.txt new file mode 100755 index 00000000..c9b900ca --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/tap/frame.txt @@ -0,0 +1,66 @@ +34 +1.500000 28.000000 0.128125 0.266667 +2.000000 30.000000 0.129167 0.250000 +-2.000000 30.000000 0.120833 0.250000 +-1.500000 28.000000 0.121875 0.266667 +8.000000 21.500000 0.141667 0.320833 +10.000000 22.000000 0.145833 0.316667 +10.000000 0.000000 0.145833 0.500000 +18.500000 10.000000 0.163542 0.416667 +18.000000 12.000000 0.162500 0.400000 +10.000000 4.000000 0.145833 0.466666 +23.500000 10.000000 0.173958 0.416667 +24.000000 12.000000 0.175000 0.400000 +-18.500000 10.000000 0.086458 0.416667 +-10.000000 4.000000 0.104167 0.466666 +-18.000000 12.000000 0.087500 0.400000 +-10.000000 0.000000 0.104167 0.500000 +-8.000000 21.500000 0.108333 0.320833 +-10.000000 22.000000 0.104167 0.316667 +28.000000 5.500000 0.183333 0.454166 +30.000000 6.000000 0.187500 0.450000 +30.000000 0.000000 0.187500 0.500000 +28.000000 0.500000 0.183333 0.495833 +0.000000 -27.500000 0.125000 0.729166 +0.000000 -30.000000 0.125000 0.750000 +-28.000000 0.500000 0.066667 0.495833 +-30.000000 0.000000 0.062500 0.500000 +-28.000000 5.500000 0.066667 0.454166 +-30.000000 6.000000 0.062500 0.450000 +-23.500000 10.000000 0.076042 0.416667 +-24.000000 12.000000 0.075000 0.400000 +8.000000 0.000000 0.141667 0.500000 +-8.000000 0.000000 0.108333 0.500000 +-10.000000 1.500000 0.104167 0.487500 +10.000000 1.500000 0.145833 0.487500 +30 +0 1 2 +3 0 2 +4 5 1 +1 0 4 +5 4 6 +7 8 9 +10 11 8 +8 7 10 +12 13 14 +15 16 17 +16 3 2 +16 2 17 +18 19 11 +11 10 18 +20 19 18 +18 21 20 +22 23 20 +20 21 22 +22 24 25 +25 23 22 +26 27 25 +25 24 26 +28 29 27 +27 26 28 +12 14 29 +29 28 12 +4 30 6 +15 31 16 +32 13 12 +7 9 33 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/tap/parts.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/tap/parts.png new file mode 100644 index 00000000..2f5c4b2d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note-itg/tap/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/ModelRenderer.ts b/app/src/chart/gameTypes/noteskin/dance/ddr-note/ModelRenderer.ts new file mode 100644 index 00000000..5bdc556f --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/ModelRenderer.ts @@ -0,0 +1,231 @@ +import { + BaseTexture, + Container, + Geometry, + Mesh, + MIPMAP_MODES, + Rectangle, + RenderTexture, + Shader, + Sprite, + Texture, +} from "pixi.js" + +import { App } from "../../../../../App" +import { Options } from "../../../../../util/Options" +import { NotedataEntry } from "../../../../sm/NoteTypes" + +import liftPartsUrl from "./lift/parts.png" +import liftQuantsUrl from "./lift/quants.png" +import mineFrameUrl from "./mine/frame.png" +import minePartsUrl from "./mine/parts.png" +import tapPartsUrl from "./tap/parts.png" + +import arrowGradientFrag from "./shader/arrow_gradient.frag?raw" +import liftGradientFrag from "./shader/lift_gradient.frag?raw" +import mineGradientFrag from "./shader/mine_gradient.frag?raw" +import noopFrag from "./shader/noop.frag?raw" +import noopVert from "./shader/noop.vert?raw" + +import arrowBodyGeomText from "./tap/body.txt?raw" +import arrowFrameGeomText from "./tap/frame.txt?raw" + +import { loadGeometry } from "../../../../../util/Util" +import liftBodyGeomText from "./lift/body.txt?raw" +import liftFrameGeomText from "./lift/frame.txt?raw" +import mineBodyGeomText from "./mine/body.txt?raw" + +const mine_frame_texture = Texture.from(mineFrameUrl) + +export class ModelRenderer { + static arrowPartsTex = BaseTexture.from(tapPartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static minePartsTex = BaseTexture.from(minePartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static liftPartsTex = BaseTexture.from(liftPartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static liftQuantsTex = BaseTexture.from(liftQuantsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + + static arrowBodyGeom: Geometry + static arrowFrameGeom: Geometry + static liftBodyGeom: Geometry + static liftFrameGeom: Geometry + static mineBodyGeom: Geometry + + static arrowFrameTex: RenderTexture + static arrowFrame: Mesh + static arrowTex: RenderTexture + static arrowContainer = new Container() + static liftTex: RenderTexture + static liftContainer = new Container() + static mineTex: RenderTexture + static mineContainer = new Container() + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + ModelRenderer.arrowFrameTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + ModelRenderer.arrowTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.liftTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.mineTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + + this.arrowBodyGeom = await loadGeometry(arrowBodyGeomText) + this.arrowFrameGeom = await loadGeometry(arrowFrameGeomText) + this.mineBodyGeom = await loadGeometry(mineBodyGeomText) + this.liftBodyGeom = await loadGeometry(liftBodyGeomText) + this.liftFrameGeom = await loadGeometry(liftFrameGeomText) + + { + const shader_frame = Shader.from(noopVert, noopFrag, { + sampler0: this.arrowPartsTex, + }) + const arrow_frame = new Mesh(ModelRenderer.arrowFrameGeom, shader_frame) + arrow_frame.x = 32 + arrow_frame.y = 32 + arrow_frame.rotation = Math.PI + this.arrowFrame = arrow_frame + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.arrowPartsTex, + time: 0, + quant: Math.min(i, 6), + }) + const arrow_frame = new Sprite(ModelRenderer.arrowFrameTex) + arrow_frame.x = (i % 3) * 64 + arrow_frame.y = Math.floor(i / 3) * 64 + const arrow_body = new Mesh(ModelRenderer.arrowBodyGeom, shader_body) + arrow_body.x = (i % 3) * 64 + 32 + arrow_body.y = Math.floor(i / 3) * 64 + 32 + arrow_body.rotation = Math.PI + arrow_body.name = "body" + i + ModelRenderer.arrowContainer.addChild(arrow_frame) + ModelRenderer.arrowContainer.addChild(arrow_body) + } + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.liftQuantsTex, + time: 0, + quant: Math.min(i, 6), + }) + const shader_frame = Shader.from(noopVert, liftGradientFrag, { + sampler0: this.liftPartsTex, + }) + const lift_body = new Mesh(ModelRenderer.liftBodyGeom, shader_body) + lift_body.x = (i % 3) * 64 + 32 + lift_body.y = Math.floor(i / 3) * 64 + 32 + lift_body.rotation = Math.PI + lift_body.name = "body" + i + ModelRenderer.liftContainer.addChild(lift_body) + + const lift_frame = new Mesh(ModelRenderer.liftFrameGeom, shader_frame) + lift_frame.x = (i % 3) * 64 + 32 + lift_frame.y = Math.floor(i / 3) * 64 + 32 + lift_frame.rotation = Math.PI + lift_frame.name = "frame" + i + ModelRenderer.liftContainer.addChild(lift_frame) + } + } + { + const shader_body = Shader.from(noopVert, mineGradientFrag, { + sampler0: this.minePartsTex, + time: 0, + }) + const mine_body = new Mesh(ModelRenderer.mineBodyGeom, shader_body) + const mine_frame = new Sprite(mine_frame_texture) + mine_frame.width = 64 + mine_frame.height = 64 + mine_frame.anchor.set(0.5) + mine_frame.pivot.y = 3 // epic recenter + ModelRenderer.mineContainer.position.set(32) + ModelRenderer.mineContainer.addChild(mine_body) + ModelRenderer.mineContainer.addChild(mine_frame) + } + + this.loaded = true + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const beat = app.chartManager.chartView!.getVisualBeat() + const second = app.chartManager.chartView!.getVisualTime() + for (let i = 0; i < 10; i++) { + const tapShader: Mesh = + ModelRenderer.arrowContainer.getChildByName("body" + i)! + tapShader.shader.uniforms.time = beat / 8 + + const liftShader: Mesh = + ModelRenderer.liftContainer.getChildByName("body" + i)! + liftShader.shader.uniforms.time = beat / 8 + const liftFrameShader: Mesh = + ModelRenderer.liftContainer.getChildByName("frame" + i)! + liftFrameShader.shader.uniforms.time = beat / 8 + } + + ;(ModelRenderer.mineContainer.children[0]).shader.uniforms.time = + second + ModelRenderer.mineContainer.rotation = (second % 1) * Math.PI * 2 + + app.renderer.render(ModelRenderer.arrowFrame, { + renderTexture: ModelRenderer.arrowFrameTex, + }) + app.renderer.render(ModelRenderer.arrowContainer, { + renderTexture: ModelRenderer.arrowTex, + }) + app.renderer.render(ModelRenderer.mineContainer, { + renderTexture: ModelRenderer.mineTex, + }) + app.renderer.render(ModelRenderer.liftContainer, { + renderTexture: ModelRenderer.liftTex, + }) + } + + static setNoteTex(arrow: Sprite, note: NotedataEntry | undefined) { + if (note !== undefined && note.type == "Mine") { + arrow.texture = ModelRenderer.mineTex + } else if (note !== undefined && note.type == "Lift") { + const i = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + note?.quant ?? 4 + ) + arrow.texture = new Texture( + ModelRenderer.liftTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } else { + const i = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + note?.quant ?? 4 + ) + arrow.texture = new Texture( + ModelRenderer.arrowTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/ddr-note/NoteFlash.ts new file mode 100644 index 00000000..6299ec83 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/NoteFlash.ts @@ -0,0 +1,183 @@ +import { + AnimatedSprite, + BLEND_MODES, + Container, + Sprite, + Texture, +} from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import { + BezierAnimator, + BezierKeyFrames, +} from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" + +import brightUrl from "./flash/flashBright.png" +import dimUrl from "./flash/flashDim.png" +import holdFlashUrl from "./flash/hold.png" +import mineUrl from "./flash/mine.png" + +const brightTex = Texture.from(brightUrl) +const dimTex = Texture.from(dimUrl) +const holdTex = Texture.from(holdFlashUrl) +const mineTex = Texture.from(mineUrl) + +const bright: BezierKeyFrames = { + "0": { + tint: 0xffffff, + alpha: 1, + "scale.x": 0.8, + "scale.y": 0.8, + }, + "0.5": { + tint: 0xffffff, + alpha: 1, + "scale.x": 1, + "scale.y": 1, + }, + "1": { + tint: 0xffffff, + alpha: 0, + "scale.x": 1, + "scale.y": 1, + }, +} + +const dim = (tint: number) => { + return { + "0": { + tint, + alpha: 1, + "scale.x": 0.4, + "scale.y": 0.4, + }, + "0.5": { + tint, + alpha: 1, + "scale.x": 0.5, + "scale.y": 0.5, + }, + "1": { + tint, + alpha: 0, + "scale.x": 0.5, + "scale.y": 0.5, + }, + } +} + +const animations: Record = { + w0: bright, + w1: bright, + w2: dim(0xffff4c), + w3: dim(0x00ff66), + held: bright, +} + +export class NoteFlashContainer extends Container { + holdExplosion = new AnimatedSprite(splitTex(holdTex, 4, 1, 160, 160)[0]) + bright = new Sprite(brightTex) + dim = new Sprite(dimTex) + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + let target = this.dim + if (event.judgement.id == "w0") { + target = this.bright + } + if (animations[event.judgement.id] === undefined) return + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate(target, animations[event.judgement.id], 0.12) + ) + } + }) + + noteskin.on(this, "held", event => { + if (col == event.columnNumber) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate(this.bright, animations.held, 0.12) + ) + } + }) + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(mineTex) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + this.holdExplosion.visible = false + this.holdExplosion.anchor.set(0.5) + this.holdExplosion.scale.set(0.5) + this.holdExplosion.play() + this.addChild(this.holdExplosion) + + this.dim.scale.set(0.5) + this.dim.anchor.set(0.5) + this.dim.alpha = 0 + this.addChild(this.dim) + + this.bright.scale.set(0.5) + this.bright.anchor.set(0.5) + this.bright.alpha = 0 + this.addChild(this.bright) + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/ddr-note/Noteskin.ts new file mode 100644 index 00000000..688d5475 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/Noteskin.ts @@ -0,0 +1,164 @@ +// Peters-DDR-Note by Pete-Lawrence https://github.com/Pete-Lawrence/Peters-Noteskins/ +// Quantized Lifts by LemmaEOF https://github.com/lemmaeof/lemmas-ddrainbow + +import { AnimatedSprite, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" + +import { ModelRenderer } from "./ModelRenderer" +import { NoteFlashContainer } from "./NoteFlash" +import receptorUrl from "./receptor.png" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" + +const receptorTex = Texture.from(receptorUrl) + +const holdTex: Record>> = {} +const rollTex: Record> = {} + +for (const dir of ["Left", "Down", "Up", "Right", "UpLeft", "UpRight"]) { + for (const asset of ["Body", "BottomCap"]) { + for (const state of ["Active", "Inactive"]) { + if (holdTex[dir] === undefined) holdTex[dir] = {} + if (holdTex[dir][state] === undefined) holdTex[dir][state] = {} + holdTex[dir][state][asset] = Texture.from( + new URL( + `./hold/${dir.toLowerCase()}${asset}${state}.png`, + import.meta.url + ).href + ) + } + } +} + +for (const asset of ["body", "bottomCap"]) { + for (const state of ["Active", "Inactive"]) { + if (rollTex[state] === undefined) rollTex[state] = {} + rollTex[state][asset] = Texture.from( + new URL(`./roll/${asset}${state}.png`, import.meta.url).href + ) + } +} + +const rotationMap: Record = { + Left: 90, + Down: 0, + Up: 180, + Right: -90, + UpLeft: 135, + UpRight: -135, + DownRight: -45, + DownLeft: 45, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", + "NoteFlash", +] + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + const tex = splitTex(receptorTex, 2, 1, 256, 256) + + const spr = new AnimatedSprite(tex[0]) + spr.width = 64 + spr.height = 64 + spr.anchor.set(0.5) + options.noteskin.on(spr, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + spr, + { + "0": { + alpha: 1.2, + width: 48, + height: 48, + }, + "1": { + alpha: 1, + width: 64, + height: 64, + }, + }, + 0.06 + ) + } + }) + options.noteskin.onUpdate(spr, r => { + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + spr.currentFrame = partialBeat < 0.2 ? 0 : 1 + }) + return spr + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + ModelRenderer.setNoteTex(spr, options.note) + spr.anchor.set(0.5) + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer(options.noteskin, options.columnNumber) + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: { element: "Tap" }, + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "Hold Active Body": opt => + new HoldBody(holdTex[opt.columnName].Active.Body), + "Hold Inactive Body": opt => + new HoldBody(holdTex[opt.columnName].Inactive.Body), + "Hold Active TopCap": () => new Sprite(Texture.EMPTY), + "Hold Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Hold Active BottomCap": opt => + new HoldTail(holdTex[opt.columnName].Active.BottomCap), + "Hold Inactive BottomCap": opt => + new HoldTail(holdTex[opt.columnName].Inactive.BottomCap), + + "Roll Active Head": { element: "Tap" }, + "Roll Inactive Head": { element: "Tap" }, + "Roll Active Body": () => new HoldBody(rollTex.Active.body), + "Roll Inactive Body": () => new HoldBody(rollTex.Inactive.body), + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": () => new HoldTail(rollTex.Active.bottomCap), + "Roll Inactive BottomCap": () => new HoldTail(rollTex.Inactive.bottomCap), + }, + }, + load: function (element, options) { + const col = element.columnName + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + init() { + ModelRenderer.initArrowTex() + }, + update(renderer) { + ModelRenderer.setArrowTexTime(renderer.chartManager.app) + }, + metrics: { + HoldBodyBottomOffset: -32, + RollBodyBottomOffset: -32, + }, + hideIcons: ["Lift"], +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/flash/flashBright.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/flash/flashBright.png new file mode 100644 index 00000000..3f3a41cc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/flash/flashBright.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/flash/flashDim.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/flash/flashDim.png new file mode 100644 index 00000000..2bcf76a4 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/flash/flashDim.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/flash/hold.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/flash/hold.png new file mode 100644 index 00000000..7d925fcc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/flash/hold.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/downBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/downBodyActive.png new file mode 100644 index 00000000..c551f969 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/downBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/downBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/downBodyInactive.png new file mode 100644 index 00000000..4a06cdb3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/downBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/downBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/downBottomCapActive.png new file mode 100644 index 00000000..b270cedb Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/downBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/downBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/downBottomCapInactive.png new file mode 100644 index 00000000..3e8b53ee Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/downBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/leftBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/leftBodyActive.png new file mode 100644 index 00000000..c7c5b29e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/leftBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/leftBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/leftBodyInactive.png new file mode 100644 index 00000000..48f5af8b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/leftBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/leftBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/leftBottomCapActive.png new file mode 100644 index 00000000..996cc870 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/leftBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/leftBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/leftBottomCapInactive.png new file mode 100644 index 00000000..0070c317 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/leftBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/rightBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/rightBodyActive.png new file mode 100644 index 00000000..6c8e4201 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/rightBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/rightBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/rightBodyInactive.png new file mode 100644 index 00000000..71d0b446 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/rightBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/rightBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/rightBottomCapActive.png new file mode 100644 index 00000000..87b69bd1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/rightBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/rightBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/rightBottomCapInactive.png new file mode 100644 index 00000000..bca5fd0c Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/rightBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upBodyActive.png new file mode 100644 index 00000000..5f4be8de Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upBodyInactive.png new file mode 100644 index 00000000..3460297e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upBottomCapActive.png new file mode 100644 index 00000000..072d9b37 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upBottomCapInactive.png new file mode 100644 index 00000000..788e62cf Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upleftBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upleftBodyActive.png new file mode 100644 index 00000000..00363322 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upleftBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upleftBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upleftBodyInactive.png new file mode 100644 index 00000000..4190bb41 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upleftBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upleftBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upleftBottomCapActive.png new file mode 100644 index 00000000..84395f44 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upleftBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upleftBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upleftBottomCapInactive.png new file mode 100644 index 00000000..c96d2bf1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/upleftBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/uprightBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/uprightBodyActive.png new file mode 100644 index 00000000..983ab3d9 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/uprightBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/uprightBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/uprightBodyInactive.png new file mode 100644 index 00000000..495cdd47 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/uprightBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/uprightBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/uprightBottomCapActive.png new file mode 100644 index 00000000..ddb39594 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/uprightBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/uprightBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/uprightBottomCapInactive.png new file mode 100644 index 00000000..4c0f1ea1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/hold/uprightBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/lift/body.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-note/lift/body.txt new file mode 100644 index 00000000..932c3a41 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/lift/body.txt @@ -0,0 +1,54 @@ +29 +1.500000 21.000000 0.503271 0.001033 +-1.500000 21.000000 0.503271 0.001033 +-4.000000 18.500000 0.503271 0.001033 +4.000000 18.500000 0.503271 0.001033 +-4.000000 9.000000 0.503271 0.001033 +4.000000 9.000000 0.503271 0.001033 +0.000000 5.000000 0.503271 0.001033 +1.500000 4.000000 0.510899 0.001033 +4.000000 -5.000000 0.510899 0.001033 +4.000000 6.500000 0.510899 0.001033 +0.000000 -9.000000 0.510899 0.001033 +-1.500000 4.000000 0.510899 0.001033 +-4.000000 6.500000 0.510899 0.001033 +-4.000000 -5.000000 0.510899 0.001033 +-1.500000 -10.000000 0.519033 0.001033 +0.000000 -21.500000 0.519033 0.001033 +1.500000 -10.000000 0.519033 0.001033 +8.500000 -6.000000 0.519033 0.001033 +-8.500000 -6.000000 0.519033 0.001033 +23.000000 4.000000 0.521130 0.001033 +22.000000 5.000000 0.521130 0.001033 +19.500000 5.000000 0.521130 0.001033 +23.000000 1.500000 0.521130 0.001033 +-19.500000 5.000000 0.517491 0.001033 +-22.000000 5.000000 0.517491 0.001033 +-23.000000 4.000000 0.517491 0.001033 +-23.000000 1.500000 0.517491 0.001033 +5.500000 -6.000000 0.519033 0.001033 +-5.500000 -6.000000 0.519033 0.001033 +23 +0 1 2 +2 3 0 +4 5 3 +4 3 2 +4 6 5 +7 8 9 +10 8 7 +10 7 11 +10 11 12 +12 13 10 +14 15 16 +15 17 16 +15 14 18 +19 20 21 +21 22 19 +23 24 25 +23 25 26 +15 18 23 +23 26 15 +21 17 15 +15 22 21 +16 17 27 +14 28 18 diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/lift/frame.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-note/lift/frame.txt new file mode 100644 index 00000000..894064c6 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/lift/frame.txt @@ -0,0 +1,68 @@ +36 +1.500000 28.000000 0.065523 0.068416 +2.000000 30.000000 0.063171 0.069380 +-2.000000 30.000000 0.087725 0.071097 +-1.500000 28.000000 0.085475 0.069820 +8.000000 21.500000 0.032200 0.128497 +10.000000 22.000000 0.028876 0.147933 +10.000000 0.000000 0.011447 0.497174 +18.500000 10.000000 0.015941 0.343234 +18.000000 12.000000 0.016983 0.314690 +10.000000 4.000000 0.014775 0.378867 +23.500000 10.000000 0.014987 0.372102 +24.000000 12.000000 0.015605 0.353105 +-18.500000 10.000000 0.131996 0.348850 +-10.000000 4.000000 0.133143 0.384499 +-18.000000 12.000000 0.130975 0.320288 +-10.000000 0.000000 0.136447 0.502826 +-8.000000 21.500000 0.116488 0.133426 +-10.000000 22.000000 0.119596 0.153069 +28.000000 5.500000 0.013081 0.436815 +30.000000 6.000000 0.013111 0.435746 +30.000000 0.000000 0.011447 0.497174 +28.000000 0.500000 0.011596 0.491616 +0.000000 -27.500000 0.072472 0.932979 +0.000000 -30.000000 0.072472 0.932979 +30.000000 0.000000 0.011625 0.497174 +28.000000 0.500000 0.011477 0.491616 +-28.000000 0.500000 0.136299 0.497268 +-30.000000 0.000000 0.136447 0.502826 +-28.000000 5.500000 0.134819 0.442462 +-30.000000 6.000000 0.134789 0.441393 +-23.500000 10.000000 0.132934 0.377733 +-24.000000 12.000000 0.132325 0.358726 +8.000000 0.000000 0.011447 0.497174 +-8.000000 0.000000 0.136447 0.502826 +-10.000000 1.500000 0.135203 0.456484 +10.000000 1.500000 0.012695 0.450835 +30 +0 1 2 +3 0 2 +4 5 1 +1 0 4 +5 4 6 +7 8 9 +10 11 8 +8 7 10 +12 13 14 +15 16 17 +16 3 2 +16 2 17 +18 19 11 +11 10 18 +20 19 18 +18 21 20 +22 23 24 +24 25 22 +22 26 27 +27 23 22 +28 29 27 +27 26 28 +30 31 29 +29 28 30 +12 14 31 +31 30 12 +4 32 6 +15 33 16 +34 13 12 +7 9 35 diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/lift/parts.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/lift/parts.png new file mode 100644 index 00000000..eb65fcf3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/lift/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/lift/quants.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/lift/quants.png new file mode 100644 index 00000000..0dbad75a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/lift/quants.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/mine/body.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-note/mine/body.txt new file mode 100755 index 00000000..0bf678f4 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/mine/body.txt @@ -0,0 +1,103 @@ +45 +-16.0 0.0 0 0.75 +-11.31 -11.31 0 0.75 + 0.0 -16.0 0 0.75 + 11.31 -11.31 0 0.75 + 16.0 0.0 0 0.75 + 11.31 11.31 0 0.75 + -0.0 16.0 0 0.75 +-11.31 11.31 0 0.75 +29.422066 0.009519 -0.999500 0.25 +25.401829 14.852878 -0.931249 0.1240015 +14.843359 25.411350 -0.751997 0.0343755 +-0.000001 29.431583 -0.500000 0.00025 +-14.843361 25.411348 -0.248003 0.0343755 +-25.401833 14.852878 -0.068751 0.1240015 +-29.422066 0.009516 -0.000500 0.25 +-25.401827 -14.833844 -0.068751 0.3759985 +-14.843357 -25.392317 -0.248003 0.4656245 +0.000004 -29.412546 -0.500000 0.49975 +14.843363 -25.392307 -0.751997 0.4656245 +25.401833 -14.833840 -0.931249 0.3759985 +19.921448 0.009518 0.838208 0.25 +17.189407 10.096646 0.791826 0.164375 +10.087127 17.198929 0.671250 0.104087 +-0.000000 19.930965 0.500000 0.080896 +-10.087129 17.198925 0.328750 0.104087 +-17.189411 10.096646 0.208174 0.164375 +-19.921448 0.009518 0.161792 0.25 +-17.189405 -10.077612 0.208174 0.335625 +-10.087127 -17.179895 0.328750 0.395913 +0.000001 -19.911928 0.500000 0.419104 +10.087130 -17.179886 0.671250 0.395913 +17.189411 -10.077609 0.791826 0.335625 +10.420829 0.009516 -0.676915 0.25 +8.976986 5.340415 -0.652403 0.2047485 +5.330894 8.986506 -0.590503 0.1737985 +0.000001 10.430347 -0.500000 0.1615425 +-5.330897 8.986505 -0.409497 0.1737985 +-8.976989 5.340414 -0.347597 0.2047485 +-10.420829 0.009520 -0.323085 0.25 +-8.976985 -5.321381 -0.347597 0.2952515 +-5.330895 -8.967473 -0.409497 0.3262015 +-0.000003 -10.411310 -0.500000 0.3384575 +5.330898 -8.967464 -0.590503 0.3262015 +8.976988 -5.321376 -0.652403 0.2952515 +0 0 0.5 0.75 +56 +44 0 1 +44 1 2 +44 2 3 +44 3 4 +44 4 5 +44 5 6 +44 6 7 +44 7 0 +8 9 20 +9 21 20 +9 10 21 +10 22 21 +10 11 22 +11 23 22 +11 12 23 +12 24 23 +12 13 24 +13 25 24 +13 14 25 +14 26 25 +14 15 26 +15 27 26 +15 16 27 +16 28 27 +16 17 28 +17 29 28 +17 18 29 +18 30 29 +18 19 30 +19 31 30 +19 8 31 +8 20 31 +20 21 32 +21 33 32 +21 22 33 +22 34 33 +22 23 34 +23 35 34 +23 24 35 +24 36 35 +24 25 36 +25 37 36 +25 26 37 +26 38 37 +26 27 38 +27 39 38 +27 28 39 +28 40 39 +28 29 40 +29 41 40 +29 30 41 +30 42 41 +30 31 42 +31 43 42 +31 20 43 +20 32 43 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/mine/frame.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/mine/frame.png new file mode 100644 index 00000000..388e7814 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/mine/frame.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/mine/parts.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/mine/parts.png new file mode 100755 index 00000000..744ba8dc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/mine/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/preview.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/preview.png new file mode 100644 index 00000000..8e4c4b1a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/receptor.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/receptor.png new file mode 100644 index 00000000..aed822d4 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/receptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/roll/bodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/roll/bodyActive.png new file mode 100644 index 00000000..606c01bc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/roll/bodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/roll/bodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/roll/bodyInactive.png new file mode 100644 index 00000000..af1bbcee Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/roll/bodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/roll/bottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/roll/bottomCapActive.png new file mode 100644 index 00000000..adcb70d0 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/roll/bottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/roll/bottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/roll/bottomCapInactive.png new file mode 100644 index 00000000..f4d93aae Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/roll/bottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/arrow_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/arrow_gradient.frag new file mode 100644 index 00000000..7d068f93 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/arrow_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform float time; +uniform float quant; + +void main() { + vec4 col = texture2D( + sampler0, + vec2(vUvs.x + 0.0625 * quant, mod(vUvs.y - time, 2.0) / 2.0) + ); + gl_FragColor = col; +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/lift_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/lift_gradient.frag new file mode 100644 index 00000000..5ee858ec --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/lift_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform float time; +uniform float quant; + +void main() { + vec4 col = texture2D( + sampler0, + vec2(mod(vUvs.x - time, 2.0) / 2.0, vUvs.y) + ); + gl_FragColor = col; +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/mine_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/mine_gradient.frag new file mode 100644 index 00000000..f1d32938 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/mine_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform sampler2D sampler1; +uniform float time; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + if (col.a < 1.0) { + discard; + } + gl_FragColor = texture2D(sampler1, vec2(mod(col.r - time, 1.0), 0.625)); +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/noop.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/noop.frag new file mode 100644 index 00000000..3a329fdc --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/noop.frag @@ -0,0 +1,12 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + + gl_FragColor = col; + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/noop.vert b/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/noop.vert new file mode 100644 index 00000000..002c3782 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/shader/noop.vert @@ -0,0 +1,24 @@ +precision mediump float; + +attribute vec2 aVertexPosition; +attribute vec2 aUvs; + +uniform mat3 translationMatrix; +uniform mat3 projectionMatrix; +uniform float time; + +varying vec2 vUvs; + +float rand(vec2 co) { + return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); +} + +void main() { + vUvs = aUvs; + gl_Position = vec4( + (projectionMatrix * translationMatrix * vec3(aVertexPosition.xy, 1.0)).xy, + 0.0, + 1.0 + ); + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/tap/body.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-note/tap/body.txt new file mode 100755 index 00000000..5bf079ad --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/tap/body.txt @@ -0,0 +1,152 @@ +78 +1.500000 21.000000 0.544734 0.059122 +1.500000 28.000000 0.544734 0.000000 +-1.500000 28.000000 0.543998 0.000000 +-1.500000 21.000000 0.543998 0.059122 +-8.000000 21.500000 0.542404 0.054899 +-4.000000 18.500000 0.543385 0.080236 +-1.500000 4.000000 0.543998 0.202703 +0.000000 5.000000 0.544366 0.194257 +-4.000000 9.000000 0.543385 0.160473 +-4.000000 6.500000 0.543385 0.181588 +1.500000 4.000000 0.544734 0.202703 +4.000000 6.500000 0.545347 0.181588 +4.000000 9.000000 0.545347 0.160473 +4.000000 18.500000 0.545347 0.080236 +8.000000 21.500000 0.546328 0.054899 +4.000000 -5.000000 0.545347 0.278716 +19.500000 5.000000 0.549148 0.194257 +18.500000 10.000000 0.548903 0.152027 +10.000000 0.000000 0.546819 0.236486 +8.500000 -6.000000 0.546451 0.287162 +5.500000 -6.000000 0.545715 0.287162 +0.000000 -9.000000 0.544366 0.312500 +1.500000 -10.000000 0.544734 0.320946 +-1.500000 -10.000000 0.543998 0.320946 +-4.000000 -5.000000 0.543385 0.278716 +-5.500000 -6.000000 0.543017 0.287162 +-10.000000 0.000000 0.541914 0.236486 +-8.500000 -6.000000 0.542282 0.287162 +-18.500000 10.000000 0.539829 0.152027 +-19.500000 5.000000 0.539584 0.194257 +-23.500000 10.000000 0.538603 0.152027 +-22.000000 5.000000 0.538971 0.194257 +-28.000000 5.500000 0.537500 0.190034 +-23.000000 4.000000 0.538726 0.202703 +-23.000000 1.500000 0.538726 0.223818 +-28.000000 0.500000 0.537500 0.232264 +0.000000 -27.500000 0.544366 0.468750 +0.000000 -21.500000 0.544366 0.418074 +28.000000 0.500000 0.551233 0.232264 +23.000000 1.500000 0.550007 0.223818 +28.000000 5.500000 0.551233 0.190034 +23.000000 4.000000 0.550007 0.202703 +23.500000 10.000000 0.550129 0.152027 +22.000000 5.000000 0.549761 0.194257 +1.500000 21.000000 0.503271 0.001033 +-1.500000 21.000000 0.503014 0.001033 +-4.000000 18.500000 0.502800 0.001033 +4.000000 18.500000 0.503485 0.001033 +-4.000000 9.000000 0.502800 0.001033 +4.000000 9.000000 0.503485 0.001033 +0.000000 5.000000 0.503271 0.001033 +1.500000 4.000000 0.510899 0.001033 +4.000000 -5.000000 0.511113 0.001033 +4.000000 6.500000 0.511113 0.001033 +0.000000 -9.000000 0.510899 0.001033 +-1.500000 4.000000 0.510642 0.001033 +0.000000 -9.000000 0.510642 0.001033 +-4.000000 6.500000 0.510428 0.001033 +-4.000000 -5.000000 0.510428 0.001033 +-1.500000 -10.000000 0.519033 0.001033 +0.000000 -21.500000 0.519161 0.001033 +1.500000 -10.000000 0.519289 0.001033 +8.500000 -6.000000 0.519889 0.001033 +-8.500000 -6.000000 0.518433 0.001033 +23.000000 4.000000 0.521130 0.001033 +22.000000 5.000000 0.521045 0.001033 +19.500000 5.000000 0.520831 0.001033 +23.000000 1.500000 0.521130 0.001033 +-19.500000 5.000000 0.517491 0.001033 +-22.000000 5.000000 0.517277 0.001033 +-23.000000 4.000000 0.517191 0.001033 +-23.000000 1.500000 0.517191 0.001033 +5.500000 -6.000000 0.519632 0.001033 +-5.500000 -6.000000 0.518690 0.001033 +8.000000 0.000000 0.546328 0.236486 +-8.000000 0.000000 0.542404 0.236486 +-10.000000 1.500000 0.541914 0.223818 +10.000000 1.500000 0.546819 0.223818 +72 +0 1 2 +2 3 0 +3 2 4 +3 4 5 +6 7 8 +8 9 6 +10 7 7 +6 10 7 +11 12 7 +10 11 7 +13 14 1 +13 1 0 +14 13 15 +16 17 18 +19 16 18 +20 19 18 +15 20 18 +21 22 20 +15 21 20 +23 22 21 +24 25 23 +24 23 21 +26 25 24 +26 27 25 +28 29 27 +27 26 28 +28 30 29 +30 31 29 +32 33 31 +31 30 32 +32 34 33 +32 35 34 +36 37 34 +34 35 36 +36 38 39 +36 39 37 +38 40 39 +40 41 39 +42 43 41 +42 41 40 +16 43 42 +16 42 17 +44 45 46 +46 47 44 +48 49 47 +48 47 46 +48 50 49 +51 52 53 +54 52 51 +54 51 55 +56 55 57 +57 58 56 +59 60 61 +60 62 61 +60 59 63 +64 65 66 +66 67 64 +68 69 70 +68 70 71 +60 63 68 +68 71 60 +66 62 60 +60 67 66 +61 62 72 +59 73 63 +15 18 74 +14 15 74 +26 24 75 +75 24 5 +5 4 75 +26 76 28 +17 77 18 diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/tap/frame.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-note/tap/frame.txt new file mode 100755 index 00000000..c9b900ca --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-note/tap/frame.txt @@ -0,0 +1,66 @@ +34 +1.500000 28.000000 0.128125 0.266667 +2.000000 30.000000 0.129167 0.250000 +-2.000000 30.000000 0.120833 0.250000 +-1.500000 28.000000 0.121875 0.266667 +8.000000 21.500000 0.141667 0.320833 +10.000000 22.000000 0.145833 0.316667 +10.000000 0.000000 0.145833 0.500000 +18.500000 10.000000 0.163542 0.416667 +18.000000 12.000000 0.162500 0.400000 +10.000000 4.000000 0.145833 0.466666 +23.500000 10.000000 0.173958 0.416667 +24.000000 12.000000 0.175000 0.400000 +-18.500000 10.000000 0.086458 0.416667 +-10.000000 4.000000 0.104167 0.466666 +-18.000000 12.000000 0.087500 0.400000 +-10.000000 0.000000 0.104167 0.500000 +-8.000000 21.500000 0.108333 0.320833 +-10.000000 22.000000 0.104167 0.316667 +28.000000 5.500000 0.183333 0.454166 +30.000000 6.000000 0.187500 0.450000 +30.000000 0.000000 0.187500 0.500000 +28.000000 0.500000 0.183333 0.495833 +0.000000 -27.500000 0.125000 0.729166 +0.000000 -30.000000 0.125000 0.750000 +-28.000000 0.500000 0.066667 0.495833 +-30.000000 0.000000 0.062500 0.500000 +-28.000000 5.500000 0.066667 0.454166 +-30.000000 6.000000 0.062500 0.450000 +-23.500000 10.000000 0.076042 0.416667 +-24.000000 12.000000 0.075000 0.400000 +8.000000 0.000000 0.141667 0.500000 +-8.000000 0.000000 0.108333 0.500000 +-10.000000 1.500000 0.104167 0.487500 +10.000000 1.500000 0.145833 0.487500 +30 +0 1 2 +3 0 2 +4 5 1 +1 0 4 +5 4 6 +7 8 9 +10 11 8 +8 7 10 +12 13 14 +15 16 17 +16 3 2 +16 2 17 +18 19 11 +11 10 18 +20 19 18 +18 21 20 +22 23 20 +20 21 22 +22 24 25 +25 23 22 +26 27 25 +25 24 26 +28 29 27 +27 26 28 +12 14 29 +29 28 12 +4 30 6 +15 31 16 +32 13 12 +7 9 33 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-note/tap/parts.png b/app/src/chart/gameTypes/noteskin/dance/ddr-note/tap/parts.png new file mode 100644 index 00000000..0dbad75a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-note/tap/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/ModelRenderer.ts b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/ModelRenderer.ts new file mode 100644 index 00000000..394a41ae --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/ModelRenderer.ts @@ -0,0 +1,231 @@ +import { + BaseTexture, + Container, + Geometry, + Mesh, + MIPMAP_MODES, + Rectangle, + RenderTexture, + Shader, + Sprite, + Texture, +} from "pixi.js" + +import { App } from "../../../../../App" +import { Options } from "../../../../../util/Options" +import { NotedataEntry } from "../../../../sm/NoteTypes" + +import liftPartsUrl from "./lift/parts.png" +import liftQuantsUrl from "./lift/quants.png" +import mineFrameUrl from "./mine/frame.png" +import minePartsUrl from "./mine/parts.png" +import tapPartsUrl from "./tap/parts.png" + +import arrowGradientFrag from "./shader/arrow_gradient.frag?raw" +import liftGradientFrag from "./shader/lift_gradient.frag?raw" +import mineGradientFrag from "./shader/mine_gradient.frag?raw" +import noopFrag from "./shader/noop.frag?raw" +import noopVert from "./shader/noop.vert?raw" + +import arrowBodyGeomText from "./tap/body.txt?raw" +import arrowFrameGeomText from "./tap/frame.txt?raw" + +import { loadGeometry } from "../../../../../util/Util" +import liftBodyGeomText from "./lift/body.txt?raw" +import liftFrameGeomText from "./lift/frame.txt?raw" +import mineBodyGeomText from "./mine/body.txt?raw" + +const mine_frame_texture = Texture.from(mineFrameUrl) + +export class ModelRenderer { + static arrowPartsTex = BaseTexture.from(tapPartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static minePartsTex = BaseTexture.from(minePartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static liftPartsTex = BaseTexture.from(liftPartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static liftQuantsTex = BaseTexture.from(liftQuantsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + + static arrowBodyGeom: Geometry + static arrowFrameGeom: Geometry + static liftBodyGeom: Geometry + static liftFrameGeom: Geometry + static mineBodyGeom: Geometry + + static arrowFrameTex: RenderTexture + static arrowFrame: Mesh + static arrowTex: RenderTexture + static arrowContainer = new Container() + static liftTex: RenderTexture + static liftContainer = new Container() + static mineTex: RenderTexture + static mineContainer = new Container() + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + ModelRenderer.arrowFrameTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + ModelRenderer.arrowTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.liftTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.mineTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + + this.arrowBodyGeom = await loadGeometry(arrowBodyGeomText) + this.arrowFrameGeom = await loadGeometry(arrowFrameGeomText) + this.mineBodyGeom = await loadGeometry(mineBodyGeomText) + this.liftBodyGeom = await loadGeometry(liftBodyGeomText) + this.liftFrameGeom = await loadGeometry(liftFrameGeomText) + + { + const shader_frame = Shader.from(noopVert, noopFrag, { + sampler0: this.arrowPartsTex, + }) + const arrow_frame = new Mesh(ModelRenderer.arrowFrameGeom, shader_frame) + arrow_frame.x = 32 + arrow_frame.y = 32 + arrow_frame.rotation = Math.PI + this.arrowFrame = arrow_frame + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.arrowPartsTex, + time: 0, + quant: Math.min(i, 8), + }) + const arrow_frame = new Sprite(ModelRenderer.arrowFrameTex) + arrow_frame.x = (i % 3) * 64 + arrow_frame.y = Math.floor(i / 3) * 64 + const arrow_body = new Mesh(ModelRenderer.arrowBodyGeom, shader_body) + arrow_body.x = (i % 3) * 64 + 32 + arrow_body.y = Math.floor(i / 3) * 64 + 32 + arrow_body.rotation = Math.PI + arrow_body.name = "body" + i + ModelRenderer.arrowContainer.addChild(arrow_frame) + ModelRenderer.arrowContainer.addChild(arrow_body) + } + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.liftQuantsTex, + time: 0, + quant: Math.min(i, 8), + }) + const shader_frame = Shader.from(noopVert, liftGradientFrag, { + sampler0: this.liftPartsTex, + }) + const lift_body = new Mesh(ModelRenderer.liftBodyGeom, shader_body) + lift_body.x = (i % 3) * 64 + 32 + lift_body.y = Math.floor(i / 3) * 64 + 32 + lift_body.rotation = Math.PI + lift_body.name = "body" + i + ModelRenderer.liftContainer.addChild(lift_body) + + const lift_frame = new Mesh(ModelRenderer.liftFrameGeom, shader_frame) + lift_frame.x = (i % 3) * 64 + 32 + lift_frame.y = Math.floor(i / 3) * 64 + 32 + lift_frame.rotation = Math.PI + lift_frame.name = "frame" + i + ModelRenderer.liftContainer.addChild(lift_frame) + } + } + { + const shader_body = Shader.from(noopVert, mineGradientFrag, { + sampler0: this.minePartsTex, + time: 0, + }) + const mine_body = new Mesh(ModelRenderer.mineBodyGeom, shader_body) + const mine_frame = new Sprite(mine_frame_texture) + mine_frame.width = 64 + mine_frame.height = 64 + mine_frame.anchor.set(0.5) + mine_frame.pivot.y = 3 // epic recenter + ModelRenderer.mineContainer.position.set(32) + ModelRenderer.mineContainer.addChild(mine_body) + ModelRenderer.mineContainer.addChild(mine_frame) + } + + this.loaded = true + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const beat = app.chartManager.chartView!.getVisualBeat() + const second = app.chartManager.chartView!.getVisualTime() + for (let i = 0; i < 10; i++) { + const tapShader: Mesh = + ModelRenderer.arrowContainer.getChildByName("body" + i)! + tapShader.shader.uniforms.time = beat / 4 + + const liftShader: Mesh = + ModelRenderer.liftContainer.getChildByName("body" + i)! + liftShader.shader.uniforms.time = beat / 4 + const liftFrameShader: Mesh = + ModelRenderer.liftContainer.getChildByName("frame" + i)! + liftFrameShader.shader.uniforms.time = beat / 4 + } + + ;(ModelRenderer.mineContainer.children[0]).shader.uniforms.time = + second + ModelRenderer.mineContainer.rotation = (second % 1) * Math.PI * 2 + + app.renderer.render(ModelRenderer.arrowFrame, { + renderTexture: ModelRenderer.arrowFrameTex, + }) + app.renderer.render(ModelRenderer.arrowContainer, { + renderTexture: ModelRenderer.arrowTex, + }) + app.renderer.render(ModelRenderer.mineContainer, { + renderTexture: ModelRenderer.mineTex, + }) + app.renderer.render(ModelRenderer.liftContainer, { + renderTexture: ModelRenderer.liftTex, + }) + } + + static setNoteTex(arrow: Sprite, note: NotedataEntry | undefined) { + if (note !== undefined && note.type == "Mine") { + arrow.texture = ModelRenderer.mineTex + } else if (note !== undefined && note.type == "Lift") { + const i = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + note?.quant ?? 4 + ) + arrow.texture = new Texture( + ModelRenderer.liftTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } else { + const i = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + note?.quant ?? 4 + ) + arrow.texture = new Texture( + ModelRenderer.arrowTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/NoteFlash.ts new file mode 100644 index 00000000..6299ec83 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/NoteFlash.ts @@ -0,0 +1,183 @@ +import { + AnimatedSprite, + BLEND_MODES, + Container, + Sprite, + Texture, +} from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import { + BezierAnimator, + BezierKeyFrames, +} from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" + +import brightUrl from "./flash/flashBright.png" +import dimUrl from "./flash/flashDim.png" +import holdFlashUrl from "./flash/hold.png" +import mineUrl from "./flash/mine.png" + +const brightTex = Texture.from(brightUrl) +const dimTex = Texture.from(dimUrl) +const holdTex = Texture.from(holdFlashUrl) +const mineTex = Texture.from(mineUrl) + +const bright: BezierKeyFrames = { + "0": { + tint: 0xffffff, + alpha: 1, + "scale.x": 0.8, + "scale.y": 0.8, + }, + "0.5": { + tint: 0xffffff, + alpha: 1, + "scale.x": 1, + "scale.y": 1, + }, + "1": { + tint: 0xffffff, + alpha: 0, + "scale.x": 1, + "scale.y": 1, + }, +} + +const dim = (tint: number) => { + return { + "0": { + tint, + alpha: 1, + "scale.x": 0.4, + "scale.y": 0.4, + }, + "0.5": { + tint, + alpha: 1, + "scale.x": 0.5, + "scale.y": 0.5, + }, + "1": { + tint, + alpha: 0, + "scale.x": 0.5, + "scale.y": 0.5, + }, + } +} + +const animations: Record = { + w0: bright, + w1: bright, + w2: dim(0xffff4c), + w3: dim(0x00ff66), + held: bright, +} + +export class NoteFlashContainer extends Container { + holdExplosion = new AnimatedSprite(splitTex(holdTex, 4, 1, 160, 160)[0]) + bright = new Sprite(brightTex) + dim = new Sprite(dimTex) + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + let target = this.dim + if (event.judgement.id == "w0") { + target = this.bright + } + if (animations[event.judgement.id] === undefined) return + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate(target, animations[event.judgement.id], 0.12) + ) + } + }) + + noteskin.on(this, "held", event => { + if (col == event.columnNumber) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate(this.bright, animations.held, 0.12) + ) + } + }) + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(mineTex) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + this.holdExplosion.visible = false + this.holdExplosion.anchor.set(0.5) + this.holdExplosion.scale.set(0.5) + this.holdExplosion.play() + this.addChild(this.holdExplosion) + + this.dim.scale.set(0.5) + this.dim.anchor.set(0.5) + this.dim.alpha = 0 + this.addChild(this.dim) + + this.bright.scale.set(0.5) + this.bright.anchor.set(0.5) + this.bright.alpha = 0 + this.addChild(this.bright) + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/Noteskin.ts new file mode 100644 index 00000000..99219138 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/Noteskin.ts @@ -0,0 +1,168 @@ +// Lemmas-DDRainbow by LemmaEOF https://github.com/lemmaeof/lemmas-ddrainbow +// Color modifications by tillvit + +import { AnimatedSprite, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" + +import { ModelRenderer } from "./ModelRenderer" +import { NoteFlashContainer } from "./NoteFlash" +import receptorUrl from "./receptor.png" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" + +const receptorTex = Texture.from(receptorUrl) + +const holdTex: Record>> = {} +const rollTex: Record>> = {} + +for (const dir of ["Left", "Down", "Up", "Right"]) { + for (const asset of ["Body", "BottomCap"]) { + for (const state of ["Active", "Inactive"]) { + if (holdTex[dir] === undefined) holdTex[dir] = {} + if (holdTex[dir][state] === undefined) holdTex[dir][state] = {} + holdTex[dir][state][asset] = Texture.from( + new URL( + `./hold/${dir.toLowerCase()}${asset}${state}.png`, + import.meta.url + ).href + ) + + if (rollTex[dir] === undefined) rollTex[dir] = {} + if (rollTex[dir][state] === undefined) rollTex[dir][state] = {} + rollTex[dir][state][asset] = Texture.from( + new URL( + `./roll/${dir.toLowerCase()}${asset}${state}.png`, + import.meta.url + ).href + ) + } + } +} + +const rotationMap: Record = { + Left: 90, + Down: 0, + Up: 180, + Right: -90, + UpLeft: 135, + UpRight: -135, + DownRight: -45, + DownLeft: 45, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", + "NoteFlash", +] + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + const tex = splitTex(receptorTex, 2, 1, 256, 256) + + const spr = new AnimatedSprite(tex[0]) + spr.width = 64 + spr.height = 64 + spr.anchor.set(0.5) + options.noteskin.on(spr, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + spr, + { + "0": { + alpha: 1.2, + width: 48, + height: 48, + }, + "1": { + alpha: 1, + width: 64, + height: 64, + }, + }, + 0.06 + ) + } + }) + options.noteskin.onUpdate(spr, r => { + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + spr.currentFrame = partialBeat < 0.2 ? 0 : 1 + }) + return spr + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + ModelRenderer.setNoteTex(spr, options.note) + spr.anchor.set(0.5) + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer(options.noteskin, options.columnNumber) + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: { element: "Tap" }, + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "Hold Active Body": opt => + new HoldBody(holdTex[opt.columnName].Active.Body), + "Hold Inactive Body": opt => + new HoldBody(holdTex[opt.columnName].Inactive.Body), + "Hold Active TopCap": () => new Sprite(Texture.EMPTY), + "Hold Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Hold Active BottomCap": opt => + new HoldTail(holdTex[opt.columnName].Active.BottomCap), + "Hold Inactive BottomCap": opt => + new HoldTail(holdTex[opt.columnName].Inactive.BottomCap), + + "Roll Active Head": { element: "Tap" }, + "Roll Inactive Head": { element: "Tap" }, + "Roll Active Body": opt => + new HoldBody(rollTex[opt.columnName].Active.Body), + "Roll Inactive Body": opt => + new HoldBody(rollTex[opt.columnName].Inactive.Body), + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": opt => + new HoldTail(rollTex[opt.columnName].Active.BottomCap), + "Roll Inactive BottomCap": opt => + new HoldTail(rollTex[opt.columnName].Inactive.BottomCap), + }, + }, + load: function (element, options) { + const col = element.columnName + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + init() { + ModelRenderer.initArrowTex() + }, + update(renderer) { + ModelRenderer.setArrowTexTime(renderer.chartManager.app) + }, + metrics: { + HoldBodyBottomOffset: -32, + RollBodyBottomOffset: -32, + }, + hideIcons: ["Lift"], +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/flash/flashBright.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/flash/flashBright.png new file mode 100644 index 00000000..3f3a41cc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/flash/flashBright.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/flash/flashDim.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/flash/flashDim.png new file mode 100644 index 00000000..2bcf76a4 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/flash/flashDim.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/flash/hold.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/flash/hold.png new file mode 100644 index 00000000..7d925fcc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/flash/hold.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/downBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/downBodyActive.png new file mode 100644 index 00000000..c551f969 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/downBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/downBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/downBodyInactive.png new file mode 100644 index 00000000..4a06cdb3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/downBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/downBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/downBottomCapActive.png new file mode 100644 index 00000000..b270cedb Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/downBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/downBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/downBottomCapInactive.png new file mode 100644 index 00000000..3e8b53ee Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/downBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/leftBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/leftBodyActive.png new file mode 100644 index 00000000..c7c5b29e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/leftBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/leftBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/leftBodyInactive.png new file mode 100644 index 00000000..48f5af8b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/leftBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/leftBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/leftBottomCapActive.png new file mode 100644 index 00000000..996cc870 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/leftBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/leftBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/leftBottomCapInactive.png new file mode 100644 index 00000000..0070c317 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/leftBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/rightBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/rightBodyActive.png new file mode 100644 index 00000000..6c8e4201 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/rightBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/rightBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/rightBodyInactive.png new file mode 100644 index 00000000..71d0b446 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/rightBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/rightBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/rightBottomCapActive.png new file mode 100644 index 00000000..87b69bd1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/rightBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/rightBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/rightBottomCapInactive.png new file mode 100644 index 00000000..bca5fd0c Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/rightBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/upBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/upBodyActive.png new file mode 100644 index 00000000..5f4be8de Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/upBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/upBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/upBodyInactive.png new file mode 100644 index 00000000..3460297e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/upBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/upBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/upBottomCapActive.png new file mode 100644 index 00000000..072d9b37 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/upBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/upBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/upBottomCapInactive.png new file mode 100644 index 00000000..788e62cf Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/hold/upBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/lift/body.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/lift/body.txt new file mode 100644 index 00000000..e3ad3f32 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/lift/body.txt @@ -0,0 +1,54 @@ +29 +1.500000 21.000000 0.440771 0.001033 +-1.500000 21.000000 0.440771 0.001033 +-4.000000 18.500000 0.440771 0.001033 +4.000000 18.500000 0.440771 0.001033 +-4.000000 9.000000 0.440771 0.001033 +4.000000 9.000000 0.440771 0.001033 +0.000000 5.000000 0.440771 0.001033 +1.500000 4.000000 0.448399 0.001033 +4.000000 -5.000000 0.448399 0.001033 +4.000000 6.500000 0.448399 0.001033 +0.000000 -9.000000 0.448399 0.001033 +-1.500000 4.000000 0.448399 0.001033 +-4.000000 6.500000 0.448399 0.001033 +-4.000000 -5.000000 0.448399 0.001033 +-1.500000 -10.000000 0.456533 0.001033 +0.000000 -21.500000 0.456533 0.001033 +1.500000 -10.000000 0.456533 0.001033 +8.500000 -6.000000 0.456533 0.001033 +-8.500000 -6.000000 0.456533 0.001033 +23.000000 4.000000 0.458630 0.001033 +22.000000 5.000000 0.458630 0.001033 +19.500000 5.000000 0.458630 0.001033 +23.000000 1.500000 0.458630 0.001033 +-19.500000 5.000000 0.454991 0.001033 +-22.000000 5.000000 0.454991 0.001033 +-23.000000 4.000000 0.454991 0.001033 +-23.000000 1.500000 0.454991 0.001033 +5.500000 -6.000000 0.456533 0.001033 +-5.500000 -6.000000 0.456533 0.001033 +23 +0 1 2 +2 3 0 +4 5 3 +4 3 2 +4 6 5 +7 8 9 +10 8 7 +10 7 11 +10 11 12 +12 13 10 +14 15 16 +15 17 16 +15 14 18 +19 20 21 +21 22 19 +23 24 25 +23 25 26 +15 18 23 +23 26 15 +21 17 15 +15 22 21 +16 17 27 +14 28 18 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/lift/frame.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/lift/frame.txt new file mode 100644 index 00000000..d83214d8 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/lift/frame.txt @@ -0,0 +1,68 @@ +36 +1.500000 28.000000 0.065523 0.068416 +2.000000 30.000000 0.063171 0.069380 +-2.000000 30.000000 0.087725 0.071097 +-1.500000 28.000000 0.085475 0.069820 +8.000000 21.500000 0.032200 0.128497 +10.000000 22.000000 0.028876 0.147933 +10.000000 0.000000 0.011447 0.497174 +18.500000 10.000000 0.015941 0.343234 +18.000000 12.000000 0.016983 0.314690 +10.000000 4.000000 0.014775 0.378867 +23.500000 10.000000 0.014987 0.372102 +24.000000 12.000000 0.015605 0.353105 +-18.500000 10.000000 0.131996 0.348850 +-10.000000 4.000000 0.133143 0.384499 +-18.000000 12.000000 0.130975 0.320288 +-10.000000 0.000000 0.136447 0.502826 +-8.000000 21.500000 0.116488 0.133426 +-10.000000 22.000000 0.119596 0.153069 +28.000000 5.500000 0.013081 0.436815 +30.000000 6.000000 0.013111 0.435746 +30.000000 0.000000 0.011447 0.497174 +28.000000 0.500000 0.011596 0.491616 +0.000000 -27.500000 0.072472 0.932979 +0.000000 -30.000000 0.072472 0.932979 +30.000000 0.000000 0.011625 0.497174 +28.000000 0.500000 0.011477 0.491616 +-28.000000 0.500000 0.136299 0.497268 +-30.000000 0.000000 0.136447 0.502826 +-28.000000 5.500000 0.134819 0.442462 +-30.000000 6.000000 0.134789 0.441393 +-23.500000 10.000000 0.132934 0.377733 +-24.000000 12.000000 0.132325 0.358726 +8.000000 0.000000 0.011447 0.497174 +-8.000000 0.000000 0.136447 0.502826 +-10.000000 1.500000 0.135203 0.456484 +10.000000 1.500000 0.012695 0.450835 +30 +0 1 2 +3 0 2 +4 5 1 +1 0 4 +5 4 6 +7 8 9 +10 11 8 +8 7 10 +12 13 14 +15 16 17 +16 3 2 +16 2 17 +18 19 11 +11 10 18 +20 19 18 +18 21 20 +22 23 24 +24 25 22 +22 26 27 +27 23 22 +28 29 27 +27 26 28 +30 31 29 +29 28 30 +12 14 31 +31 30 12 +4 32 6 +15 33 16 +34 13 12 +7 9 35 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/lift/parts.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/lift/parts.png new file mode 100644 index 00000000..eb65fcf3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/lift/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/lift/quants.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/lift/quants.png new file mode 100644 index 00000000..e7fb4b81 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/lift/quants.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/mine/body.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/mine/body.txt new file mode 100755 index 00000000..0bf678f4 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/mine/body.txt @@ -0,0 +1,103 @@ +45 +-16.0 0.0 0 0.75 +-11.31 -11.31 0 0.75 + 0.0 -16.0 0 0.75 + 11.31 -11.31 0 0.75 + 16.0 0.0 0 0.75 + 11.31 11.31 0 0.75 + -0.0 16.0 0 0.75 +-11.31 11.31 0 0.75 +29.422066 0.009519 -0.999500 0.25 +25.401829 14.852878 -0.931249 0.1240015 +14.843359 25.411350 -0.751997 0.0343755 +-0.000001 29.431583 -0.500000 0.00025 +-14.843361 25.411348 -0.248003 0.0343755 +-25.401833 14.852878 -0.068751 0.1240015 +-29.422066 0.009516 -0.000500 0.25 +-25.401827 -14.833844 -0.068751 0.3759985 +-14.843357 -25.392317 -0.248003 0.4656245 +0.000004 -29.412546 -0.500000 0.49975 +14.843363 -25.392307 -0.751997 0.4656245 +25.401833 -14.833840 -0.931249 0.3759985 +19.921448 0.009518 0.838208 0.25 +17.189407 10.096646 0.791826 0.164375 +10.087127 17.198929 0.671250 0.104087 +-0.000000 19.930965 0.500000 0.080896 +-10.087129 17.198925 0.328750 0.104087 +-17.189411 10.096646 0.208174 0.164375 +-19.921448 0.009518 0.161792 0.25 +-17.189405 -10.077612 0.208174 0.335625 +-10.087127 -17.179895 0.328750 0.395913 +0.000001 -19.911928 0.500000 0.419104 +10.087130 -17.179886 0.671250 0.395913 +17.189411 -10.077609 0.791826 0.335625 +10.420829 0.009516 -0.676915 0.25 +8.976986 5.340415 -0.652403 0.2047485 +5.330894 8.986506 -0.590503 0.1737985 +0.000001 10.430347 -0.500000 0.1615425 +-5.330897 8.986505 -0.409497 0.1737985 +-8.976989 5.340414 -0.347597 0.2047485 +-10.420829 0.009520 -0.323085 0.25 +-8.976985 -5.321381 -0.347597 0.2952515 +-5.330895 -8.967473 -0.409497 0.3262015 +-0.000003 -10.411310 -0.500000 0.3384575 +5.330898 -8.967464 -0.590503 0.3262015 +8.976988 -5.321376 -0.652403 0.2952515 +0 0 0.5 0.75 +56 +44 0 1 +44 1 2 +44 2 3 +44 3 4 +44 4 5 +44 5 6 +44 6 7 +44 7 0 +8 9 20 +9 21 20 +9 10 21 +10 22 21 +10 11 22 +11 23 22 +11 12 23 +12 24 23 +12 13 24 +13 25 24 +13 14 25 +14 26 25 +14 15 26 +15 27 26 +15 16 27 +16 28 27 +16 17 28 +17 29 28 +17 18 29 +18 30 29 +18 19 30 +19 31 30 +19 8 31 +8 20 31 +20 21 32 +21 33 32 +21 22 33 +22 34 33 +22 23 34 +23 35 34 +23 24 35 +24 36 35 +24 25 36 +25 37 36 +25 26 37 +26 38 37 +26 27 38 +27 39 38 +27 28 39 +28 40 39 +28 29 40 +29 41 40 +29 30 41 +30 42 41 +30 31 42 +31 43 42 +31 20 43 +20 32 43 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/mine/frame.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/mine/frame.png new file mode 100644 index 00000000..388e7814 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/mine/frame.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/mine/parts.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/mine/parts.png new file mode 100755 index 00000000..744ba8dc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/mine/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/preview.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/preview.png new file mode 100644 index 00000000..cba56265 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/receptor.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/receptor.png new file mode 100755 index 00000000..944c0ac9 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/receptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/downBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/downBodyActive.png new file mode 100644 index 00000000..95b53b98 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/downBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/downBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/downBodyInactive.png new file mode 100644 index 00000000..d2244272 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/downBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/downBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/downBottomCapActive.png new file mode 100644 index 00000000..c1bbbc51 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/downBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/downBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/downBottomCapInactive.png new file mode 100644 index 00000000..49a368d5 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/downBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/leftBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/leftBodyActive.png new file mode 100644 index 00000000..996fb0e7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/leftBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/leftBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/leftBodyInactive.png new file mode 100644 index 00000000..ad960a9e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/leftBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/leftBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/leftBottomCapActive.png new file mode 100644 index 00000000..370dcabc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/leftBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/leftBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/leftBottomCapInactive.png new file mode 100644 index 00000000..15bb5cb5 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/leftBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/rightBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/rightBodyActive.png new file mode 100644 index 00000000..d800e059 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/rightBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/rightBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/rightBodyInactive.png new file mode 100644 index 00000000..1c0eab5e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/rightBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/rightBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/rightBottomCapActive.png new file mode 100644 index 00000000..c3df4125 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/rightBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/rightBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/rightBottomCapInactive.png new file mode 100644 index 00000000..c394da03 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/rightBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/upBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/upBodyActive.png new file mode 100644 index 00000000..b09cbc9a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/upBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/upBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/upBodyInactive.png new file mode 100644 index 00000000..14173019 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/upBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/upBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/upBottomCapActive.png new file mode 100644 index 00000000..dd41e353 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/upBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/upBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/upBottomCapInactive.png new file mode 100644 index 00000000..00ee6e7e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/roll/upBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/arrow_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/arrow_gradient.frag new file mode 100644 index 00000000..7d068f93 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/arrow_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform float time; +uniform float quant; + +void main() { + vec4 col = texture2D( + sampler0, + vec2(vUvs.x + 0.0625 * quant, mod(vUvs.y - time, 2.0) / 2.0) + ); + gl_FragColor = col; +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/lift_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/lift_gradient.frag new file mode 100644 index 00000000..5ee858ec --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/lift_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform float time; +uniform float quant; + +void main() { + vec4 col = texture2D( + sampler0, + vec2(mod(vUvs.x - time, 2.0) / 2.0, vUvs.y) + ); + gl_FragColor = col; +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/mine_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/mine_gradient.frag new file mode 100644 index 00000000..f1d32938 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/mine_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform sampler2D sampler1; +uniform float time; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + if (col.a < 1.0) { + discard; + } + gl_FragColor = texture2D(sampler1, vec2(mod(col.r - time, 1.0), 0.625)); +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/noop.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/noop.frag new file mode 100644 index 00000000..3a329fdc --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/noop.frag @@ -0,0 +1,12 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + + gl_FragColor = col; + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/noop.vert b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/noop.vert new file mode 100644 index 00000000..002c3782 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/shader/noop.vert @@ -0,0 +1,24 @@ +precision mediump float; + +attribute vec2 aVertexPosition; +attribute vec2 aUvs; + +uniform mat3 translationMatrix; +uniform mat3 projectionMatrix; +uniform float time; + +varying vec2 vUvs; + +float rand(vec2 co) { + return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); +} + +void main() { + vUvs = aUvs; + gl_Position = vec4( + (projectionMatrix * translationMatrix * vec3(aVertexPosition.xy, 1.0)).xy, + 0.0, + 1.0 + ); + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/tap/body.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/tap/body.txt new file mode 100755 index 00000000..583cd628 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/tap/body.txt @@ -0,0 +1,152 @@ +78 +1.500000 21.000000 0.482234 0.059122 +1.500000 28.000000 0.482234 0.000000 +-1.500000 28.000000 0.481498 0.000000 +-1.500000 21.000000 0.481498 0.059122 +-8.000000 21.500000 0.479904 0.054899 +-4.000000 18.500000 0.480885 0.080236 +-1.500000 4.000000 0.481498 0.202703 +0.000000 5.000000 0.481866 0.194257 +-4.000000 9.000000 0.480885 0.160473 +-4.000000 6.500000 0.480885 0.181588 +1.500000 4.000000 0.482234 0.202703 +4.000000 6.500000 0.482847 0.181588 +4.000000 9.000000 0.482847 0.160473 +4.000000 18.500000 0.482847 0.080236 +8.000000 21.500000 0.483828 0.054899 +4.000000 -5.000000 0.482847 0.278716 +19.500000 5.000000 0.486648 0.194257 +18.500000 10.000000 0.486403 0.152027 +10.000000 0.000000 0.484319 0.236486 +8.500000 -6.000000 0.483951 0.287162 +5.500000 -6.000000 0.483215 0.287162 +0.000000 -9.000000 0.481866 0.312500 +1.500000 -10.000000 0.482234 0.320946 +-1.500000 -10.000000 0.481498 0.320946 +-4.000000 -5.000000 0.480885 0.278716 +-5.500000 -6.000000 0.480517 0.287162 +-10.000000 0.000000 0.479414 0.236486 +-8.500000 -6.000000 0.479782 0.287162 +-18.500000 10.000000 0.477329 0.152027 +-19.500000 5.000000 0.477084 0.194257 +-23.500000 10.000000 0.476103 0.152027 +-22.000000 5.000000 0.476471 0.194257 +-28.000000 5.500000 0.475000 0.190034 +-23.000000 4.000000 0.476226 0.202703 +-23.000000 1.500000 0.476226 0.223818 +-28.000000 0.500000 0.475000 0.232264 +0.000000 -27.500000 0.481866 0.468750 +0.000000 -21.500000 0.481866 0.418074 +28.000000 0.500000 0.488733 0.232264 +23.000000 1.500000 0.487507 0.223818 +28.000000 5.500000 0.488733 0.190034 +23.000000 4.000000 0.487507 0.202703 +23.500000 10.000000 0.487629 0.152027 +22.000000 5.000000 0.487261 0.194257 +1.500000 21.000000 0.440771 0.001033 +-1.500000 21.000000 0.440514 0.001033 +-4.000000 18.500000 0.440300 0.001033 +4.000000 18.500000 0.440985 0.001033 +-4.000000 9.000000 0.440300 0.001033 +4.000000 9.000000 0.440985 0.001033 +0.000000 5.000000 0.440771 0.001033 +1.500000 4.000000 0.448399 0.001033 +4.000000 -5.000000 0.448613 0.001033 +4.000000 6.500000 0.448613 0.001033 +0.000000 -9.000000 0.448399 0.001033 +-1.500000 4.000000 0.448142 0.001033 +0.000000 -9.000000 0.448142 0.001033 +-4.000000 6.500000 0.447928 0.001033 +-4.000000 -5.000000 0.447928 0.001033 +-1.500000 -10.000000 0.456533 0.001033 +0.000000 -21.500000 0.456661 0.001033 +1.500000 -10.000000 0.456789 0.001033 +8.500000 -6.000000 0.457389 0.001033 +-8.500000 -6.000000 0.455933 0.001033 +23.000000 4.000000 0.458630 0.001033 +22.000000 5.000000 0.458545 0.001033 +19.500000 5.000000 0.458331 0.001033 +23.000000 1.500000 0.458630 0.001033 +-19.500000 5.000000 0.454991 0.001033 +-22.000000 5.000000 0.454777 0.001033 +-23.000000 4.000000 0.454691 0.001033 +-23.000000 1.500000 0.454691 0.001033 +5.500000 -6.000000 0.457132 0.001033 +-5.500000 -6.000000 0.456190 0.001033 +8.000000 0.000000 0.483828 0.236486 +-8.000000 0.000000 0.479904 0.236486 +-10.000000 1.500000 0.479414 0.223818 +10.000000 1.500000 0.484319 0.223818 +72 +0 1 2 +2 3 0 +3 2 4 +3 4 5 +6 7 8 +8 9 6 +10 7 7 +6 10 7 +11 12 7 +10 11 7 +13 14 1 +13 1 0 +14 13 15 +16 17 18 +19 16 18 +20 19 18 +15 20 18 +21 22 20 +15 21 20 +23 22 21 +24 25 23 +24 23 21 +26 25 24 +26 27 25 +28 29 27 +27 26 28 +28 30 29 +30 31 29 +32 33 31 +31 30 32 +32 34 33 +32 35 34 +36 37 34 +34 35 36 +36 38 39 +36 39 37 +38 40 39 +40 41 39 +42 43 41 +42 41 40 +16 43 42 +16 42 17 +44 45 46 +46 47 44 +48 49 47 +48 47 46 +48 50 49 +51 52 53 +54 52 51 +54 51 55 +56 55 57 +57 58 56 +59 60 61 +60 62 61 +60 59 63 +64 65 66 +66 67 64 +68 69 70 +68 70 71 +60 63 68 +68 71 60 +66 62 60 +60 67 66 +61 62 72 +59 73 63 +15 18 74 +14 15 74 +26 24 75 +75 24 5 +5 4 75 +26 76 28 +17 77 18 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/tap/frame.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/tap/frame.txt new file mode 100755 index 00000000..92aa8fd2 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/tap/frame.txt @@ -0,0 +1,66 @@ +34 +1.500000 28.000000 0.128125 0.266667 +2.000000 30.000000 0.129167 0.250000 +-2.000000 30.000000 0.120833 0.250000 +-1.500000 28.000000 0.121875 0.266667 +8.000000 21.500000 0.141667 0.320833 +10.000000 22.000000 0.145833 0.316667 +10.000000 0.000000 0.145833 0.500000 +18.500000 10.000000 0.163542 0.416667 +18.000000 12.000000 0.162500 0.400000 +10.000000 4.000000 0.145833 0.466666 +23.500000 10.000000 0.173958 0.416667 +24.000000 12.000000 0.175000 0.400000 +-18.500000 10.000000 0.086458 0.416667 +-10.000000 4.000000 0.104167 0.466666 +-18.000000 12.000000 0.087500 0.400000 +-10.000000 0.000000 0.104167 0.500000 +-8.000000 21.500000 0.108333 0.320833 +-10.000000 22.000000 0.104167 0.316667 +28.000000 5.500000 0.183333 0.454166 +30.000000 6.000000 0.187500 0.450000 +30.000000 0.000000 0.187500 0.500000 +28.000000 0.500000 0.183333 0.495833 +0.000000 -27.500000 0.125000 0.729166 +0.000000 -30.000000 0.125000 0.750000 +-28.000000 0.500000 0.066667 0.495833 +-30.000000 0.000000 0.062500 0.500000 +-28.000000 5.500000 0.066667 0.454166 +-30.000000 6.000000 0.062500 0.450000 +-23.500000 10.000000 0.076042 0.416667 +-24.000000 12.000000 0.075000 0.400000 +8.000000 0.000000 0.141667 0.500000 +-8.000000 0.000000 0.108333 0.500000 +-10.000000 1.500000 0.104167 0.487500 +10.000000 1.500000 0.145833 0.487500 +30 +0 1 2 +3 0 2 +4 5 1 +1 0 4 +5 4 6 +7 8 9 +10 11 8 +8 7 10 +12 13 14 +15 16 17 +16 3 2 +16 2 17 +18 19 11 +11 10 18 +20 19 18 +18 21 20 +22 23 20 +20 21 22 +22 24 25 +25 23 22 +26 27 25 +25 24 26 +28 29 27 +27 26 28 +12 14 29 +29 28 12 +4 30 6 +15 31 16 +32 13 12 +7 9 33 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/tap/parts.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/tap/parts.png new file mode 100755 index 00000000..e45cfd5a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow-itg/tap/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/ModelRenderer.ts b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/ModelRenderer.ts new file mode 100644 index 00000000..93c38e4b --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/ModelRenderer.ts @@ -0,0 +1,231 @@ +import { + BaseTexture, + Container, + Geometry, + Mesh, + MIPMAP_MODES, + Rectangle, + RenderTexture, + Shader, + Sprite, + Texture, +} from "pixi.js" + +import { App } from "../../../../../App" +import { Options } from "../../../../../util/Options" +import { NotedataEntry } from "../../../../sm/NoteTypes" + +import liftPartsUrl from "./lift/parts.png" +import liftQuantsUrl from "./lift/quants.png" +import mineFrameUrl from "./mine/frame.png" +import minePartsUrl from "./mine/parts.png" +import tapPartsUrl from "./tap/parts.png" + +import arrowGradientFrag from "./shader/arrow_gradient.frag?raw" +import liftGradientFrag from "./shader/lift_gradient.frag?raw" +import mineGradientFrag from "./shader/mine_gradient.frag?raw" +import noopFrag from "./shader/noop.frag?raw" +import noopVert from "./shader/noop.vert?raw" + +import arrowBodyGeomText from "./tap/body.txt?raw" +import arrowFrameGeomText from "./tap/frame.txt?raw" + +import { loadGeometry } from "../../../../../util/Util" +import liftBodyGeomText from "./lift/body.txt?raw" +import liftFrameGeomText from "./lift/frame.txt?raw" +import mineBodyGeomText from "./mine/body.txt?raw" + +const mine_frame_texture = Texture.from(mineFrameUrl) + +export class ModelRenderer { + static arrowPartsTex = BaseTexture.from(tapPartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static minePartsTex = BaseTexture.from(minePartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static liftPartsTex = BaseTexture.from(liftPartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static liftQuantsTex = BaseTexture.from(liftQuantsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + + static arrowBodyGeom: Geometry + static arrowFrameGeom: Geometry + static liftBodyGeom: Geometry + static liftFrameGeom: Geometry + static mineBodyGeom: Geometry + + static arrowFrameTex: RenderTexture + static arrowFrame: Mesh + static arrowTex: RenderTexture + static arrowContainer = new Container() + static liftTex: RenderTexture + static liftContainer = new Container() + static mineTex: RenderTexture + static mineContainer = new Container() + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + ModelRenderer.arrowFrameTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + ModelRenderer.arrowTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.liftTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.mineTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + + this.arrowBodyGeom = await loadGeometry(arrowBodyGeomText) + this.arrowFrameGeom = await loadGeometry(arrowFrameGeomText) + this.mineBodyGeom = await loadGeometry(mineBodyGeomText) + this.liftBodyGeom = await loadGeometry(liftBodyGeomText) + this.liftFrameGeom = await loadGeometry(liftFrameGeomText) + + { + const shader_frame = Shader.from(noopVert, noopFrag, { + sampler0: this.arrowPartsTex, + }) + const arrow_frame = new Mesh(ModelRenderer.arrowFrameGeom, shader_frame) + arrow_frame.x = 32 + arrow_frame.y = 32 + arrow_frame.rotation = Math.PI + this.arrowFrame = arrow_frame + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.arrowPartsTex, + time: 0, + quant: Math.min(i, 6), + }) + const arrow_frame = new Sprite(ModelRenderer.arrowFrameTex) + arrow_frame.x = (i % 3) * 64 + arrow_frame.y = Math.floor(i / 3) * 64 + const arrow_body = new Mesh(ModelRenderer.arrowBodyGeom, shader_body) + arrow_body.x = (i % 3) * 64 + 32 + arrow_body.y = Math.floor(i / 3) * 64 + 32 + arrow_body.rotation = Math.PI + arrow_body.name = "body" + i + ModelRenderer.arrowContainer.addChild(arrow_frame) + ModelRenderer.arrowContainer.addChild(arrow_body) + } + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.liftQuantsTex, + time: 0, + quant: Math.min(i, 6), + }) + const shader_frame = Shader.from(noopVert, liftGradientFrag, { + sampler0: this.liftPartsTex, + }) + const lift_body = new Mesh(ModelRenderer.liftBodyGeom, shader_body) + lift_body.x = (i % 3) * 64 + 32 + lift_body.y = Math.floor(i / 3) * 64 + 32 + lift_body.rotation = Math.PI + lift_body.name = "body" + i + ModelRenderer.liftContainer.addChild(lift_body) + + const lift_frame = new Mesh(ModelRenderer.liftFrameGeom, shader_frame) + lift_frame.x = (i % 3) * 64 + 32 + lift_frame.y = Math.floor(i / 3) * 64 + 32 + lift_frame.rotation = Math.PI + lift_frame.name = "frame" + i + ModelRenderer.liftContainer.addChild(lift_frame) + } + } + { + const shader_body = Shader.from(noopVert, mineGradientFrag, { + sampler0: this.minePartsTex, + time: 0, + }) + const mine_body = new Mesh(ModelRenderer.mineBodyGeom, shader_body) + const mine_frame = new Sprite(mine_frame_texture) + mine_frame.width = 64 + mine_frame.height = 64 + mine_frame.anchor.set(0.5) + mine_frame.pivot.y = 3 // epic recenter + ModelRenderer.mineContainer.position.set(32) + ModelRenderer.mineContainer.addChild(mine_body) + ModelRenderer.mineContainer.addChild(mine_frame) + } + + this.loaded = true + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const beat = app.chartManager.chartView!.getVisualBeat() + const second = app.chartManager.chartView!.getVisualTime() + for (let i = 0; i < 10; i++) { + const tapShader: Mesh = + ModelRenderer.arrowContainer.getChildByName("body" + i)! + tapShader.shader.uniforms.time = beat / 4 + + const liftShader: Mesh = + ModelRenderer.liftContainer.getChildByName("body" + i)! + liftShader.shader.uniforms.time = beat / 4 + const liftFrameShader: Mesh = + ModelRenderer.liftContainer.getChildByName("frame" + i)! + liftFrameShader.shader.uniforms.time = beat / 4 + } + + ;(ModelRenderer.mineContainer.children[0]).shader.uniforms.time = + second + ModelRenderer.mineContainer.rotation = (second % 1) * Math.PI * 2 + + app.renderer.render(ModelRenderer.arrowFrame, { + renderTexture: ModelRenderer.arrowFrameTex, + }) + app.renderer.render(ModelRenderer.arrowContainer, { + renderTexture: ModelRenderer.arrowTex, + }) + app.renderer.render(ModelRenderer.mineContainer, { + renderTexture: ModelRenderer.mineTex, + }) + app.renderer.render(ModelRenderer.liftContainer, { + renderTexture: ModelRenderer.liftTex, + }) + } + + static setNoteTex(arrow: Sprite, note: NotedataEntry | undefined) { + if (note !== undefined && note.type == "Mine") { + arrow.texture = ModelRenderer.mineTex + } else if (note !== undefined && note.type == "Lift") { + const i = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + note?.quant ?? 4 + ) + arrow.texture = new Texture( + ModelRenderer.liftTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } else { + const i = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + note?.quant ?? 4 + ) + arrow.texture = new Texture( + ModelRenderer.arrowTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/NoteFlash.ts new file mode 100644 index 00000000..6299ec83 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/NoteFlash.ts @@ -0,0 +1,183 @@ +import { + AnimatedSprite, + BLEND_MODES, + Container, + Sprite, + Texture, +} from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import { + BezierAnimator, + BezierKeyFrames, +} from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" + +import brightUrl from "./flash/flashBright.png" +import dimUrl from "./flash/flashDim.png" +import holdFlashUrl from "./flash/hold.png" +import mineUrl from "./flash/mine.png" + +const brightTex = Texture.from(brightUrl) +const dimTex = Texture.from(dimUrl) +const holdTex = Texture.from(holdFlashUrl) +const mineTex = Texture.from(mineUrl) + +const bright: BezierKeyFrames = { + "0": { + tint: 0xffffff, + alpha: 1, + "scale.x": 0.8, + "scale.y": 0.8, + }, + "0.5": { + tint: 0xffffff, + alpha: 1, + "scale.x": 1, + "scale.y": 1, + }, + "1": { + tint: 0xffffff, + alpha: 0, + "scale.x": 1, + "scale.y": 1, + }, +} + +const dim = (tint: number) => { + return { + "0": { + tint, + alpha: 1, + "scale.x": 0.4, + "scale.y": 0.4, + }, + "0.5": { + tint, + alpha: 1, + "scale.x": 0.5, + "scale.y": 0.5, + }, + "1": { + tint, + alpha: 0, + "scale.x": 0.5, + "scale.y": 0.5, + }, + } +} + +const animations: Record = { + w0: bright, + w1: bright, + w2: dim(0xffff4c), + w3: dim(0x00ff66), + held: bright, +} + +export class NoteFlashContainer extends Container { + holdExplosion = new AnimatedSprite(splitTex(holdTex, 4, 1, 160, 160)[0]) + bright = new Sprite(brightTex) + dim = new Sprite(dimTex) + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + let target = this.dim + if (event.judgement.id == "w0") { + target = this.bright + } + if (animations[event.judgement.id] === undefined) return + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate(target, animations[event.judgement.id], 0.12) + ) + } + }) + + noteskin.on(this, "held", event => { + if (col == event.columnNumber) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate(this.bright, animations.held, 0.12) + ) + } + }) + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(mineTex) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + this.holdExplosion.visible = false + this.holdExplosion.anchor.set(0.5) + this.holdExplosion.scale.set(0.5) + this.holdExplosion.play() + this.addChild(this.holdExplosion) + + this.dim.scale.set(0.5) + this.dim.anchor.set(0.5) + this.dim.alpha = 0 + this.addChild(this.dim) + + this.bright.scale.set(0.5) + this.bright.anchor.set(0.5) + this.bright.alpha = 0 + this.addChild(this.bright) + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/Noteskin.ts new file mode 100644 index 00000000..95a10c41 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/Noteskin.ts @@ -0,0 +1,167 @@ +// Lemmas-DDRainbow by LemmaEOF https://github.com/lemmaeof/lemmas-ddrainbow + +import { AnimatedSprite, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" + +import { ModelRenderer } from "./ModelRenderer" +import { NoteFlashContainer } from "./NoteFlash" +import receptorUrl from "./receptor.png" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" + +const receptorTex = Texture.from(receptorUrl) + +const holdTex: Record>> = {} +const rollTex: Record>> = {} + +for (const dir of ["Left", "Down", "Up", "Right"]) { + for (const asset of ["Body", "BottomCap"]) { + for (const state of ["Active", "Inactive"]) { + if (holdTex[dir] === undefined) holdTex[dir] = {} + if (holdTex[dir][state] === undefined) holdTex[dir][state] = {} + holdTex[dir][state][asset] = Texture.from( + new URL( + `./hold/${dir.toLowerCase()}${asset}${state}.png`, + import.meta.url + ).href + ) + + if (rollTex[dir] === undefined) rollTex[dir] = {} + if (rollTex[dir][state] === undefined) rollTex[dir][state] = {} + rollTex[dir][state][asset] = Texture.from( + new URL( + `./roll/${dir.toLowerCase()}${asset}${state}.png`, + import.meta.url + ).href + ) + } + } +} + +const rotationMap: Record = { + Left: 90, + Down: 0, + Up: 180, + Right: -90, + UpLeft: 135, + UpRight: -135, + DownRight: -45, + DownLeft: 45, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", + "NoteFlash", +] + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + const tex = splitTex(receptorTex, 2, 1, 256, 256) + + const spr = new AnimatedSprite(tex[0]) + spr.width = 64 + spr.height = 64 + spr.anchor.set(0.5) + options.noteskin.on(spr, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + spr, + { + "0": { + alpha: 1.2, + width: 48, + height: 48, + }, + "1": { + alpha: 1, + width: 64, + height: 64, + }, + }, + 0.06 + ) + } + }) + options.noteskin.onUpdate(spr, r => { + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + spr.currentFrame = partialBeat < 0.2 ? 0 : 1 + }) + return spr + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + ModelRenderer.setNoteTex(spr, options.note) + spr.anchor.set(0.5) + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer(options.noteskin, options.columnNumber) + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: { element: "Tap" }, + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "Hold Active Body": opt => + new HoldBody(holdTex[opt.columnName].Active.Body), + "Hold Inactive Body": opt => + new HoldBody(holdTex[opt.columnName].Inactive.Body), + "Hold Active TopCap": () => new Sprite(Texture.EMPTY), + "Hold Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Hold Active BottomCap": opt => + new HoldTail(holdTex[opt.columnName].Active.BottomCap), + "Hold Inactive BottomCap": opt => + new HoldTail(holdTex[opt.columnName].Inactive.BottomCap), + + "Roll Active Head": { element: "Tap" }, + "Roll Inactive Head": { element: "Tap" }, + "Roll Active Body": opt => + new HoldBody(rollTex[opt.columnName].Active.Body), + "Roll Inactive Body": opt => + new HoldBody(rollTex[opt.columnName].Inactive.Body), + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": opt => + new HoldTail(rollTex[opt.columnName].Active.BottomCap), + "Roll Inactive BottomCap": opt => + new HoldTail(rollTex[opt.columnName].Inactive.BottomCap), + }, + }, + load: function (element, options) { + const col = element.columnName + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + init() { + ModelRenderer.initArrowTex() + }, + update(renderer) { + ModelRenderer.setArrowTexTime(renderer.chartManager.app) + }, + metrics: { + HoldBodyBottomOffset: -32, + RollBodyBottomOffset: -32, + }, + hideIcons: ["Lift"], +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/flash/flashBright.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/flash/flashBright.png new file mode 100644 index 00000000..3f3a41cc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/flash/flashBright.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/flash/flashDim.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/flash/flashDim.png new file mode 100644 index 00000000..2bcf76a4 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/flash/flashDim.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/flash/hold.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/flash/hold.png new file mode 100644 index 00000000..7d925fcc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/flash/hold.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/downBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/downBodyActive.png new file mode 100644 index 00000000..c551f969 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/downBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/downBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/downBodyInactive.png new file mode 100644 index 00000000..4a06cdb3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/downBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/downBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/downBottomCapActive.png new file mode 100644 index 00000000..b270cedb Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/downBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/downBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/downBottomCapInactive.png new file mode 100644 index 00000000..3e8b53ee Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/downBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/leftBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/leftBodyActive.png new file mode 100644 index 00000000..c7c5b29e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/leftBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/leftBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/leftBodyInactive.png new file mode 100644 index 00000000..48f5af8b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/leftBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/leftBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/leftBottomCapActive.png new file mode 100644 index 00000000..996cc870 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/leftBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/leftBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/leftBottomCapInactive.png new file mode 100644 index 00000000..0070c317 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/leftBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/rightBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/rightBodyActive.png new file mode 100644 index 00000000..6c8e4201 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/rightBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/rightBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/rightBodyInactive.png new file mode 100644 index 00000000..71d0b446 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/rightBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/rightBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/rightBottomCapActive.png new file mode 100644 index 00000000..87b69bd1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/rightBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/rightBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/rightBottomCapInactive.png new file mode 100644 index 00000000..bca5fd0c Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/rightBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/upBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/upBodyActive.png new file mode 100644 index 00000000..5f4be8de Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/upBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/upBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/upBodyInactive.png new file mode 100644 index 00000000..3460297e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/upBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/upBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/upBottomCapActive.png new file mode 100644 index 00000000..072d9b37 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/upBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/upBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/upBottomCapInactive.png new file mode 100644 index 00000000..788e62cf Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/hold/upBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/lift/body.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/lift/body.txt new file mode 100644 index 00000000..cc02425a --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/lift/body.txt @@ -0,0 +1,54 @@ +29 +1.500000 21.000000 0.503271 0.001033 +-1.500000 21.000000 0.503271 0.001033 +-4.000000 18.500000 0.503271 0.001033 +4.000000 18.500000 0.503271 0.001033 +-4.000000 9.000000 0.503271 0.001033 +4.000000 9.000000 0.503271 0.001033 +0.000000 5.000000 0.503271 0.001033 +1.500000 4.000000 0.510899 0.001033 +4.000000 -5.000000 0.510899 0.001033 +4.000000 6.500000 0.510899 0.001033 +0.000000 -9.000000 0.510899 0.001033 +-1.500000 4.000000 0.510899 0.001033 +-4.000000 6.500000 0.510899 0.001033 +-4.000000 -5.000000 0.510899 0.001033 +-1.500000 -10.000000 0.519033 0.001033 +0.000000 -21.500000 0.519033 0.001033 +1.500000 -10.000000 0.519033 0.001033 +8.500000 -6.000000 0.519033 0.001033 +-8.500000 -6.000000 0.519033 0.001033 +23.000000 4.000000 0.521130 0.001033 +22.000000 5.000000 0.521130 0.001033 +19.500000 5.000000 0.521130 0.001033 +23.000000 1.500000 0.521130 0.001033 +-19.500000 5.000000 0.517491 0.001033 +-22.000000 5.000000 0.517491 0.001033 +-23.000000 4.000000 0.517491 0.001033 +-23.000000 1.500000 0.517491 0.001033 +5.500000 -6.000000 0.519033 0.001033 +-5.500000 -6.000000 0.519033 0.001033 +23 +0 1 2 +2 3 0 +4 5 3 +4 3 2 +4 6 5 +7 8 9 +10 8 7 +10 7 11 +10 11 12 +12 13 10 +14 15 16 +15 17 16 +15 14 18 +19 20 21 +21 22 19 +23 24 25 +23 25 26 +15 18 23 +23 26 15 +21 17 15 +15 22 21 +16 17 27 +14 28 18 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/lift/frame.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/lift/frame.txt new file mode 100644 index 00000000..d83214d8 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/lift/frame.txt @@ -0,0 +1,68 @@ +36 +1.500000 28.000000 0.065523 0.068416 +2.000000 30.000000 0.063171 0.069380 +-2.000000 30.000000 0.087725 0.071097 +-1.500000 28.000000 0.085475 0.069820 +8.000000 21.500000 0.032200 0.128497 +10.000000 22.000000 0.028876 0.147933 +10.000000 0.000000 0.011447 0.497174 +18.500000 10.000000 0.015941 0.343234 +18.000000 12.000000 0.016983 0.314690 +10.000000 4.000000 0.014775 0.378867 +23.500000 10.000000 0.014987 0.372102 +24.000000 12.000000 0.015605 0.353105 +-18.500000 10.000000 0.131996 0.348850 +-10.000000 4.000000 0.133143 0.384499 +-18.000000 12.000000 0.130975 0.320288 +-10.000000 0.000000 0.136447 0.502826 +-8.000000 21.500000 0.116488 0.133426 +-10.000000 22.000000 0.119596 0.153069 +28.000000 5.500000 0.013081 0.436815 +30.000000 6.000000 0.013111 0.435746 +30.000000 0.000000 0.011447 0.497174 +28.000000 0.500000 0.011596 0.491616 +0.000000 -27.500000 0.072472 0.932979 +0.000000 -30.000000 0.072472 0.932979 +30.000000 0.000000 0.011625 0.497174 +28.000000 0.500000 0.011477 0.491616 +-28.000000 0.500000 0.136299 0.497268 +-30.000000 0.000000 0.136447 0.502826 +-28.000000 5.500000 0.134819 0.442462 +-30.000000 6.000000 0.134789 0.441393 +-23.500000 10.000000 0.132934 0.377733 +-24.000000 12.000000 0.132325 0.358726 +8.000000 0.000000 0.011447 0.497174 +-8.000000 0.000000 0.136447 0.502826 +-10.000000 1.500000 0.135203 0.456484 +10.000000 1.500000 0.012695 0.450835 +30 +0 1 2 +3 0 2 +4 5 1 +1 0 4 +5 4 6 +7 8 9 +10 11 8 +8 7 10 +12 13 14 +15 16 17 +16 3 2 +16 2 17 +18 19 11 +11 10 18 +20 19 18 +18 21 20 +22 23 24 +24 25 22 +22 26 27 +27 23 22 +28 29 27 +27 26 28 +30 31 29 +29 28 30 +12 14 31 +31 30 12 +4 32 6 +15 33 16 +34 13 12 +7 9 35 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/lift/parts.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/lift/parts.png new file mode 100644 index 00000000..eb65fcf3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/lift/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/lift/quants.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/lift/quants.png new file mode 100755 index 00000000..487bc7c1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/lift/quants.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/mine/body.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/mine/body.txt new file mode 100755 index 00000000..0bf678f4 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/mine/body.txt @@ -0,0 +1,103 @@ +45 +-16.0 0.0 0 0.75 +-11.31 -11.31 0 0.75 + 0.0 -16.0 0 0.75 + 11.31 -11.31 0 0.75 + 16.0 0.0 0 0.75 + 11.31 11.31 0 0.75 + -0.0 16.0 0 0.75 +-11.31 11.31 0 0.75 +29.422066 0.009519 -0.999500 0.25 +25.401829 14.852878 -0.931249 0.1240015 +14.843359 25.411350 -0.751997 0.0343755 +-0.000001 29.431583 -0.500000 0.00025 +-14.843361 25.411348 -0.248003 0.0343755 +-25.401833 14.852878 -0.068751 0.1240015 +-29.422066 0.009516 -0.000500 0.25 +-25.401827 -14.833844 -0.068751 0.3759985 +-14.843357 -25.392317 -0.248003 0.4656245 +0.000004 -29.412546 -0.500000 0.49975 +14.843363 -25.392307 -0.751997 0.4656245 +25.401833 -14.833840 -0.931249 0.3759985 +19.921448 0.009518 0.838208 0.25 +17.189407 10.096646 0.791826 0.164375 +10.087127 17.198929 0.671250 0.104087 +-0.000000 19.930965 0.500000 0.080896 +-10.087129 17.198925 0.328750 0.104087 +-17.189411 10.096646 0.208174 0.164375 +-19.921448 0.009518 0.161792 0.25 +-17.189405 -10.077612 0.208174 0.335625 +-10.087127 -17.179895 0.328750 0.395913 +0.000001 -19.911928 0.500000 0.419104 +10.087130 -17.179886 0.671250 0.395913 +17.189411 -10.077609 0.791826 0.335625 +10.420829 0.009516 -0.676915 0.25 +8.976986 5.340415 -0.652403 0.2047485 +5.330894 8.986506 -0.590503 0.1737985 +0.000001 10.430347 -0.500000 0.1615425 +-5.330897 8.986505 -0.409497 0.1737985 +-8.976989 5.340414 -0.347597 0.2047485 +-10.420829 0.009520 -0.323085 0.25 +-8.976985 -5.321381 -0.347597 0.2952515 +-5.330895 -8.967473 -0.409497 0.3262015 +-0.000003 -10.411310 -0.500000 0.3384575 +5.330898 -8.967464 -0.590503 0.3262015 +8.976988 -5.321376 -0.652403 0.2952515 +0 0 0.5 0.75 +56 +44 0 1 +44 1 2 +44 2 3 +44 3 4 +44 4 5 +44 5 6 +44 6 7 +44 7 0 +8 9 20 +9 21 20 +9 10 21 +10 22 21 +10 11 22 +11 23 22 +11 12 23 +12 24 23 +12 13 24 +13 25 24 +13 14 25 +14 26 25 +14 15 26 +15 27 26 +15 16 27 +16 28 27 +16 17 28 +17 29 28 +17 18 29 +18 30 29 +18 19 30 +19 31 30 +19 8 31 +8 20 31 +20 21 32 +21 33 32 +21 22 33 +22 34 33 +22 23 34 +23 35 34 +23 24 35 +24 36 35 +24 25 36 +25 37 36 +25 26 37 +26 38 37 +26 27 38 +27 39 38 +27 28 39 +28 40 39 +28 29 40 +29 41 40 +29 30 41 +30 42 41 +30 31 42 +31 43 42 +31 20 43 +20 32 43 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/mine/frame.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/mine/frame.png new file mode 100644 index 00000000..388e7814 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/mine/frame.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/mine/parts.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/mine/parts.png new file mode 100755 index 00000000..744ba8dc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/mine/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/preview.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/preview.png new file mode 100644 index 00000000..9bb6e71c Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/receptor.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/receptor.png new file mode 100755 index 00000000..944c0ac9 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/receptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/downBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/downBodyActive.png new file mode 100644 index 00000000..95b53b98 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/downBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/downBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/downBodyInactive.png new file mode 100644 index 00000000..d2244272 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/downBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/downBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/downBottomCapActive.png new file mode 100644 index 00000000..c1bbbc51 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/downBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/downBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/downBottomCapInactive.png new file mode 100644 index 00000000..49a368d5 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/downBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/leftBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/leftBodyActive.png new file mode 100644 index 00000000..996fb0e7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/leftBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/leftBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/leftBodyInactive.png new file mode 100644 index 00000000..ad960a9e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/leftBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/leftBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/leftBottomCapActive.png new file mode 100644 index 00000000..370dcabc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/leftBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/leftBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/leftBottomCapInactive.png new file mode 100644 index 00000000..15bb5cb5 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/leftBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/rightBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/rightBodyActive.png new file mode 100644 index 00000000..d800e059 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/rightBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/rightBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/rightBodyInactive.png new file mode 100644 index 00000000..1c0eab5e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/rightBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/rightBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/rightBottomCapActive.png new file mode 100644 index 00000000..c3df4125 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/rightBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/rightBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/rightBottomCapInactive.png new file mode 100644 index 00000000..c394da03 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/rightBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/upBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/upBodyActive.png new file mode 100644 index 00000000..b09cbc9a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/upBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/upBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/upBodyInactive.png new file mode 100644 index 00000000..14173019 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/upBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/upBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/upBottomCapActive.png new file mode 100644 index 00000000..dd41e353 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/upBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/upBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/upBottomCapInactive.png new file mode 100644 index 00000000..00ee6e7e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/roll/upBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/arrow_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/arrow_gradient.frag new file mode 100644 index 00000000..7d068f93 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/arrow_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform float time; +uniform float quant; + +void main() { + vec4 col = texture2D( + sampler0, + vec2(vUvs.x + 0.0625 * quant, mod(vUvs.y - time, 2.0) / 2.0) + ); + gl_FragColor = col; +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/lift_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/lift_gradient.frag new file mode 100644 index 00000000..5ee858ec --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/lift_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform float time; +uniform float quant; + +void main() { + vec4 col = texture2D( + sampler0, + vec2(mod(vUvs.x - time, 2.0) / 2.0, vUvs.y) + ); + gl_FragColor = col; +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/mine_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/mine_gradient.frag new file mode 100644 index 00000000..f1d32938 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/mine_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform sampler2D sampler1; +uniform float time; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + if (col.a < 1.0) { + discard; + } + gl_FragColor = texture2D(sampler1, vec2(mod(col.r - time, 1.0), 0.625)); +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/noop.frag b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/noop.frag new file mode 100644 index 00000000..3a329fdc --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/noop.frag @@ -0,0 +1,12 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + + gl_FragColor = col; + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/noop.vert b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/noop.vert new file mode 100644 index 00000000..002c3782 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/shader/noop.vert @@ -0,0 +1,24 @@ +precision mediump float; + +attribute vec2 aVertexPosition; +attribute vec2 aUvs; + +uniform mat3 translationMatrix; +uniform mat3 projectionMatrix; +uniform float time; + +varying vec2 vUvs; + +float rand(vec2 co) { + return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); +} + +void main() { + vUvs = aUvs; + gl_Position = vec4( + (projectionMatrix * translationMatrix * vec3(aVertexPosition.xy, 1.0)).xy, + 0.0, + 1.0 + ); + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/tap/body.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/tap/body.txt new file mode 100755 index 00000000..bd140837 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/tap/body.txt @@ -0,0 +1,152 @@ +78 +1.500000 21.000000 0.544734 0.059122 +1.500000 28.000000 0.544734 0.000000 +-1.500000 28.000000 0.543998 0.000000 +-1.500000 21.000000 0.543998 0.059122 +-8.000000 21.500000 0.542404 0.054899 +-4.000000 18.500000 0.543385 0.080236 +-1.500000 4.000000 0.543998 0.202703 +0.000000 5.000000 0.544366 0.194257 +-4.000000 9.000000 0.543385 0.160473 +-4.000000 6.500000 0.543385 0.181588 +1.500000 4.000000 0.544734 0.202703 +4.000000 6.500000 0.545347 0.181588 +4.000000 9.000000 0.545347 0.160473 +4.000000 18.500000 0.545347 0.080236 +8.000000 21.500000 0.546328 0.054899 +4.000000 -5.000000 0.545347 0.278716 +19.500000 5.000000 0.549148 0.194257 +18.500000 10.000000 0.548903 0.152027 +10.000000 0.000000 0.546819 0.236486 +8.500000 -6.000000 0.546451 0.287162 +5.500000 -6.000000 0.545715 0.287162 +0.000000 -9.000000 0.544366 0.312500 +1.500000 -10.000000 0.544734 0.320946 +-1.500000 -10.000000 0.543998 0.320946 +-4.000000 -5.000000 0.543385 0.278716 +-5.500000 -6.000000 0.543017 0.287162 +-10.000000 0.000000 0.541914 0.236486 +-8.500000 -6.000000 0.542282 0.287162 +-18.500000 10.000000 0.539829 0.152027 +-19.500000 5.000000 0.539584 0.194257 +-23.500000 10.000000 0.538603 0.152027 +-22.000000 5.000000 0.538971 0.194257 +-28.000000 5.500000 0.537500 0.190034 +-23.000000 4.000000 0.538726 0.202703 +-23.000000 1.500000 0.538726 0.223818 +-28.000000 0.500000 0.537500 0.232264 +0.000000 -27.500000 0.544366 0.468750 +0.000000 -21.500000 0.544366 0.418074 +28.000000 0.500000 0.551233 0.232264 +23.000000 1.500000 0.550007 0.223818 +28.000000 5.500000 0.551233 0.190034 +23.000000 4.000000 0.550007 0.202703 +23.500000 10.000000 0.550129 0.152027 +22.000000 5.000000 0.549761 0.194257 +1.500000 21.000000 0.503271 0.001033 +-1.500000 21.000000 0.503014 0.001033 +-4.000000 18.500000 0.502800 0.001033 +4.000000 18.500000 0.503485 0.001033 +-4.000000 9.000000 0.502800 0.001033 +4.000000 9.000000 0.503485 0.001033 +0.000000 5.000000 0.503271 0.001033 +1.500000 4.000000 0.510899 0.001033 +4.000000 -5.000000 0.511113 0.001033 +4.000000 6.500000 0.511113 0.001033 +0.000000 -9.000000 0.510899 0.001033 +-1.500000 4.000000 0.510642 0.001033 +0.000000 -9.000000 0.510642 0.001033 +-4.000000 6.500000 0.510428 0.001033 +-4.000000 -5.000000 0.510428 0.001033 +-1.500000 -10.000000 0.519033 0.001033 +0.000000 -21.500000 0.519161 0.001033 +1.500000 -10.000000 0.519289 0.001033 +8.500000 -6.000000 0.519889 0.001033 +-8.500000 -6.000000 0.518433 0.001033 +23.000000 4.000000 0.521130 0.001033 +22.000000 5.000000 0.521045 0.001033 +19.500000 5.000000 0.520831 0.001033 +23.000000 1.500000 0.521130 0.001033 +-19.500000 5.000000 0.517491 0.001033 +-22.000000 5.000000 0.517277 0.001033 +-23.000000 4.000000 0.517191 0.001033 +-23.000000 1.500000 0.517191 0.001033 +5.500000 -6.000000 0.519632 0.001033 +-5.500000 -6.000000 0.518690 0.001033 +8.000000 0.000000 0.546328 0.236486 +-8.000000 0.000000 0.542404 0.236486 +-10.000000 1.500000 0.541914 0.223818 +10.000000 1.500000 0.546819 0.223818 +72 +0 1 2 +2 3 0 +3 2 4 +3 4 5 +6 7 8 +8 9 6 +10 7 7 +6 10 7 +11 12 7 +10 11 7 +13 14 1 +13 1 0 +14 13 15 +16 17 18 +19 16 18 +20 19 18 +15 20 18 +21 22 20 +15 21 20 +23 22 21 +24 25 23 +24 23 21 +26 25 24 +26 27 25 +28 29 27 +27 26 28 +28 30 29 +30 31 29 +32 33 31 +31 30 32 +32 34 33 +32 35 34 +36 37 34 +34 35 36 +36 38 39 +36 39 37 +38 40 39 +40 41 39 +42 43 41 +42 41 40 +16 43 42 +16 42 17 +44 45 46 +46 47 44 +48 49 47 +48 47 46 +48 50 49 +51 52 53 +54 52 51 +54 51 55 +56 55 57 +57 58 56 +59 60 61 +60 62 61 +60 59 63 +64 65 66 +66 67 64 +68 69 70 +68 70 71 +60 63 68 +68 71 60 +66 62 60 +60 67 66 +61 62 72 +59 73 63 +15 18 74 +14 15 74 +26 24 75 +75 24 5 +5 4 75 +26 76 28 +17 77 18 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/tap/frame.txt b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/tap/frame.txt new file mode 100755 index 00000000..92aa8fd2 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/tap/frame.txt @@ -0,0 +1,66 @@ +34 +1.500000 28.000000 0.128125 0.266667 +2.000000 30.000000 0.129167 0.250000 +-2.000000 30.000000 0.120833 0.250000 +-1.500000 28.000000 0.121875 0.266667 +8.000000 21.500000 0.141667 0.320833 +10.000000 22.000000 0.145833 0.316667 +10.000000 0.000000 0.145833 0.500000 +18.500000 10.000000 0.163542 0.416667 +18.000000 12.000000 0.162500 0.400000 +10.000000 4.000000 0.145833 0.466666 +23.500000 10.000000 0.173958 0.416667 +24.000000 12.000000 0.175000 0.400000 +-18.500000 10.000000 0.086458 0.416667 +-10.000000 4.000000 0.104167 0.466666 +-18.000000 12.000000 0.087500 0.400000 +-10.000000 0.000000 0.104167 0.500000 +-8.000000 21.500000 0.108333 0.320833 +-10.000000 22.000000 0.104167 0.316667 +28.000000 5.500000 0.183333 0.454166 +30.000000 6.000000 0.187500 0.450000 +30.000000 0.000000 0.187500 0.500000 +28.000000 0.500000 0.183333 0.495833 +0.000000 -27.500000 0.125000 0.729166 +0.000000 -30.000000 0.125000 0.750000 +-28.000000 0.500000 0.066667 0.495833 +-30.000000 0.000000 0.062500 0.500000 +-28.000000 5.500000 0.066667 0.454166 +-30.000000 6.000000 0.062500 0.450000 +-23.500000 10.000000 0.076042 0.416667 +-24.000000 12.000000 0.075000 0.400000 +8.000000 0.000000 0.141667 0.500000 +-8.000000 0.000000 0.108333 0.500000 +-10.000000 1.500000 0.104167 0.487500 +10.000000 1.500000 0.145833 0.487500 +30 +0 1 2 +3 0 2 +4 5 1 +1 0 4 +5 4 6 +7 8 9 +10 11 8 +8 7 10 +12 13 14 +15 16 17 +16 3 2 +16 2 17 +18 19 11 +11 10 18 +20 19 18 +18 21 20 +22 23 20 +20 21 22 +22 24 25 +25 23 22 +26 27 25 +25 24 26 +28 29 27 +27 26 28 +12 14 29 +29 28 12 +4 30 6 +15 31 16 +32 13 12 +7 9 33 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/tap/parts.png b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/tap/parts.png new file mode 100644 index 00000000..7c5cedfd Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/ddr-rainbow/tap/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/default/ModelRenderer.ts b/app/src/chart/gameTypes/noteskin/dance/default/ModelRenderer.ts new file mode 100644 index 00000000..d7eb1755 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/default/ModelRenderer.ts @@ -0,0 +1,203 @@ +import { + BaseTexture, + Container, + Geometry, + Mesh, + MIPMAP_MODES, + Rectangle, + RenderTexture, + Shader, + Sprite, + Texture, +} from "pixi.js" + +import { App } from "../../../../../App" +import { Options } from "../../../../../util/Options" +import { NotedataEntry } from "../../../../sm/NoteTypes" + +import liftPartsUrl from "./lift/parts.png" +import mineFrameUrl from "./mine/frame.png" +import minePartsUrl from "./mine/parts.png" +import tapPartsUrl from "./tap/parts.png" + +import arrowGradientFrag from "./shader/arrow_gradient.frag?raw" +import liftGradientFrag from "./shader/lift_gradient.frag?raw" +import mineGradientFrag from "./shader/mine_gradient.frag?raw" +import noopFrag from "./shader/noop.frag?raw" +import noopVert from "./shader/noop.vert?raw" + +import arrowBodyGeomText from "./tap/body.txt?raw" +import arrowFrameGeomText from "./tap/frame.txt?raw" + +import { loadGeometry } from "../../../../../util/Util" +import liftBodyGeomText from "./lift/body.txt?raw" +import mineBodyGeomText from "./mine/body.txt?raw" + +const mine_frame_texture = Texture.from(mineFrameUrl) + +export class ModelRenderer { + static arrowPartsTex = BaseTexture.from(tapPartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static minePartsTex = BaseTexture.from(minePartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static liftPartsTex = BaseTexture.from(liftPartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + + static arrowBodyGeom: Geometry + static arrowFrameGeom: Geometry + static liftBodyGeom: Geometry + static mineBodyGeom: Geometry + + static arrowFrameTex: RenderTexture + static arrowFrame: Mesh + static arrowTex: RenderTexture + static arrowContainer = new Container() + static liftTex: RenderTexture + static liftContainer = new Container() + static mineTex: RenderTexture + static mineContainer = new Container() + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + ModelRenderer.arrowFrameTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + ModelRenderer.arrowTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.liftTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.mineTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + + this.arrowBodyGeom = await loadGeometry(arrowBodyGeomText) + this.arrowFrameGeom = await loadGeometry(arrowFrameGeomText) + this.mineBodyGeom = await loadGeometry(mineBodyGeomText) + this.liftBodyGeom = await loadGeometry(liftBodyGeomText) + + { + const shader_frame = Shader.from(noopVert, noopFrag, { + sampler0: this.arrowPartsTex, + }) + const arrow_frame = new Mesh(ModelRenderer.arrowFrameGeom, shader_frame) + arrow_frame.x = 32 + arrow_frame.y = 32 + arrow_frame.rotation = -Math.PI / 2 + this.arrowFrame = arrow_frame + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.arrowPartsTex, + time: 0, + quant: i, + }) + const arrow_frame = new Sprite(ModelRenderer.arrowFrameTex) + arrow_frame.x = (i % 3) * 64 + arrow_frame.y = Math.floor(i / 3) * 64 + const arrow_body = new Mesh(ModelRenderer.arrowBodyGeom, shader_body) + arrow_body.x = (i % 3) * 64 + 32 + arrow_body.y = Math.floor(i / 3) * 64 + 32 + arrow_body.rotation = -Math.PI / 2 + arrow_body.name = "body" + i + ModelRenderer.arrowContainer.addChild(arrow_frame) + ModelRenderer.arrowContainer.addChild(arrow_body) + } + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, liftGradientFrag, { + sampler0: this.liftPartsTex, + time: 0, + quant: i, + }) + const arrow_body = new Mesh(ModelRenderer.liftBodyGeom, shader_body) + arrow_body.x = (i % 3) * 64 + 32 + arrow_body.y = Math.floor(i / 3) * 64 + 32 + arrow_body.rotation = -Math.PI / 2 + arrow_body.name = "body" + i + ModelRenderer.liftContainer.addChild(arrow_body) + } + } + { + const shader_body = Shader.from(noopVert, mineGradientFrag, { + sampler0: this.minePartsTex, + time: 0, + }) + const mine_body = new Mesh(ModelRenderer.mineBodyGeom, shader_body) + const mine_frame = new Sprite(mine_frame_texture) + mine_frame.width = 64 + mine_frame.height = 64 + mine_frame.anchor.set(0.5) + mine_frame.pivot.y = 3 // epic recenter + ModelRenderer.mineContainer.position.set(32) + ModelRenderer.mineContainer.addChild(mine_body) + ModelRenderer.mineContainer.addChild(mine_frame) + } + + this.loaded = true + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const beat = app.chartManager.chartView!.getVisualBeat() + const second = app.chartManager.chartView!.getVisualTime() + for (let i = 0; i < 10; i++) { + const tapShader: Mesh = + ModelRenderer.arrowContainer.getChildByName("body" + i)! + tapShader.shader.uniforms.time = beat + const liftShader: Mesh = + ModelRenderer.liftContainer.getChildByName("body" + i)! + liftShader.shader.uniforms.time = beat + } + ;(ModelRenderer.mineContainer.children[0]).shader.uniforms.time = + second + ModelRenderer.mineContainer.rotation = (second % 1) * Math.PI * 2 + + app.renderer.render(ModelRenderer.arrowFrame, { + renderTexture: ModelRenderer.arrowFrameTex, + }) + app.renderer.render(ModelRenderer.arrowContainer, { + renderTexture: ModelRenderer.arrowTex, + }) + app.renderer.render(ModelRenderer.mineContainer, { + renderTexture: ModelRenderer.mineTex, + }) + app.renderer.render(ModelRenderer.liftContainer, { + renderTexture: ModelRenderer.liftTex, + }) + } + + static setNoteTex(arrow: Sprite, note: NotedataEntry | undefined) { + if (note !== undefined && note.type == "Mine") { + arrow.texture = ModelRenderer.mineTex + } else { + const i = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + note?.quant ?? 4 + ) + arrow.texture = new Texture( + note?.type == "Lift" ?? "Tap" + ? ModelRenderer.liftTex.baseTexture + : ModelRenderer.arrowTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/default/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/default/NoteFlash.ts new file mode 100644 index 00000000..3cb24a82 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/default/NoteFlash.ts @@ -0,0 +1,163 @@ +import { BLEND_MODES, Container, Sprite, Texture } from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import bezier from "bezier-easing" +import { BezierAnimator } from "../../../../../util/BezierEasing" +import w4Url from "./flash/decent.png" +import w2Url from "./flash/excellent.png" +import w0Url from "./flash/fantastic.png" +import w3Url from "./flash/great.png" +import holdFlashUrl from "./flash/hold.png" +import mineUrl from "./flash/mine.png" +import w5Url from "./flash/way_off.png" +import w1Url from "./flash/white_fantastic.png" + +const flashTex = { + hold: Texture.from(holdFlashUrl), + w0: Texture.from(w0Url), + w1: Texture.from(w1Url), + w2: Texture.from(w2Url), + w3: Texture.from(w3Url), + w4: Texture.from(w4Url), + w5: Texture.from(w5Url), + mine: Texture.from(mineUrl), +} + +export class NoteFlashContainer extends Container { + holdExplosion = new Sprite(flashTex.hold) + standard: Record = { + w0: new Sprite(flashTex.w0), + w1: new Sprite(flashTex.w1), + w2: new Sprite(flashTex.w2), + w3: new Sprite(flashTex.w3), + w4: new Sprite(flashTex.w4), + w5: new Sprite(flashTex.w5), + } + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + if (this.standard[event.judgement.id]) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + const obj = this.standard[event.judgement.id] + this.anims.add( + BezierAnimator.animate( + obj, + { + "0": { + alpha: 1.2, + "scale.x": 1.1, + "scale.y": 1.1, + }, + "1": { + alpha: 0, + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.15, + bezier(0.11, 0, 0.5, 0) + ) + ) + } + } + }) + + noteskin.on(this, "held", event => { + if (col == event.columnNumber) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + const obj = this.standard.w2 + BezierAnimator.animate( + obj, + { + "0": { + alpha: 1.2, + "scale.x": 1.1, + "scale.y": 1.1, + }, + "1": { + alpha: 0, + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.09, + bezier(0.11, 0, 0.5, 0) + ) + } + }) + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(flashTex.mine) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + noteskin.onUpdate(this, () => { + this.holdExplosion.alpha = + Math.sin((Date.now() / 1000) * Math.PI * 2 * 20) * 0.1 + 1 + }) + + this.holdExplosion.visible = false + this.holdExplosion.anchor.set(0.5) + this.addChild(this.holdExplosion) + for (const item of Object.values(this.standard)) { + item.alpha = 0 + item.anchor.set(0.5) + this.addChild(item) + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/default/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/default/Noteskin.ts new file mode 100644 index 00000000..79119874 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/default/Noteskin.ts @@ -0,0 +1,190 @@ +// Peters-Cel by Pete-Lawrence https://github.com/Pete-Lawrence/Peters-Noteskins/ +// Custom lifts and color modifications by tillvit + +import { BLEND_MODES, Container, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" + +import { ModelRenderer } from "./ModelRenderer" +import { NoteFlashContainer } from "./NoteFlash" +import receptorUrl from "./receptor.png" + +import holdBodyActiveUrl from "./hold/bodyActive.png" +import holdBodyInactiveUrl from "./hold/bodyInactive.png" +import holdBottomCapActiveUrl from "./hold/bottomCapActive.png" +import holdBottomCapInactiveUrl from "./hold/bottomCapInactive.png" +import holdTopCapActiveUrl from "./hold/topCapActive.png" +import holdTopCapInactiveUrl from "./hold/topCapInactive.png" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { rgbtoHex } from "../../../../../util/Color" +import { clamp } from "../../../../../util/Math" +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" +import rollBodyActiveUrl from "./roll/bodyActive.png" +import rollBodyInactiveUrl from "./roll/bodyInactive.png" +import rollBottomCapActiveUrl from "./roll/bottomCapActive.png" +import rollBottomCapInactiveUrl from "./roll/bottomCapInactive.png" +import rollTopCapActiveUrl from "./roll/topCapActive.png" +import rollTopCapInactiveUrl from "./roll/topCapInactive.png" + +const receptorTex = Texture.from(receptorUrl) + +const holdTex = { + hold: { + active: { + body: Texture.from(holdBodyActiveUrl), + topCap: Texture.from(holdTopCapActiveUrl), + bottomCap: Texture.from(holdBottomCapActiveUrl), + }, + inactive: { + body: Texture.from(holdBodyInactiveUrl), + topCap: Texture.from(holdTopCapInactiveUrl), + bottomCap: Texture.from(holdBottomCapInactiveUrl), + }, + }, + roll: { + active: { + body: Texture.from(rollBodyActiveUrl), + topCap: Texture.from(rollTopCapActiveUrl), + bottomCap: Texture.from(rollBottomCapActiveUrl), + }, + inactive: { + body: Texture.from(rollBodyInactiveUrl), + topCap: Texture.from(rollTopCapInactiveUrl), + bottomCap: Texture.from(rollBottomCapInactiveUrl), + }, + }, +} + +const rotationMap: Record = { + Left: 0, + Down: -90, + Up: 90, + Right: 180, + UpLeft: 45, + UpRight: 135, + DownRight: -135, + DownLeft: -45, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", + "NoteFlash", +] + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + const container = new Container() + const spr = new Sprite(receptorTex) + spr.width = 69 + spr.height = 69 + spr.anchor.set(0.5) + const overlay = new Sprite(receptorTex) + overlay.width = 69 + overlay.height = 69 + overlay.alpha = 0 + overlay.blendMode = BLEND_MODES.ADD + overlay.anchor.set(0.5) + container.addChild(spr, overlay) + options.noteskin.on( + container, + "press", + opt => + opt.columnNumber == options.columnNumber && (overlay.alpha = 0.2) + ) + options.noteskin.on( + container, + "lift", + opt => opt.columnNumber == options.columnNumber && (overlay.alpha = 0) + ) + options.noteskin.on(container, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + container, + { + "0": { + "scale.x": 0.75, + "scale.y": 0.75, + }, + "1": { + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.11 + ) + } + }) + options.noteskin.onUpdate(container, r => { + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + const col = clamp(1 - partialBeat, 0.5, 1) * 255 + spr.tint = rgbtoHex(col, col, col) + }) + return container + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + ModelRenderer.setNoteTex(spr, options.note) + spr.anchor.set(0.5) + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer(options.noteskin, options.columnNumber) + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: { element: "Tap" }, + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "Hold Active Body": () => new HoldBody(holdTex.hold.active.body), + "Hold Inactive Body": () => new HoldBody(holdTex.hold.inactive.body), + "Hold Active TopCap": () => new Sprite(Texture.EMPTY), + "Hold Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Hold Active BottomCap": () => + new HoldTail(holdTex.hold.active.bottomCap), + "Hold Inactive BottomCap": () => + new HoldTail(holdTex.hold.inactive.bottomCap), + + "Roll Active Head": { element: "Tap" }, + "Roll Inactive Head": { element: "Tap" }, + "Roll Active Body": () => new HoldBody(holdTex.roll.active.body), + "Roll Inactive Body": () => new HoldBody(holdTex.roll.inactive.body), + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": () => + new HoldTail(holdTex.roll.active.bottomCap), + "Roll Inactive BottomCap": () => + new HoldTail(holdTex.roll.inactive.bottomCap), + }, + }, + load: function (element, options) { + const col = element.columnName + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + init() { + ModelRenderer.initArrowTex() + }, + update(renderer) { + ModelRenderer.setArrowTexTime(renderer.chartManager.app) + }, + hideIcons: ["Lift"], +} satisfies NoteskinOptions diff --git a/app/assets/noteskin/dance/default/flash/decent.png b/app/src/chart/gameTypes/noteskin/dance/default/flash/decent.png similarity index 100% rename from app/assets/noteskin/dance/default/flash/decent.png rename to app/src/chart/gameTypes/noteskin/dance/default/flash/decent.png diff --git a/app/assets/noteskin/dance/default/flash/excellent.png b/app/src/chart/gameTypes/noteskin/dance/default/flash/excellent.png similarity index 100% rename from app/assets/noteskin/dance/default/flash/excellent.png rename to app/src/chart/gameTypes/noteskin/dance/default/flash/excellent.png diff --git a/app/assets/noteskin/dance/default/flash/fantastic.png b/app/src/chart/gameTypes/noteskin/dance/default/flash/fantastic.png similarity index 100% rename from app/assets/noteskin/dance/default/flash/fantastic.png rename to app/src/chart/gameTypes/noteskin/dance/default/flash/fantastic.png diff --git a/app/assets/noteskin/dance/default/flash/great.png b/app/src/chart/gameTypes/noteskin/dance/default/flash/great.png similarity index 100% rename from app/assets/noteskin/dance/default/flash/great.png rename to app/src/chart/gameTypes/noteskin/dance/default/flash/great.png diff --git a/app/assets/noteskin/dance/default/flash/hold.png b/app/src/chart/gameTypes/noteskin/dance/default/flash/hold.png similarity index 100% rename from app/assets/noteskin/dance/default/flash/hold.png rename to app/src/chart/gameTypes/noteskin/dance/default/flash/hold.png diff --git a/app/src/chart/gameTypes/noteskin/dance/default/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/default/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/default/flash/mine.png differ diff --git a/app/assets/noteskin/dance/default/flash/way_off.png b/app/src/chart/gameTypes/noteskin/dance/default/flash/way_off.png similarity index 100% rename from app/assets/noteskin/dance/default/flash/way_off.png rename to app/src/chart/gameTypes/noteskin/dance/default/flash/way_off.png diff --git a/app/assets/noteskin/dance/default/flash/white_fantastic.png b/app/src/chart/gameTypes/noteskin/dance/default/flash/white_fantastic.png similarity index 100% rename from app/assets/noteskin/dance/default/flash/white_fantastic.png rename to app/src/chart/gameTypes/noteskin/dance/default/flash/white_fantastic.png diff --git a/app/assets/noteskin/dance/default/hold/body.png b/app/src/chart/gameTypes/noteskin/dance/default/hold/bodyActive.png similarity index 100% rename from app/assets/noteskin/dance/default/hold/body.png rename to app/src/chart/gameTypes/noteskin/dance/default/hold/bodyActive.png diff --git a/app/src/chart/gameTypes/noteskin/dance/default/hold/bodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/default/hold/bodyInactive.png new file mode 100755 index 00000000..8901979b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/default/hold/bodyInactive.png differ diff --git a/app/assets/noteskin/dance/default/hold/cap.png b/app/src/chart/gameTypes/noteskin/dance/default/hold/bottomCapActive.png similarity index 100% rename from app/assets/noteskin/dance/default/hold/cap.png rename to app/src/chart/gameTypes/noteskin/dance/default/hold/bottomCapActive.png diff --git a/app/src/chart/gameTypes/noteskin/dance/default/hold/bottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/default/hold/bottomCapInactive.png new file mode 100755 index 00000000..20ce0a51 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/default/hold/bottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/default/hold/topCapActive.png b/app/src/chart/gameTypes/noteskin/dance/default/hold/topCapActive.png new file mode 100755 index 00000000..84ebac1b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/default/hold/topCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/default/hold/topCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/default/hold/topCapInactive.png new file mode 100755 index 00000000..3906fc16 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/default/hold/topCapInactive.png differ diff --git a/app/assets/noteskin/dance/default/lift/body.txt b/app/src/chart/gameTypes/noteskin/dance/default/lift/body.txt similarity index 100% rename from app/assets/noteskin/dance/default/lift/body.txt rename to app/src/chart/gameTypes/noteskin/dance/default/lift/body.txt diff --git a/app/assets/noteskin/dance/default/lift/parts.png b/app/src/chart/gameTypes/noteskin/dance/default/lift/parts.png similarity index 100% rename from app/assets/noteskin/dance/default/lift/parts.png rename to app/src/chart/gameTypes/noteskin/dance/default/lift/parts.png diff --git a/app/assets/noteskin/dance/default/lift/parts_old.png b/app/src/chart/gameTypes/noteskin/dance/default/lift/parts_old.png similarity index 100% rename from app/assets/noteskin/dance/default/lift/parts_old.png rename to app/src/chart/gameTypes/noteskin/dance/default/lift/parts_old.png diff --git a/app/src/chart/gameTypes/noteskin/dance/default/mine/body.txt b/app/src/chart/gameTypes/noteskin/dance/default/mine/body.txt new file mode 100755 index 00000000..0bf678f4 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/default/mine/body.txt @@ -0,0 +1,103 @@ +45 +-16.0 0.0 0 0.75 +-11.31 -11.31 0 0.75 + 0.0 -16.0 0 0.75 + 11.31 -11.31 0 0.75 + 16.0 0.0 0 0.75 + 11.31 11.31 0 0.75 + -0.0 16.0 0 0.75 +-11.31 11.31 0 0.75 +29.422066 0.009519 -0.999500 0.25 +25.401829 14.852878 -0.931249 0.1240015 +14.843359 25.411350 -0.751997 0.0343755 +-0.000001 29.431583 -0.500000 0.00025 +-14.843361 25.411348 -0.248003 0.0343755 +-25.401833 14.852878 -0.068751 0.1240015 +-29.422066 0.009516 -0.000500 0.25 +-25.401827 -14.833844 -0.068751 0.3759985 +-14.843357 -25.392317 -0.248003 0.4656245 +0.000004 -29.412546 -0.500000 0.49975 +14.843363 -25.392307 -0.751997 0.4656245 +25.401833 -14.833840 -0.931249 0.3759985 +19.921448 0.009518 0.838208 0.25 +17.189407 10.096646 0.791826 0.164375 +10.087127 17.198929 0.671250 0.104087 +-0.000000 19.930965 0.500000 0.080896 +-10.087129 17.198925 0.328750 0.104087 +-17.189411 10.096646 0.208174 0.164375 +-19.921448 0.009518 0.161792 0.25 +-17.189405 -10.077612 0.208174 0.335625 +-10.087127 -17.179895 0.328750 0.395913 +0.000001 -19.911928 0.500000 0.419104 +10.087130 -17.179886 0.671250 0.395913 +17.189411 -10.077609 0.791826 0.335625 +10.420829 0.009516 -0.676915 0.25 +8.976986 5.340415 -0.652403 0.2047485 +5.330894 8.986506 -0.590503 0.1737985 +0.000001 10.430347 -0.500000 0.1615425 +-5.330897 8.986505 -0.409497 0.1737985 +-8.976989 5.340414 -0.347597 0.2047485 +-10.420829 0.009520 -0.323085 0.25 +-8.976985 -5.321381 -0.347597 0.2952515 +-5.330895 -8.967473 -0.409497 0.3262015 +-0.000003 -10.411310 -0.500000 0.3384575 +5.330898 -8.967464 -0.590503 0.3262015 +8.976988 -5.321376 -0.652403 0.2952515 +0 0 0.5 0.75 +56 +44 0 1 +44 1 2 +44 2 3 +44 3 4 +44 4 5 +44 5 6 +44 6 7 +44 7 0 +8 9 20 +9 21 20 +9 10 21 +10 22 21 +10 11 22 +11 23 22 +11 12 23 +12 24 23 +12 13 24 +13 25 24 +13 14 25 +14 26 25 +14 15 26 +15 27 26 +15 16 27 +16 28 27 +16 17 28 +17 29 28 +17 18 29 +18 30 29 +18 19 30 +19 31 30 +19 8 31 +8 20 31 +20 21 32 +21 33 32 +21 22 33 +22 34 33 +22 23 34 +23 35 34 +23 24 35 +24 36 35 +24 25 36 +25 37 36 +25 26 37 +26 38 37 +26 27 38 +27 39 38 +27 28 39 +28 40 39 +28 29 40 +29 41 40 +29 30 41 +30 42 41 +30 31 42 +31 43 42 +31 20 43 +20 32 43 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/default/mine/frame.png b/app/src/chart/gameTypes/noteskin/dance/default/mine/frame.png new file mode 100644 index 00000000..388e7814 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/default/mine/frame.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/default/mine/parts.png b/app/src/chart/gameTypes/noteskin/dance/default/mine/parts.png new file mode 100755 index 00000000..744ba8dc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/default/mine/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/default/preview.png b/app/src/chart/gameTypes/noteskin/dance/default/preview.png new file mode 100644 index 00000000..d7347ea8 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/default/preview.png differ diff --git a/app/assets/noteskin/dance/default/receptor.png b/app/src/chart/gameTypes/noteskin/dance/default/receptor.png similarity index 100% rename from app/assets/noteskin/dance/default/receptor.png rename to app/src/chart/gameTypes/noteskin/dance/default/receptor.png diff --git a/app/assets/noteskin/dance/default/roll/body.png b/app/src/chart/gameTypes/noteskin/dance/default/roll/bodyActive.png similarity index 100% rename from app/assets/noteskin/dance/default/roll/body.png rename to app/src/chart/gameTypes/noteskin/dance/default/roll/bodyActive.png diff --git a/app/src/chart/gameTypes/noteskin/dance/default/roll/bodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/default/roll/bodyInactive.png new file mode 100755 index 00000000..312b2196 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/default/roll/bodyInactive.png differ diff --git a/app/assets/noteskin/dance/default/roll/cap.png b/app/src/chart/gameTypes/noteskin/dance/default/roll/bottomCapActive.png similarity index 100% rename from app/assets/noteskin/dance/default/roll/cap.png rename to app/src/chart/gameTypes/noteskin/dance/default/roll/bottomCapActive.png diff --git a/app/src/chart/gameTypes/noteskin/dance/default/roll/bottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/default/roll/bottomCapInactive.png new file mode 100755 index 00000000..253e3361 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/default/roll/bottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/default/roll/topCapActive.png b/app/src/chart/gameTypes/noteskin/dance/default/roll/topCapActive.png new file mode 100755 index 00000000..0ed020ec Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/default/roll/topCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/default/roll/topCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/default/roll/topCapInactive.png new file mode 100755 index 00000000..38de3524 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/default/roll/topCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/default/shader/arrow_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/default/shader/arrow_gradient.frag new file mode 100644 index 00000000..7d068f93 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/default/shader/arrow_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform float time; +uniform float quant; + +void main() { + vec4 col = texture2D( + sampler0, + vec2(vUvs.x + 0.0625 * quant, mod(vUvs.y - time, 2.0) / 2.0) + ); + gl_FragColor = col; +} diff --git a/app/src/chart/gameTypes/noteskin/dance/default/shader/lift_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/default/shader/lift_gradient.frag new file mode 100644 index 00000000..eaf25e59 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/default/shader/lift_gradient.frag @@ -0,0 +1,18 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform float time; +uniform float quant; + +void main() { + vec4 col = texture2D( + sampler0, + vec2(vUvs.x + 0.0625 * quant, mod(1.2 - vUvs.y - time, 2.0) / 2.0) + ); + if (vUvs.x < 0.375) { + col = texture2D(sampler0, vUvs); + } + gl_FragColor = col; +} diff --git a/app/src/chart/gameTypes/noteskin/dance/default/shader/mine_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/default/shader/mine_gradient.frag new file mode 100644 index 00000000..f1d32938 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/default/shader/mine_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform sampler2D sampler1; +uniform float time; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + if (col.a < 1.0) { + discard; + } + gl_FragColor = texture2D(sampler1, vec2(mod(col.r - time, 1.0), 0.625)); +} diff --git a/app/src/chart/gameTypes/noteskin/dance/default/shader/noop.frag b/app/src/chart/gameTypes/noteskin/dance/default/shader/noop.frag new file mode 100644 index 00000000..3a329fdc --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/default/shader/noop.frag @@ -0,0 +1,12 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + + gl_FragColor = col; + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/default/shader/noop.vert b/app/src/chart/gameTypes/noteskin/dance/default/shader/noop.vert new file mode 100644 index 00000000..002c3782 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/default/shader/noop.vert @@ -0,0 +1,24 @@ +precision mediump float; + +attribute vec2 aVertexPosition; +attribute vec2 aUvs; + +uniform mat3 translationMatrix; +uniform mat3 projectionMatrix; +uniform float time; + +varying vec2 vUvs; + +float rand(vec2 co) { + return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); +} + +void main() { + vUvs = aUvs; + gl_Position = vec4( + (projectionMatrix * translationMatrix * vec3(aVertexPosition.xy, 1.0)).xy, + 0.0, + 1.0 + ); + +} diff --git a/app/assets/noteskin/dance/default/tap/body.txt b/app/src/chart/gameTypes/noteskin/dance/default/tap/body.txt similarity index 100% rename from app/assets/noteskin/dance/default/tap/body.txt rename to app/src/chart/gameTypes/noteskin/dance/default/tap/body.txt diff --git a/app/assets/noteskin/dance/default/tap/frame.txt b/app/src/chart/gameTypes/noteskin/dance/default/tap/frame.txt similarity index 100% rename from app/assets/noteskin/dance/default/tap/frame.txt rename to app/src/chart/gameTypes/noteskin/dance/default/tap/frame.txt diff --git a/app/assets/noteskin/dance/default/tap/parts.png b/app/src/chart/gameTypes/noteskin/dance/default/tap/parts.png similarity index 100% rename from app/assets/noteskin/dance/default/tap/parts.png rename to app/src/chart/gameTypes/noteskin/dance/default/tap/parts.png diff --git a/app/src/chart/gameTypes/noteskin/dance/dividebyzero/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/NoteFlash.ts new file mode 100644 index 00000000..279b9816 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/NoteFlash.ts @@ -0,0 +1,90 @@ +import { + AnimatedSprite, + BLEND_MODES, + Container, + Sprite, + Texture, +} from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" +import holdUrl from "./flash/hold.png" +import mineUrl from "./flash/mine.png" + +const holdTex = Texture.from(holdUrl) +const mineTex = Texture.from(mineUrl) + +export class NoteFlashContainer extends Container { + hold = new AnimatedSprite(splitTex(holdTex, 2, 1, 72, 128)[0]) + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.hold.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.hold.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.hold.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.hold.visible = false + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(mineTex) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + noteskin.onUpdate(this, () => { + this.hold.alpha = + Math.sin((Date.now() / 1000) * Math.PI * 2 * 20) * 0.1 + 1 + }) + + this.hold.visible = false + this.hold.anchor.set(0.5) + + this.addChild(this.hold) + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/dividebyzero/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/Noteskin.ts new file mode 100644 index 00000000..c57973b2 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/Noteskin.ts @@ -0,0 +1,193 @@ +// DivideByZero, bundled with Etterna +// Custom lifts by tillvit + +import { AnimatedSprite, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" + +import { BezierAnimator } from "../../../../../util/BezierEasing" + +import { NoteFlashContainer } from "./NoteFlash" + +import { splitTex } from "../../../../../util/Util" + +import rollBodyUrl from "./rollBody.png" + +import holdBodyUrl from "./holdBody.png" + +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" +import holdHeadUrl from "./holdHead.png" +import liftUrl from "./lift.png" +import mineUrl from "./mine.png" +import receptorUrl from "./receptor.png" +import tapUrl from "./tap.png" + +const receptorTex = Texture.from(receptorUrl) + +const mineTex = splitTex(Texture.from(mineUrl), 8, 1, 64, 64)[0] + +const tapTex = splitTex(Texture.from(tapUrl), 1, 8, 64, 64) +const holdHeadTex = splitTex(Texture.from(holdHeadUrl), 1, 8, 64, 64) + +const liftTex = splitTex(Texture.from(liftUrl), 1, 8, 64, 64) + +const holdTex = Texture.from(holdBodyUrl) +const rollTex = Texture.from(rollBodyUrl) + +const rotationMap: Record = { + Left: 90, + Down: 0, + Up: 180, + Right: -90, + UpLeft: 135, + UpRight: -135, + DownRight: -45, + DownLeft: 45, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", + "NoteFlash", +] + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + const spr = new AnimatedSprite(splitTex(receptorTex, 2, 1, 64, 64)[0]) + spr.width = 64 + spr.height = 64 + spr.anchor.set(0.5) + options.noteskin.on(spr, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + spr, + { + "0": { + alpha: 1.2, + width: 48, + height: 48, + }, + "1": { + alpha: 1, + width: 64, + height: 64, + }, + }, + 0.06 + ) + } + }) + options.noteskin.onUpdate(spr, r => { + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + spr.currentFrame = partialBeat < 0.2 ? 0 : 1 + }) + return spr + }, + Tap: options => { + const tex = + tapTex[ + Math.min( + [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + options.note?.quant ?? 4 + ) ?? 0, + 7 + ) + ] + const spr = new Sprite(tex[0]) + spr.anchor.set(0.5) + spr.width = 64 + spr.height = 64 + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer(options.noteskin, options.columnNumber) + }, + Fake: { element: "Tap" }, + Lift: options => { + const tex = + liftTex[ + Math.min( + [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + options.note?.quant ?? 4 + ) ?? 0, + 7 + ) + ] + const spr = new Sprite(tex[0]) + spr.anchor.set(0.5) + spr.width = 64 + spr.height = 64 + return spr + }, + Mine: opt => { + const spr = new AnimatedSprite(mineTex) + spr.anchor.set(0.5) + spr.width = 64 + spr.height = 64 + opt.noteskin.onUpdate( + spr, + r => + (spr.currentFrame = Math.floor( + (((r.getVisualBeat() % 4) + 4) % 4) * 2 + )) + ) + return spr + }, + "Hold Active Head": options => { + const tex = + holdHeadTex[ + Math.min( + [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + options.note?.quant ?? 4 + ) ?? 0, + 7 + ) + ] + const spr = new Sprite(tex[0]) + spr.anchor.set(0.5) + spr.width = 64 + spr.height = 64 + return spr + }, + "Hold Inactive Head": { element: "Tap" }, + "Hold Active Body": () => new HoldBody(holdTex), + "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 HoldTail(Texture.EMPTY), + "Hold Inactive BottomCap": { element: "Hold Active BottomCap" }, + + "Roll Active Head": { element: "Hold Active Head" }, + "Roll Inactive Head": { element: "Tap" }, + "Roll Active Body": () => new HoldBody(rollTex), + "Roll Inactive Body": { element: "Roll Active Body" }, + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": () => new HoldTail(Texture.EMPTY), + "Roll Inactive BottomCap": { element: "Roll Active BottomCap" }, + }, + }, + load: function (element, options) { + const col = element.columnName + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + hideIcons: ["Lift"], +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/dance/dividebyzero/flash/hold.png b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/flash/hold.png new file mode 100644 index 00000000..d251847f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/flash/hold.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/dividebyzero/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/dividebyzero/holdBody.png b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/holdBody.png new file mode 100644 index 00000000..1aa2d9e3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/holdBody.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/dividebyzero/holdHead.png b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/holdHead.png new file mode 100644 index 00000000..1a0fad6e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/holdHead.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/dividebyzero/lift.png b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/lift.png new file mode 100644 index 00000000..1b63871e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/lift.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/dividebyzero/mine.png b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/mine.png new file mode 100644 index 00000000..a44fad7b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/dividebyzero/preview.png b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/preview.png new file mode 100644 index 00000000..47e5b302 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/dividebyzero/receptor.png b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/receptor.png new file mode 100644 index 00000000..4b105671 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/receptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/dividebyzero/rollBody.png b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/rollBody.png new file mode 100644 index 00000000..3fac73cb Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/rollBody.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/dividebyzero/tap.png b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/tap.png new file mode 100644 index 00000000..38b0555a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/dividebyzero/tap.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/ModelRenderer.ts b/app/src/chart/gameTypes/noteskin/dance/metal/ModelRenderer.ts new file mode 100644 index 00000000..d6012b6d --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/metal/ModelRenderer.ts @@ -0,0 +1,202 @@ +import { + BaseTexture, + Container, + Geometry, + Mesh, + MIPMAP_MODES, + Rectangle, + RenderTexture, + Shader, + Sprite, + Texture, +} from "pixi.js" + +import liftPartsUrl from "./lift/parts.png" +import mineFrameUrl from "./mine/frame.png" +import minePartsUrl from "./mine/parts.png" +import tapPartsUrl from "./tap/parts.png" + +import arrowGradientFrag from "./shader/arrow_gradient.frag?raw" +import liftGradientFrag from "./shader/lift_gradient.frag?raw" +import mineGradientFrag from "./shader/mine_gradient.frag?raw" +import noopFrag from "./shader/noop.frag?raw" +import noopVert from "./shader/noop.vert?raw" + +import arrowBodyGeomText from "./tap/body.txt?raw" +import arrowFrameGeomText from "./tap/frame.txt?raw" + +import { App } from "../../../../../App" +import { Options } from "../../../../../util/Options" +import { loadGeometry } from "../../../../../util/Util" +import { NotedataEntry } from "../../../../sm/NoteTypes" +import liftBodyGeomText from "./lift/body.txt?raw" +import mineBodyGeomText from "./mine/body.txt?raw" + +const mine_frame_texture = Texture.from(mineFrameUrl) + +export class ModelRenderer { + static arrowPartsTex = BaseTexture.from(tapPartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static minePartsTex = BaseTexture.from(minePartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + static liftPartsTex = BaseTexture.from(liftPartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + + static arrowBodyGeom: Geometry + static arrowFrameGeom: Geometry + static liftBodyGeom: Geometry + static mineBodyGeom: Geometry + + static arrowFrameTex: RenderTexture + static arrowFrame: Mesh + static arrowTex: RenderTexture + static arrowContainer = new Container() + static liftTex: RenderTexture + static liftContainer = new Container() + static mineTex: RenderTexture + static mineConainer = new Container() + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + ModelRenderer.arrowFrameTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + ModelRenderer.arrowTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.liftTex = RenderTexture.create({ + width: 256, + height: 320, + resolution: Options.performance.resolution, + }) + ModelRenderer.mineTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + + this.arrowBodyGeom = await loadGeometry(arrowBodyGeomText) + this.arrowFrameGeom = await loadGeometry(arrowFrameGeomText) + this.mineBodyGeom = await loadGeometry(mineBodyGeomText) + this.liftBodyGeom = await loadGeometry(liftBodyGeomText) + + { + const shader_frame = Shader.from(noopVert, noopFrag, { + sampler0: this.arrowPartsTex, + }) + const arrow_frame = new Mesh(ModelRenderer.arrowFrameGeom, shader_frame) + arrow_frame.x = 32 + arrow_frame.y = 32 + arrow_frame.rotation = -Math.PI / 2 + this.arrowFrame = arrow_frame + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, arrowGradientFrag, { + sampler0: this.arrowPartsTex, + time: 0, + quant: i, + }) + const arrow_frame = new Sprite(ModelRenderer.arrowFrameTex) + arrow_frame.x = (i % 3) * 64 + arrow_frame.y = Math.floor(i / 3) * 64 + const arrow_body = new Mesh(ModelRenderer.arrowBodyGeom, shader_body) + arrow_body.x = (i % 3) * 64 + 32 + arrow_body.y = Math.floor(i / 3) * 64 + 32 + arrow_body.rotation = -Math.PI / 2 + arrow_body.name = "body" + i + ModelRenderer.arrowContainer.addChild(arrow_frame) + ModelRenderer.arrowContainer.addChild(arrow_body) + } + } + { + for (let i = 0; i < 10; i++) { + const shader_body = Shader.from(noopVert, liftGradientFrag, { + sampler0: this.liftPartsTex, + time: 0, + quant: i, + }) + const arrow_body = new Mesh(ModelRenderer.liftBodyGeom, shader_body) + arrow_body.x = (i % 3) * 64 + 32 + arrow_body.y = Math.floor(i / 3) * 64 + 32 + arrow_body.rotation = -Math.PI / 2 + arrow_body.name = "body" + i + ModelRenderer.liftContainer.addChild(arrow_body) + } + } + { + const shader_body = Shader.from(noopVert, mineGradientFrag, { + sampler0: this.minePartsTex, + time: 0, + }) + const mine_body = new Mesh(ModelRenderer.mineBodyGeom, shader_body) + const mine_frame = new Sprite(mine_frame_texture) + mine_frame.width = 64 + mine_frame.height = 64 + mine_frame.anchor.set(0.5) + mine_frame.pivot.y = 3 // epic recenter + ModelRenderer.mineConainer.position.set(32) + ModelRenderer.mineConainer.addChild(mine_body) + ModelRenderer.mineConainer.addChild(mine_frame) + } + + this.loaded = true + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const beat = app.chartManager.chartView!.getVisualBeat() + const second = app.chartManager.chartView!.getVisualTime() + for (let i = 0; i < 10; i++) { + const tapShader: Mesh = + ModelRenderer.arrowContainer.getChildByName("body" + i)! + tapShader.shader.uniforms.time = beat + const liftShader: Mesh = + ModelRenderer.liftContainer.getChildByName("body" + i)! + liftShader.shader.uniforms.time = beat + } + ;(ModelRenderer.mineConainer.children[0]).shader.uniforms.time = + second + ModelRenderer.mineConainer.rotation = (second % 1) * Math.PI * 2 + + app.renderer.render(ModelRenderer.arrowFrame, { + renderTexture: ModelRenderer.arrowFrameTex, + }) + app.renderer.render(ModelRenderer.arrowContainer, { + renderTexture: ModelRenderer.arrowTex, + }) + app.renderer.render(ModelRenderer.mineConainer, { + renderTexture: ModelRenderer.mineTex, + }) + app.renderer.render(ModelRenderer.liftContainer, { + renderTexture: ModelRenderer.liftTex, + }) + } + + static setNoteTex(arrow: Sprite, note: NotedataEntry | undefined) { + if (note !== undefined && note.type == "Mine") { + arrow.texture = ModelRenderer.mineTex + } else { + const i = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + note?.quant ?? 4 + ) + arrow.texture = new Texture( + note?.type == "Lift" ?? "Tap" + ? ModelRenderer.liftTex.baseTexture + : ModelRenderer.arrowTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/metal/NoteFlash.ts new file mode 100644 index 00000000..3cb24a82 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/metal/NoteFlash.ts @@ -0,0 +1,163 @@ +import { BLEND_MODES, Container, Sprite, Texture } from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import bezier from "bezier-easing" +import { BezierAnimator } from "../../../../../util/BezierEasing" +import w4Url from "./flash/decent.png" +import w2Url from "./flash/excellent.png" +import w0Url from "./flash/fantastic.png" +import w3Url from "./flash/great.png" +import holdFlashUrl from "./flash/hold.png" +import mineUrl from "./flash/mine.png" +import w5Url from "./flash/way_off.png" +import w1Url from "./flash/white_fantastic.png" + +const flashTex = { + hold: Texture.from(holdFlashUrl), + w0: Texture.from(w0Url), + w1: Texture.from(w1Url), + w2: Texture.from(w2Url), + w3: Texture.from(w3Url), + w4: Texture.from(w4Url), + w5: Texture.from(w5Url), + mine: Texture.from(mineUrl), +} + +export class NoteFlashContainer extends Container { + holdExplosion = new Sprite(flashTex.hold) + standard: Record = { + w0: new Sprite(flashTex.w0), + w1: new Sprite(flashTex.w1), + w2: new Sprite(flashTex.w2), + w3: new Sprite(flashTex.w3), + w4: new Sprite(flashTex.w4), + w5: new Sprite(flashTex.w5), + } + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + if (this.standard[event.judgement.id]) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + const obj = this.standard[event.judgement.id] + this.anims.add( + BezierAnimator.animate( + obj, + { + "0": { + alpha: 1.2, + "scale.x": 1.1, + "scale.y": 1.1, + }, + "1": { + alpha: 0, + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.15, + bezier(0.11, 0, 0.5, 0) + ) + ) + } + } + }) + + noteskin.on(this, "held", event => { + if (col == event.columnNumber) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + const obj = this.standard.w2 + BezierAnimator.animate( + obj, + { + "0": { + alpha: 1.2, + "scale.x": 1.1, + "scale.y": 1.1, + }, + "1": { + alpha: 0, + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.09, + bezier(0.11, 0, 0.5, 0) + ) + } + }) + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(flashTex.mine) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + noteskin.onUpdate(this, () => { + this.holdExplosion.alpha = + Math.sin((Date.now() / 1000) * Math.PI * 2 * 20) * 0.1 + 1 + }) + + this.holdExplosion.visible = false + this.holdExplosion.anchor.set(0.5) + this.addChild(this.holdExplosion) + for (const item of Object.values(this.standard)) { + item.alpha = 0 + item.anchor.set(0.5) + this.addChild(item) + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/metal/Noteskin.ts new file mode 100644 index 00000000..c37e64fd --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/metal/Noteskin.ts @@ -0,0 +1,190 @@ +// Peters-Metal by Pete-Lawrence https://github.com/Pete-Lawrence/Peters-Noteskins/ +// Custom lifts and color modifications by tillvit + +import { BLEND_MODES, Container, Sprite, Texture } from "pixi.js" + +import { ModelRenderer } from "./ModelRenderer" +import { NoteFlashContainer } from "./NoteFlash" +import receptorUrl from "./receptor.png" + +import holdBodyActiveUrl from "./hold/bodyActive.png" +import holdBodyInactiveUrl from "./hold/bodyInactive.png" +import holdBottomCapActiveUrl from "./hold/bottomCapActive.png" +import holdBottomCapInactiveUrl from "./hold/bottomCapInactive.png" +import holdTopCapActiveUrl from "./hold/topCapActive.png" +import holdTopCapInactiveUrl from "./hold/topCapInactive.png" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { rgbtoHex } from "../../../../../util/Color" +import { clamp } from "../../../../../util/Math" +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" +import { NoteskinOptions } from "../../Noteskin" +import rollBodyActiveUrl from "./roll/bodyActive.png" +import rollBodyInactiveUrl from "./roll/bodyInactive.png" +import rollBottomCapActiveUrl from "./roll/bottomCapActive.png" +import rollBottomCapInactiveUrl from "./roll/bottomCapInactive.png" +import rollTopCapActiveUrl from "./roll/topCapActive.png" +import rollTopCapInactiveUrl from "./roll/topCapInactive.png" + +const receptorTex = Texture.from(receptorUrl) + +const holdTex = { + hold: { + active: { + body: Texture.from(holdBodyActiveUrl), + topCap: Texture.from(holdTopCapActiveUrl), + bottomCap: Texture.from(holdBottomCapActiveUrl), + }, + inactive: { + body: Texture.from(holdBodyInactiveUrl), + topCap: Texture.from(holdTopCapInactiveUrl), + bottomCap: Texture.from(holdBottomCapInactiveUrl), + }, + }, + roll: { + active: { + body: Texture.from(rollBodyActiveUrl), + topCap: Texture.from(rollTopCapActiveUrl), + bottomCap: Texture.from(rollBottomCapActiveUrl), + }, + inactive: { + body: Texture.from(rollBodyInactiveUrl), + topCap: Texture.from(rollTopCapInactiveUrl), + bottomCap: Texture.from(rollBottomCapInactiveUrl), + }, + }, +} + +const rotationMap: Record = { + Left: 0, + Down: -90, + Up: 90, + Right: 180, + UpLeft: 45, + UpRight: 135, + DownRight: -135, + DownLeft: -45, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", + "NoteFlash", +] + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + const container = new Container() + const spr = new Sprite(receptorTex) + spr.width = 64 + spr.height = 64 + spr.anchor.set(0.5) + const overlay = new Sprite(receptorTex) + overlay.width = 64 + overlay.height = 64 + overlay.alpha = 0 + overlay.blendMode = BLEND_MODES.ADD + overlay.anchor.set(0.5) + container.addChild(spr, overlay) + options.noteskin.on( + container, + "press", + opt => + opt.columnNumber == options.columnNumber && (overlay.alpha = 0.2) + ) + options.noteskin.on( + container, + "lift", + opt => opt.columnNumber == options.columnNumber && (overlay.alpha = 0) + ) + options.noteskin.on(container, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + container, + { + "0": { + "scale.x": 0.75, + "scale.y": 0.75, + }, + "1": { + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.11 + ) + } + }) + options.noteskin.onUpdate(container, r => { + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + const col = clamp(1 - partialBeat, 0.5, 1) * 255 + spr.tint = rgbtoHex(col, col, col) + }) + return container + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + ModelRenderer.setNoteTex(spr, options.note) + spr.anchor.set(0.5) + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer(options.noteskin, options.columnNumber) + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: { element: "Tap" }, + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "Hold Active Body": () => new HoldBody(holdTex.hold.active.body), + "Hold Inactive Body": () => new HoldBody(holdTex.hold.inactive.body), + "Hold Active TopCap": () => new Sprite(Texture.EMPTY), + "Hold Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Hold Active BottomCap": () => + new HoldTail(holdTex.hold.active.bottomCap), + "Hold Inactive BottomCap": () => + new HoldTail(holdTex.hold.inactive.bottomCap), + + "Roll Active Head": { element: "Tap" }, + "Roll Inactive Head": { element: "Tap" }, + "Roll Active Body": () => new HoldBody(holdTex.roll.active.body), + "Roll Inactive Body": () => new HoldBody(holdTex.roll.inactive.body), + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": () => + new HoldTail(holdTex.roll.active.bottomCap), + "Roll Inactive BottomCap": () => + new HoldTail(holdTex.roll.inactive.bottomCap), + }, + }, + load: function (element, options) { + const col = element.columnName + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + init() { + ModelRenderer.initArrowTex() + }, + update(renderer) { + ModelRenderer.setArrowTexTime(renderer.chartManager.app) + }, + hideIcons: ["Lift"], +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/flash/decent.png b/app/src/chart/gameTypes/noteskin/dance/metal/flash/decent.png new file mode 100644 index 00000000..99a41e02 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/flash/decent.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/flash/excellent.png b/app/src/chart/gameTypes/noteskin/dance/metal/flash/excellent.png new file mode 100644 index 00000000..78bc5755 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/flash/excellent.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/flash/fantastic.png b/app/src/chart/gameTypes/noteskin/dance/metal/flash/fantastic.png new file mode 100644 index 00000000..c529fc4d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/flash/fantastic.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/flash/great.png b/app/src/chart/gameTypes/noteskin/dance/metal/flash/great.png new file mode 100644 index 00000000..cac2f56e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/flash/great.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/flash/hold.png b/app/src/chart/gameTypes/noteskin/dance/metal/flash/hold.png new file mode 100644 index 00000000..695ba53a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/flash/hold.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/metal/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/flash/way_off.png b/app/src/chart/gameTypes/noteskin/dance/metal/flash/way_off.png new file mode 100644 index 00000000..cb7f447d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/flash/way_off.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/flash/white_fantastic.png b/app/src/chart/gameTypes/noteskin/dance/metal/flash/white_fantastic.png new file mode 100644 index 00000000..00277ca9 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/flash/white_fantastic.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/hold/bodyActive.png b/app/src/chart/gameTypes/noteskin/dance/metal/hold/bodyActive.png new file mode 100644 index 00000000..ca7e359d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/hold/bodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/hold/bodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/metal/hold/bodyInactive.png new file mode 100644 index 00000000..e541a886 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/hold/bodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/hold/bottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/metal/hold/bottomCapActive.png new file mode 100644 index 00000000..94cfd38e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/hold/bottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/hold/bottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/metal/hold/bottomCapInactive.png new file mode 100644 index 00000000..5ca99c14 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/hold/bottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/hold/topCapActive.png b/app/src/chart/gameTypes/noteskin/dance/metal/hold/topCapActive.png new file mode 100644 index 00000000..87cf9c05 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/hold/topCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/hold/topCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/metal/hold/topCapInactive.png new file mode 100644 index 00000000..836c6981 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/hold/topCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/lift/body.txt b/app/src/chart/gameTypes/noteskin/dance/metal/lift/body.txt new file mode 100644 index 00000000..8c53ed0b --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/metal/lift/body.txt @@ -0,0 +1,171 @@ +79 +-9.000000 1.000000 0.375000 0.472100 +-6.000000 25.000000 0.421875 0.855300 +-9.000000 28.000000 0.375000 0.903200 +6.000000 25.000000 0.421875 0.855300 +9.000000 28.000000 0.375000 0.903200 +-19.000000 14.000000 0.033500 0.417300 +-19.000000 11.000000 0.033500 0.434600 +-11.000000 6.000000 0.045700 0.466200 +-24.000000 1.000000 0.094600 0.472100 +-25.000000 1.000000 0.094600 0.472100 +0.000000 -23.000000 0.094600 0.088900 +19.000000 7.000000 0.094600 0.567900 +19.000000 6.000000 0.094600 0.552000 +25.000000 1.000000 0.094600 0.472100 +0.000000 -24.000000 0.437500 0.072900 +0.000000 -25.000000 0.421875 0.057000 +26.000000 1.000000 0.421875 0.472100 +-6.000000 -5.000000 0.421875 0.376300 +-19.000000 7.000000 0.437500 0.567900 +-5.375000 -6.375000 0.421875 0.368300 +9.000000 1.000000 0.375000 0.472100 +19.000000 8.000000 0.421875 0.583900 +19.000000 11.000000 0.375000 0.631800 +0.000000 -28.000000 0.375000 0.009100 +29.000000 1.000000 0.375000 0.472100 +-26.000000 1.000000 0.421875 0.472100 +5.375000 -6.375000 0.094600 0.392300 +5.000000 24.000000 0.094600 0.839400 +5.000000 -8.000000 0.094600 0.408300 +5.375000 24.375000 0.437500 0.847300 +-19.000000 8.000000 0.421875 0.583900 +-19.000000 11.000000 0.375000 0.631800 +-29.000000 1.000000 0.375000 0.472100 +11.000000 30.000000 0.079300 0.320000 +-11.000000 30.000000 0.045700 0.320000 +-11.000000 30.000000 0.045700 0.320000 +-19.000000 14.000000 0.033500 0.417300 +11.000000 6.000000 0.079300 0.466200 +11.000000 30.000000 0.079300 0.320000 +-32.000000 1.000000 0.013500 0.497200 +0.000000 -31.000000 0.062500 0.693100 +0.000000 -31.000000 0.062500 0.693100 +19.000000 14.000000 0.091500 0.417300 +11.000000 6.000000 0.079300 0.466200 +-11.000000 6.000000 0.045700 0.466200 +32.000000 1.000000 0.111500 0.497200 +19.000000 14.000000 0.091500 0.417300 +-32.000000 1.000000 0.013500 0.497200 +32.000000 1.000000 0.111500 0.497200 +-5.375000 -6.375000 0.094600 0.352400 +-19.000000 6.000000 0.094600 0.552000 +-5.000000 -8.000000 0.094600 0.336400 +19.000000 11.000000 0.091500 0.434600 +-5.000000 24.000000 0.094600 0.839400 +-5.375000 -6.375000 0.094600 0.392300 +-5.000000 -8.000000 0.094600 0.408300 +-6.000000 -5.000000 0.421875 0.392300 +-29.000000 1.000000 0.017800 0.497200 +-9.000000 1.000000 0.048800 0.495700 +-19.000000 7.000000 0.094600 0.567900 +0.000000 -24.000000 0.094600 0.072900 +24.000000 1.000000 0.094600 0.472100 +5.375000 -6.375000 0.094600 0.352400 +5.000000 -8.000000 0.094600 0.336400 +6.000000 -5.000000 0.421875 0.392300 +19.000000 7.000000 0.437500 0.567900 +5.375000 -6.375000 0.437500 0.352400 +-25.000000 1.000000 0.437500 0.472100 +25.000000 1.000000 0.437500 0.472100 +6.000000 -5.000000 0.421875 0.376300 +5.375000 24.375000 0.094600 0.847300 +-5.375000 24.375000 0.437500 0.847300 +-5.375000 -6.375000 0.437500 0.400300 +-9.000000 28.000000 0.048800 0.332200 +0.000000 -28.000000 0.062500 0.675800 +9.000000 28.000000 0.076200 0.332200 +29.000000 1.000000 0.107200 0.497200 +9.000000 1.000000 0.076200 0.495700 +-5.375000 24.375000 0.094600 0.847300 +90 +0 1 2 +2 3 4 +5 6 7 +8 9 10 +11 12 13 +14 15 16 +17 18 19 +20 21 22 +3 20 4 +16 23 24 +25 23 15 +26 27 28 +3 1 29 +30 0 31 +30 32 25 +16 22 21 +33 34 35 +7 36 5 +37 38 33 +39 40 41 +42 43 37 +35 44 7 +45 46 42 +39 36 47 +45 40 48 +49 50 51 +52 45 42 +53 54 55 +0 56 1 +2 1 3 +5 39 57 +6 58 7 +5 57 6 +8 50 59 +60 13 10 +13 61 10 +8 59 9 +60 10 9 +11 62 12 +12 61 13 +62 63 12 +21 64 65 +64 66 65 +18 30 25 +67 18 25 +16 21 65 +16 65 68 +67 25 15 +16 68 14 +14 67 15 +17 30 18 +20 69 21 +3 64 20 +16 15 23 +25 32 23 +26 70 27 +29 66 64 +1 56 71 +56 72 71 +29 64 3 +1 71 29 +30 17 0 +30 31 32 +16 24 22 +33 38 34 +7 44 36 +37 43 38 +39 47 40 +42 46 43 +35 34 44 +45 48 46 +39 5 36 +45 41 40 +49 59 50 +35 7 73 +7 58 73 +57 39 41 +74 57 41 +33 35 73 +33 73 75 +37 33 75 +76 74 41 +37 75 77 +37 77 52 +76 41 45 +42 37 52 +52 76 45 +53 27 70 +78 54 53 +53 70 78 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/lift/parts.png b/app/src/chart/gameTypes/noteskin/dance/metal/lift/parts.png new file mode 100644 index 00000000..da86b2a6 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/lift/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/lift/parts_old.png b/app/src/chart/gameTypes/noteskin/dance/metal/lift/parts_old.png new file mode 100644 index 00000000..de13243a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/lift/parts_old.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/mine/body.txt b/app/src/chart/gameTypes/noteskin/dance/metal/mine/body.txt new file mode 100755 index 00000000..ee3d064a --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/metal/mine/body.txt @@ -0,0 +1,35 @@ +17 +0.000000 0.000000 0.308600 0.752000 +-11.379168 -4.712500 -0.000000 0.752000 +-8.873334 -8.873334 -0.000000 0.752000 +-4.712500 -11.379168 -0.000000 0.752000 +0.000000 -12.551667 -0.000000 0.752000 +4.712500 -11.379168 -0.000000 0.752000 +8.873334 -8.873334 -0.000000 0.752000 +11.379168 -4.712500 -0.000000 0.752000 +12.551667 0.000000 -0.000000 0.752000 +11.379168 4.712500 -0.000000 0.752000 +8.873334 8.873334 -0.000000 0.752000 +4.712500 11.379168 -0.000000 0.752000 +0.000000 12.551667 -0.000000 0.752000 +-4.712500 11.379168 -0.000000 0.752000 +-8.873334 8.873334 -0.000000 0.752000 +-11.379168 4.712500 -0.000000 0.752000 +-12.551667 0.000000 -0.000000 0.752000 +16 +0 1 2 +0 3 4 +0 5 6 +0 7 8 +0 9 10 +0 11 12 +0 13 14 +0 14 15 +13 0 12 +15 16 0 +1 0 16 +3 0 2 +5 0 4 +7 0 6 +9 0 8 +11 0 10 diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/mine/frame.png b/app/src/chart/gameTypes/noteskin/dance/metal/mine/frame.png new file mode 100644 index 00000000..388e7814 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/mine/frame.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/mine/parts.png b/app/src/chart/gameTypes/noteskin/dance/metal/mine/parts.png new file mode 100755 index 00000000..744ba8dc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/mine/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/preview.png b/app/src/chart/gameTypes/noteskin/dance/metal/preview.png new file mode 100644 index 00000000..930a1cf9 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/receptor.png b/app/src/chart/gameTypes/noteskin/dance/metal/receptor.png new file mode 100644 index 00000000..b3ad280f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/receptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/roll/bodyActive.png b/app/src/chart/gameTypes/noteskin/dance/metal/roll/bodyActive.png new file mode 100644 index 00000000..012e3054 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/roll/bodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/roll/bodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/metal/roll/bodyInactive.png new file mode 100644 index 00000000..fd10bea7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/roll/bodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/roll/bottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/metal/roll/bottomCapActive.png new file mode 100644 index 00000000..10f6ac43 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/roll/bottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/roll/bottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/metal/roll/bottomCapInactive.png new file mode 100644 index 00000000..4e159fe1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/roll/bottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/roll/topCapActive.png b/app/src/chart/gameTypes/noteskin/dance/metal/roll/topCapActive.png new file mode 100644 index 00000000..a2b69614 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/roll/topCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/roll/topCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/metal/roll/topCapInactive.png new file mode 100644 index 00000000..5ba539c7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/roll/topCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/shader/arrow_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/metal/shader/arrow_gradient.frag new file mode 100644 index 00000000..7d068f93 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/metal/shader/arrow_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform float time; +uniform float quant; + +void main() { + vec4 col = texture2D( + sampler0, + vec2(vUvs.x + 0.0625 * quant, mod(vUvs.y - time, 2.0) / 2.0) + ); + gl_FragColor = col; +} diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/shader/lift_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/metal/shader/lift_gradient.frag new file mode 100644 index 00000000..eaf25e59 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/metal/shader/lift_gradient.frag @@ -0,0 +1,18 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform float time; +uniform float quant; + +void main() { + vec4 col = texture2D( + sampler0, + vec2(vUvs.x + 0.0625 * quant, mod(1.2 - vUvs.y - time, 2.0) / 2.0) + ); + if (vUvs.x < 0.375) { + col = texture2D(sampler0, vUvs); + } + gl_FragColor = col; +} diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/shader/mine_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/metal/shader/mine_gradient.frag new file mode 100644 index 00000000..f1d32938 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/metal/shader/mine_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform sampler2D sampler1; +uniform float time; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + if (col.a < 1.0) { + discard; + } + gl_FragColor = texture2D(sampler1, vec2(mod(col.r - time, 1.0), 0.625)); +} diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/shader/noop.frag b/app/src/chart/gameTypes/noteskin/dance/metal/shader/noop.frag new file mode 100644 index 00000000..3a329fdc --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/metal/shader/noop.frag @@ -0,0 +1,12 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + + gl_FragColor = col; + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/shader/noop.vert b/app/src/chart/gameTypes/noteskin/dance/metal/shader/noop.vert new file mode 100644 index 00000000..002c3782 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/metal/shader/noop.vert @@ -0,0 +1,24 @@ +precision mediump float; + +attribute vec2 aVertexPosition; +attribute vec2 aUvs; + +uniform mat3 translationMatrix; +uniform mat3 projectionMatrix; +uniform float time; + +varying vec2 vUvs; + +float rand(vec2 co) { + return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); +} + +void main() { + vUvs = aUvs; + gl_Position = vec4( + (projectionMatrix * translationMatrix * vec3(aVertexPosition.xy, 1.0)).xy, + 0.0, + 1.0 + ); + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/tap/body.txt b/app/src/chart/gameTypes/noteskin/dance/metal/tap/body.txt new file mode 100755 index 00000000..e57f5867 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/metal/tap/body.txt @@ -0,0 +1,49 @@ +27 +24.000000 1.000000 0.421875 0.495100 +5.000000 -8.000000 0.421875 0.789400 +0.000000 -23.000000 0.421875 0.999500 +19.000000 6.000000 0.421875 0.495100 +0.000000 -13.000000 0.421875 0.894500 +5.000000 -3.000000 0.421875 0.736800 +0.000000 -3.000000 0.421875 0.789400 +0.000000 -8.000000 0.421875 0.841900 +-24.000000 1.000000 0.421875 0.495800 +-5.000000 -8.000000 0.421875 0.790100 +5.000000 4.000000 0.421875 0.663300 +0.000000 4.000000 0.421875 0.715800 +-19.000000 6.000000 0.421875 0.495800 +-5.000000 -3.000000 0.421875 0.737500 +-5.000000 4.000000 0.421875 0.664000 +5.000000 14.000000 0.421875 0.558200 +0.000000 14.000000 0.421875 0.610700 +0.000000 7.000000 0.421875 0.684300 +-5.000000 14.000000 0.421875 0.558900 +0.000000 17.000000 0.421875 0.579200 +5.000000 17.000000 0.421875 0.526700 +5.000000 24.000000 0.421875 0.453100 +-5.000000 24.000000 0.421875 0.453800 +-5.000000 17.000000 0.421875 0.527400 +-5.000000 7.000000 0.421875 0.632400 +0.000000 24.000000 0.421875 0.505600 +5.000000 7.000000 0.421875 0.631700 +20 +0 1 2 +1 0 3 +1 4 2 +5 6 7 +8 2 9 +5 10 11 +9 12 8 +9 2 4 +13 7 6 +11 14 13 +11 13 6 +5 11 6 +15 16 17 +17 16 18 +19 20 21 +22 23 19 +17 18 24 +22 19 25 +15 17 26 +19 21 25 diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/tap/frame.txt b/app/src/chart/gameTypes/noteskin/dance/metal/tap/frame.txt new file mode 100755 index 00000000..5e8f9221 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/metal/tap/frame.txt @@ -0,0 +1,280 @@ +128 +-19.000000 7.000000 0.219600 0.567900 +-26.000000 1.000000 0.202600 0.472100 +-25.000000 1.000000 0.219600 0.472100 +0.000000 -28.000000 0.062500 0.675800 +-29.000000 1.000000 0.017800 0.497200 +-32.000000 1.000000 0.013500 0.497200 +-6.000000 15.000000 0.202600 0.695700 +-6.000000 16.000000 0.202600 0.711600 +-9.000000 28.000000 0.125400 0.903200 +6.000000 25.000000 0.202600 0.855300 +9.000000 28.000000 0.125400 0.903200 +-19.000000 14.000000 0.033500 0.417300 +-19.000000 11.000000 0.033500 0.434600 +-11.000000 6.000000 0.045700 0.466200 +-24.000000 1.000000 0.094600 0.472100 +-25.000000 1.000000 0.094600 0.472100 +0.000000 -23.000000 0.094600 0.088900 +-6.000000 25.000000 0.202600 0.855300 +-5.500000 16.500000 0.219600 0.719600 +-5.500000 24.500000 0.219600 0.847300 +5.500000 16.500000 0.219600 0.719600 +6.000000 16.000000 0.202600 0.711600 +19.000000 7.000000 0.094600 0.567900 +0.000000 -12.000000 0.094600 0.264500 +19.000000 6.000000 0.094600 0.552000 +9.000000 1.000000 0.125400 0.472100 +6.000000 -4.000000 0.202600 0.392300 +6.000000 -5.000000 0.202600 0.376300 +5.500000 14.500000 0.219600 0.687700 +-5.500000 14.500000 0.219600 0.687700 +-6.000000 6.000000 0.202600 0.552000 +-5.500000 6.500000 0.219600 0.559900 +-6.000000 5.000000 0.202600 0.536000 +5.500000 4.500000 0.219600 0.528000 +6.000000 5.000000 0.202600 0.536000 +0.000000 -12.000000 0.219600 0.264500 +-6.000000 -5.000000 0.202600 0.376300 +5.500000 -3.500000 0.219600 0.400300 +0.000000 -25.000000 0.202600 0.057000 +0.000000 -24.000000 0.219600 0.072900 +19.000000 8.000000 0.202600 0.583900 +19.000000 11.000000 0.125400 0.631800 +25.000000 1.000000 0.219600 0.472100 +19.000000 7.000000 0.219600 0.567900 +-5.500000 -3.500000 0.219600 0.400300 +-6.000000 -4.000000 0.202600 0.392300 +0.000000 -9.000000 0.219600 0.312400 +6.000000 6.000000 0.202600 0.552000 +19.000000 11.000000 0.091500 0.434600 +19.000000 14.000000 0.091500 0.417300 +11.000000 6.000000 0.079300 0.466200 +5.500000 24.500000 0.219600 0.847300 +5.500000 6.500000 0.219600 0.559900 +26.000000 1.000000 0.202600 0.472100 +0.000000 -28.000000 0.125400 0.009100 +29.000000 1.000000 0.125400 0.472100 +6.000000 15.000000 0.202600 0.695700 +6.000000 16.000000 0.195600 0.711600 +-6.000000 15.000000 0.195600 0.695700 +6.000000 15.000000 0.195600 0.695700 +0.000000 -10.000000 0.195600 0.296500 +6.000000 -5.000000 0.195600 0.376300 +6.000000 -4.000000 0.195600 0.392300 +-9.000000 1.000000 0.125400 0.472100 +-6.000000 5.000000 0.195600 0.536000 +6.000000 6.000000 0.195600 0.552000 +-6.000000 6.000000 0.195600 0.552000 +5.500000 4.500000 0.094600 0.528000 +-5.000000 4.000000 0.094600 0.520000 +5.000000 4.000000 0.094600 0.520000 +5.500000 14.500000 0.094600 0.687700 +5.000000 14.000000 0.094600 0.679700 +5.000000 7.000000 0.094600 0.567900 +-5.500000 6.500000 0.094600 0.559900 +-5.000000 7.000000 0.094600 0.567900 +-5.000000 14.000000 0.094600 0.679700 +-5.000000 24.000000 0.094600 0.839400 +-5.500000 16.500000 0.094600 0.719600 +-5.000000 17.000000 0.094600 0.727600 +5.000000 17.000000 0.094600 0.727600 +5.500000 16.500000 0.094600 0.719600 +0.000000 -9.000000 0.094600 0.312400 +0.000000 -8.000000 0.094600 0.328400 +-5.500000 -3.500000 0.094600 0.400300 +-19.000000 8.000000 0.202600 0.583900 +-19.000000 11.000000 0.125400 0.631800 +-29.000000 1.000000 0.125400 0.472100 +11.000000 30.000000 0.079300 0.320000 +-11.000000 30.000000 0.045700 0.320000 +-11.000000 30.000000 0.045700 0.320000 +-19.000000 14.000000 0.033500 0.417300 +11.000000 30.000000 0.079300 0.320000 +0.000000 -31.000000 0.062500 0.693100 +0.000000 -31.000000 0.062500 0.693100 +11.000000 6.000000 0.079300 0.466200 +-11.000000 6.000000 0.045700 0.466200 +32.000000 1.000000 0.111500 0.497200 +19.000000 14.000000 0.091500 0.417300 +-32.000000 1.000000 0.013500 0.497200 +32.000000 1.000000 0.111500 0.497200 +-9.000000 1.000000 0.048800 0.495700 +-19.000000 6.000000 0.094600 0.552000 +-19.000000 7.000000 0.094600 0.567900 +0.000000 -24.000000 0.094600 0.072900 +25.000000 1.000000 0.094600 0.472100 +24.000000 1.000000 0.094600 0.472100 +0.000000 -13.000000 0.094600 0.248600 +-5.500000 4.500000 0.219600 0.528000 +0.000000 -11.000000 0.202600 0.280500 +0.000000 -10.000000 0.202600 0.296500 +-9.000000 28.000000 0.048800 0.332200 +29.000000 1.000000 0.107200 0.497200 +9.000000 28.000000 0.076200 0.332200 +9.000000 1.000000 0.076200 0.495700 +-6.000000 16.000000 0.195600 0.711600 +-6.000000 -4.000000 0.195600 0.392300 +-6.000000 -5.000000 0.195600 0.376300 +0.000000 -11.000000 0.195600 0.280500 +6.000000 5.000000 0.195600 0.536000 +-5.500000 4.500000 0.094600 0.528000 +5.500000 6.500000 0.094600 0.559900 +-5.500000 14.500000 0.094600 0.687700 +5.000000 24.000000 0.094600 0.839400 +5.500000 24.500000 0.094600 0.847300 +-5.500000 24.500000 0.094600 0.847300 +5.500000 -3.500000 0.094600 0.400300 +5.000000 -3.000000 0.094600 0.408300 +-5.000000 -3.000000 0.094600 0.408300 +150 +0 1 2 +3 4 5 +6 7 8 +8 9 10 +11 12 13 +14 15 16 +17 18 19 +9 20 21 +22 23 24 +25 26 27 +28 6 29 +29 30 31 +32 33 34 +0 35 36 +18 21 20 +26 33 37 +2 38 39 +25 40 41 +42 40 43 +44 45 46 +47 34 25 +48 49 50 +17 51 9 +30 52 31 +53 54 55 +56 52 47 +57 58 59 +60 61 62 +45 63 36 +64 65 66 +67 68 69 +1 54 38 +70 71 72 +73 74 75 +76 77 78 +79 77 80 +81 82 83 +84 63 85 +84 86 1 +53 39 38 +27 35 43 +53 41 40 +87 88 89 +13 90 11 +50 91 87 +5 92 93 +49 94 50 +89 95 13 +96 97 49 +5 90 98 +96 92 99 +0 84 1 +8 63 6 +32 30 63 +63 45 32 +7 17 8 +63 30 6 +8 17 9 +11 5 4 +12 100 13 +11 4 12 +14 101 102 +103 104 16 +104 105 16 +14 102 15 +103 16 15 +17 7 18 +9 51 20 +23 102 101 +24 105 104 +23 101 106 +24 104 22 +23 106 24 +28 56 6 +29 6 30 +32 107 33 +108 36 35 +84 0 36 +18 7 21 +26 34 33 +2 1 38 +25 27 40 +42 53 40 +26 37 46 +44 107 32 +109 26 46 +44 32 45 +109 46 45 +25 10 56 +9 21 10 +56 47 25 +34 26 25 +10 21 56 +89 13 110 +13 100 110 +3 5 93 +111 3 93 +87 89 110 +87 110 112 +50 87 112 +111 93 96 +50 112 113 +48 111 96 +50 113 48 +48 96 49 +17 19 51 +30 47 52 +53 38 54 +56 28 52 +57 114 58 +60 115 116 +117 61 60 +60 116 117 +64 118 65 +67 119 68 +1 86 54 +72 74 73 +120 70 72 +72 73 120 +75 71 70 +121 73 75 +75 70 121 +76 122 123 +124 77 76 +76 123 124 +123 122 79 +79 78 77 +80 123 79 +125 67 126 +67 69 126 +68 119 127 +127 119 83 +81 125 82 +82 127 83 +125 126 82 +84 36 63 +84 85 86 +53 42 39 +43 40 27 +27 108 35 +53 55 41 +87 91 88 +13 95 90 +50 94 91 +5 98 92 +49 97 94 +89 88 95 +96 99 97 +5 11 90 +96 93 92 diff --git a/app/src/chart/gameTypes/noteskin/dance/metal/tap/parts.png b/app/src/chart/gameTypes/noteskin/dance/metal/tap/parts.png new file mode 100644 index 00000000..fea12e6a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/metal/tap/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/ModelRenderer.ts b/app/src/chart/gameTypes/noteskin/dance/pastel/ModelRenderer.ts new file mode 100644 index 00000000..208d9762 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/pastel/ModelRenderer.ts @@ -0,0 +1,79 @@ +import { + BaseTexture, + Container, + Geometry, + Mesh, + MIPMAP_MODES, + RenderTexture, + Shader, + Sprite, + Texture, +} from "pixi.js" + +import { App } from "../../../../../App" +import { Options } from "../../../../../util/Options" + +import mineFrameUrl from "./mine/frame.png" +import minePartsUrl from "./mine/parts.png" + +import mineGradientFrag from "./shader/mine_gradient.frag?raw" +import noopVert from "./shader/noop.vert?raw" + +import { loadGeometry } from "../../../../../util/Util" + +import mineBodyGeomText from "./mine/body.txt?raw" + +const mine_frame_texture = Texture.from(mineFrameUrl) + +export class ModelRenderer { + static minePartsTex = BaseTexture.from(minePartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + + static mineBodyGeom: Geometry + + static mineTex: RenderTexture + static mineContainer = new Container() + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + ModelRenderer.mineTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + + this.mineBodyGeom = await loadGeometry(mineBodyGeomText) + + const shader_body = Shader.from(noopVert, mineGradientFrag, { + sampler0: this.minePartsTex, + time: 0, + }) + const mine_body = new Mesh(ModelRenderer.mineBodyGeom, shader_body) + const mine_frame = new Sprite(mine_frame_texture) + mine_frame.width = 64 + mine_frame.height = 64 + mine_frame.anchor.set(0.5) + mine_frame.pivot.y = 3 // epic recenter + ModelRenderer.mineContainer.position.set(32) + ModelRenderer.mineContainer.addChild(mine_body) + ModelRenderer.mineContainer.addChild(mine_frame) + + this.loaded = true + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const second = app.chartManager.chartView!.getVisualTime() + ;(ModelRenderer.mineContainer.children[0]).shader.uniforms.time = + second + ModelRenderer.mineContainer.rotation = (second % 1) * Math.PI * 2 + + app.renderer.render(ModelRenderer.mineContainer, { + renderTexture: ModelRenderer.mineTex, + }) + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/pastel/NoteFlash.ts new file mode 100644 index 00000000..859fe11f --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/pastel/NoteFlash.ts @@ -0,0 +1,139 @@ +import { BLEND_MODES, Container, Sprite, Texture } from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import bezier from "bezier-easing" +import { BezierAnimator } from "../../../../../util/BezierEasing" +import flashUrl from "./flash/flash.png" +import mineUrl from "./flash/mine.png" + +const flashTex = Texture.from(flashUrl) +const mineTex = Texture.from(mineUrl) + +export class NoteFlashContainer extends Container { + standard = new Sprite(flashTex) + hold = new Sprite(flashTex) + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate( + this.standard, + { + "0": { + alpha: 1.2, + "scale.x": 1.1, + "scale.y": 1.1, + }, + "1": { + alpha: 0, + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.15, + bezier(0.11, 0, 0.5, 0) + ) + ) + } + }) + + noteskin.on(this, "held", event => { + if (col == event.columnNumber) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate( + this.standard, + { + "0": { + alpha: 1.2, + "scale.x": 1.1, + "scale.y": 1.1, + }, + "1": { + alpha: 0, + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.09, + bezier(0.11, 0, 0.5, 0) + ) + ) + } + }) + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.hold.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.hold.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.hold.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.hold.visible = false + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(mineTex) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + this.standard.alpha = 0 + this.standard.anchor.set(0.5) + this.addChild(this.standard) + + noteskin.onUpdate(this, () => { + this.hold.alpha = + Math.sin((Date.now() / 1000) * Math.PI * 2 * 20) * 0.1 + 1 + }) + + this.hold.visible = false + this.hold.anchor.set(0.5) + this.addChild(this.hold) + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/pastel/Noteskin.ts new file mode 100644 index 00000000..de1b2db0 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/pastel/Noteskin.ts @@ -0,0 +1,180 @@ +// Pastel by halcyoniix +// Custom lifts by tillvit + +import { AnimatedSprite, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { ModelRenderer } from "./ModelRenderer" +import { NoteFlashContainer } from "./NoteFlash" +import receptorUrl from "./receptor.png" + +import rollBodyUrl from "./roll/body.png" +import rollCapUrl from "./roll/cap.png" + +import { splitTex } from "../../../../../util/Util" +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" +import holdBodyUrl from "./hold/body.png" +import holdCapUrl from "./hold/cap.png" + +const receptorTex = Texture.from(receptorUrl) + +const tapTex: Texture[] = [] + +for (let i = 0; i < 9; i++) { + tapTex[i] = Texture.from(new URL(`./tap/${i}.png`, import.meta.url).href) +} + +const liftTex: Texture[] = [] + +for (let i = 0; i < 9; i++) { + liftTex[i] = Texture.from(new URL(`./lift/${i}.png`, import.meta.url).href) +} + +const holdTex = { + hold: { + body: Texture.from(holdBodyUrl), + cap: Texture.from(holdCapUrl), + }, + roll: { + body: Texture.from(rollBodyUrl), + cap: Texture.from(rollCapUrl), + }, +} + +const rotationMap: Record = { + Left: 90, + Down: 0, + Up: 180, + Right: -90, + UpLeft: 135, + UpRight: -135, + DownRight: -45, + DownLeft: 45, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", + "NoteFlash", +] + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + const spr = new AnimatedSprite(splitTex(receptorTex, 2, 1, 128, 128)[0]) + spr.width = 64 + spr.height = 64 + spr.anchor.set(0.5) + options.noteskin.on(spr, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + spr, + { + "0": { + alpha: 1.2, + width: 48, + height: 48, + }, + "1": { + alpha: 1, + width: 64, + height: 64, + }, + }, + 0.06 + ) + } + }) + options.noteskin.onUpdate(spr, r => { + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + spr.currentFrame = partialBeat < 0.2 ? 0 : 1 + }) + return spr + }, + Tap: options => { + const tex = + tapTex[ + [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + options.note?.quant ?? 4 + ) ?? 0 + ] + const spr = new Sprite(tex) + spr.anchor.set(0.5) + spr.width = 64 + spr.height = 64 + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer(options.noteskin, options.columnNumber) + }, + Fake: { element: "Tap" }, + Lift: options => { + const tex = + liftTex[ + [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + options.note?.quant ?? 4 + ) ?? 0 + ] + const spr = new Sprite(tex) + spr.anchor.set(0.5) + spr.width = 64 + spr.height = 64 + return spr + }, + Mine: () => { + const spr = new Sprite(ModelRenderer.mineTex) + spr.anchor.set(0.5) + spr.width = 64 + spr.height = 64 + return spr + }, + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "Hold Active Body": () => new HoldBody(holdTex.hold.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 HoldTail(holdTex.hold.cap), + "Hold Inactive BottomCap": { element: "Hold Active BottomCap" }, + + "Roll Active Head": { element: "Tap" }, + "Roll Inactive Head": { element: "Tap" }, + "Roll Active Body": () => new HoldBody(holdTex.roll.body), + "Roll Inactive Body": { element: "Roll Active Body" }, + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": () => new HoldTail(holdTex.roll.cap), + "Roll Inactive BottomCap": { element: "Roll Active BottomCap" }, + }, + }, + load: function (element, options) { + const col = element.columnName + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + init() { + ModelRenderer.initArrowTex() + }, + update(renderer) { + ModelRenderer.setArrowTexTime(renderer.chartManager.app) + }, + hideIcons: ["Lift"], +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/flash/flash.png b/app/src/chart/gameTypes/noteskin/dance/pastel/flash/flash.png new file mode 100755 index 00000000..6d6ef4f3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/flash/flash.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/pastel/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/hold/body.png b/app/src/chart/gameTypes/noteskin/dance/pastel/hold/body.png new file mode 100644 index 00000000..552f100d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/hold/body.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/hold/cap.png b/app/src/chart/gameTypes/noteskin/dance/pastel/hold/cap.png new file mode 100644 index 00000000..99dcafa3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/hold/cap.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/lift/0.png b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/0.png new file mode 100644 index 00000000..b23e13a2 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/0.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/lift/1.png b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/1.png new file mode 100644 index 00000000..9fea8ce6 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/1.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/lift/2.png b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/2.png new file mode 100644 index 00000000..2a58e8a7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/2.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/lift/3.png b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/3.png new file mode 100644 index 00000000..c946fe6e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/3.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/lift/4.png b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/4.png new file mode 100644 index 00000000..64ac6777 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/4.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/lift/5.png b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/5.png new file mode 100644 index 00000000..083c67cc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/5.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/lift/6.png b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/6.png new file mode 100644 index 00000000..c3b12bb2 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/6.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/lift/7.png b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/7.png new file mode 100644 index 00000000..9ef8e30a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/7.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/lift/8.png b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/8.png new file mode 100644 index 00000000..d3dd628d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/lift/8.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/mine/body.txt b/app/src/chart/gameTypes/noteskin/dance/pastel/mine/body.txt new file mode 100755 index 00000000..0bf678f4 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/pastel/mine/body.txt @@ -0,0 +1,103 @@ +45 +-16.0 0.0 0 0.75 +-11.31 -11.31 0 0.75 + 0.0 -16.0 0 0.75 + 11.31 -11.31 0 0.75 + 16.0 0.0 0 0.75 + 11.31 11.31 0 0.75 + -0.0 16.0 0 0.75 +-11.31 11.31 0 0.75 +29.422066 0.009519 -0.999500 0.25 +25.401829 14.852878 -0.931249 0.1240015 +14.843359 25.411350 -0.751997 0.0343755 +-0.000001 29.431583 -0.500000 0.00025 +-14.843361 25.411348 -0.248003 0.0343755 +-25.401833 14.852878 -0.068751 0.1240015 +-29.422066 0.009516 -0.000500 0.25 +-25.401827 -14.833844 -0.068751 0.3759985 +-14.843357 -25.392317 -0.248003 0.4656245 +0.000004 -29.412546 -0.500000 0.49975 +14.843363 -25.392307 -0.751997 0.4656245 +25.401833 -14.833840 -0.931249 0.3759985 +19.921448 0.009518 0.838208 0.25 +17.189407 10.096646 0.791826 0.164375 +10.087127 17.198929 0.671250 0.104087 +-0.000000 19.930965 0.500000 0.080896 +-10.087129 17.198925 0.328750 0.104087 +-17.189411 10.096646 0.208174 0.164375 +-19.921448 0.009518 0.161792 0.25 +-17.189405 -10.077612 0.208174 0.335625 +-10.087127 -17.179895 0.328750 0.395913 +0.000001 -19.911928 0.500000 0.419104 +10.087130 -17.179886 0.671250 0.395913 +17.189411 -10.077609 0.791826 0.335625 +10.420829 0.009516 -0.676915 0.25 +8.976986 5.340415 -0.652403 0.2047485 +5.330894 8.986506 -0.590503 0.1737985 +0.000001 10.430347 -0.500000 0.1615425 +-5.330897 8.986505 -0.409497 0.1737985 +-8.976989 5.340414 -0.347597 0.2047485 +-10.420829 0.009520 -0.323085 0.25 +-8.976985 -5.321381 -0.347597 0.2952515 +-5.330895 -8.967473 -0.409497 0.3262015 +-0.000003 -10.411310 -0.500000 0.3384575 +5.330898 -8.967464 -0.590503 0.3262015 +8.976988 -5.321376 -0.652403 0.2952515 +0 0 0.5 0.75 +56 +44 0 1 +44 1 2 +44 2 3 +44 3 4 +44 4 5 +44 5 6 +44 6 7 +44 7 0 +8 9 20 +9 21 20 +9 10 21 +10 22 21 +10 11 22 +11 23 22 +11 12 23 +12 24 23 +12 13 24 +13 25 24 +13 14 25 +14 26 25 +14 15 26 +15 27 26 +15 16 27 +16 28 27 +16 17 28 +17 29 28 +17 18 29 +18 30 29 +18 19 30 +19 31 30 +19 8 31 +8 20 31 +20 21 32 +21 33 32 +21 22 33 +22 34 33 +22 23 34 +23 35 34 +23 24 35 +24 36 35 +24 25 36 +25 37 36 +25 26 37 +26 38 37 +26 27 38 +27 39 38 +27 28 39 +28 40 39 +28 29 40 +29 41 40 +29 30 41 +30 42 41 +30 31 42 +31 43 42 +31 20 43 +20 32 43 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/mine/frame.png b/app/src/chart/gameTypes/noteskin/dance/pastel/mine/frame.png new file mode 100644 index 00000000..388e7814 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/mine/frame.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/mine/parts.png b/app/src/chart/gameTypes/noteskin/dance/pastel/mine/parts.png new file mode 100755 index 00000000..744ba8dc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/mine/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/preview.png b/app/src/chart/gameTypes/noteskin/dance/pastel/preview.png new file mode 100644 index 00000000..3289bd63 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/receptor.png b/app/src/chart/gameTypes/noteskin/dance/pastel/receptor.png new file mode 100644 index 00000000..98f70c6d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/receptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/roll/body.png b/app/src/chart/gameTypes/noteskin/dance/pastel/roll/body.png new file mode 100644 index 00000000..f3aa3c22 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/roll/body.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/roll/cap.png b/app/src/chart/gameTypes/noteskin/dance/pastel/roll/cap.png new file mode 100644 index 00000000..5a40f57d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/roll/cap.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/shader/mine_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/pastel/shader/mine_gradient.frag new file mode 100644 index 00000000..f1d32938 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/pastel/shader/mine_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform sampler2D sampler1; +uniform float time; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + if (col.a < 1.0) { + discard; + } + gl_FragColor = texture2D(sampler1, vec2(mod(col.r - time, 1.0), 0.625)); +} diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/shader/noop.vert b/app/src/chart/gameTypes/noteskin/dance/pastel/shader/noop.vert new file mode 100644 index 00000000..002c3782 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/pastel/shader/noop.vert @@ -0,0 +1,24 @@ +precision mediump float; + +attribute vec2 aVertexPosition; +attribute vec2 aUvs; + +uniform mat3 translationMatrix; +uniform mat3 projectionMatrix; +uniform float time; + +varying vec2 vUvs; + +float rand(vec2 co) { + return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); +} + +void main() { + vUvs = aUvs; + gl_Position = vec4( + (projectionMatrix * translationMatrix * vec3(aVertexPosition.xy, 1.0)).xy, + 0.0, + 1.0 + ); + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/tap/0.png b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/0.png new file mode 100644 index 00000000..ac5cd354 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/0.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/tap/1.png b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/1.png new file mode 100644 index 00000000..22ba96de Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/1.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/tap/2.png b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/2.png new file mode 100644 index 00000000..c6252de3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/2.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/tap/3.png b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/3.png new file mode 100644 index 00000000..3324604d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/3.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/tap/4.png b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/4.png new file mode 100644 index 00000000..aa919865 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/4.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/tap/5.png b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/5.png new file mode 100644 index 00000000..ddddca87 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/5.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/tap/6.png b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/6.png new file mode 100644 index 00000000..c5dd95ee Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/6.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/tap/7.png b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/7.png new file mode 100644 index 00000000..ea3536c6 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/7.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/pastel/tap/8.png b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/8.png new file mode 100644 index 00000000..091d7126 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/pastel/tap/8.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/ModelRenderer.ts b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/ModelRenderer.ts new file mode 100644 index 00000000..b5b715a9 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/ModelRenderer.ts @@ -0,0 +1,155 @@ +import { + AnimatedSprite, + BaseTexture, + Container, + Geometry, + Mesh, + MIPMAP_MODES, + Rectangle, + RenderTexture, + Shader, + Sprite, + Texture, +} from "pixi.js" + +import { App } from "../../../../../App" +import { Options } from "../../../../../util/Options" +import { NotedataEntry } from "../../../../sm/NoteTypes" + +import mineFrameUrl from "./mine/frame.png" +import minePartsUrl from "./mine/parts.png" + +import mineGradientFrag from "./shader/mine_gradient.frag?raw" +import noopVert from "./shader/noop.vert?raw" + +import { loadGeometry, splitTex } from "../../../../../util/Util" +import mineBodyGeomText from "./mine/body.txt?raw" + +import liftUrl from "./lift.png" +import tapUrl from "./tap.png" + +const mine_frame_texture = Texture.from(mineFrameUrl) +const tapTex = splitTex(Texture.from(tapUrl), 8, 8, 64, 64) +const liftTex = splitTex(Texture.from(liftUrl), 4, 1, 64, 64)[0] + +export class ModelRenderer { + static minePartsTex = BaseTexture.from(minePartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + + static mineBodyGeom: Geometry + + static arrowTex: RenderTexture + static arrowContainer = new Container() + static mineTex: RenderTexture + static mineContainer = new Container() + static liftTex: RenderTexture + static liftContainer = new Container() + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + ModelRenderer.arrowTex = RenderTexture.create({ + width: 256, + height: 256, + resolution: Options.performance.resolution, + }) + ModelRenderer.mineTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + ModelRenderer.liftTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + + this.mineBodyGeom = await loadGeometry(mineBodyGeomText) + + { + for (let i = 0; i < 8; i++) { + const note = new AnimatedSprite(tapTex[i]) + note.x = (i % 3) * 64 + 32 + note.y = Math.floor(i / 3) * 64 + 32 + note.anchor.set(0.5) + note.name = "note" + i + ModelRenderer.arrowContainer.addChild(note) + } + } + { + const note = new AnimatedSprite(liftTex) + note.x = 32 + note.y = 32 + note.anchor.set(0.5) + + note.name = "note" + ModelRenderer.liftContainer.addChild(note) + } + { + const shader_body = Shader.from(noopVert, mineGradientFrag, { + sampler0: this.minePartsTex, + time: 0, + }) + const mine_body = new Mesh(ModelRenderer.mineBodyGeom, shader_body) + const mine_frame = new Sprite(mine_frame_texture) + mine_frame.width = 64 + mine_frame.height = 64 + mine_frame.anchor.set(0.5) + mine_frame.pivot.y = 3 // epic recenter + ModelRenderer.mineContainer.position.set(32) + ModelRenderer.mineContainer.addChild(mine_body) + ModelRenderer.mineContainer.addChild(mine_frame) + } + + this.loaded = true + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const beat = app.chartManager.chartView!.getVisualBeat() + const second = app.chartManager.chartView!.getVisualTime() + const partialBeat = ((beat % 4) + 4) % 4 + for (let i = 0; i < 8; i++) { + const tap: AnimatedSprite = ModelRenderer.arrowContainer.getChildByName( + "note" + i + )! + tap.currentFrame = Math.floor(partialBeat * 2) + } + const lift = ModelRenderer.liftContainer.children[0] as AnimatedSprite + lift.currentFrame = Math.floor(partialBeat) + ;(ModelRenderer.mineContainer.children[0]).shader.uniforms.time = + second + ModelRenderer.mineContainer.rotation = (second % 1) * Math.PI * 2 + + app.renderer.render(ModelRenderer.arrowContainer, { + renderTexture: ModelRenderer.arrowTex, + }) + app.renderer.render(ModelRenderer.mineContainer, { + renderTexture: ModelRenderer.mineTex, + }) + app.renderer.render(ModelRenderer.liftContainer, { + renderTexture: ModelRenderer.liftTex, + }) + } + + static setNoteTex(arrow: Sprite, note: NotedataEntry | undefined) { + if (note !== undefined && note.type == "Mine") { + arrow.texture = ModelRenderer.mineTex + } else if (note !== undefined && note.type == "Lift") { + arrow.texture = ModelRenderer.liftTex + } else { + const i = Math.min( + [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf(note?.quant ?? 4), + 7 + ) + arrow.texture = new Texture( + ModelRenderer.arrowTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/NoteFlash.ts new file mode 100644 index 00000000..1cacb0ed --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/NoteFlash.ts @@ -0,0 +1,169 @@ +import { + AnimatedSprite, + BLEND_MODES, + Container, + Sprite, + Texture, +} from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { rgbtoHex } from "../../../../../util/Color" +import { splitTex } from "../../../../../util/Util" +import flashUrl from "./flash/flash.png" +import holdFlashUrl from "./flash/hold.png" +import mineUrl from "./flash/mine.png" + +const holdTex = Texture.from(holdFlashUrl) +const mineTex = Texture.from(mineUrl) +const flashTex = Texture.from(flashUrl) + +const FLASH_COLORS: Record = { + w0: [1.0, 1.0, 1.0, 1], + w2: [1.0, 1.0, 0.3, 1], + w3: [0.0, 1.0, 0.4, 1], + w4: [0.3, 0.8, 1.0, 1], + w5: [0.8, 0.0, 0.6, 1], + held: [1.0, 1.0, 1.0, 1], +} + +export class NoteFlashContainer extends Container { + holdExplosion = new AnimatedSprite(splitTex(holdTex, 2, 1, 128, 128)[0]) + standard = new Sprite(flashTex) + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + const col = FLASH_COLORS[event.judgement.id] ?? [1, 1, 1, 1] + this.standard.tint = rgbtoHex(col[0] * 255, col[1] * 255, col[2] * 255) + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate( + this.standard, + { + "0": { + alpha: 1.0, + "scale.x": 1, + "scale.y": 1, + }, + "0.5": { + alpha: 1.0, + "scale.x": 1.1, + "scale.y": 1.1, + }, + "1": { + alpha: 0, + "scale.x": 1.1, + "scale.y": 1.1, + }, + }, + 0.12 + ) + ) + } + }) + + noteskin.on(this, "held", event => { + if (col == event.columnNumber) { + const col = FLASH_COLORS.w2 + this.standard.tint = rgbtoHex(col[0] * 255, col[1] * 255, col[2] * 255) + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate( + this.standard, + { + "0": { + alpha: 1.0, + "scale.x": 1, + "scale.y": 1, + }, + "0.5": { + alpha: 1.0, + "scale.x": 1.1, + "scale.y": 1.1, + }, + "1": { + alpha: 0, + "scale.x": 1.1, + "scale.y": 1.1, + }, + }, + 0.12 + ) + ) + } + }) + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(mineTex) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + noteskin.onUpdate(this, () => { + this.holdExplosion.rotation = -this.rotation + }) + + this.holdExplosion.visible = false + this.holdExplosion.anchor.set(0.5) + this.holdExplosion.play() + this.addChild(this.holdExplosion) + + this.standard.alpha = 0 + this.standard.anchor.set(0.5) + this.addChild(this.standard) + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/Noteskin.ts new file mode 100644 index 00000000..0054ce3a --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/Noteskin.ts @@ -0,0 +1,186 @@ +// Peters-Cel by Pete-Lawrence https://github.com/Pete-Lawrence/Peters-Noteskins/ +// Custom lifts and color modifications by tillvit + +import { BLEND_MODES, Container, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" + +import { ModelRenderer } from "./ModelRenderer" +import { NoteFlashContainer } from "./NoteFlash" +import receptorUrl from "./receptor.png" + +import holdBodyActiveUrl from "./hold/bodyActive.png" +import holdBodyInactiveUrl from "./hold/bodyInactive.png" +import holdBottomCapActiveUrl from "./hold/bottomCapActive.png" +import holdBottomCapInactiveUrl from "./hold/bottomCapInactive.png" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { rgbtoHex } from "../../../../../util/Color" +import { clamp } from "../../../../../util/Math" +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" +import rollBodyActiveUrl from "./roll/bodyActive.png" +import rollBodyInactiveUrl from "./roll/bodyInactive.png" +import rollBottomCapActiveUrl from "./roll/bottomCapActive.png" +import rollBottomCapInactiveUrl from "./roll/bottomCapInactive.png" + +const receptorTex = Texture.from(receptorUrl) + +const holdTex = { + hold: { + active: { + body: Texture.from(holdBodyActiveUrl), + bottomCap: Texture.from(holdBottomCapActiveUrl), + }, + inactive: { + body: Texture.from(holdBodyInactiveUrl), + bottomCap: Texture.from(holdBottomCapInactiveUrl), + }, + }, + roll: { + active: { + body: Texture.from(rollBodyActiveUrl), + bottomCap: Texture.from(rollBottomCapActiveUrl), + }, + inactive: { + body: Texture.from(rollBodyInactiveUrl), + bottomCap: Texture.from(rollBottomCapInactiveUrl), + }, + }, +} + +const rotationMap: Record = { + Left: 90, + Down: 0, + Up: 180, + Right: -90, + UpLeft: 135, + UpRight: -135, + DownRight: -45, + DownLeft: 45, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", + "NoteFlash", +] + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + const container = new Container() + const spr = new Sprite(receptorTex) + spr.width = 64 + spr.height = 64 + spr.anchor.set(0.5) + const overlay = new Sprite(receptorTex) + overlay.width = 64 + overlay.height = 64 + overlay.alpha = 0 + overlay.blendMode = BLEND_MODES.ADD + overlay.anchor.set(0.5) + container.addChild(spr, overlay) + options.noteskin.on( + container, + "press", + opt => + opt.columnNumber == options.columnNumber && (overlay.alpha = 0.2) + ) + options.noteskin.on( + container, + "lift", + opt => opt.columnNumber == options.columnNumber && (overlay.alpha = 0) + ) + options.noteskin.on(container, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + container, + { + "0": { + "scale.x": 0.75, + "scale.y": 0.75, + }, + "1": { + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.11 + ) + } + }) + options.noteskin.onUpdate(container, r => { + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + const col = clamp(1 - partialBeat, 0.5, 1) * 255 + spr.tint = rgbtoHex(col, col, col) + }) + return container + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + ModelRenderer.setNoteTex(spr, options.note) + spr.anchor.set(0.5) + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer(options.noteskin, options.columnNumber) + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: { element: "Tap" }, + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "Hold Active Body": () => new HoldBody(holdTex.hold.active.body), + "Hold Inactive Body": () => new HoldBody(holdTex.hold.inactive.body), + "Hold Active TopCap": () => new Sprite(Texture.EMPTY), + "Hold Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Hold Active BottomCap": () => + new HoldTail(holdTex.hold.active.bottomCap), + "Hold Inactive BottomCap": () => + new HoldTail(holdTex.hold.inactive.bottomCap), + + "Roll Active Head": { element: "Tap" }, + "Roll Inactive Head": { element: "Tap" }, + "Roll Active Body": () => new HoldBody(holdTex.roll.active.body), + "Roll Inactive Body": () => new HoldBody(holdTex.roll.inactive.body), + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": () => + new HoldTail(holdTex.roll.active.bottomCap), + "Roll Inactive BottomCap": () => + new HoldTail(holdTex.roll.inactive.bottomCap), + }, + }, + load: function (element, options) { + const col = element.columnName + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + init() { + ModelRenderer.initArrowTex() + }, + update(renderer) { + ModelRenderer.setArrowTexTime(renderer.chartManager.app) + }, + hideIcons: ["Lift"], + metrics: { + HoldBodyBottomOffset: -32, + RollBodyBottomOffset: -32, + }, +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/flash/flash.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/flash/flash.png new file mode 100644 index 00000000..b22443c7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/flash/flash.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/flash/hold.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/flash/hold.png new file mode 100644 index 00000000..a91319d7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/flash/hold.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/hold/bodyActive.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/hold/bodyActive.png new file mode 100644 index 00000000..3ce7ee94 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/hold/bodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/hold/bodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/hold/bodyInactive.png new file mode 100644 index 00000000..7dcc2fe3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/hold/bodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/hold/bottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/hold/bottomCapActive.png new file mode 100644 index 00000000..36dd4737 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/hold/bottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/hold/bottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/hold/bottomCapInactive.png new file mode 100644 index 00000000..f923094d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/hold/bottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/lift.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/lift.png new file mode 100644 index 00000000..dffab9b5 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/lift.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/lift/body.txt b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/lift/body.txt new file mode 100644 index 00000000..8c53ed0b --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/lift/body.txt @@ -0,0 +1,171 @@ +79 +-9.000000 1.000000 0.375000 0.472100 +-6.000000 25.000000 0.421875 0.855300 +-9.000000 28.000000 0.375000 0.903200 +6.000000 25.000000 0.421875 0.855300 +9.000000 28.000000 0.375000 0.903200 +-19.000000 14.000000 0.033500 0.417300 +-19.000000 11.000000 0.033500 0.434600 +-11.000000 6.000000 0.045700 0.466200 +-24.000000 1.000000 0.094600 0.472100 +-25.000000 1.000000 0.094600 0.472100 +0.000000 -23.000000 0.094600 0.088900 +19.000000 7.000000 0.094600 0.567900 +19.000000 6.000000 0.094600 0.552000 +25.000000 1.000000 0.094600 0.472100 +0.000000 -24.000000 0.437500 0.072900 +0.000000 -25.000000 0.421875 0.057000 +26.000000 1.000000 0.421875 0.472100 +-6.000000 -5.000000 0.421875 0.376300 +-19.000000 7.000000 0.437500 0.567900 +-5.375000 -6.375000 0.421875 0.368300 +9.000000 1.000000 0.375000 0.472100 +19.000000 8.000000 0.421875 0.583900 +19.000000 11.000000 0.375000 0.631800 +0.000000 -28.000000 0.375000 0.009100 +29.000000 1.000000 0.375000 0.472100 +-26.000000 1.000000 0.421875 0.472100 +5.375000 -6.375000 0.094600 0.392300 +5.000000 24.000000 0.094600 0.839400 +5.000000 -8.000000 0.094600 0.408300 +5.375000 24.375000 0.437500 0.847300 +-19.000000 8.000000 0.421875 0.583900 +-19.000000 11.000000 0.375000 0.631800 +-29.000000 1.000000 0.375000 0.472100 +11.000000 30.000000 0.079300 0.320000 +-11.000000 30.000000 0.045700 0.320000 +-11.000000 30.000000 0.045700 0.320000 +-19.000000 14.000000 0.033500 0.417300 +11.000000 6.000000 0.079300 0.466200 +11.000000 30.000000 0.079300 0.320000 +-32.000000 1.000000 0.013500 0.497200 +0.000000 -31.000000 0.062500 0.693100 +0.000000 -31.000000 0.062500 0.693100 +19.000000 14.000000 0.091500 0.417300 +11.000000 6.000000 0.079300 0.466200 +-11.000000 6.000000 0.045700 0.466200 +32.000000 1.000000 0.111500 0.497200 +19.000000 14.000000 0.091500 0.417300 +-32.000000 1.000000 0.013500 0.497200 +32.000000 1.000000 0.111500 0.497200 +-5.375000 -6.375000 0.094600 0.352400 +-19.000000 6.000000 0.094600 0.552000 +-5.000000 -8.000000 0.094600 0.336400 +19.000000 11.000000 0.091500 0.434600 +-5.000000 24.000000 0.094600 0.839400 +-5.375000 -6.375000 0.094600 0.392300 +-5.000000 -8.000000 0.094600 0.408300 +-6.000000 -5.000000 0.421875 0.392300 +-29.000000 1.000000 0.017800 0.497200 +-9.000000 1.000000 0.048800 0.495700 +-19.000000 7.000000 0.094600 0.567900 +0.000000 -24.000000 0.094600 0.072900 +24.000000 1.000000 0.094600 0.472100 +5.375000 -6.375000 0.094600 0.352400 +5.000000 -8.000000 0.094600 0.336400 +6.000000 -5.000000 0.421875 0.392300 +19.000000 7.000000 0.437500 0.567900 +5.375000 -6.375000 0.437500 0.352400 +-25.000000 1.000000 0.437500 0.472100 +25.000000 1.000000 0.437500 0.472100 +6.000000 -5.000000 0.421875 0.376300 +5.375000 24.375000 0.094600 0.847300 +-5.375000 24.375000 0.437500 0.847300 +-5.375000 -6.375000 0.437500 0.400300 +-9.000000 28.000000 0.048800 0.332200 +0.000000 -28.000000 0.062500 0.675800 +9.000000 28.000000 0.076200 0.332200 +29.000000 1.000000 0.107200 0.497200 +9.000000 1.000000 0.076200 0.495700 +-5.375000 24.375000 0.094600 0.847300 +90 +0 1 2 +2 3 4 +5 6 7 +8 9 10 +11 12 13 +14 15 16 +17 18 19 +20 21 22 +3 20 4 +16 23 24 +25 23 15 +26 27 28 +3 1 29 +30 0 31 +30 32 25 +16 22 21 +33 34 35 +7 36 5 +37 38 33 +39 40 41 +42 43 37 +35 44 7 +45 46 42 +39 36 47 +45 40 48 +49 50 51 +52 45 42 +53 54 55 +0 56 1 +2 1 3 +5 39 57 +6 58 7 +5 57 6 +8 50 59 +60 13 10 +13 61 10 +8 59 9 +60 10 9 +11 62 12 +12 61 13 +62 63 12 +21 64 65 +64 66 65 +18 30 25 +67 18 25 +16 21 65 +16 65 68 +67 25 15 +16 68 14 +14 67 15 +17 30 18 +20 69 21 +3 64 20 +16 15 23 +25 32 23 +26 70 27 +29 66 64 +1 56 71 +56 72 71 +29 64 3 +1 71 29 +30 17 0 +30 31 32 +16 24 22 +33 38 34 +7 44 36 +37 43 38 +39 47 40 +42 46 43 +35 34 44 +45 48 46 +39 5 36 +45 41 40 +49 59 50 +35 7 73 +7 58 73 +57 39 41 +74 57 41 +33 35 73 +33 73 75 +37 33 75 +76 74 41 +37 75 77 +37 77 52 +76 41 45 +42 37 52 +52 76 45 +53 27 70 +78 54 53 +53 70 78 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/lift/parts.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/lift/parts.png new file mode 100644 index 00000000..da86b2a6 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/lift/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/lift/parts_old.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/lift/parts_old.png new file mode 100644 index 00000000..de13243a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/lift/parts_old.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/mine/body.txt b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/mine/body.txt new file mode 100755 index 00000000..0bf678f4 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/mine/body.txt @@ -0,0 +1,103 @@ +45 +-16.0 0.0 0 0.75 +-11.31 -11.31 0 0.75 + 0.0 -16.0 0 0.75 + 11.31 -11.31 0 0.75 + 16.0 0.0 0 0.75 + 11.31 11.31 0 0.75 + -0.0 16.0 0 0.75 +-11.31 11.31 0 0.75 +29.422066 0.009519 -0.999500 0.25 +25.401829 14.852878 -0.931249 0.1240015 +14.843359 25.411350 -0.751997 0.0343755 +-0.000001 29.431583 -0.500000 0.00025 +-14.843361 25.411348 -0.248003 0.0343755 +-25.401833 14.852878 -0.068751 0.1240015 +-29.422066 0.009516 -0.000500 0.25 +-25.401827 -14.833844 -0.068751 0.3759985 +-14.843357 -25.392317 -0.248003 0.4656245 +0.000004 -29.412546 -0.500000 0.49975 +14.843363 -25.392307 -0.751997 0.4656245 +25.401833 -14.833840 -0.931249 0.3759985 +19.921448 0.009518 0.838208 0.25 +17.189407 10.096646 0.791826 0.164375 +10.087127 17.198929 0.671250 0.104087 +-0.000000 19.930965 0.500000 0.080896 +-10.087129 17.198925 0.328750 0.104087 +-17.189411 10.096646 0.208174 0.164375 +-19.921448 0.009518 0.161792 0.25 +-17.189405 -10.077612 0.208174 0.335625 +-10.087127 -17.179895 0.328750 0.395913 +0.000001 -19.911928 0.500000 0.419104 +10.087130 -17.179886 0.671250 0.395913 +17.189411 -10.077609 0.791826 0.335625 +10.420829 0.009516 -0.676915 0.25 +8.976986 5.340415 -0.652403 0.2047485 +5.330894 8.986506 -0.590503 0.1737985 +0.000001 10.430347 -0.500000 0.1615425 +-5.330897 8.986505 -0.409497 0.1737985 +-8.976989 5.340414 -0.347597 0.2047485 +-10.420829 0.009520 -0.323085 0.25 +-8.976985 -5.321381 -0.347597 0.2952515 +-5.330895 -8.967473 -0.409497 0.3262015 +-0.000003 -10.411310 -0.500000 0.3384575 +5.330898 -8.967464 -0.590503 0.3262015 +8.976988 -5.321376 -0.652403 0.2952515 +0 0 0.5 0.75 +56 +44 0 1 +44 1 2 +44 2 3 +44 3 4 +44 4 5 +44 5 6 +44 6 7 +44 7 0 +8 9 20 +9 21 20 +9 10 21 +10 22 21 +10 11 22 +11 23 22 +11 12 23 +12 24 23 +12 13 24 +13 25 24 +13 14 25 +14 26 25 +14 15 26 +15 27 26 +15 16 27 +16 28 27 +16 17 28 +17 29 28 +17 18 29 +18 30 29 +18 19 30 +19 31 30 +19 8 31 +8 20 31 +20 21 32 +21 33 32 +21 22 33 +22 34 33 +22 23 34 +23 35 34 +23 24 35 +24 36 35 +24 25 36 +25 37 36 +25 26 37 +26 38 37 +26 27 38 +27 39 38 +27 28 39 +28 40 39 +28 29 40 +29 41 40 +29 30 41 +30 42 41 +30 31 42 +31 43 42 +31 20 43 +20 32 43 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/mine/frame.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/mine/frame.png new file mode 100644 index 00000000..388e7814 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/mine/frame.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/mine/parts.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/mine/parts.png new file mode 100755 index 00000000..744ba8dc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/mine/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/preview.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/preview.png new file mode 100644 index 00000000..f69e9c00 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/receptor.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/receptor.png new file mode 100644 index 00000000..52f50615 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/receptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/roll/bodyActive.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/roll/bodyActive.png new file mode 100644 index 00000000..da7091ad Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/roll/bodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/roll/bodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/roll/bodyInactive.png new file mode 100644 index 00000000..4e630cb6 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/roll/bodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/roll/bottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/roll/bottomCapActive.png new file mode 100644 index 00000000..b6174a74 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/roll/bottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/roll/bottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/roll/bottomCapInactive.png new file mode 100644 index 00000000..af6aa128 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/roll/bottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/shader/mine_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/shader/mine_gradient.frag new file mode 100644 index 00000000..f1d32938 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/shader/mine_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform sampler2D sampler1; +uniform float time; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + if (col.a < 1.0) { + discard; + } + gl_FragColor = texture2D(sampler1, vec2(mod(col.r - time, 1.0), 0.625)); +} diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/shader/noop.vert b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/shader/noop.vert new file mode 100644 index 00000000..002c3782 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/shader/noop.vert @@ -0,0 +1,24 @@ +precision mediump float; + +attribute vec2 aVertexPosition; +attribute vec2 aUvs; + +uniform mat3 translationMatrix; +uniform mat3 projectionMatrix; +uniform float time; + +varying vec2 vUvs; + +float rand(vec2 co) { + return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); +} + +void main() { + vUvs = aUvs; + gl_Position = vec4( + (projectionMatrix * translationMatrix * vec3(aVertexPosition.xy, 1.0)).xy, + 0.0, + 1.0 + ); + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4-bold/tap.png b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/tap.png new file mode 100644 index 00000000..7dd88912 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4-bold/tap.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/ModelRenderer.ts b/app/src/chart/gameTypes/noteskin/dance/sm4/ModelRenderer.ts new file mode 100644 index 00000000..b5b715a9 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4/ModelRenderer.ts @@ -0,0 +1,155 @@ +import { + AnimatedSprite, + BaseTexture, + Container, + Geometry, + Mesh, + MIPMAP_MODES, + Rectangle, + RenderTexture, + Shader, + Sprite, + Texture, +} from "pixi.js" + +import { App } from "../../../../../App" +import { Options } from "../../../../../util/Options" +import { NotedataEntry } from "../../../../sm/NoteTypes" + +import mineFrameUrl from "./mine/frame.png" +import minePartsUrl from "./mine/parts.png" + +import mineGradientFrag from "./shader/mine_gradient.frag?raw" +import noopVert from "./shader/noop.vert?raw" + +import { loadGeometry, splitTex } from "../../../../../util/Util" +import mineBodyGeomText from "./mine/body.txt?raw" + +import liftUrl from "./lift.png" +import tapUrl from "./tap.png" + +const mine_frame_texture = Texture.from(mineFrameUrl) +const tapTex = splitTex(Texture.from(tapUrl), 8, 8, 64, 64) +const liftTex = splitTex(Texture.from(liftUrl), 4, 1, 64, 64)[0] + +export class ModelRenderer { + static minePartsTex = BaseTexture.from(minePartsUrl, { + mipmap: MIPMAP_MODES.OFF, + }) + + static mineBodyGeom: Geometry + + static arrowTex: RenderTexture + static arrowContainer = new Container() + static mineTex: RenderTexture + static mineContainer = new Container() + static liftTex: RenderTexture + static liftContainer = new Container() + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + ModelRenderer.arrowTex = RenderTexture.create({ + width: 256, + height: 256, + resolution: Options.performance.resolution, + }) + ModelRenderer.mineTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + ModelRenderer.liftTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + + this.mineBodyGeom = await loadGeometry(mineBodyGeomText) + + { + for (let i = 0; i < 8; i++) { + const note = new AnimatedSprite(tapTex[i]) + note.x = (i % 3) * 64 + 32 + note.y = Math.floor(i / 3) * 64 + 32 + note.anchor.set(0.5) + note.name = "note" + i + ModelRenderer.arrowContainer.addChild(note) + } + } + { + const note = new AnimatedSprite(liftTex) + note.x = 32 + note.y = 32 + note.anchor.set(0.5) + + note.name = "note" + ModelRenderer.liftContainer.addChild(note) + } + { + const shader_body = Shader.from(noopVert, mineGradientFrag, { + sampler0: this.minePartsTex, + time: 0, + }) + const mine_body = new Mesh(ModelRenderer.mineBodyGeom, shader_body) + const mine_frame = new Sprite(mine_frame_texture) + mine_frame.width = 64 + mine_frame.height = 64 + mine_frame.anchor.set(0.5) + mine_frame.pivot.y = 3 // epic recenter + ModelRenderer.mineContainer.position.set(32) + ModelRenderer.mineContainer.addChild(mine_body) + ModelRenderer.mineContainer.addChild(mine_frame) + } + + this.loaded = true + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const beat = app.chartManager.chartView!.getVisualBeat() + const second = app.chartManager.chartView!.getVisualTime() + const partialBeat = ((beat % 4) + 4) % 4 + for (let i = 0; i < 8; i++) { + const tap: AnimatedSprite = ModelRenderer.arrowContainer.getChildByName( + "note" + i + )! + tap.currentFrame = Math.floor(partialBeat * 2) + } + const lift = ModelRenderer.liftContainer.children[0] as AnimatedSprite + lift.currentFrame = Math.floor(partialBeat) + ;(ModelRenderer.mineContainer.children[0]).shader.uniforms.time = + second + ModelRenderer.mineContainer.rotation = (second % 1) * Math.PI * 2 + + app.renderer.render(ModelRenderer.arrowContainer, { + renderTexture: ModelRenderer.arrowTex, + }) + app.renderer.render(ModelRenderer.mineContainer, { + renderTexture: ModelRenderer.mineTex, + }) + app.renderer.render(ModelRenderer.liftContainer, { + renderTexture: ModelRenderer.liftTex, + }) + } + + static setNoteTex(arrow: Sprite, note: NotedataEntry | undefined) { + if (note !== undefined && note.type == "Mine") { + arrow.texture = ModelRenderer.mineTex + } else if (note !== undefined && note.type == "Lift") { + arrow.texture = ModelRenderer.liftTex + } else { + const i = Math.min( + [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf(note?.quant ?? 4), + 7 + ) + arrow.texture = new Texture( + ModelRenderer.arrowTex.baseTexture, + new Rectangle((i % 3) * 64, Math.floor(i / 3) * 64, 64, 64) + ) + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/sm4/NoteFlash.ts new file mode 100644 index 00000000..1cacb0ed --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4/NoteFlash.ts @@ -0,0 +1,169 @@ +import { + AnimatedSprite, + BLEND_MODES, + Container, + Sprite, + Texture, +} from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { rgbtoHex } from "../../../../../util/Color" +import { splitTex } from "../../../../../util/Util" +import flashUrl from "./flash/flash.png" +import holdFlashUrl from "./flash/hold.png" +import mineUrl from "./flash/mine.png" + +const holdTex = Texture.from(holdFlashUrl) +const mineTex = Texture.from(mineUrl) +const flashTex = Texture.from(flashUrl) + +const FLASH_COLORS: Record = { + w0: [1.0, 1.0, 1.0, 1], + w2: [1.0, 1.0, 0.3, 1], + w3: [0.0, 1.0, 0.4, 1], + w4: [0.3, 0.8, 1.0, 1], + w5: [0.8, 0.0, 0.6, 1], + held: [1.0, 1.0, 1.0, 1], +} + +export class NoteFlashContainer extends Container { + holdExplosion = new AnimatedSprite(splitTex(holdTex, 2, 1, 128, 128)[0]) + standard = new Sprite(flashTex) + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + const col = FLASH_COLORS[event.judgement.id] ?? [1, 1, 1, 1] + this.standard.tint = rgbtoHex(col[0] * 255, col[1] * 255, col[2] * 255) + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate( + this.standard, + { + "0": { + alpha: 1.0, + "scale.x": 1, + "scale.y": 1, + }, + "0.5": { + alpha: 1.0, + "scale.x": 1.1, + "scale.y": 1.1, + }, + "1": { + alpha: 0, + "scale.x": 1.1, + "scale.y": 1.1, + }, + }, + 0.12 + ) + ) + } + }) + + noteskin.on(this, "held", event => { + if (col == event.columnNumber) { + const col = FLASH_COLORS.w2 + this.standard.tint = rgbtoHex(col[0] * 255, col[1] * 255, col[2] * 255) + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate( + this.standard, + { + "0": { + alpha: 1.0, + "scale.x": 1, + "scale.y": 1, + }, + "0.5": { + alpha: 1.0, + "scale.x": 1.1, + "scale.y": 1.1, + }, + "1": { + alpha: 0, + "scale.x": 1.1, + "scale.y": 1.1, + }, + }, + 0.12 + ) + ) + } + }) + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(mineTex) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + noteskin.onUpdate(this, () => { + this.holdExplosion.rotation = -this.rotation + }) + + this.holdExplosion.visible = false + this.holdExplosion.anchor.set(0.5) + this.holdExplosion.play() + this.addChild(this.holdExplosion) + + this.standard.alpha = 0 + this.standard.anchor.set(0.5) + this.addChild(this.standard) + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/sm4/Noteskin.ts new file mode 100644 index 00000000..977a71f2 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4/Noteskin.ts @@ -0,0 +1,185 @@ +// SM4 Default, bundled with SM4 + +import { BLEND_MODES, Container, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" + +import { ModelRenderer } from "./ModelRenderer" +import { NoteFlashContainer } from "./NoteFlash" +import receptorUrl from "./receptor.png" + +import holdBodyActiveUrl from "./hold/bodyActive.png" +import holdBodyInactiveUrl from "./hold/bodyInactive.png" +import holdBottomCapActiveUrl from "./hold/bottomCapActive.png" +import holdBottomCapInactiveUrl from "./hold/bottomCapInactive.png" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { rgbtoHex } from "../../../../../util/Color" +import { clamp } from "../../../../../util/Math" +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" +import rollBodyActiveUrl from "./roll/bodyActive.png" +import rollBodyInactiveUrl from "./roll/bodyInactive.png" +import rollBottomCapActiveUrl from "./roll/bottomCapActive.png" +import rollBottomCapInactiveUrl from "./roll/bottomCapInactive.png" + +const receptorTex = Texture.from(receptorUrl) + +const holdTex = { + hold: { + active: { + body: Texture.from(holdBodyActiveUrl), + bottomCap: Texture.from(holdBottomCapActiveUrl), + }, + inactive: { + body: Texture.from(holdBodyInactiveUrl), + bottomCap: Texture.from(holdBottomCapInactiveUrl), + }, + }, + roll: { + active: { + body: Texture.from(rollBodyActiveUrl), + bottomCap: Texture.from(rollBottomCapActiveUrl), + }, + inactive: { + body: Texture.from(rollBodyInactiveUrl), + bottomCap: Texture.from(rollBottomCapInactiveUrl), + }, + }, +} + +const rotationMap: Record = { + Left: 90, + Down: 0, + Up: 180, + Right: -90, + UpLeft: 135, + UpRight: -135, + DownRight: -45, + DownLeft: 45, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", + "NoteFlash", +] + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + const container = new Container() + const spr = new Sprite(receptorTex) + spr.width = 64 + spr.height = 64 + spr.anchor.set(0.5) + const overlay = new Sprite(receptorTex) + overlay.width = 64 + overlay.height = 64 + overlay.alpha = 0 + overlay.blendMode = BLEND_MODES.ADD + overlay.anchor.set(0.5) + container.addChild(spr, overlay) + options.noteskin.on( + container, + "press", + opt => + opt.columnNumber == options.columnNumber && (overlay.alpha = 0.2) + ) + options.noteskin.on( + container, + "lift", + opt => opt.columnNumber == options.columnNumber && (overlay.alpha = 0) + ) + options.noteskin.on(container, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + container, + { + "0": { + "scale.x": 0.75, + "scale.y": 0.75, + }, + "1": { + "scale.x": 1, + "scale.y": 1, + }, + }, + 0.11 + ) + } + }) + options.noteskin.onUpdate(container, r => { + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + const col = clamp(1 - partialBeat, 0.5, 1) * 255 + spr.tint = rgbtoHex(col, col, col) + }) + return container + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + ModelRenderer.setNoteTex(spr, options.note) + spr.anchor.set(0.5) + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer(options.noteskin, options.columnNumber) + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: { element: "Tap" }, + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "Hold Active Body": () => new HoldBody(holdTex.hold.active.body), + "Hold Inactive Body": () => new HoldBody(holdTex.hold.inactive.body), + "Hold Active TopCap": () => new Sprite(Texture.EMPTY), + "Hold Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Hold Active BottomCap": () => + new HoldTail(holdTex.hold.active.bottomCap), + "Hold Inactive BottomCap": () => + new HoldTail(holdTex.hold.inactive.bottomCap), + + "Roll Active Head": { element: "Tap" }, + "Roll Inactive Head": { element: "Tap" }, + "Roll Active Body": () => new HoldBody(holdTex.roll.active.body), + "Roll Inactive Body": () => new HoldBody(holdTex.roll.inactive.body), + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": () => + new HoldTail(holdTex.roll.active.bottomCap), + "Roll Inactive BottomCap": () => + new HoldTail(holdTex.roll.inactive.bottomCap), + }, + }, + load: function (element, options) { + const col = element.columnName + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + init() { + ModelRenderer.initArrowTex() + }, + update(renderer) { + ModelRenderer.setArrowTexTime(renderer.chartManager.app) + }, + hideIcons: ["Lift"], + metrics: { + HoldBodyBottomOffset: -32, + RollBodyBottomOffset: -32, + }, +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/flash/flash.png b/app/src/chart/gameTypes/noteskin/dance/sm4/flash/flash.png new file mode 100644 index 00000000..963374c4 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/flash/flash.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/flash/hold.png b/app/src/chart/gameTypes/noteskin/dance/sm4/flash/hold.png new file mode 100644 index 00000000..a91319d7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/flash/hold.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/sm4/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/hold/bodyActive.png b/app/src/chart/gameTypes/noteskin/dance/sm4/hold/bodyActive.png new file mode 100644 index 00000000..3ce7ee94 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/hold/bodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/hold/bodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/sm4/hold/bodyInactive.png new file mode 100644 index 00000000..7dcc2fe3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/hold/bodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/hold/bottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/sm4/hold/bottomCapActive.png new file mode 100644 index 00000000..36dd4737 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/hold/bottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/hold/bottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/sm4/hold/bottomCapInactive.png new file mode 100644 index 00000000..f923094d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/hold/bottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/lift.png b/app/src/chart/gameTypes/noteskin/dance/sm4/lift.png new file mode 100644 index 00000000..dffab9b5 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/lift.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/lift/body.txt b/app/src/chart/gameTypes/noteskin/dance/sm4/lift/body.txt new file mode 100644 index 00000000..8c53ed0b --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4/lift/body.txt @@ -0,0 +1,171 @@ +79 +-9.000000 1.000000 0.375000 0.472100 +-6.000000 25.000000 0.421875 0.855300 +-9.000000 28.000000 0.375000 0.903200 +6.000000 25.000000 0.421875 0.855300 +9.000000 28.000000 0.375000 0.903200 +-19.000000 14.000000 0.033500 0.417300 +-19.000000 11.000000 0.033500 0.434600 +-11.000000 6.000000 0.045700 0.466200 +-24.000000 1.000000 0.094600 0.472100 +-25.000000 1.000000 0.094600 0.472100 +0.000000 -23.000000 0.094600 0.088900 +19.000000 7.000000 0.094600 0.567900 +19.000000 6.000000 0.094600 0.552000 +25.000000 1.000000 0.094600 0.472100 +0.000000 -24.000000 0.437500 0.072900 +0.000000 -25.000000 0.421875 0.057000 +26.000000 1.000000 0.421875 0.472100 +-6.000000 -5.000000 0.421875 0.376300 +-19.000000 7.000000 0.437500 0.567900 +-5.375000 -6.375000 0.421875 0.368300 +9.000000 1.000000 0.375000 0.472100 +19.000000 8.000000 0.421875 0.583900 +19.000000 11.000000 0.375000 0.631800 +0.000000 -28.000000 0.375000 0.009100 +29.000000 1.000000 0.375000 0.472100 +-26.000000 1.000000 0.421875 0.472100 +5.375000 -6.375000 0.094600 0.392300 +5.000000 24.000000 0.094600 0.839400 +5.000000 -8.000000 0.094600 0.408300 +5.375000 24.375000 0.437500 0.847300 +-19.000000 8.000000 0.421875 0.583900 +-19.000000 11.000000 0.375000 0.631800 +-29.000000 1.000000 0.375000 0.472100 +11.000000 30.000000 0.079300 0.320000 +-11.000000 30.000000 0.045700 0.320000 +-11.000000 30.000000 0.045700 0.320000 +-19.000000 14.000000 0.033500 0.417300 +11.000000 6.000000 0.079300 0.466200 +11.000000 30.000000 0.079300 0.320000 +-32.000000 1.000000 0.013500 0.497200 +0.000000 -31.000000 0.062500 0.693100 +0.000000 -31.000000 0.062500 0.693100 +19.000000 14.000000 0.091500 0.417300 +11.000000 6.000000 0.079300 0.466200 +-11.000000 6.000000 0.045700 0.466200 +32.000000 1.000000 0.111500 0.497200 +19.000000 14.000000 0.091500 0.417300 +-32.000000 1.000000 0.013500 0.497200 +32.000000 1.000000 0.111500 0.497200 +-5.375000 -6.375000 0.094600 0.352400 +-19.000000 6.000000 0.094600 0.552000 +-5.000000 -8.000000 0.094600 0.336400 +19.000000 11.000000 0.091500 0.434600 +-5.000000 24.000000 0.094600 0.839400 +-5.375000 -6.375000 0.094600 0.392300 +-5.000000 -8.000000 0.094600 0.408300 +-6.000000 -5.000000 0.421875 0.392300 +-29.000000 1.000000 0.017800 0.497200 +-9.000000 1.000000 0.048800 0.495700 +-19.000000 7.000000 0.094600 0.567900 +0.000000 -24.000000 0.094600 0.072900 +24.000000 1.000000 0.094600 0.472100 +5.375000 -6.375000 0.094600 0.352400 +5.000000 -8.000000 0.094600 0.336400 +6.000000 -5.000000 0.421875 0.392300 +19.000000 7.000000 0.437500 0.567900 +5.375000 -6.375000 0.437500 0.352400 +-25.000000 1.000000 0.437500 0.472100 +25.000000 1.000000 0.437500 0.472100 +6.000000 -5.000000 0.421875 0.376300 +5.375000 24.375000 0.094600 0.847300 +-5.375000 24.375000 0.437500 0.847300 +-5.375000 -6.375000 0.437500 0.400300 +-9.000000 28.000000 0.048800 0.332200 +0.000000 -28.000000 0.062500 0.675800 +9.000000 28.000000 0.076200 0.332200 +29.000000 1.000000 0.107200 0.497200 +9.000000 1.000000 0.076200 0.495700 +-5.375000 24.375000 0.094600 0.847300 +90 +0 1 2 +2 3 4 +5 6 7 +8 9 10 +11 12 13 +14 15 16 +17 18 19 +20 21 22 +3 20 4 +16 23 24 +25 23 15 +26 27 28 +3 1 29 +30 0 31 +30 32 25 +16 22 21 +33 34 35 +7 36 5 +37 38 33 +39 40 41 +42 43 37 +35 44 7 +45 46 42 +39 36 47 +45 40 48 +49 50 51 +52 45 42 +53 54 55 +0 56 1 +2 1 3 +5 39 57 +6 58 7 +5 57 6 +8 50 59 +60 13 10 +13 61 10 +8 59 9 +60 10 9 +11 62 12 +12 61 13 +62 63 12 +21 64 65 +64 66 65 +18 30 25 +67 18 25 +16 21 65 +16 65 68 +67 25 15 +16 68 14 +14 67 15 +17 30 18 +20 69 21 +3 64 20 +16 15 23 +25 32 23 +26 70 27 +29 66 64 +1 56 71 +56 72 71 +29 64 3 +1 71 29 +30 17 0 +30 31 32 +16 24 22 +33 38 34 +7 44 36 +37 43 38 +39 47 40 +42 46 43 +35 34 44 +45 48 46 +39 5 36 +45 41 40 +49 59 50 +35 7 73 +7 58 73 +57 39 41 +74 57 41 +33 35 73 +33 73 75 +37 33 75 +76 74 41 +37 75 77 +37 77 52 +76 41 45 +42 37 52 +52 76 45 +53 27 70 +78 54 53 +53 70 78 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/lift/parts.png b/app/src/chart/gameTypes/noteskin/dance/sm4/lift/parts.png new file mode 100644 index 00000000..da86b2a6 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/lift/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/lift/parts_old.png b/app/src/chart/gameTypes/noteskin/dance/sm4/lift/parts_old.png new file mode 100644 index 00000000..de13243a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/lift/parts_old.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/mine/body.txt b/app/src/chart/gameTypes/noteskin/dance/sm4/mine/body.txt new file mode 100755 index 00000000..0bf678f4 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4/mine/body.txt @@ -0,0 +1,103 @@ +45 +-16.0 0.0 0 0.75 +-11.31 -11.31 0 0.75 + 0.0 -16.0 0 0.75 + 11.31 -11.31 0 0.75 + 16.0 0.0 0 0.75 + 11.31 11.31 0 0.75 + -0.0 16.0 0 0.75 +-11.31 11.31 0 0.75 +29.422066 0.009519 -0.999500 0.25 +25.401829 14.852878 -0.931249 0.1240015 +14.843359 25.411350 -0.751997 0.0343755 +-0.000001 29.431583 -0.500000 0.00025 +-14.843361 25.411348 -0.248003 0.0343755 +-25.401833 14.852878 -0.068751 0.1240015 +-29.422066 0.009516 -0.000500 0.25 +-25.401827 -14.833844 -0.068751 0.3759985 +-14.843357 -25.392317 -0.248003 0.4656245 +0.000004 -29.412546 -0.500000 0.49975 +14.843363 -25.392307 -0.751997 0.4656245 +25.401833 -14.833840 -0.931249 0.3759985 +19.921448 0.009518 0.838208 0.25 +17.189407 10.096646 0.791826 0.164375 +10.087127 17.198929 0.671250 0.104087 +-0.000000 19.930965 0.500000 0.080896 +-10.087129 17.198925 0.328750 0.104087 +-17.189411 10.096646 0.208174 0.164375 +-19.921448 0.009518 0.161792 0.25 +-17.189405 -10.077612 0.208174 0.335625 +-10.087127 -17.179895 0.328750 0.395913 +0.000001 -19.911928 0.500000 0.419104 +10.087130 -17.179886 0.671250 0.395913 +17.189411 -10.077609 0.791826 0.335625 +10.420829 0.009516 -0.676915 0.25 +8.976986 5.340415 -0.652403 0.2047485 +5.330894 8.986506 -0.590503 0.1737985 +0.000001 10.430347 -0.500000 0.1615425 +-5.330897 8.986505 -0.409497 0.1737985 +-8.976989 5.340414 -0.347597 0.2047485 +-10.420829 0.009520 -0.323085 0.25 +-8.976985 -5.321381 -0.347597 0.2952515 +-5.330895 -8.967473 -0.409497 0.3262015 +-0.000003 -10.411310 -0.500000 0.3384575 +5.330898 -8.967464 -0.590503 0.3262015 +8.976988 -5.321376 -0.652403 0.2952515 +0 0 0.5 0.75 +56 +44 0 1 +44 1 2 +44 2 3 +44 3 4 +44 4 5 +44 5 6 +44 6 7 +44 7 0 +8 9 20 +9 21 20 +9 10 21 +10 22 21 +10 11 22 +11 23 22 +11 12 23 +12 24 23 +12 13 24 +13 25 24 +13 14 25 +14 26 25 +14 15 26 +15 27 26 +15 16 27 +16 28 27 +16 17 28 +17 29 28 +17 18 29 +18 30 29 +18 19 30 +19 31 30 +19 8 31 +8 20 31 +20 21 32 +21 33 32 +21 22 33 +22 34 33 +22 23 34 +23 35 34 +23 24 35 +24 36 35 +24 25 36 +25 37 36 +25 26 37 +26 38 37 +26 27 38 +27 39 38 +27 28 39 +28 40 39 +28 29 40 +29 41 40 +29 30 41 +30 42 41 +30 31 42 +31 43 42 +31 20 43 +20 32 43 \ No newline at end of file diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/mine/frame.png b/app/src/chart/gameTypes/noteskin/dance/sm4/mine/frame.png new file mode 100644 index 00000000..388e7814 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/mine/frame.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/mine/parts.png b/app/src/chart/gameTypes/noteskin/dance/sm4/mine/parts.png new file mode 100755 index 00000000..744ba8dc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/mine/parts.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/preview.png b/app/src/chart/gameTypes/noteskin/dance/sm4/preview.png new file mode 100644 index 00000000..257be99f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/receptor.png b/app/src/chart/gameTypes/noteskin/dance/sm4/receptor.png new file mode 100644 index 00000000..c31d7d67 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/receptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/roll/bodyActive.png b/app/src/chart/gameTypes/noteskin/dance/sm4/roll/bodyActive.png new file mode 100644 index 00000000..da7091ad Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/roll/bodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/roll/bodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/sm4/roll/bodyInactive.png new file mode 100644 index 00000000..4e630cb6 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/roll/bodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/roll/bottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/sm4/roll/bottomCapActive.png new file mode 100644 index 00000000..b6174a74 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/roll/bottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/roll/bottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/sm4/roll/bottomCapInactive.png new file mode 100644 index 00000000..af6aa128 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/roll/bottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/shader/mine_gradient.frag b/app/src/chart/gameTypes/noteskin/dance/sm4/shader/mine_gradient.frag new file mode 100644 index 00000000..f1d32938 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4/shader/mine_gradient.frag @@ -0,0 +1,15 @@ +precision mediump float; + +varying vec2 vUvs; + +uniform sampler2D sampler0; +uniform sampler2D sampler1; +uniform float time; + +void main() { + vec4 col = texture2D(sampler0, vUvs); + if (col.a < 1.0) { + discard; + } + gl_FragColor = texture2D(sampler1, vec2(mod(col.r - time, 1.0), 0.625)); +} diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/shader/noop.vert b/app/src/chart/gameTypes/noteskin/dance/sm4/shader/noop.vert new file mode 100644 index 00000000..002c3782 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/sm4/shader/noop.vert @@ -0,0 +1,24 @@ +precision mediump float; + +attribute vec2 aVertexPosition; +attribute vec2 aUvs; + +uniform mat3 translationMatrix; +uniform mat3 projectionMatrix; +uniform float time; + +varying vec2 vUvs; + +float rand(vec2 co) { + return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); +} + +void main() { + vUvs = aUvs; + gl_Position = vec4( + (projectionMatrix * translationMatrix * vec3(aVertexPosition.xy, 1.0)).xy, + 0.0, + 1.0 + ); + +} diff --git a/app/src/chart/gameTypes/noteskin/dance/sm4/tap.png b/app/src/chart/gameTypes/noteskin/dance/sm4/tap.png new file mode 100644 index 00000000..0c8e3c01 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/sm4/tap.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/NoteFlash.ts new file mode 100644 index 00000000..5ee75058 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/NoteFlash.ts @@ -0,0 +1,221 @@ +import { + AnimatedSprite, + BLEND_MODES, + Container, + Sprite, + Texture, +} from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import { BezierAnimator } from "../../../../../util/BezierEasing" + +import bezier from "bezier-easing" +import { Options } from "../../../../../util/Options" +import { splitTex } from "../../../../../util/Util" +import holdFlashUrl from "./flash/hold.png" +import mineUrl from "./flash/mine.png" +import particleUrl from "./flash/particles.png" +import { rotationMap } from "./Noteskin" + +const holdTex = Texture.from(holdFlashUrl) +const mineTex = splitTex(Texture.from(mineUrl), 16, 1, 160, 1024)[0] +const particleTex = Texture.from(particleUrl) + +const judges = ["w0", "w1", "w2", "w3", "w4", "w5"] + +const tex: Record = {} +judges.forEach( + name => + (tex[name] = Texture.from( + new URL(`./flash/${name}.png`, import.meta.url).href + )) +) + +export class NoteFlashContainer extends Container { + holdExplosion = new Sprite(holdTex) + particles = new Sprite(particleTex) + mine = new AnimatedSprite(mineTex) + standard: Record = {} + + anims = new Set() + particleAnim?: string + mineAnim?: string + + constructor(noteskin: Noteskin, col: number, columnName: string) { + super() + + const baseScale = 0.5 + + judges.forEach(name => { + const spr = new Sprite(tex[name]) + spr.anchor.set(0.5) + spr.alpha = 0 + spr.blendMode = BLEND_MODES.ADD + this.standard[name] = spr + this.addChild(spr) + }) + + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + const target = this.standard[event.judgement.id] + if (!target) return + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate( + target, + { + "0": { + alpha: 0.875, + "scale.x": 0.8 * baseScale, + "scale.y": 0.8 * baseScale, + }, + "0.5": { + alpha: 0.875, + "scale.x": 1.1 * baseScale, + "scale.y": 1.1 * baseScale, + }, + "1": { + alpha: 0, + "scale.x": 1.1 * baseScale, + "scale.y": 1.1 * baseScale, + }, + }, + 0.12 + ) + ) + } + }) + + noteskin.on(this, "held", event => { + if (col == event.columnNumber) { + this.anims.forEach(anim => BezierAnimator.finish(anim)) + this.anims.clear() + this.anims.add( + BezierAnimator.animate( + this.standard.w0, + { + "0": { + alpha: 0.875, + "scale.x": 0.8 * baseScale, + "scale.y": 0.8 * baseScale, + }, + "0.5": { + alpha: 0.875, + "scale.x": 1.1 * baseScale, + "scale.y": 1.1 * baseScale, + }, + "1": { + alpha: 0, + "scale.x": 1.1 * baseScale, + "scale.y": 1.1 * baseScale, + }, + }, + 0.12 + ) + ) + } + }) + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.holdExplosion.visible = false + } + }) + + noteskin.onUpdate(this, () => { + this.holdExplosion.alpha = + Math.sin((Date.now() / 1000) * Math.PI * 2 * 20) * 0.1 + 1 + }) + + this.holdExplosion.visible = false + this.holdExplosion.anchor.set(0.5) + this.holdExplosion.scale.set(0.5) + this.addChild(this.holdExplosion) + + this.particles.alpha = 0 + this.particles.blendMode = BLEND_MODES.ADD + this.particles.anchor.set(0.5) + + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + this.particles.rotation = Math.random() * Math.PI * 2 + BezierAnimator.finish(this.particleAnim) + if (!["w0", "w1", "w2", "w3"].includes(event.judgement.id)) return + this.particleAnim = BezierAnimator.animate( + this.particles, + { + "0": { + alpha: 0.6, + "scale.x": 1 * baseScale, + "scale.y": 1 * baseScale, + }, + "0.7": { + alpha: 0.6, + "scale.x": 1.2 * baseScale, + "scale.y": 1.2 * baseScale, + }, + "1": { + alpha: 0, + "scale.x": 1.2 * baseScale, + "scale.y": 1.2 * baseScale, + }, + }, + 0.185, + bezier(0.16, 0.73, 0.63, 0.75) + ) + } + }) + + this.addChild(this.particles) + + this.mine.anchor.x = 0.5 + this.mine.scale.set(0.5) + this.mine.rotation = (-rotationMap[columnName] * Math.PI) / 180 + this.mine.animationSpeed = 1 / 3 + this.mine.alpha = 0 + this.addChild(this.mine) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + BezierAnimator.finish(this.mineAnim) + this.mine.anchor.y = Options.chart.reverse ? 1 : 0 + this.mine.currentFrame = 0 + this.mine.play() + this.mineAnim = BezierAnimator.animate( + this.mine, + { + "0": { + alpha: 1, + }, + "0.5": { + alpha: 1, + }, + "1": { + alpha: 0, + }, + }, + 0.6 + ) + } + }) + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/NoteRenderer.ts b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/NoteRenderer.ts new file mode 100644 index 00000000..9df00237 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/NoteRenderer.ts @@ -0,0 +1,133 @@ +import { + AnimatedSprite, + Container, + Rectangle, + RenderTexture, + Sprite, + Texture, + TilingSprite, +} from "pixi.js" + +import { App } from "../../../../../App" +import { Options } from "../../../../../util/Options" +import { splitTex } from "../../../../../util/Util" +import { NotedataEntry } from "../../../../sm/NoteTypes" + +const TAP_TEXTURES: Record = {} +;["colors", "mask", "note", "outline", "stroke", "top"].forEach( + name => + (TAP_TEXTURES[name] = Texture.from( + new URL(`./tap/${name}.png`, import.meta.url).href + )) +) + +const TAP_TOP_TEX = splitTex(TAP_TEXTURES.top, 4, 1, 384, 384)[0] + +const colorOffsets = [-80, 0, 80] + +export class NoteRenderer { + static arrowFrameTex: RenderTexture + static arrowFrameContainer = new Container() + static arrowTex: RenderTexture + static arrowContainer = new Container() + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + NoteRenderer.arrowFrameTex = RenderTexture.create({ + width: 64, + height: 64, + resolution: Options.performance.resolution, + }) + NoteRenderer.arrowTex = RenderTexture.create({ + width: 64 * 3, + height: 64, + resolution: Options.performance.resolution, + }) + + { + const note = new Sprite(TAP_TEXTURES.note) + note.width = 64 + note.height = 64 + const outline = new Sprite(TAP_TEXTURES.outline) + outline.width = 64 + outline.height = 64 + const stroke = new Sprite(TAP_TEXTURES.stroke) + stroke.width = 64 + stroke.height = 64 + this.arrowFrameContainer.addChild(note, outline, stroke) + } + { + for (let i = 0; i < 3; i++) { + const arrow_frame = new Sprite(this.arrowFrameTex) + arrow_frame.x = i * 64 + + const mask = new Sprite(TAP_TEXTURES.mask) + mask.scale.set(1 / 6) + mask.x = i * 64 + mask.y = 32 + mask.anchor.y = 0.5 + mask.alpha = 1 + + const color = new TilingSprite(TAP_TEXTURES.colors, 256, 1024) + color.tileScale.y = 1 / 4 + color.uvRespectAnchor = true + color.x = i * 64 + color.y = 32 + color.tilePosition.y = 128 + colorOffsets[i] + color.anchor.y = 0.5 + color.height = 64 + color.width = 64 + color.alpha = 1 + color.mask = mask + color.name = "c" + i + + const top = new AnimatedSprite(TAP_TOP_TEX) + top.scale.set(1 / 6) + top.x = i * 64 + top.y = 32 + top.anchor.y = 0.5 + top.alpha = 1 + top.name = "t" + i + + this.arrowContainer.addChild(arrow_frame, color, mask, top) + } + } + + this.loaded = true + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const beat = app.chartManager.chartView!.getVisualBeat() + const partialBeat = ((beat % 1) + 1) % 1 + const partialMeasure = ((beat % 4) + 4) % 4 + for (let i = 0; i < 3; i++) { + this.arrowContainer.getChildByName( + "c" + i + )!.tilePosition.y = 128 + colorOffsets[i] - (partialMeasure / 4) * 256 + this.arrowContainer.getChildByName( + "t" + i + )!.currentFrame = Math.floor(partialBeat * 4) + } + + app.renderer.render(NoteRenderer.arrowFrameContainer, { + renderTexture: NoteRenderer.arrowFrameTex, + }) + app.renderer.render(NoteRenderer.arrowContainer, { + renderTexture: NoteRenderer.arrowTex, + }) + } + + static setNoteTex(arrow: Sprite, note: NotedataEntry | undefined) { + let i = [4, 8].indexOf(note?.quant ?? 4) + if (i == -1) i = 2 + arrow.texture = new Texture( + NoteRenderer.arrowTex.baseTexture, + new Rectangle(i * 64, 0, 64, 64) + ) + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/Noteskin.ts new file mode 100644 index 00000000..f0488547 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/Noteskin.ts @@ -0,0 +1,311 @@ +// SLNEXXT-vivid, bundled with the DanceDanceRevolution XX -STARLiGHT- theme, from https://github.com/MidflightDigital/STARLiGHT-NEXXT + +import { + AnimatedSprite, + BLEND_MODES, + Container, + Sprite, + Texture, +} from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" + +import { NoteFlashContainer } from "./NoteFlash" +import { NoteRenderer } from "./NoteRenderer" + +import bezier from "bezier-easing" +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" + +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" +import mineUrl from "./mine/mine.png" +import sparkUrl from "./mine/spark.png" +import receptorUrl from "./receptor.png" +import receptorFlashUrl from "./receptorFlash.png" + +const receptorTex = Texture.from(receptorUrl) +const receptorFlashTex = Texture.from(receptorFlashUrl) + +const mineTex = Texture.from(mineUrl) +const sparkTex = splitTex(Texture.from(sparkUrl), 4, 4, 160, 128).flat() + +const sparkOffsets: Record = { + Left: 0, + Down: 5, + Up: 8, + Right: 13, +} + +const holdTex: Record>> = {} +const rollTex: Record>> = {} + +const headTex = { + Hold: { + Active: Texture.from(new URL(`./hold/active.png`, import.meta.url).href), + Inactive: Texture.from( + new URL(`./hold/inactive.png`, import.meta.url).href + ), + }, + Roll: { + Active: Texture.from(new URL(`./roll/active.png`, import.meta.url).href), + Inactive: Texture.from( + new URL(`./roll/inactive.png`, import.meta.url).href + ), + }, +} + +for (const dir of ["Left", "Down", "Up", "Right"]) { + for (const asset of ["Body", "BottomCap"]) { + for (const state of ["Active", "Inactive"]) { + if (holdTex[dir] === undefined) holdTex[dir] = {} + if (holdTex[dir][state] === undefined) holdTex[dir][state] = {} + holdTex[dir][state][asset] = Texture.from( + new URL( + `./hold/${dir.toLowerCase()}${asset}${state}.png`, + import.meta.url + ).href + ) + + if (rollTex[dir] === undefined) rollTex[dir] = {} + if (rollTex[dir][state] === undefined) rollTex[dir][state] = {} + rollTex[dir][state][asset] = Texture.from( + new URL( + `./roll/${dir.toLowerCase()}${asset}${state}.png`, + import.meta.url + ).href + ) + } + } +} + +export const rotationMap: Record = { + Left: 90, + Down: 0, + Up: 180, + Right: -90, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", + "NoteFlash", +] + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + let pressanim: string | undefined + const tex = splitTex(receptorTex, 2, 1, 404, 404) + + const container = new Container() + + const spr = new AnimatedSprite(tex[0]) + spr.width = 64 + spr.height = 64 + spr.anchor.set(0.5) + options.noteskin.on(spr, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + spr, + { + "0": { + alpha: 0.9, + width: 0.85 * 64, + height: 0.85 * 64, + }, + "1": { + alpha: 1, + width: 64, + height: 64, + }, + }, + 0.11 + ) + } + }) + + const flash = new Sprite(receptorFlashTex) + flash.blendMode = BLEND_MODES.ADD + flash.width = 64 + flash.height = 64 + flash.anchor.set(0.5) + flash.alpha = 0 + + options.noteskin.on(container, "press", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(pressanim) + pressanim = BezierAnimator.animate( + flash, + { + "0": { + alpha: 0, + width: 0.85 * 64, + height: 0.85 * 64, + }, + "1": { + alpha: 0.6, + width: 64, + height: 64, + }, + }, + 0.12, + bezier(0.5, 1, 0.89, 1) + ) + } + }) + + options.noteskin.on(container, "lift", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(pressanim) + pressanim = BezierAnimator.animate( + flash, + { + "0": { + alpha: "inherit", + width: "inherit", + height: "inherit", + }, + "1": { + alpha: 0, + width: 64 * 1.2, + height: 64, + }, + }, + 0.12, + bezier(0.11, 0, 0.5, 1) + ) + } + }) + + options.noteskin.onUpdate(container, r => { + const firstBeat = r.chart.getNotedata()[0]?.beat + if (firstBeat === undefined || r.getVisualBeat() < firstBeat - 8) { + spr.currentFrame = 1 + return + } + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + spr.currentFrame = partialBeat < 0.2 ? 0 : 1 + }) + + container.addChild(spr, flash) + + return container + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + NoteRenderer.setNoteTex(spr, options.note) + spr.anchor.set(0.5) + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer( + options.noteskin, + options.columnNumber, + options.columnName + ) + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: opt => { + const container = new Container() + const spr = new Sprite(mineTex) + spr.anchor.set(0.5) + spr.width = 64 + spr.height = 64 + spr.rotation = (rotationMap[opt.columnName] * Math.PI) / 180 + + const spark = new AnimatedSprite(sparkTex) + spark.anchor.set(0.5) + spark.scale.set(0.5) + + container.addChild(spr, spark) + + opt.noteskin.onUpdate(container, r => { + const second = r.getVisualTime() + spark.currentFrame = + (((Math.floor(second * 20) + sparkOffsets[opt.columnName]) % 16) + + 16) % + 16 + }) + + return container + }, + "Hold Active Head": () => { + const spr = new Sprite(headTex.Hold.Active) + spr.anchor.set(0.5) + spr.scale.set(1 / 6) + return spr + }, + "Hold Inactive Head": () => { + const spr = new Sprite(headTex.Hold.Inactive) + spr.anchor.set(0.5) + spr.scale.set(1 / 6) + return spr + }, + "Hold Active Body": opt => + new HoldBody(holdTex[opt.columnName].Active.Body), + "Hold Inactive Body": opt => + new HoldBody(holdTex[opt.columnName].Inactive.Body), + "Hold Active TopCap": () => new Sprite(Texture.EMPTY), + "Hold Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Hold Active BottomCap": opt => + new HoldTail(holdTex[opt.columnName].Active.BottomCap), + "Hold Inactive BottomCap": opt => + new HoldTail(holdTex[opt.columnName].Inactive.BottomCap), + + "Roll Active Head": () => { + const spr = new Sprite(headTex.Roll.Active) + spr.anchor.set(0.5) + spr.scale.set(1 / 6) + return spr + }, + "Roll Inactive Head": () => { + const spr = new Sprite(headTex.Roll.Inactive) + spr.anchor.set(0.5) + spr.scale.set(1 / 6) + return spr + }, + "Roll Active Body": opt => + new HoldBody(rollTex[opt.columnName].Active.Body), + "Roll Inactive Body": opt => + new HoldBody(rollTex[opt.columnName].Inactive.Body), + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": opt => + new HoldTail(rollTex[opt.columnName].Active.BottomCap), + "Roll Inactive BottomCap": opt => + new HoldTail(rollTex[opt.columnName].Inactive.BottomCap), + }, + }, + load: function (element, options) { + const col = element.columnName + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + init() { + NoteRenderer.initArrowTex() + }, + update(renderer) { + NoteRenderer.setArrowTexTime(renderer.chartManager.app) + }, + metrics: { + HoldBodyBottomOffset: -16, + RollBodyBottomOffset: -16, + }, +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/hold.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/hold.png new file mode 100644 index 00000000..435304e6 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/hold.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/mine.png new file mode 100644 index 00000000..9cf1e7e7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/particles.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/particles.png new file mode 100644 index 00000000..db814ce1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/particles.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w0.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w0.png new file mode 100644 index 00000000..8bbef67a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w0.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w1.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w1.png new file mode 100644 index 00000000..194ad415 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w1.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w2.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w2.png new file mode 100644 index 00000000..8c67d538 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w2.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w3.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w3.png new file mode 100644 index 00000000..299b9d12 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w3.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w4.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w4.png new file mode 100644 index 00000000..537ec348 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w4.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w5.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w5.png new file mode 100644 index 00000000..508883d3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/flash/w5.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/active.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/active.png new file mode 100644 index 00000000..180e8680 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/active.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/downBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/downBodyActive.png new file mode 100644 index 00000000..7cd9a68c Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/downBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/downBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/downBodyInactive.png new file mode 100644 index 00000000..6d3e510f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/downBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/downBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/downBottomCapActive.png new file mode 100644 index 00000000..136bc710 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/downBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/downBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/downBottomCapInactive.png new file mode 100644 index 00000000..6bcf78a9 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/downBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/inactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/inactive.png new file mode 100644 index 00000000..e24f85e9 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/inactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/leftBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/leftBodyActive.png new file mode 100644 index 00000000..75adbd02 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/leftBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/leftBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/leftBodyInactive.png new file mode 100644 index 00000000..4a6fcaf0 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/leftBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/leftBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/leftBottomCapActive.png new file mode 100644 index 00000000..812ee0e7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/leftBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/leftBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/leftBottomCapInactive.png new file mode 100644 index 00000000..20420478 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/leftBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/rightBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/rightBodyActive.png new file mode 100644 index 00000000..a63d69f1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/rightBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/rightBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/rightBodyInactive.png new file mode 100644 index 00000000..e64e9330 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/rightBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/rightBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/rightBottomCapActive.png new file mode 100644 index 00000000..c1ef2bab Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/rightBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/rightBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/rightBottomCapInactive.png new file mode 100644 index 00000000..d8949350 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/rightBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/upBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/upBodyActive.png new file mode 100644 index 00000000..3494e0bb Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/upBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/upBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/upBodyInactive.png new file mode 100644 index 00000000..cab70cdf Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/upBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/upBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/upBottomCapActive.png new file mode 100644 index 00000000..462656ff Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/upBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/upBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/upBottomCapInactive.png new file mode 100644 index 00000000..03546075 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/hold/upBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/mine/mine.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/mine/mine.png new file mode 100644 index 00000000..9888b3e4 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/mine/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/mine/spark.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/mine/spark.png new file mode 100644 index 00000000..beaab155 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/mine/spark.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/preview.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/preview.png new file mode 100644 index 00000000..5cce7102 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/receptor.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/receptor.png new file mode 100644 index 00000000..0eaa9eef Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/receptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/receptorFlash.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/receptorFlash.png new file mode 100644 index 00000000..3e58ec03 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/receptorFlash.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/active.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/active.png new file mode 100644 index 00000000..b3997dcc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/active.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/downBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/downBodyActive.png new file mode 100644 index 00000000..1a0aaace Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/downBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/downBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/downBodyInactive.png new file mode 100644 index 00000000..ba56aa0b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/downBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/downBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/downBottomCapActive.png new file mode 100644 index 00000000..84a97f11 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/downBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/downBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/downBottomCapInactive.png new file mode 100644 index 00000000..b1a1c11b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/downBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/inactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/inactive.png new file mode 100644 index 00000000..d6e48a10 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/inactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/leftBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/leftBodyActive.png new file mode 100644 index 00000000..225e9f45 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/leftBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/leftBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/leftBodyInactive.png new file mode 100644 index 00000000..31d09d93 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/leftBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/leftBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/leftBottomCapActive.png new file mode 100644 index 00000000..5721df1c Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/leftBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/leftBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/leftBottomCapInactive.png new file mode 100644 index 00000000..2f3a8e2e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/leftBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/rightBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/rightBodyActive.png new file mode 100644 index 00000000..998e3184 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/rightBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/rightBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/rightBodyInactive.png new file mode 100644 index 00000000..4a53ce9c Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/rightBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/rightBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/rightBottomCapActive.png new file mode 100644 index 00000000..0001d5d2 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/rightBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/rightBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/rightBottomCapInactive.png new file mode 100644 index 00000000..3d20f89f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/rightBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/upBodyActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/upBodyActive.png new file mode 100644 index 00000000..b5b096cd Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/upBodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/upBodyInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/upBodyInactive.png new file mode 100644 index 00000000..371abe46 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/upBodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/upBottomCapActive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/upBottomCapActive.png new file mode 100644 index 00000000..7966a9c4 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/upBottomCapActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/upBottomCapInactive.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/upBottomCapInactive.png new file mode 100644 index 00000000..368e3d1f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/roll/upBottomCapInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/colors.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/colors.png new file mode 100644 index 00000000..179caf9b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/colors.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/mask.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/mask.png new file mode 100644 index 00000000..b0a25cb4 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/mask.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/note.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/note.png new file mode 100644 index 00000000..1d90f102 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/note.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/outline.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/outline.png new file mode 100644 index 00000000..d2c978cc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/outline.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/stroke.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/stroke.png new file mode 100644 index 00000000..bb356b9f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/stroke.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/top.png b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/top.png new file mode 100644 index 00000000..5393c83f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/starlight-vivid/tap/top.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/NoteFlash.ts new file mode 100644 index 00000000..5d1ccc59 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/NoteFlash.ts @@ -0,0 +1,90 @@ +import { + AnimatedSprite, + BLEND_MODES, + Container, + Sprite, + Texture, +} from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" +import holdUrl from "./flash/hold.png" +import mineUrl from "./flash/mine.png" + +const holdTex = Texture.from(holdUrl) +const mineTex = Texture.from(mineUrl) + +export class NoteFlashContainer extends Container { + hold = new AnimatedSprite(splitTex(holdTex, 2, 1, 64, 64)[0]) + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.hold.visible = true + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.hold.visible = false + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.hold.visible = true + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.hold.visible = false + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(mineTex) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + noteskin.onUpdate(this, () => { + this.hold.alpha = + Math.sin((Date.now() / 1000) * Math.PI * 2 * 20) * 0.1 + 1 + }) + + this.hold.visible = false + this.hold.anchor.set(0.5) + + this.addChild(this.hold) + } +} diff --git a/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/Noteskin.ts b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/Noteskin.ts new file mode 100644 index 00000000..68b9e0da --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/Noteskin.ts @@ -0,0 +1,165 @@ +// SubtractByZero, bundled with Etterna +// Custom lifts by tillvit + +import { AnimatedSprite, Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" + +import { BezierAnimator } from "../../../../../util/BezierEasing" + +import { NoteFlashContainer } from "./NoteFlash" + +import { splitTex } from "../../../../../util/Util" + +import rollBodyUrl from "./rollBody.png" + +import holdBodyUrl from "./holdBody.png" + +import { HoldBody } from "../../_template/HoldBody" +import { HoldTail } from "../../_template/HoldTail" +import holdHeadUrl from "./holdHead.png" +import liftUrl from "./lift.png" +import mineUrl from "./mine.png" +import receptorUrl from "./receptor.png" +import tapUrl from "./tap.png" + +const receptorTex = splitTex(Texture.from(receptorUrl), 2, 1, 64, 64)[0] + +const mineTex = splitTex(Texture.from(mineUrl), 8, 1, 64, 64)[0] + +const tapTex = splitTex(Texture.from(tapUrl), 1, 8, 64, 64) +const holdHeadTex = splitTex(Texture.from(holdHeadUrl), 1, 8, 64, 64) + +const liftTex = splitTex(Texture.from(liftUrl), 1, 8, 64, 64) + +const holdTex = Texture.from(holdBodyUrl) +const rollTex = Texture.from(rollBodyUrl) + +export default { + elements: { + Left: { + Receptor: options => { + let zoomanim: string | undefined + const spr = new AnimatedSprite(receptorTex) + spr.width = 64 + spr.height = 64 + spr.anchor.set(0.5) + options.noteskin.on(spr, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + spr, + { + "0": { + alpha: 1.2, + width: 48, + height: 48, + }, + "1": { + alpha: 1, + width: 64, + height: 64, + }, + }, + 0.06 + ) + } + }) + options.noteskin.onUpdate(spr, r => { + const partialBeat = ((r.getVisualBeat() % 1) + 1) % 1 + spr.currentFrame = partialBeat < 0.2 ? 0 : 1 + }) + return spr + }, + Tap: options => { + const tex = + tapTex[ + Math.min( + [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + options.note?.quant ?? 4 + ) ?? 0, + 7 + ) + ] + const spr = new Sprite(tex[0]) + spr.anchor.set(0.5) + spr.width = 64 + spr.height = 64 + return spr + }, + NoteFlash: options => { + return new NoteFlashContainer(options.noteskin, options.columnNumber) + }, + Fake: { element: "Tap" }, + Lift: options => { + const tex = + liftTex[ + Math.min( + [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + options.note?.quant ?? 4 + ) ?? 0, + 7 + ) + ] + const spr = new Sprite(tex[0]) + spr.anchor.set(0.5) + spr.width = 64 + spr.height = 64 + return spr + }, + Mine: opt => { + const spr = new AnimatedSprite(mineTex) + spr.anchor.set(0.5) + spr.width = 64 + spr.height = 64 + opt.noteskin.onUpdate( + spr, + r => + (spr.currentFrame = Math.floor( + (((r.getVisualBeat() % 4) + 4) % 4) * 2 + )) + ) + return spr + }, + "Hold Active Head": options => { + const tex = + holdHeadTex[ + Math.min( + [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf( + options.note?.quant ?? 4 + ) ?? 0, + 7 + ) + ] + const spr = new Sprite(tex[0]) + spr.anchor.set(0.5) + spr.width = 64 + spr.height = 64 + return spr + }, + "Hold Inactive Head": { element: "Tap" }, + "Hold Active Body": () => new HoldBody(holdTex), + "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 HoldTail(Texture.EMPTY), + "Hold Inactive BottomCap": { element: "Hold Active BottomCap" }, + + "Roll Active Head": { element: "Hold Active Head" }, + "Roll Inactive Head": { element: "Tap" }, + "Roll Active Body": () => new HoldBody(rollTex), + "Roll Inactive Body": { element: "Roll Active Body" }, + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": () => new HoldTail(Texture.EMPTY), + "Roll Inactive BottomCap": { element: "Roll Active BottomCap" }, + }, + }, + load: function (element, options) { + element.columnName = "Left" + + const sprite = this.loadElement(element, options) + + return sprite + }, + hideIcons: ["Lift"], +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/flash/hold.png b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/flash/hold.png new file mode 100644 index 00000000..ab9933cc Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/flash/hold.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/flash/mine.png b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/holdBody.png b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/holdBody.png new file mode 100644 index 00000000..1aa2d9e3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/holdBody.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/holdHead.png b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/holdHead.png new file mode 100644 index 00000000..adbecfea Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/holdHead.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/lift.png b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/lift.png new file mode 100644 index 00000000..9125c11a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/lift.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/mine.png b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/mine.png new file mode 100644 index 00000000..a44fad7b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/preview.png b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/preview.png new file mode 100644 index 00000000..ed0338b0 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/receptor.png b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/receptor.png new file mode 100644 index 00000000..86a51010 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/receptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/rollBody.png b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/rollBody.png new file mode 100644 index 00000000..3fac73cb Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/rollBody.png differ diff --git a/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/tap.png b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/tap.png new file mode 100644 index 00000000..adbecfea Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/dance/subtractbyzero/tap.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/pump/default/NoteFlash.ts new file mode 100644 index 00000000..3772e13c --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/pump/default/NoteFlash.ts @@ -0,0 +1,156 @@ +import { + AnimatedSprite, + BLEND_MODES, + Container, + Sprite, + Texture, +} from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" +import flashUrl from "./flash/flash.png" +import mineUrl from "./flash/mine.png" +import pressUrl from "./flash/press.png" +import { tapTex } from "./NoteRenderer" +import { texOrder } from "./Noteskin" + +const flashTex = splitTex(Texture.from(flashUrl), 5, 1, 128, 128)[0] +const mineTex = Texture.from(mineUrl) + +const pressTex = splitTex(Texture.from(pressUrl), 5, 2, 96, 96) + +const doJudge = ["w0", "w1", "w2", "w3"] + +export class NoteFlashContainer extends Container { + press + pressAnim: string | undefined + + hitContainer = new Container() + + tap + note + flash + + hitAnim: string | undefined + + anims = new Set() + + constructor(noteskin: Noteskin, colName: string, col: number) { + super() + + const baseScale = 1 / 1.5 + + this.press = new Sprite(pressTex[1][texOrder.indexOf(colName)]) + this.press.alpha = 0 + this.press.anchor.set(0.5) + + this.tap = new Sprite(pressTex[0][texOrder.indexOf(colName)]) + this.tap.blendMode = BLEND_MODES.ADD + this.tap.scale.set(baseScale) + this.tap.anchor.set(0.5) + + this.note = new AnimatedSprite(tapTex[colName]) + this.note.scale.set(baseScale) + this.note.blendMode = BLEND_MODES.ADD + this.note.animationSpeed = 1 / 3 + this.note.play() + this.note.anchor.set(0.5) + + this.flash = new AnimatedSprite(flashTex) + this.flash.scale.set(2) + this.flash.blendMode = BLEND_MODES.ADD + this.flash.animationSpeed = 1 / 3 + this.flash.loop = false + this.flash.visible = false + this.flash.anchor.set(0.5) + this.flash.onComplete = () => { + this.flash.visible = false + this.flash.stop() + } + + this.hitContainer.alpha = 0 + this.hitContainer.addChild(this.tap, this.note) + + noteskin.on(this, "ghosttap", event => { + if (col == event.columnNumber) { + BezierAnimator.finish(this.pressAnim) + this.pressAnim = BezierAnimator.animate( + this.press, + { + "0": { + alpha: 1, + "scale.x": 1 * baseScale, + "scale.y": 1 * baseScale, + }, + "1": { + alpha: 0, + "scale.x": 1.3 * baseScale, + "scale.y": 1.3 * baseScale, + }, + }, + 0.25 + ) + } + }) + + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + if (!doJudge.includes(event.judgement.id)) return + BezierAnimator.finish(this.pressAnim) + BezierAnimator.finish(this.hitAnim) + this.hitAnim = BezierAnimator.animate( + this.hitContainer, + { + "0": { + alpha: 1, + "scale.x": 1, + "scale.y": 1, + }, + "1": { + alpha: 0, + "scale.x": 1.2, + "scale.y": 1.2, + }, + }, + 0.4 + ) + this.flash.visible = true + this.flash.currentFrame = 0 + this.flash.play() + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(mineTex) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + this.addChild(this.press, this.hitContainer, this.flash) + } +} diff --git a/app/src/chart/gameTypes/noteskin/pump/default/NoteRenderer.ts b/app/src/chart/gameTypes/noteskin/pump/default/NoteRenderer.ts new file mode 100644 index 00000000..634ce4be --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/pump/default/NoteRenderer.ts @@ -0,0 +1,154 @@ +import { + AnimatedSprite, + Container, + Rectangle, + RenderTexture, + Sprite, + Texture, +} from "pixi.js" +import { Options } from "../../../../../util/Options" + +import { App } from "../../../../../App" +import { splitTex } from "../../../../../util/Util" +import { NotedataEntry } from "../../../../sm/NoteTypes" + +export const tapTex: Record = {} + +const cols = ["DownLeft", "UpLeft", "Center", "UpRight", "DownRight"] + +for (const name of cols) { + tapTex[name] = splitTex( + Texture.from(new URL(`./tap/${name}.png`, import.meta.url).href), + 3, + 2, + 96, + 96 + ).flat() +} + +const rollTex: Record = {} + +for (const name of cols) { + rollTex[name] = splitTex( + Texture.from(new URL(`./roll/${name}.png`, import.meta.url).href), + 3, + 2, + 96, + 96 + ).flat() +} + +const mineTex = splitTex( + Texture.from(new URL(`./mine.png`, import.meta.url).href), + 3, + 2, + 96, + 96 +).flat() + +export class NoteRenderer { + static noteTex: RenderTexture + static noteContainer = new Container() + + static rollTex: RenderTexture + static rollContainer = new Container() + + static mineTex: RenderTexture + static mine: AnimatedSprite + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + NoteRenderer.noteTex = RenderTexture.create({ + width: 96 * 5, + height: 96, + resolution: Options.performance.resolution, + }) + + NoteRenderer.rollTex = RenderTexture.create({ + width: 96 * 5, + height: 96, + resolution: Options.performance.resolution, + }) + + NoteRenderer.mineTex = RenderTexture.create({ + width: 96, + height: 96, + resolution: Options.performance.resolution, + }) + + for (const col of cols) { + this.createSprite(this.noteContainer, col, tapTex) + } + + for (const col of cols) { + this.createSprite(this.rollContainer, col, rollTex) + } + + this.mine = new AnimatedSprite(mineTex) + + this.loaded = true + } + + static createSprite( + container: Container, + column: string, + texes: Record + ) { + const xOffset = cols.indexOf(column) * 96 + const spr = new AnimatedSprite(texes[column]) + spr.x = xOffset + container.addChild(spr) + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const time = app.chartManager.chartView!.getVisualTime() + + const frame = Math.floor(((((time % 0.3) + 0.3) % 0.3) / 0.3) * 6) + + this.noteContainer.children.forEach(a => (a.currentFrame = frame)) + this.rollContainer.children.forEach(a => (a.currentFrame = frame)) + this.mine.currentFrame = frame + + app.renderer.render(NoteRenderer.noteContainer, { + renderTexture: NoteRenderer.noteTex, + }) + + app.renderer.render(NoteRenderer.rollContainer, { + renderTexture: NoteRenderer.rollTex, + }) + + app.renderer.render(NoteRenderer.mine, { + renderTexture: NoteRenderer.mineTex, + }) + } + + static setNoteTex( + arrow: Sprite, + note: NotedataEntry | undefined, + columnName: string + ) { + if (note === undefined) return Texture.WHITE + if (note.type == "Mine") { + arrow.texture = this.mineTex + } else { + const x = cols.indexOf(columnName) * 96 + arrow.texture = new Texture( + this.noteTex.baseTexture, + new Rectangle(x, 0, 96, 96) + ) + } + } + + static setRollTex(arrow: Sprite, columnName: string) { + const x = cols.indexOf(columnName) * 96 + arrow.texture = new Texture( + this.rollTex.baseTexture, + new Rectangle(x, 0, 96, 96) + ) + } +} diff --git a/app/src/chart/gameTypes/noteskin/pump/default/Noteskin.ts b/app/src/chart/gameTypes/noteskin/pump/default/Noteskin.ts new file mode 100644 index 00000000..77e46592 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/pump/default/Noteskin.ts @@ -0,0 +1,150 @@ +// FIESTA, from https://github.com/cesarmades/piunoteskins + +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) + +export const texOrder = ["DownLeft", "UpLeft", "Center", "UpRight", "DownRight"] + +const holdTex: Record = {} + +const tailHeight = 96 + +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 { + elements: { + DownLeft: { + Receptor: options => { + const container = new Container() + const tO = texOrder.indexOf(options.columnName) + const spr = new Sprite(receptorTex[0][tO]) + spr.width = 72 + spr.height = 72 + spr.anchor.set(0.5) + + const highlight = new Sprite(receptorTex[1][tO]) + highlight.width = spr.width + highlight.height = spr.height + highlight.anchor.set(0.5) + highlight.blendMode = BLEND_MODES.ADD + + container.addChild(spr, highlight) + + options.noteskin.onUpdate(container, r => { + const beat = r.getVisualBeat() + const partialBeat = ((beat % 1) + 1) % 1 + highlight.alpha = (1 - partialBeat) / 2 + }) + return container + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + NoteRenderer.setNoteTex(spr, options.note, options.columnName) + spr.anchor.set(0.5) + spr.width = 72 + spr.height = 72 + return spr + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: { element: "Tap" }, + NoteFlash: options => + new NoteFlashContainer( + options.noteskin, + options.columnName, + options.columnNumber + ), + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "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": 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) + spr.anchor.set(0.5) + spr.width = 72 + spr.height = 72 + return spr + }, + "Roll Inactive Head": { element: "Roll Active Head" }, + "Roll Active Body": { element: "Hold Active Body" }, + "Roll Inactive Body": { element: "Hold Active Body" }, + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": { element: "Hold Active BottomCap" }, + "Roll Inactive BottomCap": { element: "Hold Active BottomCap" }, + }, + }, + load: function (element, options) { + element.columnName = "DownLeft" + + const sprite = this.loadElement(element, options) + + return sprite + }, + init() { + NoteRenderer.initArrowTex() + }, + update(renderer) { + NoteRenderer.setArrowTexTime(renderer.chartManager.app) + }, + metrics: { + HoldBodyBottomOffset: -36, + RollBodyBottomOffset: -36, + }, +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/pump/default/flash/flash.png b/app/src/chart/gameTypes/noteskin/pump/default/flash/flash.png new file mode 100644 index 00000000..868c14d2 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/flash/flash.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/flash/mine.png b/app/src/chart/gameTypes/noteskin/pump/default/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/flash/press.png b/app/src/chart/gameTypes/noteskin/pump/default/flash/press.png new file mode 100644 index 00000000..9d11ac65 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/flash/press.png differ 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/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/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/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/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/mine.png b/app/src/chart/gameTypes/noteskin/pump/default/mine.png new file mode 100644 index 00000000..473e59d5 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/preview.png b/app/src/chart/gameTypes/noteskin/pump/default/preview.png new file mode 100644 index 00000000..5276ad0e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/receptors.png b/app/src/chart/gameTypes/noteskin/pump/default/receptors.png new file mode 100644 index 00000000..c40bf258 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/receptors.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/roll/Center.png b/app/src/chart/gameTypes/noteskin/pump/default/roll/Center.png new file mode 100644 index 00000000..06d647d3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/roll/Center.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/roll/DownLeft.png b/app/src/chart/gameTypes/noteskin/pump/default/roll/DownLeft.png new file mode 100644 index 00000000..3ec7e27e Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/roll/DownLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/roll/DownRight.png b/app/src/chart/gameTypes/noteskin/pump/default/roll/DownRight.png new file mode 100644 index 00000000..50bb05b3 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/roll/DownRight.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/roll/UpLeft.png b/app/src/chart/gameTypes/noteskin/pump/default/roll/UpLeft.png new file mode 100644 index 00000000..91e731bd Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/roll/UpLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/roll/UpRight.png b/app/src/chart/gameTypes/noteskin/pump/default/roll/UpRight.png new file mode 100644 index 00000000..6e6c7878 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/roll/UpRight.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/tap/Center.png b/app/src/chart/gameTypes/noteskin/pump/default/tap/Center.png new file mode 100644 index 00000000..98aee53c Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/tap/Center.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/tap/DownLeft.png b/app/src/chart/gameTypes/noteskin/pump/default/tap/DownLeft.png new file mode 100644 index 00000000..b1aa72df Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/tap/DownLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/tap/DownRight.png b/app/src/chart/gameTypes/noteskin/pump/default/tap/DownRight.png new file mode 100644 index 00000000..61cb1d3b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/tap/DownRight.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/tap/UpLeft.png b/app/src/chart/gameTypes/noteskin/pump/default/tap/UpLeft.png new file mode 100644 index 00000000..ffb819f1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/tap/UpLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/default/tap/UpRight.png b/app/src/chart/gameTypes/noteskin/pump/default/tap/UpRight.png new file mode 100644 index 00000000..1560cb17 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/default/tap/UpRight.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteFlash.ts new file mode 100644 index 00000000..226e6432 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteFlash.ts @@ -0,0 +1,150 @@ +import { BLEND_MODES, Container, Sprite, Texture } from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { rgbtoHex } from "../../../../../util/Color" +import flashUrl from "./flash/flash.png" +import mineUrl from "./flash/mine.png" + +const FLASH_COLORS: Record = { + w0: [1.0, 1.0, 1.0, 1], + w2: [1.0, 1.0, 0.3, 1], + w3: [0.0, 1.0, 0.4, 1], + w4: [0.3, 0.8, 1.0, 1], + w5: [0.8, 0.0, 0.6, 1], + held: [1.0, 1.0, 1.0, 1], +} + +const flashTex = Texture.from(flashUrl) +const mineTex = Texture.from(mineUrl) + +export class NoteFlashContainer extends Container { + standard = new Sprite(flashTex) + + standardAnim: string | undefined + + anims = new Set() + + constructor(noteskin: Noteskin, col: number) { + super() + + this.standard.blendMode = BLEND_MODES.ADD + + const baseScale = 0.5 + + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + const col = FLASH_COLORS[event.judgement.id] ?? [1, 1, 1, 1] + this.standard.tint = rgbtoHex(col[0] * 255, col[1] * 255, col[2] * 255) + BezierAnimator.finish(this.standardAnim) + this.standardAnim = BezierAnimator.animate( + this.standard, + { + "0": { + alpha: 1, + "scale.x": 1 * baseScale, + "scale.y": 1 * baseScale, + }, + "0.5": { + alpha: 1.1, + "scale.x": 1.1 * baseScale, + "scale.y": 1.1 * baseScale, + }, + "1": { + alpha: 0, + "scale.x": 1.1 * baseScale, + "scale.y": 1.1 * baseScale, + }, + }, + 0.12 + ) + } + }) + + noteskin.on(this, "held", event => { + if (col == event.columnNumber) { + this.standard.tint = 0xffffff + BezierAnimator.finish(this.standardAnim) + this.standardAnim = BezierAnimator.animate( + this.standard, + { + "0": { + alpha: 1, + "scale.x": 1 * baseScale, + "scale.y": 1 * baseScale, + }, + "0.5": { + alpha: 1.1, + "scale.x": 1.1 * baseScale, + "scale.y": 1.1 * baseScale, + }, + "1": { + alpha: 0, + "scale.x": 1.1 * baseScale, + "scale.y": 1.1 * baseScale, + }, + }, + 0.12 + ) + } + }) + + noteskin.on(this, "holdon", event => { + if (col == event.columnNumber) { + this.standard.alpha = 1 + } + }) + + noteskin.on(this, "holdoff", event => { + if (col == event.columnNumber) { + this.standard.alpha = 0 + } + }) + + noteskin.on(this, "rollon", event => { + if (col == event.columnNumber) { + this.standard.alpha = 1 + } + }) + + noteskin.on(this, "rolloff", event => { + if (col == event.columnNumber) { + this.standard.alpha = 0 + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(mineTex) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + this.standard.alpha = 0 + this.standard.anchor.set(0.5) + this.addChild(this.standard) + } +} diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteRenderer.ts b/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteRenderer.ts new file mode 100644 index 00000000..f20ea780 --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteRenderer.ts @@ -0,0 +1,199 @@ +import { + AnimatedSprite, + Container, + Rectangle, + RenderTexture, + Sprite, + Texture, +} from "pixi.js" +import { Options } from "../../../../../util/Options" + +import { App } from "../../../../../App" +import { splitTex } from "../../../../../util/Util" +import { NotedataEntry } from "../../../../sm/NoteTypes" + +import tapCenterUrl from "./tapCenter.png" +import tapDownLeftUrl from "./tapDownLeft.png" + +import liftCenterUrl from "./liftCenter.png" +import liftDownLeftUrl from "./liftDownLeft.png" + +import fakeCenterUrl from "./fakeCenter.png" +import fakeDownLeftUrl from "./fakeDownLeft.png" + +import { rgbtoHex } from "../../../../../util/Color" +import mineBaseUrl from "./mine/base.png" +import mineOverlayUrl from "./mine/overlay.png" +import mineUnderlayUrl from "./mine/underlay.png" + +const TEXTURES: Record> = { + Tap: { + DownLeft: Texture.from(tapDownLeftUrl), + Center: Texture.from(tapCenterUrl), + }, + Lift: { + DownLeft: Texture.from(liftDownLeftUrl), + Center: Texture.from(liftCenterUrl), + }, + Fake: { + DownLeft: Texture.from(fakeDownLeftUrl), + Center: Texture.from(fakeCenterUrl), + }, + Mine: { + Base: Texture.from(mineBaseUrl), + Overlay: Texture.from(mineOverlayUrl), + Underlay: Texture.from(mineUnderlayUrl), + }, +} + +export class NoteRenderer { + static downLeftTex: RenderTexture + static downLeftContainer = new Container() + static centerTex: RenderTexture + static centerContainer = new Container() + static mineTex: RenderTexture + static mineContainer = new Container() + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + NoteRenderer.downLeftTex = RenderTexture.create({ + width: 128 * 9, + height: 128 * 3, + resolution: Options.performance.resolution, + }) + NoteRenderer.centerTex = RenderTexture.create({ + width: 128 * 9, + height: 128 * 3, + resolution: Options.performance.resolution, + }) + NoteRenderer.mineTex = RenderTexture.create({ + width: 128 * 9, + height: 128, + resolution: Options.performance.resolution, + }) + + this.layoutRow(this.downLeftContainer, TEXTURES.Tap.DownLeft, 0) + this.layoutRow(this.downLeftContainer, TEXTURES.Fake.DownLeft, 1) + this.layoutRow(this.downLeftContainer, TEXTURES.Lift.DownLeft, 2) + + this.layoutRow(this.centerContainer, TEXTURES.Tap.Center, 0) + this.layoutRow(this.centerContainer, TEXTURES.Fake.Center, 1) + this.layoutRow(this.centerContainer, TEXTURES.Lift.Center, 2) + + this.createMines(this.mineContainer) + + this.loaded = true + } + + static layoutRow(container: Container, tex: Texture, offset: number) { + const frames = splitTex(tex, 6, 9, 128, 128) + for (let i = 0; i < 9; i++) { + const spr = new AnimatedSprite(frames[i]) + spr.x = i * 128 + spr.y = offset * 128 + container.addChild(spr) + } + } + + static createMines(container: Container) { + const baseTex = splitTex(TEXTURES.Mine.Base, 1, 9, 128, 128).map(x => x[0]) + const overlayTex = splitTex(TEXTURES.Mine.Overlay, 1, 9, 128, 128).map( + x => x[0] + ) + const underlayTex = splitTex(TEXTURES.Mine.Underlay, 1, 9, 128, 128).map( + x => x[0] + ) + for (let i = 0; i < 9; i++) { + const underlay = new Sprite(underlayTex[i]) + underlay.x = i * 128 + 64 + underlay.y = 64 + underlay.name = "u" + i + underlay.anchor.set(0.5) + + const base = new Sprite(baseTex[i]) + base.x = i * 128 + 64 + base.y = 64 + base.name = "b" + i + base.anchor.set(0.5) + + const overlay = new Sprite(overlayTex[i]) + overlay.x = i * 128 + 64 + overlay.y = 64 + overlay.name = "o" + i + overlay.anchor.set(0.5) + + container.addChild(underlay, base, overlay) + } + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const beat = app.chartManager.chartView!.getVisualBeat() + + const partialBeat = ((beat % 1) + 1) % 1 + const frame = Math.floor(partialBeat * 6) + + this.downLeftContainer.children.forEach(a => (a.currentFrame = frame)) + this.centerContainer.children.forEach(a => (a.currentFrame = frame)) + + app.renderer.render(NoteRenderer.downLeftContainer, { + renderTexture: NoteRenderer.downLeftTex, + }) + app.renderer.render(NoteRenderer.centerContainer, { + renderTexture: NoteRenderer.centerTex, + }) + app.renderer.render(NoteRenderer.mineContainer, { + renderTexture: NoteRenderer.mineTex, + }) + + for (let i = 0; i < 9; i++) { + this.mineContainer.getChildByName("u" + i)!.tint = rgbtoHex( + Math.round(255 - 153 * partialBeat), + 0, + 0 + ) + this.mineContainer.getChildByName("b" + i)!.rotation = + (80 / 180) * Math.PI * beat + this.mineContainer.getChildByName("o" + i)!.rotation = + (-40 / 180) * Math.PI * beat + } + } + + static setNoteTex( + arrow: Sprite, + note: NotedataEntry | undefined, + columnName: string + ) { + if (note === undefined) return Texture.WHITE + if (note.type == "Mine") { + const quant = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf(note.quant) + arrow.texture = new Texture( + this.mineTex.baseTexture, + new Rectangle(Math.min(8, quant) * 128, 0, 128, 128) + ) + } else { + const quant = [4, 8, 12, 16, 24, 32, 48, 64, 96, 192].indexOf(note.quant) + const row = this.getNoteRow(note.type) + const tex = columnName == "Center" ? this.centerTex : this.downLeftTex + arrow.texture = new Texture( + tex.baseTexture, + new Rectangle(Math.min(8, quant) * 128, row * 128, 128, 128) + ) + } + } + + static getNoteRow(noteType: string) { + switch (noteType) { + case "Fake": + return 1 + case "Lift": + return 2 + default: + return 0 + } + } +} diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteSkin.ts b/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteSkin.ts new file mode 100644 index 00000000..29fafccd --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/pump/fourv2/NoteSkin.ts @@ -0,0 +1,166 @@ +// SM4 Bold, bundled with SM4 + +import { Sprite, Texture } from "pixi.js" +import { NoteskinOptions } from "../../Noteskin" +import { NoteRenderer } from "./NoteRenderer" + +import cReceptor from "./centerReceptor.png" +import dlReceptor from "./downLeftReceptor.png" + +import holdBodyActiveUrl from "./hold/bodyActive.png" +import holdBodyInactiveUrl from "./hold/bodyInactive.png" +import holdCapActiveUrl from "./hold/capActive.png" +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" +import rollCapActiveUrl from "./roll/capActive.png" +import rollCapInactiveUrl from "./roll/capInactive.png" + +const dlReceptorTex = Texture.from(dlReceptor) +const cReceptorTex = Texture.from(cReceptor) + +const holdTex = { + hold: { + active: { + body: Texture.from(holdBodyActiveUrl), + cap: Texture.from(holdCapActiveUrl), + }, + inactive: { + body: Texture.from(holdBodyInactiveUrl), + cap: Texture.from(holdCapInactiveUrl), + }, + }, + roll: { + active: { + body: Texture.from(rollBodyActiveUrl), + cap: Texture.from(rollCapActiveUrl), + }, + inactive: { + body: Texture.from(rollBodyInactiveUrl), + cap: Texture.from(rollCapInactiveUrl), + }, + }, +} +const rotationMap: Record = { + Center: 0, + DownLeft: 0, + UpLeft: 90, + UpRight: 180, + DownRight: 270, +} + +const toRotate = [ + "Receptor", + "Tap", + "Lift", + "Fake", + "Hold Inactive Head", + "Hold Active Head", + "Roll Inactive Head", + "Roll Active Head", +] + +export default { + elements: { + DownLeft: { + Receptor: options => { + const spr = new Sprite( + options.columnName == "Center" ? cReceptorTex : dlReceptorTex + ) + spr.width = 72 + spr.height = 72 + spr.anchor.set(0.5) + + let zoomanim: string | undefined + options.noteskin.on(spr, "ghosttap", opt => { + if (opt.columnNumber == options.columnNumber) { + BezierAnimator.finish(zoomanim) + zoomanim = BezierAnimator.animate( + spr, + { + "0": { + width: 54, + height: 54, + }, + "1": { + width: 72, + height: 72, + }, + }, + 0.11 + ) + } + }) + + options.noteskin.onUpdate(spr, r => { + const beat = r.getVisualBeat() + let brightness = 204 + if (((beat % 1) + 1) % 1 > 0.2) brightness = 102 + spr.tint = rgbtoHex(brightness, brightness, brightness) + }) + return spr + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + NoteRenderer.setNoteTex(spr, options.note, options.columnName) + spr.anchor.set(0.5) + spr.width = 72 + spr.height = 72 + return spr + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: { element: "Tap" }, + NoteFlash: options => + new NoteFlashContainer(options.noteskin, options.columnNumber), + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "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": () => 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) { + const col = element.columnName + + element.columnName = "DownLeft" + + const sprite = this.loadElement(element, options) + + if (toRotate.includes(element.element)) { + sprite.rotation = (rotationMap[col] * Math.PI) / 180 + } + + return sprite + }, + init() { + NoteRenderer.initArrowTex() + }, + update(renderer) { + NoteRenderer.setArrowTexTime(renderer.chartManager.app) + }, + hideIcons: ["Lift", "Fake"], +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/centerReceptor.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/centerReceptor.png new file mode 100644 index 00000000..d4e1b18d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/centerReceptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/downLeftReceptor.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/downLeftReceptor.png new file mode 100644 index 00000000..be2f6586 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/downLeftReceptor.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/fakeCenter.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/fakeCenter.png new file mode 100644 index 00000000..5ed5e3ce Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/fakeCenter.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/fakeDownLeft.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/fakeDownLeft.png new file mode 100644 index 00000000..f1b01490 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/fakeDownLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/flash/flash.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/flash/flash.png new file mode 100644 index 00000000..7edf20a8 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/flash/flash.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/flash/mine.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/hold/bodyActive.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/hold/bodyActive.png new file mode 100644 index 00000000..ee70ab7a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/hold/bodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/hold/bodyInactive.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/hold/bodyInactive.png new file mode 100644 index 00000000..597a5e0d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/hold/bodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/hold/capActive.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/hold/capActive.png new file mode 100644 index 00000000..7edc5c4b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/hold/capActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/hold/capInactive.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/hold/capInactive.png new file mode 100644 index 00000000..6442a61c Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/hold/capInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/liftCenter.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/liftCenter.png new file mode 100644 index 00000000..22c23806 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/liftCenter.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/liftDownLeft.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/liftDownLeft.png new file mode 100644 index 00000000..293714fd Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/liftDownLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/mine/base.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/mine/base.png new file mode 100644 index 00000000..dae08720 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/mine/base.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/mine/overlay.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/mine/overlay.png new file mode 100644 index 00000000..2ae52114 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/mine/overlay.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/mine/underlay.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/mine/underlay.png new file mode 100644 index 00000000..a8d92a22 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/mine/underlay.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/preview.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/preview.png new file mode 100644 index 00000000..4fb5b79d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/roll/bodyActive.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/roll/bodyActive.png new file mode 100644 index 00000000..f81e27b7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/roll/bodyActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/roll/bodyInactive.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/roll/bodyInactive.png new file mode 100644 index 00000000..deccf4e9 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/roll/bodyInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/roll/capActive.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/roll/capActive.png new file mode 100644 index 00000000..98ac7072 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/roll/capActive.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/roll/capInactive.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/roll/capInactive.png new file mode 100644 index 00000000..3792490b Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/roll/capInactive.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/tapCenter.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/tapCenter.png new file mode 100644 index 00000000..11c34283 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/tapCenter.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/fourv2/tapDownLeft.png b/app/src/chart/gameTypes/noteskin/pump/fourv2/tapDownLeft.png new file mode 100644 index 00000000..bf5fdc66 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/fourv2/tapDownLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/NoteFlash.ts b/app/src/chart/gameTypes/noteskin/pump/prime/NoteFlash.ts new file mode 100644 index 00000000..3772e13c --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/pump/prime/NoteFlash.ts @@ -0,0 +1,156 @@ +import { + AnimatedSprite, + BLEND_MODES, + Container, + Sprite, + Texture, +} from "pixi.js" +import { Noteskin } from "../../Noteskin" + +import { BezierAnimator } from "../../../../../util/BezierEasing" +import { splitTex } from "../../../../../util/Util" +import flashUrl from "./flash/flash.png" +import mineUrl from "./flash/mine.png" +import pressUrl from "./flash/press.png" +import { tapTex } from "./NoteRenderer" +import { texOrder } from "./Noteskin" + +const flashTex = splitTex(Texture.from(flashUrl), 5, 1, 128, 128)[0] +const mineTex = Texture.from(mineUrl) + +const pressTex = splitTex(Texture.from(pressUrl), 5, 2, 96, 96) + +const doJudge = ["w0", "w1", "w2", "w3"] + +export class NoteFlashContainer extends Container { + press + pressAnim: string | undefined + + hitContainer = new Container() + + tap + note + flash + + hitAnim: string | undefined + + anims = new Set() + + constructor(noteskin: Noteskin, colName: string, col: number) { + super() + + const baseScale = 1 / 1.5 + + this.press = new Sprite(pressTex[1][texOrder.indexOf(colName)]) + this.press.alpha = 0 + this.press.anchor.set(0.5) + + this.tap = new Sprite(pressTex[0][texOrder.indexOf(colName)]) + this.tap.blendMode = BLEND_MODES.ADD + this.tap.scale.set(baseScale) + this.tap.anchor.set(0.5) + + this.note = new AnimatedSprite(tapTex[colName]) + this.note.scale.set(baseScale) + this.note.blendMode = BLEND_MODES.ADD + this.note.animationSpeed = 1 / 3 + this.note.play() + this.note.anchor.set(0.5) + + this.flash = new AnimatedSprite(flashTex) + this.flash.scale.set(2) + this.flash.blendMode = BLEND_MODES.ADD + this.flash.animationSpeed = 1 / 3 + this.flash.loop = false + this.flash.visible = false + this.flash.anchor.set(0.5) + this.flash.onComplete = () => { + this.flash.visible = false + this.flash.stop() + } + + this.hitContainer.alpha = 0 + this.hitContainer.addChild(this.tap, this.note) + + noteskin.on(this, "ghosttap", event => { + if (col == event.columnNumber) { + BezierAnimator.finish(this.pressAnim) + this.pressAnim = BezierAnimator.animate( + this.press, + { + "0": { + alpha: 1, + "scale.x": 1 * baseScale, + "scale.y": 1 * baseScale, + }, + "1": { + alpha: 0, + "scale.x": 1.3 * baseScale, + "scale.y": 1.3 * baseScale, + }, + }, + 0.25 + ) + } + }) + + noteskin.on(this, "hit", event => { + if (col == event.columnNumber) { + if (!doJudge.includes(event.judgement.id)) return + BezierAnimator.finish(this.pressAnim) + BezierAnimator.finish(this.hitAnim) + this.hitAnim = BezierAnimator.animate( + this.hitContainer, + { + "0": { + alpha: 1, + "scale.x": 1, + "scale.y": 1, + }, + "1": { + alpha: 0, + "scale.x": 1.2, + "scale.y": 1.2, + }, + }, + 0.4 + ) + this.flash.visible = true + this.flash.currentFrame = 0 + this.flash.play() + } + }) + + noteskin.on(this, "hitmine", event => { + if (col == event.columnNumber) { + const mine = new Sprite(mineTex) + mine.alpha = 0 + mine.anchor.set(0.5) + mine.blendMode = BLEND_MODES.ADD + this.addChild(mine) + BezierAnimator.animate( + mine, + { + "0": { + alpha: 1, + rotation: 0, + }, + "0.5": { + alpha: 1, + rotation: (90 * Math.PI) / 180, + }, + "1": { + alpha: 0, + rotation: (180 * Math.PI) / 180, + }, + }, + 0.4, + undefined, + () => mine.destroy() + ) + } + }) + + this.addChild(this.press, this.hitContainer, this.flash) + } +} diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/NoteRenderer.ts b/app/src/chart/gameTypes/noteskin/pump/prime/NoteRenderer.ts new file mode 100644 index 00000000..634ce4be --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/pump/prime/NoteRenderer.ts @@ -0,0 +1,154 @@ +import { + AnimatedSprite, + Container, + Rectangle, + RenderTexture, + Sprite, + Texture, +} from "pixi.js" +import { Options } from "../../../../../util/Options" + +import { App } from "../../../../../App" +import { splitTex } from "../../../../../util/Util" +import { NotedataEntry } from "../../../../sm/NoteTypes" + +export const tapTex: Record = {} + +const cols = ["DownLeft", "UpLeft", "Center", "UpRight", "DownRight"] + +for (const name of cols) { + tapTex[name] = splitTex( + Texture.from(new URL(`./tap/${name}.png`, import.meta.url).href), + 3, + 2, + 96, + 96 + ).flat() +} + +const rollTex: Record = {} + +for (const name of cols) { + rollTex[name] = splitTex( + Texture.from(new URL(`./roll/${name}.png`, import.meta.url).href), + 3, + 2, + 96, + 96 + ).flat() +} + +const mineTex = splitTex( + Texture.from(new URL(`./mine.png`, import.meta.url).href), + 3, + 2, + 96, + 96 +).flat() + +export class NoteRenderer { + static noteTex: RenderTexture + static noteContainer = new Container() + + static rollTex: RenderTexture + static rollContainer = new Container() + + static mineTex: RenderTexture + static mine: AnimatedSprite + + private static loaded = false + + static async initArrowTex() { + if (this.loaded) return + + // Initialize rendertextures in here so we can read options + NoteRenderer.noteTex = RenderTexture.create({ + width: 96 * 5, + height: 96, + resolution: Options.performance.resolution, + }) + + NoteRenderer.rollTex = RenderTexture.create({ + width: 96 * 5, + height: 96, + resolution: Options.performance.resolution, + }) + + NoteRenderer.mineTex = RenderTexture.create({ + width: 96, + height: 96, + resolution: Options.performance.resolution, + }) + + for (const col of cols) { + this.createSprite(this.noteContainer, col, tapTex) + } + + for (const col of cols) { + this.createSprite(this.rollContainer, col, rollTex) + } + + this.mine = new AnimatedSprite(mineTex) + + this.loaded = true + } + + static createSprite( + container: Container, + column: string, + texes: Record + ) { + const xOffset = cols.indexOf(column) * 96 + const spr = new AnimatedSprite(texes[column]) + spr.x = xOffset + container.addChild(spr) + } + + static setArrowTexTime(app: App) { + if (!this.loaded) return + const time = app.chartManager.chartView!.getVisualTime() + + const frame = Math.floor(((((time % 0.3) + 0.3) % 0.3) / 0.3) * 6) + + this.noteContainer.children.forEach(a => (a.currentFrame = frame)) + this.rollContainer.children.forEach(a => (a.currentFrame = frame)) + this.mine.currentFrame = frame + + app.renderer.render(NoteRenderer.noteContainer, { + renderTexture: NoteRenderer.noteTex, + }) + + app.renderer.render(NoteRenderer.rollContainer, { + renderTexture: NoteRenderer.rollTex, + }) + + app.renderer.render(NoteRenderer.mine, { + renderTexture: NoteRenderer.mineTex, + }) + } + + static setNoteTex( + arrow: Sprite, + note: NotedataEntry | undefined, + columnName: string + ) { + if (note === undefined) return Texture.WHITE + if (note.type == "Mine") { + arrow.texture = this.mineTex + } else { + const x = cols.indexOf(columnName) * 96 + arrow.texture = new Texture( + this.noteTex.baseTexture, + new Rectangle(x, 0, 96, 96) + ) + } + } + + static setRollTex(arrow: Sprite, columnName: string) { + const x = cols.indexOf(columnName) * 96 + arrow.texture = new Texture( + this.rollTex.baseTexture, + new Rectangle(x, 0, 96, 96) + ) + } +} diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/Noteskin.ts b/app/src/chart/gameTypes/noteskin/pump/prime/Noteskin.ts new file mode 100644 index 00000000..840d300d --- /dev/null +++ b/app/src/chart/gameTypes/noteskin/pump/prime/Noteskin.ts @@ -0,0 +1,149 @@ +// PRIME, from https://github.com/cesarmades/piunoteskins + +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) + +export const texOrder = ["DownLeft", "UpLeft", "Center", "UpRight", "DownRight"] + +const holdTex: Record = {} + +const tailHeight = 96 + +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 { + elements: { + DownLeft: { + Receptor: options => { + const container = new Container() + const tO = texOrder.indexOf(options.columnName) + const spr = new Sprite(receptorTex[0][tO]) + spr.width = 72 + spr.height = 72 + spr.anchor.set(0.5) + + const highlight = new Sprite(receptorTex[1][tO]) + highlight.width = spr.width + highlight.height = spr.height + highlight.anchor.set(0.5) + highlight.blendMode = BLEND_MODES.ADD + + container.addChild(spr, highlight) + + options.noteskin.onUpdate(container, r => { + const beat = r.getVisualBeat() + const partialBeat = ((beat % 1) + 1) % 1 + highlight.alpha = (1 - partialBeat) / 2 + }) + return container + }, + Tap: options => { + const spr = new Sprite(Texture.WHITE) + NoteRenderer.setNoteTex(spr, options.note, options.columnName) + spr.anchor.set(0.5) + spr.width = 72 + spr.height = 72 + return spr + }, + Fake: { element: "Tap" }, + Lift: { element: "Tap" }, + Mine: { element: "Tap" }, + NoteFlash: options => + new NoteFlashContainer( + options.noteskin, + options.columnName, + options.columnNumber + ), + "Hold Active Head": { element: "Tap" }, + "Hold Inactive Head": { element: "Tap" }, + "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": 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) + spr.anchor.set(0.5) + spr.width = 72 + spr.height = 72 + return spr + }, + "Roll Inactive Head": { element: "Roll Active Head" }, + "Roll Active Body": { element: "Hold Active Body" }, + "Roll Inactive Body": { element: "Hold Active Body" }, + "Roll Active TopCap": () => new Sprite(Texture.EMPTY), + "Roll Inactive TopCap": () => new Sprite(Texture.EMPTY), + "Roll Active BottomCap": { element: "Hold Active BottomCap" }, + "Roll Inactive BottomCap": { element: "Hold Active BottomCap" }, + }, + }, + load: function (element, options) { + element.columnName = "DownLeft" + + const sprite = this.loadElement(element, options) + + return sprite + }, + init() { + NoteRenderer.initArrowTex() + }, + update(renderer) { + NoteRenderer.setArrowTexTime(renderer.chartManager.app) + }, + metrics: { + HoldBodyBottomOffset: -36, + RollBodyBottomOffset: -36, + }, +} satisfies NoteskinOptions diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/flash/flash.png b/app/src/chart/gameTypes/noteskin/pump/prime/flash/flash.png new file mode 100644 index 00000000..973543d0 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/flash/flash.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/flash/mine.png b/app/src/chart/gameTypes/noteskin/pump/prime/flash/mine.png new file mode 100644 index 00000000..34c26623 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/flash/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/flash/press.png b/app/src/chart/gameTypes/noteskin/pump/prime/flash/press.png new file mode 100644 index 00000000..00c5ab8f Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/flash/press.png differ 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/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/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/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/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/mine.png b/app/src/chart/gameTypes/noteskin/pump/prime/mine.png new file mode 100644 index 00000000..473e59d5 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/mine.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/preview.png b/app/src/chart/gameTypes/noteskin/pump/prime/preview.png new file mode 100644 index 00000000..70a7342a Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/preview.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/receptors.png b/app/src/chart/gameTypes/noteskin/pump/prime/receptors.png new file mode 100644 index 00000000..fe69ce43 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/receptors.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/roll/Center.png b/app/src/chart/gameTypes/noteskin/pump/prime/roll/Center.png new file mode 100644 index 00000000..1cce86dd Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/roll/Center.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/roll/DownLeft.png b/app/src/chart/gameTypes/noteskin/pump/prime/roll/DownLeft.png new file mode 100644 index 00000000..f5d4f183 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/roll/DownLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/roll/DownRight.png b/app/src/chart/gameTypes/noteskin/pump/prime/roll/DownRight.png new file mode 100644 index 00000000..5a7ebfc1 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/roll/DownRight.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/roll/UpLeft.png b/app/src/chart/gameTypes/noteskin/pump/prime/roll/UpLeft.png new file mode 100644 index 00000000..f257b1d7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/roll/UpLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/roll/UpRight.png b/app/src/chart/gameTypes/noteskin/pump/prime/roll/UpRight.png new file mode 100644 index 00000000..641843a7 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/roll/UpRight.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/tap/Center.png b/app/src/chart/gameTypes/noteskin/pump/prime/tap/Center.png new file mode 100644 index 00000000..c9b872e5 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/tap/Center.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/tap/DownLeft.png b/app/src/chart/gameTypes/noteskin/pump/prime/tap/DownLeft.png new file mode 100644 index 00000000..4011df7d Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/tap/DownLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/tap/DownRight.png b/app/src/chart/gameTypes/noteskin/pump/prime/tap/DownRight.png new file mode 100644 index 00000000..04e7c832 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/tap/DownRight.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/tap/UpLeft.png b/app/src/chart/gameTypes/noteskin/pump/prime/tap/UpLeft.png new file mode 100644 index 00000000..35725323 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/tap/UpLeft.png differ diff --git a/app/src/chart/gameTypes/noteskin/pump/prime/tap/UpRight.png b/app/src/chart/gameTypes/noteskin/pump/prime/tap/UpRight.png new file mode 100644 index 00000000..7456aa68 Binary files /dev/null and b/app/src/chart/gameTypes/noteskin/pump/prime/tap/UpRight.png differ diff --git a/app/src/chart/gameTypes/pump/PumpGameLogic.ts b/app/src/chart/gameTypes/pump/PumpGameLogic.ts new file mode 100644 index 00000000..e0d9047b --- /dev/null +++ b/app/src/chart/gameTypes/pump/PumpGameLogic.ts @@ -0,0 +1,370 @@ +import { Options } from "../../../util/Options" +import { bsearch, getNoteEnd } from "../../../util/Util" +import { ChartManager } from "../../ChartManager" +import { TimingWindowCollection } from "../../play/TimingWindowCollection" +import { + HoldNotedataEntry, + Notedata, + NotedataEntry, + isHoldNote, + isTapNote, +} from "../../sm/NoteTypes" +import { TimingData } from "../../sm/TimingData" +import { BasicGameLogic } from "../common/BasicGameLogic" + +export class PumpGameLogic extends BasicGameLogic { + protected tickProgress: Set<{ + ticks: number[] + hold: HoldNotedataEntry + nextTick: number + hitLast: boolean + }> = new Set() + protected collection: TimingWindowCollection = + TimingWindowCollection.getCollection("ITG") + + protected holdIndex = 0 + usesHoldTicks = true + + update(chartManager: ChartManager): void { + if (!chartManager.loadedChart || !chartManager.chartView) return + const beat = chartManager.chartView.getBeatWithOffset() + const hitTime = chartManager.chartView.getTimeWithOffset() + const hitWindowStart = + hitTime - (this.collection.maxWindowMS() / 1000) * Options.audio.rate + let lastChord = -1 + // Do Misses + while ( + chartManager.loadedChart.getNotedata()[this.missNoteIndex] && + chartManager.loadedChart.getNotedata()[this.missNoteIndex].second < + hitWindowStart + ) { + const note = chartManager.loadedChart.getNotedata()[this.missNoteIndex] + if ( + note.beat != lastChord && + note.type != "Mine" && + !note.fake && + !note.warped && + !note.gameplay!.hasHit + ) { + lastChord = note.beat + chartManager.chartView.doJudgement( + note, + null, + this.collection.getMissJudgement() + ) + const chord = this.chordCohesion.get(note.beat)! + chartManager.gameStats?.addDataPoint( + chord, + this.collection.getMissJudgement(), + null + ) + } + this.missNoteIndex++ + } + + while ( + chartManager.loadedChart.getNotedata()[this.holdIndex] && + chartManager.loadedChart.getNotedata()[this.holdIndex].beat < beat + ) { + const note = chartManager.loadedChart.getNotedata()[this.holdIndex] + if (isHoldNote(note) && !note.fake) { + this.tickProgress.add({ + ticks: this.generateHoldTicks( + chartManager.loadedChart.timingData, + note + ), + hold: note, + nextTick: 0, + hitLast: false, + }) + } + this.holdIndex++ + } + + // Do Holds/Rolls + for (const dat of this.tickProgress) { + const hold = dat.hold + const ticks = dat.ticks + if (hold.type == "Hold") { + if (this.heldCols.isPressed(hold.col)) { + hold.gameplay!.lastHoldActivation = Date.now() + hold.gameplay!.droppedHoldBeat = undefined + if (hold.gameplay?.droppedHoldBeat !== undefined) { + chartManager.chartView.getNotefield().activateHold(hold.col) + } + } else if (hold.gameplay!.droppedHoldBeat === undefined) { + hold.gameplay!.droppedHoldBeat = + chartManager.chartView.getBeatWithOffset() + chartManager.chartView.getNotefield().releaseHold(hold.col) + } + } + if (hold.type == "Roll") { + if ( + this.shouldDropHold(hold, Date.now()) && + hold.gameplay!.droppedHoldBeat === undefined + ) { + hold.gameplay!.droppedHoldBeat = + chartManager.chartView.getBeatWithOffset() + chartManager.chartView.getNotefield().releaseRoll(hold.col) + } + } + while (ticks[dat.nextTick] !== undefined && ticks[dat.nextTick] < beat) { + if (hold.type == "Hold") { + if (this.heldCols.isPressed(hold.col)) { + chartManager.chartView.doJudgement( + hold, + null, + this.collection.getStandardWindows()[0] + ) + chartManager.gameStats?.addDataPoint( + [hold], + this.collection.getStandardWindows()[0], + null + ) + if (dat.nextTick == ticks.length - 1) { + dat.hitLast = true + } + } else { + chartManager.chartView.doJudgement( + hold, + null, + this.collection.getMissJudgement() + ) + chartManager.gameStats?.addDataPoint( + [hold], + this.collection.getMissJudgement(), + null + ) + } + } + if (hold.type == "Roll") { + if ( + hold.gameplay?.droppedHoldBeat === undefined && + hold.gameplay?.lastHoldActivation !== undefined + ) { + chartManager.chartView.doJudgement( + hold, + null, + this.collection.getStandardWindows()[0] + ) + chartManager.gameStats?.addDataPoint( + [hold], + this.collection.getStandardWindows()[0], + null + ) + if (dat.nextTick == ticks.length - 1) { + dat.hitLast = true + } + } else { + chartManager.chartView.doJudgement( + hold, + null, + this.collection.getMissJudgement() + ) + chartManager.gameStats?.addDataPoint( + [hold], + this.collection.getMissJudgement(), + null + ) + } + } + dat.nextTick++ + } + + if ( + chartManager.chartView.getTimeWithOffset() >= + chartManager.chartView.chart.getSecondsFromBeat(hold.beat + hold.hold) + ) { + hold.gameplay!.hideNote = + dat.hitLast || + ticks.length == 0 || + hold.gameplay?.droppedHoldBeat === undefined + this.tickProgress.delete(dat) + } + } + + // Do Mines + for (const col of this.heldCols.getHeldCols()) { + const mine = this.getClosestNote( + chartManager.loadedChart.getNotedata(), + chartManager.chartView.getTimeWithOffset() - + this.collection.getMineJudgement().getTimingWindowMS() / 2000, + col, + ["Mine"], + this.collection.getMineJudgement().getTimingWindowMS() / 2 + ) + if (mine) { + mine.gameplay!.hasHit = true + mine.gameplay!.hideNote = true + chartManager.chartView.doJudgement( + mine, + 0, + this.collection.getMineJudgement() + ) + chartManager.gameStats?.addDataPoint( + [mine], + this.collection.getMineJudgement(), + 0 + ) + chartManager.mine.play() + } + } + } + + protected hitNote( + chartManager: ChartManager, + note: NotedataEntry, + hitTime: number + ) { + note.gameplay!.hasHit = true + const chord = this.chordCohesion.get(note.beat)! + if (chord.every(note => note.gameplay!.hasHit)) { + const judge = this.collection.judgeInput( + (hitTime - note.second) / Options.audio.rate + ) + const hideNote = this.collection.shouldHideNote(judge) + chord.forEach(note => { + chartManager.chartView!.doJudgement( + note, + (hitTime - note.second) / Options.audio.rate, + judge + ) + if (hideNote && isTapNote(note)) note.gameplay!.hideNote = true + }) + chartManager.gameStats?.addDataPoint( + chord, + judge, + (hitTime - note.second) / Options.audio.rate + ) + } + } + + startPlay(chartManager: ChartManager): void { + if (!chartManager.loadedChart || !chartManager.chartView) return + this.collection = TimingWindowCollection.getCollection( + Options.play.timingCollection + ) + this.chordCohesion.clear() + for (const note of chartManager.loadedChart.getNotedata()) { + if (note.type == "Mine" || note.fake || note.warped) continue + if (!this.chordCohesion.has(note.beat)) + this.chordCohesion.set(note.beat, []) + this.chordCohesion.get(note.beat)!.push(note) + } + const hitTime = chartManager.chartView.getTimeWithOffset() + const hitWindowStart = + hitTime - (this.collection.maxWindowMS() / 1000) * Options.audio.rate + let firstHittableNote = + bsearch( + chartManager.loadedChart.getNotedata(), + hitWindowStart, + a => a.second + ) + 1 + if ( + firstHittableNote >= 1 && + hitWindowStart <= + chartManager.loadedChart.getNotedata()[firstHittableNote - 1].second + ) + firstHittableNote-- + this.missNoteIndex = firstHittableNote + this.holdIndex = firstHittableNote + this.tickProgress = new Set() + this.heldCols.reset() + } + + keyDown(chartManager: ChartManager, col: number): void { + if (!chartManager.loadedChart || !chartManager.chartView) return + const hitTime = chartManager.chartView.getTimeWithOffset() + const closestNote = this.getClosestNote( + chartManager.loadedChart.getNotedata(), + hitTime, + col, + ["Tap", "Hold", "Roll"] + ) + this.heldCols.keyDown(col) + chartManager.chartView.getNotefield().press(col) + for (const { hold } of this.tickProgress) { + if (hold.type == "Roll" && hold.col == col) { + hold.gameplay!.lastHoldActivation = Date.now() + hold.gameplay!.droppedHoldBeat = undefined + chartManager.chartView.getNotefield().activateRoll(hold.col) + } + } + if (closestNote) this.hitNote(chartManager, closestNote, hitTime) + else chartManager.chartView.getNotefield().ghostTap(col) + } + + protected shouldDropHold(note: HoldNotedataEntry, time: number): boolean { + if (!note.gameplay?.lastHoldActivation) return false + const window = this.collection.getHeldJudgement(note) + if (!window) return false + return time - note.gameplay.lastHoldActivation >= window.getTimingWindowMS() + } + + protected generateHoldTicks(timingData: TimingData, hold: HoldNotedataEntry) { + // Jump to start of hold + + const tickCounts = timingData.getTimingData("TICKCOUNTS") + let tickIndex = bsearch(tickCounts, hold.beat, a => a.beat) + const latestEvent = tickCounts[tickIndex] ?? { + type: "TICKCOUNTS", + beat: 0, + value: 4, + } + let tickLength = 1 / latestEvent.value + let currentTick = Math.round(hold.beat / tickLength) * tickLength + if (currentTick == hold.beat) currentTick += tickLength + const ticks = [] + if ((tickCounts[tickIndex]?.value ?? 4) != 0) ticks.push(currentTick) + else currentTick = hold.beat + while (currentTick < getNoteEnd(hold)) { + if ( + (tickCounts[tickIndex]?.value ?? 4) == 0 || + tickCounts[tickIndex + 1]?.beat <= currentTick + ) { + tickIndex += 1 + currentTick = tickCounts[tickIndex].beat + tickLength = 1 / tickCounts[tickIndex].value + if (currentTick === undefined) { + return ticks.filter( + tick => tick <= getNoteEnd(hold) && !timingData.isBeatWarped(tick) + ) + } + } else { + currentTick += tickLength + } + if ( + (tickCounts[tickIndex]?.value ?? 4) != 0 && + ticks.at(-1) != currentTick + ) + ticks.push(currentTick) + } + return ticks.filter( + tick => tick <= getNoteEnd(hold) && !timingData.isBeatWarped(tick) + ) + } + + calculateMaxDP(notedata: Notedata, timingData: TimingData): number { + const chordCohesion: Map = new Map() + let maxDancePoints = 0 + for (const note of notedata) { + if (note.type == "Mine" || note.fake) continue + if (isHoldNote(note)) { + maxDancePoints += + TimingWindowCollection.getCollection( + Options.play.timingCollection + ).getMaxDancePoints() * + this.generateHoldTicks(timingData, note).length + } + if (note.warped) continue + if (!chordCohesion.has(note.beat)) chordCohesion.set(note.beat, []) + chordCohesion.get(note.beat)!.push(note) + } + maxDancePoints += + chordCohesion.size * + TimingWindowCollection.getCollection( + Options.play.timingCollection + ).getMaxDancePoints() + return maxDancePoints + } +} diff --git a/app/src/chart/play/GameplayStats.ts b/app/src/chart/play/GameplayStats.ts index 9d705309..8ec46857 100644 --- a/app/src/chart/play/GameplayStats.ts +++ b/app/src/chart/play/GameplayStats.ts @@ -21,24 +21,24 @@ import { TimingWindowCollection, } from "./TimingWindowCollection" -interface JudgmentDataPoint { +interface JudgementDataPoint { second: number - error: number - judgment: TimingWindow + error: number | null + judgement: TimingWindow notes: NotedataEntry[] } export class GameplayStats { - private judgmentCounts: Map = new Map() - private holdJudgmentCounts: Map = + private judgementCounts: Map = new Map() + private holdJudgementCounts: Map = new Map() private dancePoints = 0 private maxCumulativeDancePoints = 0 private maxDancePoints = 0 private chartManager: ChartManager private readonly notedata: Notedata - private dataPoints: JudgmentDataPoint[] = [] - private handlers: ((error: number, judge: TimingWindow) => void)[] = [] + private dataPoints: JudgementDataPoint[] = [] + private handlers: ((error: number | null, judge: TimingWindow) => void)[] = [] private combo = 0 private missCombo = 0 private maxCombo = 0 @@ -53,33 +53,36 @@ export class GameplayStats { this.calculateMaxDP() } - onJudge(handler: (error: number, judge: TimingWindow) => void) { + onJudge(handler: (error: number | null, judge: TimingWindow) => void) { this.handlers.push(handler) } applyOffset(offset: number) { this.dataPoints = this.dataPoints.map(point => { - if (isStandardMissTimingWindow(point.judgment)) return point - if (!isStandardTimingWindow(point.judgment)) return point + if (isStandardMissTimingWindow(point.judgement)) return point + if (!isStandardTimingWindow(point.judgement)) return point return { ...point, - error: point.error + offset, + error: point.error !== null ? point.error + offset : null, } }) - this.recalculate() } /** - * Adds a new judgment. + * Adds a new judgement. * * @param {NotedataEntry[]} notes - The notes in this row. - * @param {TimingWindow} judge - The judgment received + * @param {TimingWindow} judge - The judgement received * @param {number} error - The timing error in ms * @memberof GameplayStats */ - addDataPoint(notes: NotedataEntry[], judge: TimingWindow, error: number) { - if (!this.judgmentCounts.has(judge)) this.judgmentCounts.set(judge, 0) - this.judgmentCounts.set(judge, this.judgmentCounts.get(judge)! + 1) + addDataPoint( + notes: NotedataEntry[], + judge: TimingWindow, + error: number | null + ) { + if (!this.judgementCounts.has(judge)) this.judgementCounts.set(judge, 0) + this.judgementCounts.set(judge, this.judgementCounts.get(judge)! + 1) this.dancePoints += judge.dancePoints const comboMult = this.chartManager.loadedChart!.timingData.getEventAtBeat( @@ -94,16 +97,18 @@ export class GameplayStats { Options.play.timingCollection ).getMaxDancePoints() if (isStandardMissTimingWindow(judge)) { - this.maxCumulativeDancePoints += notes - .filter(isHoldNote) - .reduce((totalDP, note) => { - return ( - totalDP + - TimingWindowCollection.getCollection( - Options.play.timingCollection - ).getMaxHoldDancePoints(note.type) - ) - }, 0) + if (!this.chartManager.loadedChart!.gameType.gameLogic.usesHoldTicks) { + this.maxCumulativeDancePoints += notes + .filter(isHoldNote) + .reduce((totalDP, note) => { + return ( + totalDP + + TimingWindowCollection.getCollection( + Options.play.timingCollection + ).getMaxHoldDancePoints(note.type) + ) + }, 0) + } this.combo = 0 this.missCombo += missMult this.bestJudge = undefined @@ -130,33 +135,33 @@ export class GameplayStats { this.dataPoints.push({ second: notes[0].second, error, - judgment: judge, + judgement: judge, notes, }) } /** - * Add a new judgment for holds + * Add a new judgement for holds * * @param {HoldNotedataEntry} note - The hold note - * @param {(HoldTimingWindow | HoldDroppedTimingWindow)} judge - The judgment received + * @param {(HoldTimingWindow | HoldDroppedTimingWindow)} judge - The judgement received * @memberof GameplayStats */ addHoldDataPoint( note: HoldNotedataEntry, judge: HoldTimingWindow | HoldDroppedTimingWindow ) { - if (!this.judgmentCounts.has(judge)) this.judgmentCounts.set(judge, 0) - this.judgmentCounts.set(judge, this.judgmentCounts.get(judge)! + 1) + if (!this.judgementCounts.has(judge)) this.judgementCounts.set(judge, 0) + this.judgementCounts.set(judge, this.judgementCounts.get(judge)! + 1) const holdJudge = TimingWindowCollection.getCollection( Options.play.timingCollection ).getHeldJudgement(note) - if (!this.holdJudgmentCounts.has(holdJudge)) - this.holdJudgmentCounts.set(holdJudge, [0, 0]) - const count = this.holdJudgmentCounts.get(holdJudge)! + if (!this.holdJudgementCounts.has(holdJudge)) + this.holdJudgementCounts.set(holdJudge, [0, 0]) + const count = this.holdJudgementCounts.get(holdJudge)! if (isHoldTimingWindow(judge)) count[0]++ else count[1]++ - this.holdJudgmentCounts.set(holdJudge, count) + this.holdJudgementCounts.set(holdJudge, count) this.dancePoints += judge.dancePoints this.maxCumulativeDancePoints += TimingWindowCollection.getCollection( Options.play.timingCollection @@ -180,7 +185,7 @@ export class GameplayStats { /** * Returns the cumulative score. - * Cumulative score is based on the number of arrows that have received a judgment. + * Cumulative score is based on the number of arrows that have received a judgement. * 1 is 100%. * * @return {*} {number} @@ -191,7 +196,7 @@ export class GameplayStats { return this.dancePoints / this.maxCumulativeDancePoints } - getDataPoints(): JudgmentDataPoint[] { + getDataPoints(): JudgementDataPoint[] { return this.dataPoints } @@ -200,10 +205,11 @@ export class GameplayStats { this.dataPoints .filter( point => - !isStandardMissTimingWindow(point.judgment) && - isStandardTimingWindow(point.judgment) + !isStandardMissTimingWindow(point.judgement) && + isStandardTimingWindow(point.judgement) && + point.error != null ) - .map(data => data.error) + .map(data => data.error!) ) } @@ -217,97 +223,23 @@ export class GameplayStats { return this.maxCombo } - /** - * Recalculates the judgments and scores of this object using the current timing windows. - * - * @memberof GameplayStats - */ - recalculate() { - this.calculateMaxDP() - this.dancePoints = 0 - this.maxCumulativeDancePoints = 0 - for (const entry of this.holdJudgmentCounts.entries()) { - const judge = entry[0] - this.dancePoints += entry[0].dancePoints * entry[1][0] - this.maxCumulativeDancePoints += - (entry[1][0] + entry[1][1]) * - TimingWindowCollection.getCollection( - Options.play.timingCollection - ).getMaxHoldDancePoints(judge.noteType) - } - this.judgmentCounts.clear() - for (const dataPoint of this.dataPoints) { - let judge: TimingWindow = TimingWindowCollection.getCollection( - Options.play.timingCollection - ).judgeInput(dataPoint.error) - if ( - isStandardMissTimingWindow(dataPoint.judgment) || - isMineTimingWindow(dataPoint.judgment) - ) - judge = dataPoint.judgment - if (!this.judgmentCounts.has(judge)) this.judgmentCounts.set(judge, 0) - this.judgmentCounts.set(judge, this.judgmentCounts.get(judge)! + 1) - this.dancePoints += judge.dancePoints - dataPoint.judgment = judge - if (!isMineTimingWindow(judge)) - this.maxCumulativeDancePoints += TimingWindowCollection.getCollection( - Options.play.timingCollection - ).getMaxDancePoints() - if (isStandardMissTimingWindow(judge)) { - this.maxCumulativeDancePoints += dataPoint.notes - .filter(isHoldNote) - .reduce((totalDP, note) => { - return ( - totalDP + - TimingWindowCollection.getCollection( - Options.play.timingCollection - ).getMaxHoldDancePoints(note.type) - ) - }, 0) - } - } - } - private calculateMaxDP() { - const chordCohesion: Map = new Map() - const numHoldsMap: Map = new Map() - for (const note of this.notedata) { - if (note.type == "Mine" || note.fake) continue - if (isHoldNote(note)) { - if (!numHoldsMap.has(note.type)) numHoldsMap.set(note.type, 0) - numHoldsMap.set(note.type, numHoldsMap.get(note.type)! + 1) - } - if (!chordCohesion.has(note.beat)) chordCohesion.set(note.beat, []) - chordCohesion.get(note.beat)!.push(note) - } this.maxDancePoints = - chordCohesion.size * - TimingWindowCollection.getCollection( - Options.play.timingCollection - ).getMaxDancePoints() - this.maxDancePoints += Array.from(numHoldsMap.entries()).reduce( - (totalDP, entry) => { - return ( - totalDP + - entry[1] * - TimingWindowCollection.getCollection( - Options.play.timingCollection - ).getMaxHoldDancePoints(entry[0]) - ) - }, - 0 - ) + this.chartManager.loadedChart!.gameType.gameLogic.calculateMaxDP( + this.notedata, + this.chartManager.loadedChart!.timingData + ) } /** - * Returns the number of judgments for a given judgment. + * Returns the number of judgements for a given judgement. * * @param {TimingWindow} window * @return {*} {number} * @memberof GameplayStats */ getCount(window: TimingWindow): number { - return this.judgmentCounts.get(window) ?? 0 + return this.judgementCounts.get(window) ?? 0 } /** @@ -331,7 +263,7 @@ export class GameplayStats { } /** - * Returns the best judgment received + * Returns the best judgement received * * @return {*} {(StandardTimingWindow | undefined)} * @memberof GameplayStats diff --git a/app/src/chart/play/JudgmentTexture.ts b/app/src/chart/play/JudgementTexture.ts similarity index 75% rename from app/src/chart/play/JudgmentTexture.ts rename to app/src/chart/play/JudgementTexture.ts index 0e8c6146..488b2f9d 100644 --- a/app/src/chart/play/JudgmentTexture.ts +++ b/app/src/chart/play/JudgementTexture.ts @@ -1,11 +1,11 @@ import { Assets, Rectangle, Texture } from "pixi.js" import { StandardTimingWindow } from "./StandardTimingWindow" -import judgmentITGUrl from "../../../assets/judgment/judgmentITG.png" -import judgmentWaterfallUrl from "../../../assets/judgment/judgmentWaterfall.png" +import judgementITGUrl from "../../../assets/judgement/judgementITG.png" +import judgementWaterfallUrl from "../../../assets/judgement/judgementWaterfall.png" -export class JudgmentTexture { - static ITG = new JudgmentTexture(judgmentITGUrl, [ +export class JudgementTexture { + static ITG = new JudgementTexture(judgementITGUrl, [ "w0", "w1", "w2", @@ -14,7 +14,7 @@ export class JudgmentTexture { "w5", "miss", ]) - static WATERFALL = new JudgmentTexture(judgmentWaterfallUrl, [ + static WATERFALL = new JudgementTexture(judgementWaterfallUrl, [ "w0", "w1", "w2", @@ -45,13 +45,14 @@ export class JudgmentTexture { error: number, judgment: StandardTimingWindow ): Texture | undefined { + if (!this.texture) return if (!this.judgeNames.includes(judgment.id)) return let tex_coord_x = 0 const tex_coord_y = (this.judgeNames.indexOf(judgment.id) * this.texHeight) / this.judgeNames.length if (error >= 0) tex_coord_x += this.texWidth / 2 - this.texture!.frame = new Rectangle( + this.texture.frame = new Rectangle( tex_coord_x, tex_coord_y, this.texWidth / 2, diff --git a/app/src/chart/play/StandardMissTimingWindow.ts b/app/src/chart/play/StandardMissTimingWindow.ts index 939af29a..c02d72be 100644 --- a/app/src/chart/play/StandardMissTimingWindow.ts +++ b/app/src/chart/play/StandardMissTimingWindow.ts @@ -1,4 +1,4 @@ -import { JudgmentTexture } from "./JudgmentTexture" +import { JudgementTexture } from "./JudgementTexture" import { StandardTimingWindow } from "./StandardTimingWindow" export class StandardMissTimingWindow extends StandardTimingWindow { @@ -7,8 +7,8 @@ export class StandardMissTimingWindow extends StandardTimingWindow { color: number, dancePoints: number, lifeChange: number, - judgmentTexture: JudgmentTexture + judgementTexture: JudgementTexture ) { - super("miss", name, color, 0, dancePoints, lifeChange, judgmentTexture) + super("miss", name, color, 0, dancePoints, lifeChange, judgementTexture) } } diff --git a/app/src/chart/play/StandardTimingWindow.ts b/app/src/chart/play/StandardTimingWindow.ts index 6e291a78..6ddc3d17 100644 --- a/app/src/chart/play/StandardTimingWindow.ts +++ b/app/src/chart/play/StandardTimingWindow.ts @@ -1,11 +1,11 @@ -import { JudgmentTexture } from "./JudgmentTexture" +import { JudgementTexture } from "./JudgementTexture" import { TimingWindow } from "./TimingWindow" export class StandardTimingWindow extends TimingWindow { id: string name: string color: number - judgmentTexture: JudgmentTexture + judgementTexture: JudgementTexture constructor( id: string, @@ -14,13 +14,13 @@ export class StandardTimingWindow extends TimingWindow { timingWindowMS: number, dancePoints: number, lifeChange: number, - judgmentTexture: JudgmentTexture + judgementTexture: JudgementTexture ) { super(timingWindowMS, dancePoints, lifeChange) this.id = id this.name = name this.color = color - this.judgmentTexture = judgmentTexture + this.judgementTexture = judgementTexture } } @@ -31,5 +31,5 @@ export const TIMING_WINDOW_AUTOPLAY = new StandardTimingWindow( 0, 0, 0, - JudgmentTexture.ITG + JudgementTexture.ITG ) diff --git a/app/src/chart/play/TimingWindowCollection.ts b/app/src/chart/play/TimingWindowCollection.ts index e8696f58..0593709c 100644 --- a/app/src/chart/play/TimingWindowCollection.ts +++ b/app/src/chart/play/TimingWindowCollection.ts @@ -1,7 +1,7 @@ import { HoldNotedataEntry } from "../sm/NoteTypes" import { HoldDroppedTimingWindow } from "./HoldDroppedTimingWindow" import { HoldTimingWindow } from "./HoldTimingWindow" -import { JudgmentTexture } from "./JudgmentTexture" +import { JudgementTexture } from "./JudgementTexture" import { MineTimingWindow } from "./MineTimingWindow" import { StandardMissTimingWindow } from "./StandardMissTimingWindow" import { StandardTimingWindow } from "./StandardTimingWindow" @@ -54,7 +54,7 @@ export class TimingWindowCollection { 23, 5, 0.008, - JudgmentTexture.ITG + JudgementTexture.ITG ), new StandardTimingWindow( "w2", @@ -63,7 +63,7 @@ export class TimingWindowCollection { 44.5, 4, 0.008, - JudgmentTexture.ITG + JudgementTexture.ITG ), new StandardTimingWindow( "w3", @@ -72,7 +72,7 @@ export class TimingWindowCollection { 103.5, 2, 0.004, - JudgmentTexture.ITG + JudgementTexture.ITG ), new StandardTimingWindow( "w4", @@ -81,7 +81,7 @@ export class TimingWindowCollection { 136.5, 0, 0, - JudgmentTexture.ITG + JudgementTexture.ITG ), new StandardTimingWindow( "w5", @@ -90,14 +90,14 @@ export class TimingWindowCollection { 181.5, -6, -0.05, - JudgmentTexture.ITG + JudgementTexture.ITG ), new StandardMissTimingWindow( "Miss", 0xff3030, -12, -0.1, - JudgmentTexture.ITG + JudgementTexture.ITG ), new HoldTimingWindow("Hold", 321.5, 5, -0.008), new HoldTimingWindow("Roll", 351.5, 5, -0.008), @@ -116,7 +116,7 @@ export class TimingWindowCollection { 15, 3.5, 0.008, - JudgmentTexture.ITG + JudgementTexture.ITG ), new StandardTimingWindow( "w1", @@ -125,7 +125,7 @@ export class TimingWindowCollection { 23, 3, 0.008, - JudgmentTexture.ITG + JudgementTexture.ITG ), new StandardTimingWindow( "w2", @@ -134,7 +134,7 @@ export class TimingWindowCollection { 44.5, 2, 0.008, - JudgmentTexture.ITG + JudgementTexture.ITG ), new StandardTimingWindow( "w3", @@ -143,7 +143,7 @@ export class TimingWindowCollection { 103.5, 1, 0.004, - JudgmentTexture.ITG + JudgementTexture.ITG ), new StandardTimingWindow( "w4", @@ -152,7 +152,7 @@ export class TimingWindowCollection { 136.5, 0, 0, - JudgmentTexture.ITG + JudgementTexture.ITG ), new StandardTimingWindow( "w5", @@ -161,14 +161,14 @@ export class TimingWindowCollection { 181.5, 0, -0.05, - JudgmentTexture.ITG + JudgementTexture.ITG ), new StandardMissTimingWindow( "Miss", 0xff3030, 0, -0.1, - JudgmentTexture.ITG + JudgementTexture.ITG ), new HoldTimingWindow("Hold", 321.5, 1, -0.008), new HoldTimingWindow("Roll", 351.5, 1, -0.008), @@ -187,7 +187,7 @@ export class TimingWindowCollection { 15, 10, 0.008, - JudgmentTexture.WATERFALL + JudgementTexture.WATERFALL ), new StandardTimingWindow( "w2", @@ -196,7 +196,7 @@ export class TimingWindowCollection { 30, 9, 0.008, - JudgmentTexture.WATERFALL + JudgementTexture.WATERFALL ), new StandardTimingWindow( "w3", @@ -205,7 +205,7 @@ export class TimingWindowCollection { 50, 6, 0.008, - JudgmentTexture.WATERFALL + JudgementTexture.WATERFALL ), new StandardTimingWindow( "w4", @@ -214,7 +214,7 @@ export class TimingWindowCollection { 100, 3, 0.004, - JudgmentTexture.WATERFALL + JudgementTexture.WATERFALL ), new StandardTimingWindow( "w5", @@ -223,14 +223,14 @@ export class TimingWindowCollection { 160, 0, 0, - JudgmentTexture.WATERFALL + JudgementTexture.WATERFALL ), new StandardMissTimingWindow( "Miss", 0xff3030, 0, -0.1, - JudgmentTexture.WATERFALL + JudgementTexture.WATERFALL ), new HoldTimingWindow("Hold", 300, 6, -0.008), new HoldTimingWindow("Roll", 350, 6, -0.008), @@ -254,7 +254,7 @@ export class TimingWindowCollection { 0xff3030, -12, -0.1, - JudgmentTexture.ITG + JudgementTexture.ITG ) this.droppedWindow = new HoldDroppedTimingWindow(0, -0.08) this.mineWindow = new MineTimingWindow(71.5, -1, -0.05) @@ -271,7 +271,7 @@ export class TimingWindowCollection { } /** - * Returns the achieved judgment given an error in ms. + * Returns the achieved judgement given an error in ms. * * @param {number} error * @return {*} {StandardTimingWindow} @@ -287,7 +287,7 @@ export class TimingWindowCollection { } /** - * Gets the held judgment in this collection for a given note. + * Gets the held judgement in this collection for a given note. * * @param {HoldNotedataEntry} note * @return {*} {HoldTimingWindow} @@ -298,48 +298,50 @@ export class TimingWindowCollection { } /** - * Gets this miss judgment in this collection. + * Gets this miss judgement in this collection. * * @return {*} {StandardMissTimingWindow} * @memberof TimingWindowCollection */ - getMissJudgment(): StandardMissTimingWindow { + getMissJudgement(): StandardMissTimingWindow { return this.missWindow } /** - * Gets the dropped judgment in this collection. + * Gets the dropped judgement in this collection. * * @return {*} {HoldDroppedTimingWindow} * @memberof TimingWindowCollection */ - getDroppedJudgment(): HoldDroppedTimingWindow { + getDroppedJudgement(): HoldDroppedTimingWindow { return this.droppedWindow } /** - * Gets the mine judgment in this collection. + * Gets the mine judgement in this collection. * * @return {*} {MineTimingWindow} * @memberof TimingWindowCollection */ - getMineJudgment(): MineTimingWindow { + getMineJudgement(): MineTimingWindow { return this.mineWindow } /** * Determines if a note should be hidden. * - * @param {StandardTimingWindow} judgment + * @param {StandardTimingWindow} judgement * @return {*} {boolean} * @memberof TimingWindowCollection */ - shouldHideNote(judgment: StandardTimingWindow): boolean { - return judgment.id != "miss" && judgment.timingWindowMS <= this.hideLimitMS + shouldHideNote(judgement: StandardTimingWindow): boolean { + return ( + judgement.id != "miss" && judgement.timingWindowMS <= this.hideLimitMS + ) } /** - * Returns the maximum MS to get a judgment (non-miss). + * Returns the maximum MS to get a judgement (non-miss). * * @return {*} {number} * @memberof TimingWindowCollection @@ -349,7 +351,7 @@ export class TimingWindowCollection { } /** - * Returns the maximum dance points achievable for one judgment. + * Returns the maximum dance points achievable for one judgement. * * @return {*} {number} * @memberof TimingWindowCollection @@ -362,7 +364,7 @@ export class TimingWindowCollection { } /** - * Returns the maximum dance points achievable for one hold judgment. + * Returns the maximum dance points achievable for one hold judgement. * * @return {*} {number} * @memberof TimingWindowCollection diff --git a/app/src/chart/sm/Chart.ts b/app/src/chart/sm/Chart.ts index 404d8896..804c3021 100644 --- a/app/src/chart/sm/Chart.ts +++ b/app/src/chart/sm/Chart.ts @@ -32,6 +32,9 @@ export class Chart { private _notedataStats!: Record private _npsGraph!: number[] + private _lastBeat = 0 + private _lastSecond = 0 + constructor(sm: Simfile, data?: string | { [key: string]: string }) { this.timingData = sm.timingData.createChartTimingData(this) this.timingData.reloadCache() @@ -134,6 +137,14 @@ export class Chart { return max } + getLastBeat() { + return this._lastBeat + } + + getLastSecond() { + return this._lastSecond + } + getSecondsFromBeat( beat: number, option?: "noclamp" | "before" | "after" | "" @@ -157,6 +168,23 @@ export class Chart { return this.timingData.isBeatFaked(beat) } + private recalculateLastNote() { + let lastBeat = 0 + let lastSecond = 0 + this.notedata.forEach(note => { + const endBeat = note.beat + (isHoldNote(note) ? note.hold : 0) + const endSecond = this.timingData.getSecondsFromBeat(endBeat) + if (endBeat > lastBeat) { + lastBeat = endBeat + } + if (endSecond > lastSecond) { + lastSecond = endSecond + } + }) + this._lastBeat = lastBeat + this._lastSecond = lastSecond + } + private getNoteIndex(note: PartialNotedataEntry): number { if (this.notedata.includes(note as NotedataEntry)) { return this.notedata.indexOf(note as NotedataEntry) @@ -268,6 +296,7 @@ export class Chart { this.notedata, this.timingData ) + this.recalculateLastNote() } getMusicPath(): string { diff --git a/app/src/chart/sm/NoteTypes.ts b/app/src/chart/sm/NoteTypes.ts index 181b3c58..15a43066 100644 --- a/app/src/chart/sm/NoteTypes.ts +++ b/app/src/chart/sm/NoteTypes.ts @@ -2,16 +2,12 @@ export type Notedata = NotedataEntry[] export type PartialNotedata = PartialNotedataEntry[] -export const NOTE_TYPES = [ - "Tap", - "Hold", - "Roll", - "Mine", - "Lift", - "Fake", -] as const +export const HOLD_NOTE_TYPES = ["Hold", "Roll"] as const +export const TAP_NOTE_TYPES = ["Tap", "Mine", "Lift", "Fake"] as const -export type NoteType = (typeof NOTE_TYPES)[number] +export type NoteType = TapNoteType | HoldNoteType +export type HoldNoteType = (typeof HOLD_NOTE_TYPES)[number] +export type TapNoteType = (typeof TAP_NOTE_TYPES)[number] export interface PartialTapNotedataEntry { beat: number @@ -45,8 +41,8 @@ export type TapNotedataEntry = PartialTapNotedataEntry & ExtraNotedata export type HoldNotedataEntry = PartialHoldNotedataEntry & ExtraNotedata & { gameplay?: { - lastHoldActivation: number - droppedHoldBeat: number + lastHoldActivation?: number + droppedHoldBeat?: number } } export type NotedataEntry = TapNotedataEntry | HoldNotedataEntry @@ -54,11 +50,11 @@ export type NotedataEntry = TapNotedataEntry | HoldNotedataEntry export function isTapNote( note: T ): note is Exclude { - return note.type != "Hold" && note.type != "Roll" + return !HOLD_NOTE_TYPES.includes(note.type as HoldNoteType) } export function isHoldNote( note: T ): note is Extract { - return note.type == "Hold" || note.type == "Roll" + return HOLD_NOTE_TYPES.includes(note.type as HoldNoteType) } diff --git a/app/src/data/GameplayKeybindData.ts b/app/src/data/GameplayKeybindData.ts index ee20abbb..2a3e6155 100644 --- a/app/src/data/GameplayKeybindData.ts +++ b/app/src/data/GameplayKeybindData.ts @@ -206,4 +206,27 @@ export const GAMEPLAY_KEYBINDS: { [key: string]: GameplayKeybind[] } = { keys: ["E"], }, ], + // Pump + "pump-single": [ + { + label: "DownLeft", + keys: [], + }, + { + label: "UpLeft", + keys: [], + }, + { + label: "Center", + keys: [], + }, + { + label: "UpRight", + keys: [], + }, + { + label: "DownRight", + keys: [], + }, + ], } diff --git a/app/src/data/KeybindData.ts b/app/src/data/KeybindData.ts index 7651f0ea..d29fa48f 100644 --- a/app/src/data/KeybindData.ts +++ b/app/src/data/KeybindData.ts @@ -1,6 +1,6 @@ import { App } from "../App" import { EditMode, EditTimingMode } from "../chart/ChartManager" -import { isHoldNote } from "../chart/sm/NoteTypes" +import { isHoldNote, NotedataEntry } from "../chart/sm/NoteTypes" import { WaterfallManager } from "../gui/element/WaterfallManager" import { ChartListWindow } from "../gui/window/ChartListWindow" import { DirectoryWindow } from "../gui/window/DirectoryWindow" @@ -9,6 +9,7 @@ import { ExportNotedataWindow } from "../gui/window/ExportNotedataWindow" import { GameplayKeybindWindow } from "../gui/window/GameplayKeybindWindow" import { KeybindWindow } from "../gui/window/KeybindWindow" import { NewSongWindow } from "../gui/window/NewSongWindow" +import { NoteskinWindow } from "../gui/window/NoteskinWindow" import { OffsetWindow } from "../gui/window/OffsetWindow" import { SMPropertiesWindow } from "../gui/window/SMPropertiesWindow" import { SyncWindow } from "../gui/window/SyncWindow" @@ -530,17 +531,6 @@ export const KEYBIND_DATA: { [key: string]: Keybind } = { ) }, }, - renderWaveform: { - label: "Render waveform", - combos: [], - disabled: false, - callback: () => { - Options.chart.waveform.enabled = !Options.chart.waveform.enabled - WaterfallManager.create( - "Waveform: " + (Options.chart.waveform.enabled ? "on" : "off") - ) - }, - }, XMod: { label: "XMod (Beat-based)", combos: [{ key: "X", mods: [Modifier.SHIFT] }], @@ -782,7 +772,8 @@ export const KEYBIND_DATA: { [key: string]: Keybind } = { app.chartManager.getMode() != EditMode.Edit, callback: app => { app.chartManager.modifySelection(note => { - if (note.type == "Hold" || note.type == "Roll") note.type = "Tap" + if (note.type == "Hold" || note.type == "Roll") + (note as NotedataEntry).type = "Tap" return note }) }, @@ -1432,4 +1423,12 @@ export const KEYBIND_DATA: { [key: string]: Keybind } = { window.open("/smeditor/guide/") }, }, + + noteskinWindow: { + label: "Noteskins...", + bindLabel: "Open Noteskin Window", + combos: [{ mods: [Modifier.SHIFT], key: "N" }], + disabled: false, + callback: app => app.windowManager.openWindow(new NoteskinWindow(app)), + }, } diff --git a/app/src/data/MenubarData.ts b/app/src/data/MenubarData.ts index cab644b2..38d724d3 100644 --- a/app/src/data/MenubarData.ts +++ b/app/src/data/MenubarData.ts @@ -579,6 +579,10 @@ export const MENUBAR_DATA: { [key: string]: MenuMain } = { type: "selection", id: "gameplayKeybinds", }, + { + type: "selection", + id: "noteskinWindow", + }, ], }, help: { diff --git a/app/src/data/UserOptionsWindowData.ts b/app/src/data/UserOptionsWindowData.ts index bbd500ae..b9fe97f8 100644 --- a/app/src/data/UserOptionsWindowData.ts +++ b/app/src/data/UserOptionsWindowData.ts @@ -256,6 +256,16 @@ export const USER_OPTIONS_WINDOW_DATA: UserOption[] = [ type: "checkbox", }, }, + { + type: "item", + label: "Draw note icons", + id: "chart.drawIcons", + input: { + type: "checkbox", + }, + tooltip: + "Draw indicators above notes that some noteskins may not differentiate, like Fakes and Lifts.", + }, ], }, ], @@ -413,6 +423,55 @@ export const USER_OPTIONS_WINDOW_DATA: UserOption[] = [ }, ], }, + { + type: "subgroup", + label: "Note Layout", + children: [ + { + type: "item", + label: "Show Note Layout", + id: "chart.noteLayout.enabled", + input: { + type: "checkbox", + }, + }, + ], + }, + { + type: "subgroup", + label: "NPS Graph", + children: [ + { + type: "item", + label: "Show NPS Graph", + id: "chart.npsGraph.enabled", + input: { + type: "checkbox", + }, + }, + { + type: "subgroup", + children: [ + { + type: "item", + label: "Start Color", + id: "chart.npsGraph.color1", + input: { + type: "color", + }, + }, + { + type: "item", + label: "End Color", + id: "chart.npsGraph.color2", + input: { + type: "color", + }, + }, + ], + }, + ], + }, ], }, { @@ -581,8 +640,8 @@ export const USER_OPTIONS_WINDOW_DATA: UserOption[] = [ children: [ { type: "item", - label: "Judgment tilt", - id: "play.judgmentTilt", + label: "Judgement tilt", + id: "play.judgementTilt", input: { type: "checkbox", }, diff --git a/app/src/gui/widget/BaseTimelineWidget.ts b/app/src/gui/widget/BaseTimelineWidget.ts new file mode 100644 index 00000000..e32ea0a6 --- /dev/null +++ b/app/src/gui/widget/BaseTimelineWidget.ts @@ -0,0 +1,225 @@ +import { Container, FederatedPointerEvent, Sprite, Texture } from "pixi.js" +import { EditMode } from "../../chart/ChartManager" +import { Chart } from "../../chart/sm/Chart" +import { BetterRoundedRect } from "../../util/BetterRoundedRect" +import { EventHandler } from "../../util/EventHandler" +import { Flags } from "../../util/Flags" +import { clamp, lerp, maxArr, minArr, unlerp } from "../../util/Math" +import { Options } from "../../util/Options" +import { getNoteEnd } from "../../util/Util" +import { Widget } from "./Widget" +import { WidgetManager } from "./WidgetManager" + +export class BaseTimelineWidget extends Widget { + backing: BetterRoundedRect = new BetterRoundedRect() + overlay: Sprite = new Sprite(Texture.WHITE) + selectionOverlay: Sprite = new Sprite(Texture.WHITE) + container: Container = new Container() + + protected lastHeight = 0 + protected lastCMod + protected mouseDown = false + protected queued = false + + protected verticalMargin = 40 + protected backingVerticalPadding = 10 + protected backingWidth = 32 + xOffset = 20 + + constructor( + manager: WidgetManager, + xOffset: number = 20, + backingWidth: number = 32 + ) { + super(manager) + this.backingWidth = backingWidth + this.xOffset = xOffset + + this.addChild(this.backing) + this.addChild(this.container) + this.visible = false + + this.backing.tint = 0 + this.backing.alpha = 0.3 + + this.overlay.anchor.x = 0.5 + this.overlay.anchor.y = 0 + this.overlay.alpha = 0.3 + + this.lastCMod = Options.chart.CMod + this.addChild(this.overlay) + + this.x = this.manager.app.renderer.screen.width / 2 - this.xOffset + + EventHandler.on("chartLoaded", () => { + this.queued = false + this.populate() + }) + EventHandler.on("chartModifiedAfter", () => { + if (!this.queued) this.populate() + this.queued = true + }) + const interval = setInterval(() => { + if (this.queued) { + this.queued = false + this.populate() + } + }, 3000) + + this.on("destroyed", () => clearInterval(interval)) + + this.eventMode = "static" + this.on("mousedown", event => { + this.mouseDown = true + this.handleMouse(event) + }) + this.on("mousemove", event => { + if (this.mouseDown) this.handleMouse(event) + }) + + this.on("mouseup", () => { + this.mouseDown = false + }) + + this.on("mouseleave", () => { + this.mouseDown = false + }) + } + + protected handleMouse(event: FederatedPointerEvent) { + if (this.manager.chartManager.getMode() == EditMode.Play) return + if (!this.getChart()) return + let t = + (this.container.toLocal(event.global).y + this.container.height / 2) / + this.container.height + t = clamp(t, 0, 1) + const lastNote = this.getChart().getNotedata().at(-1) + if (!lastNote) return + if (Options.chart.CMod) { + this.manager.chartManager.setTime( + lerp( + -this.getChart().timingData.getOffset(), + this.getChart().getLastSecond(), + t + ) + ) + } else { + this.manager.chartManager.setBeat(this.getChart().getLastBeat() * t) + } + } + + update() { + this.scale.y = Options.chart.reverse ? -1 : 1 + const width = this.manager.app.renderer.screen.height - this.verticalMargin + this.backing.height = width + this.backingVerticalPadding + this.backing.position.y = -this.backing.height / 2 + this.backing.position.x = -this.backing.width / 2 + this.x = this.manager.app.renderer.screen.width / 2 - this.xOffset + const chart = this.getChart() + const chartView = this.manager.chartManager.chartView! + if (!chart || !chartView || !Flags.layout) { + this.visible = false + return + } + this.visible = true + const lastNote = chart.getNotedata().at(-1) + if (!lastNote) { + this.overlay.height = 0 + return + } + + const overlayStart = Options.chart.CMod + ? chartView.getSecondFromYPos( + -this.manager.app.renderer.screen.height / 2 + ) + : chartView.getBeatFromYPos( + -this.manager.app.renderer.screen.height / 2, + true + ) + const overlayEnd = Options.chart.CMod + ? chartView.getSecondFromYPos(this.manager.app.renderer.screen.height / 2) + : chartView.getBeatFromYPos( + this.manager.app.renderer.screen.height / 2, + true + ) + const overlayRange = this.getYFromRange(chart, overlayStart, overlayEnd) + this.overlay.y = overlayRange.startY + this.overlay.height = overlayRange.endY - overlayRange.startY + this.overlay.height = Math.max(2, this.overlay.height) + + const selection = this.manager.chartManager.selection.notes + if (selection.length < 1) { + this.selectionOverlay.visible = false + } else { + this.selectionOverlay.visible = true + let selectionStart, selectionEnd + if (Options.chart.CMod) { + selectionStart = minArr(selection.map(note => note.second)) + selectionEnd = maxArr( + selection.map(note => chart.getSecondsFromBeat(getNoteEnd(note))) + ) + } else { + selectionStart = minArr(selection.map(note => note.beat)) + selectionEnd = maxArr(selection.map(note => getNoteEnd(note))) + } + const selectionRange = this.getYFromRange( + chart, + selectionStart, + selectionEnd + ) + this.selectionOverlay.y = selectionRange.startY + this.selectionOverlay.height = selectionRange.endY - selectionRange.startY + this.selectionOverlay.height = Math.max(2, this.selectionOverlay.height) + } + + if ( + this.manager.app.renderer.screen.height != this.lastHeight || + this.lastCMod != Options.chart.CMod + ) { + this.lastCMod = Options.chart.CMod + this.lastHeight = this.manager.app.renderer.screen.height + this.updateDimensions() + this.populate() + } + } + + private getYFromRange(chart: Chart, start: number, end: number) { + const lastBeat = chart.getLastBeat() + const lastSecond = chart.getLastSecond() + let t_startY = unlerp(0, lastBeat, start) + let t_endY = unlerp(0, lastBeat, end) + if (Options.chart.CMod) { + t_startY = unlerp(-chart.timingData.getOffset(), lastSecond, start) + t_endY = unlerp(-chart.timingData.getOffset(), lastSecond, end) + } + t_startY = clamp(t_startY, 0, 1) + t_endY = clamp(t_endY, 0, 1) + if (t_startY > t_endY) [t_startY, t_endY] = [t_endY, t_startY] + const startY = (t_startY - 0.5) * this.container.height + const endY = (t_endY - 0.5) * this.container.height + return { + startY, + endY, + } + } + + updateDimensions() { + const chart = this.getChart() + if (!chart) { + return + } + + const height = this.manager.app.renderer.screen.height - this.verticalMargin + this.backing.height = height + this.backingVerticalPadding + this.backing.width = this.backingWidth + this.overlay.width = this.backingWidth + this.selectionOverlay.width = this.backingWidth + this.pivot.x = this.backing.width / 2 + } + + populate() {} + + protected getChart(): Chart { + return this.manager.chartManager.loadedChart! + } +} diff --git a/app/src/gui/widget/NPSGraphWidget.ts b/app/src/gui/widget/NPSGraphWidget.ts new file mode 100644 index 00000000..a95c8e1b --- /dev/null +++ b/app/src/gui/widget/NPSGraphWidget.ts @@ -0,0 +1,242 @@ +import { BitmapText, FederatedPointerEvent, Graphics, Texture } from "pixi.js" +import { EventHandler } from "../../util/EventHandler" +import { clamp, lerp, unlerp } from "../../util/Math" +import { Options } from "../../util/Options" +import { BaseTimelineWidget } from "./BaseTimelineWidget" +import { WidgetManager } from "./WidgetManager" + +export class NPSGraphWidget extends BaseTimelineWidget { + npsGraph: Graphics + private graphGradient: Texture | null = null + private graphWidth: number = 40 + private npsText: BitmapText = new BitmapText("", { + fontName: "Main", + fontSize: 12, + }) + + constructor(manager: WidgetManager) { + const graphWidth = 40 + super(manager, 60, graphWidth) + this.graphWidth = graphWidth + + this.graphGradient = this.makeGradient() + + this.npsGraph = new Graphics() + this.container.addChild(this.npsGraph) + + this.npsText.visible = false + this.npsText.anchor.x = 1 + this.npsText.anchor.y = 0.5 + this.addChild(this.npsText) + + EventHandler.on("userOptionUpdated", optionId => { + if ( + optionId == "chart.npsGraph.color1" || + optionId == "chart.npsGraph.color2" + ) { + this.graphGradient = this.makeGradient() + this.populate() + } + }) + + this.on("mouseleave", () => { + this.hideNpsDisplay() + }) + + this.on("mouseenter", () => { + this.showNpsDisplay() + }) + + this.on("mousemove", event => { + this.updateNpsDisplay(event) + }) + + this.populate() + } + + private updateNpsDisplay(event: FederatedPointerEvent) { + const chart = this.getChart() + if (!chart) { + this.npsText.visible = false + return + } + const lastNote = chart.getNotedata().at(-1) + if (!lastNote) { + this.npsText.visible = false + return + } + const npsGraphData = chart.getNPSGraph() + + let t = this.npsGraph.toLocal(event.global).y / this.npsGraph.height + t = clamp(t, 0, 1) + + let beat = lerp(0, chart.getLastBeat(), t) + if (Options.chart.CMod) { + const second = lerp( + -chart.timingData.getOffset(), + chart.getLastSecond(), + t + ) + beat = chart.timingData.getBeatFromSeconds(second) + } + + const npsIndex = Math.floor(chart.timingData.getMeasure(beat)) + + // npsGraph is a sparse array, so make sure we have a valid number, + // otherwise the nps for that measure is 0 + const nps = npsGraphData[npsIndex] ?? 0 + + this.npsText.text = nps.toFixed(1) + " nps" + this.npsText.position.y = this.getYFromBeat(beat) - this.npsGraph.height / 2 + this.npsText.position.x = -this.backing.width / 2 - 10 + this.npsText.visible = true + } + + private hideNpsDisplay() { + this.npsText.visible = false + } + + private showNpsDisplay() { + const chart = this.getChart() + if (!chart) { + this.npsText.visible = false + return + } + const lastNote = chart.getNotedata().at(-1) + if (!lastNote) { + this.npsText.visible = false + return + } + this.npsText.visible = true + } + + update() { + if (!Options.chart.npsGraph.enabled) { + this.visible = false + return + } + this.visible = true + const layoutWidget = this.manager.getChildByName("note-layout") as + | NPSGraphWidget + | undefined + if (layoutWidget !== undefined && layoutWidget.visible) { + this.xOffset = layoutWidget.backingWidth + 28 + } else { + this.xOffset = 20 + } + + this.npsText.scale.y = Options.chart.reverse ? -1 : 1 + + super.update() + } + + populate() { + const chart = this.getChart() + if (!chart) { + return + } + + const height = this.manager.app.renderer.screen.height - 40 + + if (chart.getNotedata().length == 0) { + return + } + + const maxNps = chart.getMaxNPS() + const npsGraphData = chart.getNPSGraph() + const lastBeat = chart.getLastBeat() + + this.npsGraph.clear() + if (this.graphGradient) { + this.npsGraph.beginTextureFill({ texture: this.graphGradient }) + } else { + this.npsGraph.beginFill(0x000000, 1) + } + + this.npsGraph.pivot.x = this.backing.width / 2 + this.npsGraph.pivot.y = height / 2 + + const lastMeasure = npsGraphData.length + + const startY = this.getYFromBeat(0) + this.npsGraph.moveTo(0, startY) + + for (let measureIndex = 0; measureIndex < lastMeasure; measureIndex++) { + const nps = npsGraphData[measureIndex] ?? 0 + const beat = chart.timingData.getBeatFromMeasure(measureIndex) + const endOfMeasureBeat = Math.min( + lastBeat, + chart.timingData.getBeatFromMeasure(measureIndex + 1) + ) + const x = unlerp(0, maxNps, nps) * this.graphWidth + const y = this.getYFromBeat(beat) + const endOfMeasureY = this.getYFromBeat(endOfMeasureBeat) + + this.npsGraph.lineTo(x, y) + this.npsGraph.lineTo(x, endOfMeasureY) + } + + const lastnps = npsGraphData.at(-1) ?? 0 + const lastX = unlerp(0, maxNps, lastnps) * this.graphWidth + const lastY = this.getYFromBeat(lastBeat) + this.npsGraph.lineTo(lastX, lastY) + this.npsGraph.lineTo(0, lastY) + this.npsGraph.endFill() + } + + private getYFromBeat(beat: number): number { + if (Options.chart.CMod) { + return this.getYFromSecond( + this.getChart().timingData.getSecondsFromBeat(beat) + ) + } + let t = unlerp(0, this.getChart().getLastBeat(), beat) + t = clamp(t, 0, 1) + return t * (this.backing.height - 10) + } + + private getYFromSecond(second: number): number { + if (!Options.chart.CMod) { + return this.getYFromBeat( + this.getChart().timingData.getBeatFromSeconds(second) + ) + } + let t = unlerp( + -this.getChart().timingData.getOffset(), + this.getChart().getLastSecond(), + second + ) + t = clamp(t, 0, 1) + return t * (this.backing.height - 10) + } + + private makeGradient() { + const quality = this.graphWidth + const canvas = document.createElement("canvas") + + canvas.width = quality + canvas.height = quality + + const ctx = canvas.getContext("2d") + + if (ctx == null) { + return null + } + // use canvas2d API to create gradient + const grd = ctx.createLinearGradient(0, 0, quality, 0) + + const color1 = `#${Options.chart.npsGraph.color1 + .toString(16) + .padStart(6, "0")}` + const color2 = `#${Options.chart.npsGraph.color2 + .toString(16) + .padStart(6, "0")}` + grd.addColorStop(0, color1) + grd.addColorStop(0.8, color2) + + ctx.fillStyle = grd + ctx.fillRect(0, 0, quality, quality) + + return Texture.from(canvas) + } +} diff --git a/app/src/gui/widget/NoteLayoutWidget.ts b/app/src/gui/widget/NoteLayoutWidget.ts index 928e9604..573b82dc 100644 --- a/app/src/gui/widget/NoteLayoutWidget.ts +++ b/app/src/gui/widget/NoteLayoutWidget.ts @@ -1,45 +1,29 @@ -import { - FederatedPointerEvent, - ParticleContainer, - RenderTexture, - Sprite, - Texture, -} from "pixi.js" -import { EditMode } from "../../chart/ChartManager" +import { ParticleContainer, RenderTexture, Sprite, Texture } from "pixi.js" import { QUANT_COLORS } from "../../chart/component/edit/SnapContainer" -import { Chart } from "../../chart/sm/Chart" import { isHoldNote } from "../../chart/sm/NoteTypes" -import { BetterRoundedRect } from "../../util/BetterRoundedRect" -import { EventHandler } from "../../util/EventHandler" -import { Flags } from "../../util/Flags" -import { clamp, lerp, maxArr, minArr, unlerp } from "../../util/Math" +import { unlerp } from "../../util/Math" import { Options } from "../../util/Options" -import { destroyChildIf, getDivision, getNoteEnd } from "../../util/Util" -import { Widget } from "./Widget" +import { destroyChildIf } from "../../util/Util" +import { BaseTimelineWidget } from "./BaseTimelineWidget" import { WidgetManager } from "./WidgetManager" -export class NoteLayoutWidget extends Widget { +export class NoteLayoutWidget extends BaseTimelineWidget { barContainer = new ParticleContainer( 1500, { position: true, scale: true, tint: true }, 16384, true ) - backing: BetterRoundedRect = new BetterRoundedRect() + bars: Sprite barTexture: RenderTexture - overlay: Sprite = new Sprite(Texture.WHITE) - selectionOverlay: Sprite = new Sprite(Texture.WHITE) - - private lastHeight = 0 - private lastCMod - private mouseDown = false - private queued = false constructor(manager: WidgetManager) { super(manager) this.addChild(this.backing) + this.addChild(this.container) this.visible = false + this.name = "note-layout" this.backing.tint = 0 this.backing.alpha = 0.3 @@ -50,165 +34,17 @@ export class NoteLayoutWidget extends Widget { this.bars = new Sprite(this.barTexture) this.bars.anchor.set(0.5) - this.addChild(this.bars) - - this.overlay.anchor.x = 0.5 - this.overlay.anchor.y = 0 - this.overlay.alpha = 0.3 - - this.selectionOverlay.anchor.x = 0.5 - this.selectionOverlay.anchor.y = 0 - this.selectionOverlay.alpha = 0.3 - this.selectionOverlay.tint = 0x4e6fa3 - - this.lastCMod = Options.chart.CMod - this.addChild(this.overlay) - this.addChild(this.selectionOverlay) - this.x = this.manager.app.renderer.screen.width / 2 - 20 - EventHandler.on("chartLoaded", () => { - this.queued = false - this.populate() - }) - EventHandler.on("chartModifiedAfter", () => { - if (!this.queued) this.populate() - this.queued = true - }) - const interval = setInterval(() => { - if (this.queued) { - this.queued = false - this.populate() - } - }, 3000) - - this.on("destroyed", () => clearInterval(interval)) + this.container.addChild(this.bars) this.populate() - - this.eventMode = "static" - this.on("mousedown", event => { - this.mouseDown = true - this.handleMouse(event) - }) - this.on("mousemove", event => { - if (this.mouseDown) this.handleMouse(event) - }) - window.onmouseup = () => { - this.mouseDown = false - } - } - - private handleMouse(event: FederatedPointerEvent) { - if (this.manager.chartManager.getMode() == EditMode.Play) return - if (!this.getChart()) return - let t = - (this.bars.toLocal(event.global).y + this.bars.height / 2) / - this.bars.height - t = clamp(t, 0, 1) - const lastNote = this.getChart().getNotedata().at(-1) - if (!lastNote) return - const lastBeat = lastNote.beat + (isHoldNote(lastNote) ? lastNote.hold : 0) - const lastSecond = this.getChart().getSecondsFromBeat(lastBeat) - if (Options.chart.CMod) { - this.manager.chartManager.setTime( - lerp(-this.getChart().timingData.getOffset(), lastSecond, t) - ) - } else { - this.manager.chartManager.setBeat(lastBeat * t) - } } update() { - this.scale.y = Options.chart.reverse ? -1 : 1 - const height = this.manager.app.renderer.screen.height - 40 - this.backing.height = height + 10 - this.backing.position.y = -this.backing.height / 2 - this.backing.position.x = -this.backing.width / 2 - this.bars.height = height - this.x = this.manager.app.renderer.screen.width / 2 - 20 - const chart = this.getChart() - const chartView = this.manager.chartManager.chartView! - if (!chart || !chartView || !Flags.layout) { + if (!Options.chart.noteLayout.enabled) { this.visible = false return } this.visible = true - const lastNote = chart.getNotedata().at(-1) - if (!lastNote) { - this.overlay.height = 0 - return - } - - const overlayStart = Options.chart.CMod - ? chartView.getSecondFromYPos( - -this.manager.app.renderer.screen.height / 2 - ) - : chartView.getBeatFromYPos( - -this.manager.app.renderer.screen.height / 2, - true - ) - const overlayEnd = Options.chart.CMod - ? chartView.getSecondFromYPos(this.manager.app.renderer.screen.height / 2) - : chartView.getBeatFromYPos( - this.manager.app.renderer.screen.height / 2, - true - ) - const overlayRange = this.getYFromRange(chart, overlayStart, overlayEnd) - this.overlay.y = overlayRange.startY - this.overlay.height = overlayRange.endY - overlayRange.startY - this.overlay.height = Math.max(2, this.overlay.height) - - const selection = this.manager.chartManager.selection.notes - if (selection.length < 1) { - this.selectionOverlay.visible = false - } else { - this.selectionOverlay.visible = true - let selectionStart, selectionEnd - if (Options.chart.CMod) { - selectionStart = minArr(selection.map(note => note.second)) - selectionEnd = maxArr( - selection.map(note => chart.getSecondsFromBeat(getNoteEnd(note))) - ) - } else { - selectionStart = minArr(selection.map(note => note.beat)) - selectionEnd = maxArr(selection.map(note => getNoteEnd(note))) - } - const selectionRange = this.getYFromRange( - chart, - selectionStart, - selectionEnd - ) - this.selectionOverlay.y = selectionRange.startY - this.selectionOverlay.height = selectionRange.endY - selectionRange.startY - this.selectionOverlay.height = Math.max(2, this.selectionOverlay.height) - } - - if ( - this.manager.app.renderer.screen.height != this.lastHeight || - this.lastCMod != Options.chart.CMod - ) { - this.lastCMod = Options.chart.CMod - this.lastHeight = this.manager.app.renderer.screen.height - this.populate() - } - } - - private getYFromRange(chart: Chart, start: number, end: number) { - const lastBeat = getNoteEnd(chart.getNotedata().at(-1)!) - const lastSecond = chart.getSecondsFromBeat(lastBeat) - let t_startY = unlerp(0, lastBeat, start) - let t_endY = unlerp(0, lastBeat, end) - if (Options.chart.CMod) { - t_startY = unlerp(-chart.timingData.getOffset(), lastSecond, start) - t_endY = unlerp(-chart.timingData.getOffset(), lastSecond, end) - } - t_startY = clamp(t_startY, 0, 1) - t_endY = clamp(t_endY, 0, 1) - if (t_startY > t_endY) [t_startY, t_endY] = [t_endY, t_startY] - const startY = (t_startY - 0.5) * (this.backing.height - 10) - const endY = (t_endY - 0.5) * (this.backing.height - 10) - return { - startY, - endY, - } + super.update() } populate() { @@ -226,13 +62,9 @@ export class NoteLayoutWidget extends Widget { const lastNote = chart.getNotedata().at(-1) const height = this.manager.app.renderer.screen.height - 40 - this.backing.height = height - this.backing.width = numCols * 6 + 8 - this.overlay.width = numCols * 6 + 8 - this.selectionOverlay.width = numCols * 6 + 8 - this.pivot.x = this.backing.width / 2 - this.barTexture.resize(numCols * 6, height) + this.backingWidth = numCols * 6 + 8 + this.updateDimensions() if (!lastNote) { destroyChildIf(this.barContainer.children, () => true) @@ -259,7 +91,7 @@ export class NoteLayoutWidget extends Widget { let t = unlerp(0, lastBeat, note.beat) if (Options.chart.CMod) t = unlerp(songOffset, lastSecond, note.second) obj.y = t * height - obj.tint = QUANT_COLORS[getDivision(note.beat)] + obj.tint = QUANT_COLORS[note.quant] if (note.type == "Mine") obj.tint = 0x808080 childIndex++ if (isHoldNote(note)) { @@ -296,8 +128,4 @@ export class NoteLayoutWidget extends Widget { renderTexture: this.barTexture, }) } - - private getChart(): Chart { - return this.manager.chartManager.loadedChart! - } } diff --git a/app/src/gui/widget/PlayInfoWidget.ts b/app/src/gui/widget/PlayInfoWidget.ts index 68d48478..f20daf80 100644 --- a/app/src/gui/widget/PlayInfoWidget.ts +++ b/app/src/gui/widget/PlayInfoWidget.ts @@ -418,7 +418,7 @@ export class PlayInfoWidget extends Widget { for (const window of [ ...collection.getStandardWindows(), - collection.getMissJudgment(), + collection.getMissJudgement(), ]) { const label = new BitmapText(window.name, { fontName: "Main", @@ -438,12 +438,14 @@ export class PlayInfoWidget extends Widget { count.anchor.y = 0.5 count.anchor.x = 1 } - const extraNumWindows = collection.getHoldWindows().length + 2 + const ht = + this.manager.chartManager.loadedChart?.gameType.gameLogic.usesHoldTicks ?? + false + const extraNumWindows = (ht ? 0 : collection.getHoldWindows().length) + 2 i = 0 - for (const window of [ - ...collection.getHoldWindows(), - collection.getMineJudgment(), - ]) { + for (const window of ht + ? [collection.getMineJudgement()] + : [...collection.getHoldWindows(), collection.getMineJudgement()]) { const name = isHoldTimingWindow(window) ? window.noteType : "Mine" const label = new BitmapText(name, { fontName: "Main", @@ -544,6 +546,7 @@ export class PlayInfoWidget extends Widget { roundDigit(gameStats.getCumulativeScore() * 100, 2).toFixed(2) if (isStandardMissTimingWindow(judge)) return if (!isStandardTimingWindow(judge)) return + if (error == null) return const ms = Math.round(error * 1000) for (let i = -3; i <= 3; i++) { if (!this.barlines.children[ms + windowSize + i]) continue @@ -586,8 +589,9 @@ export class PlayInfoWidget extends Widget { ) const windowSize = Math.round(collection.maxWindowMS()) gameStats.getDataPoints().forEach(point => { - if (isStandardMissTimingWindow(point.judgment)) return - if (!isStandardTimingWindow(point.judgment)) return + if (isStandardMissTimingWindow(point.judgement)) return + if (!isStandardTimingWindow(point.judgement)) return + if (point.error === null) return const ms = Math.round(point.error * 1000) for (let i = -3; i <= 3; i++) { if (!this.barlines.children[ms + windowSize + i]) continue diff --git a/app/src/gui/widget/StatusWidget.ts b/app/src/gui/widget/StatusWidget.ts index c2a81268..f363b8b7 100644 --- a/app/src/gui/widget/StatusWidget.ts +++ b/app/src/gui/widget/StatusWidget.ts @@ -1,7 +1,13 @@ import { Parser } from "expr-eval" -import { Container, Sprite, Texture } from "pixi.js" +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" @@ -17,7 +23,7 @@ import { WidgetManager } from "./WidgetManager" interface NoteArrow { element: HTMLButtonElement - sprite: Container + sprite: NoteskinSprite bg: Sprite highlight: BetterRoundedRect type: string @@ -466,7 +472,7 @@ export class StatusWidget extends Widget { this.idleFrames = 5 }) - EventHandler.on("chartLoaded", () => { + EventHandler.on("noteskinLoaded", () => { this.stepsContainer.replaceChildren() this.noteArrows.forEach(noteArrow => { this.removeChild(noteArrow.sprite) @@ -480,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() - .getNoteSprite({ - type, + .createNote({ + type: type as TapNoteType, beat: 0, col: 0, quant: 4, @@ -491,8 +498,7 @@ export class StatusWidget extends Widget { warped: false, fake: false, }) - sprite.width = 32 - sprite.height = 32 + sprite.scale.set(0.5) const bg = new Sprite(Texture.WHITE) bg.tint = 0 bg.alpha = 0.5 diff --git a/app/src/gui/widget/WidgetManager.ts b/app/src/gui/widget/WidgetManager.ts index 9864845f..655a8dc5 100644 --- a/app/src/gui/widget/WidgetManager.ts +++ b/app/src/gui/widget/WidgetManager.ts @@ -2,6 +2,7 @@ import { Container } from "pixi.js" import { App } from "../../App" import { ChartManager } from "../../chart/ChartManager" import { DebugWidget } from "./DebugWidget" +import { NPSGraphWidget } from "./NPSGraphWidget" import { NoteLayoutWidget } from "./NoteLayoutWidget" import { PlayInfoWidget } from "./PlayInfoWidget" import { PlaybackOptionsWidget } from "./PlaybackOptionsWidget" @@ -21,6 +22,7 @@ export class WidgetManager extends Container { this.addChild(new PlayInfoWidget(this)) this.addChild(new StatusWidget(this)) this.addChild(new DebugWidget(this)) + this.addChild(new NPSGraphWidget(this)) this.addChild(new PlaybackOptionsWidget(this)) this.zIndex = 2 } diff --git a/app/src/gui/window/NoteskinWindow.ts b/app/src/gui/window/NoteskinWindow.ts new file mode 100644 index 00000000..d3c7a04e --- /dev/null +++ b/app/src/gui/window/NoteskinWindow.ts @@ -0,0 +1,137 @@ +import { App } from "../../App" +import { NoteskinRegistry } from "../../chart/gameTypes/noteskin/NoteskinRegistry" +import { EventHandler } from "../../util/EventHandler" +import { Options } from "../../util/Options" +import { Window } from "./Window" + +import placeholderPreview from "../../../assets/preview.png" + +export class NoteskinWindow extends Window { + app: App + + private grid!: HTMLDivElement + private lastGameType: string | null = null + + constructor(app: App) { + super({ + title: "Noteskin Selection", + width: 600, + height: 400, + disableClose: false, + win_id: "noteskin-selection", + blocking: false, + }) + this.app = app + + this.initView() + this.loadGrid() + + EventHandler.on("chartLoaded", () => { + const gameType = app.chartManager.loadedChart!.gameType.id + if (this.lastGameType != gameType) { + this.loadGrid() + } + }) + } + + initView(): void { + // Create the window + this.viewElement.replaceChildren() + + //Padding container + const padding = document.createElement("div") + padding.classList.add("padding") + + const searchBar = document.createElement("input") + searchBar.classList.add("pref-search-bar") + searchBar.type = "text" + searchBar.placeholder = "Search for a noteskin..." + + searchBar.oninput = () => { + this.filterGrid(searchBar.value) + } + + const grid = document.createElement("div") + grid.classList.add("noteskin-grid") + this.grid = grid + + padding.replaceChildren(searchBar, grid) + + this.viewElement.appendChild(padding) + } + + loadGrid() { + this.grid.replaceChildren() + if (!this.app.chartManager.loadedChart) return + const gameType = this.app.chartManager.loadedChart.gameType + this.lastGameType = gameType.id + const noteskins = NoteskinRegistry.getNoteskins().get(gameType.id) + if (!noteskins) return + for (const [id, options] of noteskins.entries()) { + const container = document.createElement("div") + const image = document.createElement("img") + const labelContainer = document.createElement("div") + const title = document.createElement("div") + const subtitle = document.createElement("div") + + container.classList.add("noteskin-cell") + labelContainer.classList.add("noteskin-label") + title.classList.add("noteskin-title") + subtitle.classList.add("noteskin-subtitle") + + title.innerText = options.title ?? id + subtitle.innerText = options.subtitle ?? "" + + image.src = NoteskinRegistry.getPreviewUrl(gameType, id) + image.onerror = () => { + image.src = placeholderPreview + } + + container.replaceChildren(image, labelContainer) + labelContainer.replaceChildren(title, subtitle) + + this.grid.appendChild(container) + + if (id == Options.chart.noteskin.name) { + container.classList.add("selected") + setTimeout(() => { + container.scrollIntoView({ behavior: "smooth", block: "center" }) + }) + } + + container.dataset.id = id + container.dataset.title = options.title ?? "" + container.dataset.subtitle = options.subtitle ?? "" + + container.onclick = () => { + if (Options.chart.noteskin.name == id) return + this.app.chartManager.chartView?.swapNoteskin(id) + this.removeAllSelections() + container.classList.add("selected") + } + } + } + + removeAllSelections() { + ;[...this.grid.querySelectorAll(".selected")].forEach(e => + e.classList.remove("selected") + ) + } + + filterGrid(query: string) { + ;[...this.grid.children].forEach(element => { + if (!(element instanceof HTMLDivElement)) return + const div: HTMLDivElement = element + const shouldDisplay = + this.containsQuery(query, div.dataset.id) || + this.containsQuery(query, div.dataset.title) || + this.containsQuery(query, div.dataset.subtitle) + div.style.display = shouldDisplay ? "" : "none" + }) + } + + containsQuery(query: string, string: string | undefined) { + if (!string) return false + return string.toLowerCase().includes(query.trim().toLowerCase()) + } +} diff --git a/app/src/gui/window/UserOptionsWindow.ts b/app/src/gui/window/UserOptionsWindow.ts index 17ac5fa4..e6c64252 100644 --- a/app/src/gui/window/UserOptionsWindow.ts +++ b/app/src/gui/window/UserOptionsWindow.ts @@ -5,6 +5,7 @@ import { UserOption, UserOptionGroup, } from "../../data/UserOptionsWindowData" +import { EventHandler } from "../../util/EventHandler" import { clamp, roundDigit } from "../../util/Math" import { Options } from "../../util/Options" import { parseString } from "../../util/Util" @@ -125,6 +126,7 @@ export class UserOptionsWindow extends Window { revert.style.width = "12px" revert.addEventListener("click", () => { Options.applyOption([option.id, Options.getDefaultOption(option.id)]) + EventHandler.emit("userOptionUpdated", option.id) // Reload the option item.replaceWith(this.makeOption(option)) }) @@ -149,6 +151,7 @@ export class UserOptionsWindow extends Window { checkbox.onblur = null checkbox.onchange = () => { Options.applyOption([option.id, checkbox.checked]) + EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) @@ -174,6 +177,7 @@ export class UserOptionsWindow extends Window { ) dropdown.onChange(value => { Options.applyOption([option.id, deserializer(value)]) + EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) @@ -188,6 +192,7 @@ export class UserOptionsWindow extends Window { const dropdown = Dropdown.create(option.input.items, optionValue) dropdown.onChange(value => { Options.applyOption([option.id, value]) + EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) @@ -219,6 +224,7 @@ export class UserOptionsWindow extends Window { return } Options.applyOption([option.id, deserializer(value)]) + EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) @@ -264,9 +270,12 @@ export class UserOptionsWindow extends Window { value = clamp(value, hardMin, hardMax) numberInput.value = roundDigit(value, 3).toString() numberInput.blur() - if (numberInput.value == "") + if (numberInput.value == "") { numberInput.value = serializer(value).toString() - else Options.applyOption([option.id, deserializer(value)]) + } else { + Options.applyOption([option.id, deserializer(value)]) + EventHandler.emit("userOptionUpdated", option.id) + } slider.value = value.toString() revert.style.display = Options.getDefaultOption(option.id) === @@ -301,6 +310,7 @@ export class UserOptionsWindow extends Window { textInput.value = optionValue.toString() textInput.onblur = () => { Options.applyOption([option.id, textInput.value]) + EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) @@ -317,12 +327,17 @@ export class UserOptionsWindow extends Window { case "color": { const colorInput = document.createElement("input") colorInput.type = "color" - colorInput.value = "#" + optionValue.toString(16) + colorInput.value = "#" + optionValue.toString(16).padStart(6, "0") + // 'change' event is fired when the user closes the color picker + colorInput.onchange = () => { + colorInput.blur() + } colorInput.onblur = () => { Options.applyOption([ option.id, parseInt(colorInput.value.slice(1), 16), ]) + EventHandler.emit("userOptionUpdated", option.id) revert.style.display = Options.getDefaultOption(option.id) === Options.getOption(option.id) diff --git a/app/src/util/BezierEasing.ts b/app/src/util/BezierEasing.ts index fb67a43c..6ca7eac5 100644 --- a/app/src/util/BezierEasing.ts +++ b/app/src/util/BezierEasing.ts @@ -8,7 +8,7 @@ export type BezierKeyFrames = { export interface BezierAnimation { obj: T animation: BezierKeyFrames - speed: number + seconds: number progress: number curve: bezier.EasingFunction onend: (obj: T) => void @@ -80,7 +80,7 @@ export class BezierAnimator { } else { animation.progress = Math.min( 1, - animation.progress + animation.speed * delta + animation.progress + animation.seconds * delta ) this.updateObject( animation.obj, @@ -146,10 +146,15 @@ export class BezierAnimator { this.animations.delete(id) } + static finish(id: string | undefined) { + if (id === undefined) return + this.stop(id, 1) + } + static animate( obj: any, animation: BezierKeyFrames, - speed: number, + seconds: number, curve?: bezier.EasingFunction, onend = () => {}, id?: string @@ -158,7 +163,7 @@ export class BezierAnimator { this.animations.set(id, { obj, animation, - speed: 1 / (60 * speed), + seconds: 1 / (60 * seconds), progress: 0, curve: void 0 !== curve ? curve : noEase, onend: onend, diff --git a/app/src/util/Options.ts b/app/src/util/Options.ts index 8f1cd73f..02ac1b5a 100644 --- a/app/src/util/Options.ts +++ b/app/src/util/Options.ts @@ -25,6 +25,7 @@ export class DefaultOptions { hideFakedArrows: false, doSpeedChanges: true, drawNoteFlash: true, + drawIcons: true, receptorYPos: -200, maxDrawBeats: 20, maxDrawBeatsBack: 10, @@ -45,6 +46,14 @@ export class DefaultOptions { lineHeight: 1, speedChanges: true, }, + noteLayout: { + enabled: true, + }, + npsGraph: { + enabled: false, + color1: 0x4aa7bc, + color2: 0x423c7a, + }, timingEventOrder: { left: [ "LABELS", @@ -82,6 +91,10 @@ export class DefaultOptions { ATTACKS: true, } as { [key in TimingEventType]: boolean }, noteskin: { + type: "dance-single", + name: "default", + }, + lastNoteskins: { "dance-single": "default", "dance-double": "default", "dance-couple": "default", @@ -89,6 +102,11 @@ export class DefaultOptions { "dance-solodouble": "default", "dance-threepanel": "default", "dance-threedouble": "default", + "pump-single": "default", + "pump-double": "default", + "pump-versus": "default", + "pump-couple": "default", + "pump-halfdouble": "default", } as Record, } static audio = { @@ -105,7 +123,7 @@ export class DefaultOptions { effectOffset: 0, visualOffset: 0, hideBarlines: false, - judgmentTilt: true, + judgementTilt: true, timingCollection: "ITG", timingWindowScale: 1, timingWindowAdd: 0, @@ -126,6 +144,7 @@ export class DefaultOptions { showFPS: false, showTimers: false, showScroll: false, + showNoteskinErrors: false, } static experimental = {} } diff --git a/app/src/util/Util.ts b/app/src/util/Util.ts index 49501715..7c760af3 100644 --- a/app/src/util/Util.ts +++ b/app/src/util/Util.ts @@ -1,5 +1,11 @@ import { Parser } from "expr-eval" -import { DisplayObject, FederatedMouseEvent } from "pixi.js" +import { + DisplayObject, + FederatedMouseEvent, + Geometry, + Rectangle, + Texture, +} from "pixi.js" import { PartialNotedataEntry, isHoldNote } from "../chart/sm/NoteTypes" import { IS_OSX } from "../data/KeybindData" @@ -115,3 +121,66 @@ export function isIFrame() { return true } } + +export function splitTex( + texture: Texture, + xFrames: number, + yFrames: number, + xWidth: number, + yWidth: number +) { + const frames: Texture[][] = [] + for (let y = 0; y < yFrames; y++) { + const row = [] + for (let x = 0; x < xFrames; x++) { + row.push( + new Texture( + texture.baseTexture, + new Rectangle(xWidth * x, yWidth * y, xWidth, yWidth) + ) + ) + } + frames.push(row) + } + return frames +} + +export async function loadGeometry(data: string): Promise { + const lines = data.split("\n") + const numVertices = parseInt(lines[0]) + const numTriangles = parseInt(lines[numVertices + 1]) + const vPos = [] + const vUvs = [] + const vIndex = [] + for (let i = 0; i < numVertices; i++) { + const match = /(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)/.exec( + lines[i + 1] + ) + if (match) { + vPos.push(parseFloat(match[1])) + vPos.push(parseFloat(match[2])) + vUvs.push(parseFloat(match[3])) + vUvs.push(parseFloat(match[4])) + } else { + console.error("Failed to load vertex " + lines[i + 1]) + return new Geometry() + } + } + for (let i = 0; i < numTriangles; i++) { + const match = /(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)/.exec( + lines[i + 2 + numVertices] + ) + if (match) { + vIndex.push(parseFloat(match[1])) + vIndex.push(parseFloat(match[2])) + vIndex.push(parseFloat(match[3])) + } else { + console.error("Failed to load triangle " + lines[i + 2 + numVertices]) + return new Geometry() + } + } + return new Geometry() + .addAttribute("aVertexPosition", vPos, 2) + .addAttribute("aUvs", vUvs, 2) + .addIndex(vIndex) +} diff --git a/app/src/util/VertCropSprite.ts b/app/src/util/VertCropSprite.ts new file mode 100644 index 00000000..2932ed1d --- /dev/null +++ b/app/src/util/VertCropSprite.ts @@ -0,0 +1,65 @@ +import { NineSlicePlane, Texture } from "pixi.js" + +// More performant version to crop a sprite vertically in one way + +export class VertCropSprite extends NineSlicePlane { + private offsetY = 0 + private setY = 0 + + private _last = 0 + private _lastTop = false + + constructor(texture: Texture) { + super(texture, 0, 0, 0, 0) + this.scale.cb = () => { + this.refresh() + } + } + + cropBottom(pixels: number, force = false) { + if (this._last == pixels && !this._lastTop && !force) { + return + } + this._last = pixels + this._lastTop = false + this._height = this.texture.height - pixels / Math.abs(this.scale.y) + this._bottomHeight = 0 + this.offsetY = 0 + this.topHeight = this.texture.height - pixels / Math.abs(this.scale.y) + this._updateY() + } + + cropTop(pixels: number, force = false) { + if (this._last == pixels && this._lastTop && !force) { + return + } + this._last = pixels + this._lastTop = true + this._height = this.texture.height - pixels / Math.abs(this.scale.y) + this._topHeight = 0 + this.bottomHeight = this.texture.height - pixels / Math.abs(this.scale.y) + this.offsetY = pixels / Math.abs(this.scale.y) + this._updateY() + } + + get y() { + return this.setY + } + + set y(value: number) { + this.setY = value + this._updateY() + } + + private _updateY() { + super.y = this.setY + this.offsetY * Math.abs(this.scale.y) + } + + refresh() { + if (this._lastTop) { + this.cropTop(this._last, true) + } else { + this.cropBottom(this._last, true) + } + } +} diff --git a/guide/auto-sync.md b/guide/auto-sync.md index de04c863..6df82347 100644 --- a/guide/auto-sync.md +++ b/guide/auto-sync.md @@ -83,11 +83,13 @@ usually work well, but these can be tweaked for specific situations. ### Onsets **FFT Step** + The FFT step represents the amount of audio to analyze around a block. A higher value results in a more frequency-accurate, but a less time-accurate spectrogram. Additionally, a higher value will take longer to analyze. **Window Step** + The window step changes the number of blocks per second of audio. A lower value results in more time-accurate spectrograms, but may take more time and mess up tempo analysis. This value must be less than **FFT Step**. @@ -95,11 +97,13 @@ but may take more time and mess up tempo analysis. This value must be less than ### Tempogram **FFT Step** + The FFT step represents the amount of the onset graph to analyze around a block. A higher value results in more accurate tempos, but less accurate timings. Additionally, a higher value will take longer to analyze. **Window Step** + The window step changes the number of blocks per second of audio. A lower value results in more time-accurate tempograms, but may take more time. This value must be less than **FFT Step**. diff --git a/guide/scripting.md b/guide/scripting.md index 357019f3..cd8d92fd 100644 --- a/guide/scripting.md +++ b/guide/scripting.md @@ -103,6 +103,8 @@ function stutterStops(beat, length, factor = 2) { ```