diff --git a/assets/scripts/core/game-scene.js b/assets/scripts/core/game-scene.js index 9fdead19..0256f1f2 100644 --- a/assets/scripts/core/game-scene.js +++ b/assets/scripts/core/game-scene.js @@ -7396,6 +7396,15 @@ _showwippopup() { this._endCamTween = null; this._spaceWasDown = false; this._physicsFrame = 0; + if (this._mirrorTween) { + this._mirrorTween.stop(); + this._mirrorTween = null; + } + if (this._mirrorAnim) { + this._mirrorAnim.p = 0; + } else { + this._mirrorAnim = { p: 0 }; + } } _restartLevel() { this._attempts++; @@ -7648,6 +7657,15 @@ _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._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,7 +7940,9 @@ _showwippopup() { } _updateBackground() { - this._bg.tilePositionX += (this._cameraX - this._prevCameraX) * this._bgSpeedX; + const isMirrored = this._mirrorAnim ? (this._mirrorAnim.p >= 0.5) : this._state.mirrored; + const deltaX = this._cameraX - this._prevCameraX; + this._bg.tilePositionX += isMirrored ? -deltaX * this._bgSpeedX : deltaX * this._bgSpeedX; this._prevCameraX = this._cameraX; let tileY = this._bgInitY - this._cameraY * this._bgSpeedY; if (this._bgMirrorTileHeight > 0) { @@ -8655,34 +8675,80 @@ _showwippopup() { this._applyMirrorEffect(); } -_applyMirrorEffect() { - const isMirrored = this._state.mirrored; - 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; - } - for (const tile of this._level._groundTiles) { - tile.x = screenWidth - tile.x - this._level._tileW; - tile.setFlipX(true); - } - for (const tile of this._level._ceilingTiles) { - tile.x = screenWidth - tile.x - this._level._tileW; - tile.setFlipX(true); - } - } else { - for (const c of containers) { - if (c.scaleX !== 1) c.scaleX = 1; - } - for (const tile of this._level._groundTiles) { - tile.setFlipX(false); - } - for (const tile of this._level._ceilingTiles) { - tile.setFlipX(false); + 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; + this._attemptsLabel.setVisible(isMirrored); + 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; + this._attemptsLabel.setVisible(this._state.mirrored); } + }); + } + + _applyMirrorEffect() { + const p = this._mirrorAnim ? this._mirrorAnim.p : (this._state.mirrored ? 1 : 0); + const containers = [this._level.additiveContainer, this._level.container, this._level.topContainer]; + const scaleX = 1 - 2 * p; + const containerX = screenWidth / 2 * (1 - scaleX) - this._cameraX * scaleX; + + for (const c of containers) { + c.scaleX = scaleX; + c.x = containerX; } - this._bg.setFlipX(isMirrored); + + this._bg.setFlipX(false); + + const isFlipped = p >= 0.5; + + for (let i = 0; i < this._level._groundTiles.length; i++) { + const tile = this._level._groundTiles[i]; + const tileScreenX = tile._worldX - this._cameraX; + tile.x = isFlipped ? (screenWidth - tileScreenX - this._level._tileW) : tileScreenX; + + const ground2Tile = this._level._ground2Tiles?.[i]; + if (ground2Tile) ground2Tile.x = tile.x; + + const ceilingTile = this._level._ceilingTiles[i]; + ceilingTile.x = tile.x; + + const ceiling2Tile = this._level._ceiling2Tiles?.[i]; + if (ceiling2Tile) ceiling2Tile.x = tile.x; + } + + const isAnimating = this._mirrorTween !== null; + this._attemptsLabel.setVisible(isAnimating ? false : this._state.mirrored); } _getDualSharedSignature(state) { if (!state) return "0|0"; @@ -9012,7 +9078,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/level.js b/assets/scripts/core/level.js index 087b3249..dd98c181 100644 --- a/assets/scripts/core/level.js +++ b/assets/scripts/core/level.js @@ -1393,7 +1393,9 @@ window.LevelObject = class LevelObject { maxWorldX = groundTile._worldX; this._maxGroundWorldX = maxWorldX; } - let tileScreenX = groundTile._worldX - cameraX; + const isMirrored = this._scene._mirrorAnim ? (this._scene._mirrorAnim.p >= 0.5) : this._scene._state.mirrored; + const normalX = groundTile._worldX - cameraX; + const tileScreenX = isMirrored ? (screenWidth - normalX - tileWidth) : normalX; groundTile.x = tileScreenX; groundTile.y = leftTileIndex; const ground2Tile = this._ground2Tiles?.[i]; 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)) { diff --git a/index.html b/index.html index 8d033615..37a7e00c 100644 --- a/index.html +++ b/index.html @@ -45,7 +45,7 @@ - +