diff --git a/assets/scripts/core/game-scene.js b/assets/scripts/core/game-scene.js index 9fdead19..fee46911 100644 --- a/assets/scripts/core/game-scene.js +++ b/assets/scripts/core/game-scene.js @@ -7396,6 +7396,23 @@ _showwippopup() { this._endCamTween = null; this._spaceWasDown = false; this._physicsFrame = 0; + if (this._mirrorTween) { + this._mirrorTween.stop(); + this._mirrorTween = null; + } + if (this._mirrorClipGfx) { + for (const c of [this._level?.additiveContainer, this._level?.container, this._level?.topContainer]) { + c?.clearMask(); + } + this._mirrorClipGfx.destroy(); + this._mirrorClipGfx = null; + this._mirrorClipMask = null; + } + if (this._mirrorAnim) { + this._mirrorAnim.p = 0; + } else { + this._mirrorAnim = { p: 0 }; + } } _restartLevel() { this._attempts++; @@ -7648,6 +7665,23 @@ _showwippopup() { this._state.gravity = checkpoint.gravity; this._state.jumpPower = checkpoint.jumpPower; this._state.mirrored = checkpoint.mirrored; + if (this._mirrorTween) { + this._mirrorTween.stop(); + this._mirrorTween = null; + } + if (this._mirrorClipGfx) { + for (const c of [this._level?.additiveContainer, this._level?.container, this._level?.topContainer]) { + c?.clearMask(); + } + this._mirrorClipGfx.destroy(); + this._mirrorClipGfx = null; + this._mirrorClipMask = null; + } + if (this._mirrorAnim) { + this._mirrorAnim.p = checkpoint.mirrored ? 1 : 0; + } else { + this._mirrorAnim = { p: checkpoint.mirrored ? 1 : 0 }; + } this._state.isDashing = checkpoint.isDashing; this._state.dashYVelocity = checkpoint.dashYVelocity; this._state.ballShouldRotate = checkpoint.ballShouldRotate || false; @@ -7922,8 +7956,11 @@ _showwippopup() { } _updateBackground() { - this._bg.tilePositionX += (this._cameraX - this._prevCameraX) * this._bgSpeedX; - this._prevCameraX = this._cameraX; + const isTransitioning = this._mirrorAnim ? this._mirrorAnim.p > 0.001 && this._mirrorAnim.p < 0.999 : false; + if (!isTransitioning) { + this._bg.tilePositionX += (this._cameraX - this._prevCameraX) * this._bgSpeedX; + this._prevCameraX = this._cameraX; + } let tileY = this._bgInitY - this._cameraY * this._bgSpeedY; if (this._bgMirrorTileHeight > 0) { tileY = ((tileY % this._bgMirrorTileHeight) + this._bgMirrorTileHeight) % this._bgMirrorTileHeight; @@ -8655,34 +8692,119 @@ _showwippopup() { this._applyMirrorEffect(); } -_applyMirrorEffect() { - const isMirrored = this._state.mirrored; + toggleMirror(isMirrored) { + const currentP = this._mirrorAnim + ? this._mirrorAnim.p + : (this._state.mirrored ? 1 : 0); + + this._state.mirrored = isMirrored; + if (this._state2) this._state2.mirrored = isMirrored; + + if (!this._mirrorAnim) { + this._mirrorAnim = { p: currentP }; + } + + // Freeze ground/bg at current positions when transition starts + if (currentP > 0.001 && currentP < 0.999) { + this._mirrorFrozenTiles = true; + } else if (currentP <= 0.001 || currentP >= 0.999) { + this._mirrorFrozenTiles = false; + } + + if (this._editorPlaytestActive) { + this._mirrorAnim.p = isMirrored ? 1 : 0; + return; + } + + if (this._mirrorTween) { + this._mirrorTween.stop(); + this._mirrorTween = null; + } + this._mirrorTween = this.tweens.add({ + targets: this._mirrorAnim, + p: isMirrored ? 1 : 0, + duration: 400, + ease: "Sine.easeInOut", + onComplete: () => { + this._mirrorTween = null; + this._mirrorFrozenTiles = false; + } + }); + } + + _applyMirrorEffect() { + const p = this._mirrorAnim ? this._mirrorAnim.p : (this._state.mirrored ? 1 : 0); const containers = [this._level.additiveContainer, this._level.container, this._level.topContainer]; - if (isMirrored) { - for (const c of containers) { - c.scaleX = -1; - c.x = this._cameraX + screenWidth; + const raw = 1 - 2 * p; + const scaleX = Math.abs(raw) < 0.0001 ? 0.0001 : raw; + const containerX = screenWidth / 2 * (1 - scaleX) - this._cameraX * scaleX; + + for (const c of containers) { + c.scaleX = scaleX; + c.x = containerX; + } + + const isTransitioning = p > 0.001 && p < 0.999; + if (isTransitioning) { + const visibleWidth = Math.max(1, screenWidth * Math.abs(scaleX)); + const visibleLeft = (screenWidth - visibleWidth) / 2; + + if (!this._mirrorClipGfx) { + this._mirrorClipGfx = this.make.graphics({ add: false }); + this._mirrorClipMask = this._mirrorClipGfx.createGeometryMask(); + for (const c of containers) { + c.setMask(this._mirrorClipMask); + } } - for (const tile of this._level._groundTiles) { - tile.x = screenWidth - tile.x - this._level._tileW; - tile.setFlipX(true); + + this._mirrorClipGfx.clear(); + this._mirrorClipGfx.fillStyle(0xffffff, 1); + this._mirrorClipGfx.fillRect(visibleLeft, 0, visibleWidth, screenHeight); + } else if (this._mirrorClipGfx) { + for (const c of containers) { + c.clearMask(); } - for (const tile of this._level._ceilingTiles) { - tile.x = screenWidth - tile.x - this._level._tileW; - tile.setFlipX(true); + this._mirrorClipGfx.destroy(); + this._mirrorClipGfx = null; + this._mirrorClipMask = null; + } + + this._bg.setFlipX(false); + + const isFlipped = p >= 0.5; + + // Freeze ground during transition, resume after + if (!isTransitioning && this._mirrorFrozenTiles) { + this._mirrorFrozenTiles = false; + } + + for (let i = 0; i < this._level._groundTiles.length; i++) { + const tile = this._level._groundTiles[i]; + const tileScreenX = tile._worldX - this._cameraX; + + if (!this._mirrorFrozenTiles) { + tile.x = isFlipped ? (screenWidth - tileScreenX - this._level._tileW) : tileScreenX; } - } else { - for (const c of containers) { - if (c.scaleX !== 1) c.scaleX = 1; + + const ground2Tile = this._level._ground2Tiles?.[i]; + if (ground2Tile) { + if (!this._mirrorFrozenTiles) { + ground2Tile.x = tile.x; + } } - for (const tile of this._level._groundTiles) { - tile.setFlipX(false); + + const ceilingTile = this._level._ceilingTiles[i]; + if (!this._mirrorFrozenTiles) { + ceilingTile.x = tile.x; } - for (const tile of this._level._ceilingTiles) { - tile.setFlipX(false); + + const ceiling2Tile = this._level._ceiling2Tiles?.[i]; + if (ceiling2Tile) { + if (!this._mirrorFrozenTiles) { + ceiling2Tile.x = tile.x; + } } } - this._bg.setFlipX(isMirrored); } _getDualSharedSignature(state) { if (!state) return "0|0"; @@ -9012,7 +9134,8 @@ _applyMirrorEffect() { } _getMirrorXOffset(xOffset) { - return this._state.mirrored ? screenWidth - xOffset : xOffset; + const p = this._mirrorAnim ? this._mirrorAnim.p : (this._state.mirrored ? 1 : 0); + return xOffset + (screenWidth - 2 * xOffset) * p; } _enableDualMode() { if (this._isDual) return; diff --git a/assets/scripts/core/player.js b/assets/scripts/core/player.js index 0ee23f03..41f7a0df 100644 --- a/assets/scripts/core/player.js +++ b/assets/scripts/core/player.js @@ -2430,6 +2430,35 @@ if (this.p.isFlying || this.p.isUfo) { }); } + spawnPortalCircle(color, gameObj) { + if (this._scene?._editorPlaytestActive || window.enableLDM || !this._scene) return; + const scene = this._scene; + const graphics = scene.add.graphics().setDepth(15).setBlendMode(S); + this._gameLayer.topContainer.add(graphics); + const maxRadius = 75; + const startX = gameObj ? Number(gameObj.x) : (scene._playerWorldX || 0); + const startY = gameObj ? b(Number(gameObj.y)) : b(this.p.y || 30); + graphics.setPosition(startX, startY); + const state = { progress: 0 }; + scene.tweens.add({ + targets: state, + progress: 1, + duration: 350, + ease: "Quad.Out", + onUpdate: () => { + const radius = 8 + state.progress * (maxRadius - 8); + const alpha = Math.max(0, 1 - state.progress); + graphics.clear(); + graphics.setAlpha(1); + graphics.lineStyle(4, color, alpha); + graphics.strokeCircle(0, 0, radius); + }, + onComplete: () => { + graphics.destroy(); + } + }); + } + playGravityEffect(toUp) { if (this._scene?._editorPlaytestActive || window.enableLDM || !this._scene) return; const scene = this._scene; @@ -4417,17 +4446,19 @@ if (this.p.isFlying || this.p.isUfo) { if (!this._isObjectActivated(gameObj)) { this._setObjectActivated(gameObj, true); this._playPortalShine(gameObj); - if (!this._scene?._editorPlaytestActive) { - this.p.mirrored = true; - } + this.spawnPortalCircle(0xff9900, gameObj); + if (this._streakManager) this._streakManager.reset(); + this.p.mirrored = true; + this._scene?.toggleMirror?.(true); } } else if (_colType === "portal_mirror_off") { if (!this._isObjectActivated(gameObj)) { this._setObjectActivated(gameObj, true); this._playPortalShine(gameObj); - if (!this._scene?._editorPlaytestActive) { - this.p.mirrored = false; - } + this.spawnPortalCircle(0x00ffff, gameObj); + if (this._streakManager) this._streakManager.reset(); + this.p.mirrored = false; + this._scene?.toggleMirror?.(false); } } else if (_colType === "portal_mini_on") { if (!this._isObjectActivated(gameObj)) {