diff --git a/assets/scripts/core/game-scene.js b/assets/scripts/core/game-scene.js index 98b31055..679aee0e 100644 --- a/assets/scripts/core/game-scene.js +++ b/assets/scripts/core/game-scene.js @@ -6162,6 +6162,9 @@ _buildSettingsPopup() { */ const updateEntries = [ { text: "Update Log", scale: 1, font: "goldFont" }, + { text: "Slopes OVERHAULED", scale: 0.75, color: 0xff9944 }, + { text: "Bug reports are appreciated", scale: 0.75, }, + { text: "-Bari", scale: 0.75, }, { text: "Credits menu fixed :3", scale: 0.75, }, { text: "Small Icon Kit changes", scale: 0.75, }, { text: "Low Detail Mode", scale: 0.75, }, @@ -6178,9 +6181,6 @@ _buildSettingsPopup() { { text: "Breakable blocks break now.", scale: 0.75, }, { text: "Fixed objects not showing in editor", scale: 0.65, }, { text: "^ I, Amethyst, did all this btw. ^", scale: 0.6, color: 0x9966cc}, - { text: "Slopes (very buggy)", scale: 0.75, color: 0xff9944 }, - { text: "THEY WILL BE FIXED-", scale: 0.75, }, - { text: "OVER TIME.", scale: 0.75, }, { text: "Slopes work in imported-", scale: 0.75, }, { text: "levels now (thanks lasokadadyy)", scale: 0.7, }, { text: "Fixed SOME objects", scale: 0.75 }, diff --git a/assets/scripts/core/level.js b/assets/scripts/core/level.js index 8e41777c..5485ff83 100644 --- a/assets/scripts/core/level.js +++ b/assets/scripts/core/level.js @@ -11,29 +11,31 @@ class Collider { this.slopeDir = 1; this.slopeIsFilled = false; this.slopeFlipY = false; + this.hypoAx = 0; this.hypoAy = 0; + this.hypoBx = 0; this.hypoBy = 0; + this.rightAx = 0; this.rightAy = 0; + this.slopeSolidBelow = true; } getSlopeSurfaceY(worldX) { if (this.type !== slopeType) return null; - const halfW = this.w / 2; - const left = this.x - halfW; - const right = this.x + halfW; - if (worldX < left || worldX > right) return null; - const frac = (worldX - left) / (right - left); - let surfaceFrac = this.slopeDir > 0 ? frac : (1 - frac); - if (this.slopeFlipY) surfaceFrac = 1 - surfaceFrac; - return (this.y - this.h / 2) + surfaceFrac * this.h; + const ax = this.x + this.hypoAx, ay = this.y + this.hypoAy; + const bx = this.x + this.hypoBx, by = this.y + this.hypoBy; + const lo = ax <= bx ? { x: ax, y: ay } : { x: bx, y: by }; + const hi = ax <= bx ? { x: bx, y: by } : { x: ax, y: ay }; + if (hi.x - lo.x < 0.01) return null; + if (worldX < lo.x || worldX > hi.x) return null; + const t = (worldX - lo.x) / (hi.x - lo.x); + return lo.y + t * (hi.y - lo.y); } getSlopeAngleRad() { - let angleDeg = this.slopeAngleDeg; - if (this.slopeDir < 0) angleDeg = -angleDeg; - if (this.slopeFlipY) angleDeg = -angleDeg; - return angleDeg * Math.PI / 180; + if (this.type !== slopeType) return 0; + return Math.atan2(this.hypoBy - this.hypoAy, this.hypoBx - this.hypoAx); } usesFloorLanding(gravityFlipped) { return this.isSolidBelowSurface(gravityFlipped); } isSolidBelowSurface(gravityFlipped) { - return this.slopeFlipY === gravityFlipped; + return this.slopeSolidBelow !== gravityFlipped; } isSlopeSolidAt(worldX, worldY, gravityFlipped = false) { if (this.type !== slopeType) return false; @@ -209,6 +211,9 @@ const _SLOPE_DATA = { 294:{gw:1,gh:1,angle:45,sq:false},295:{gw:2,gh:1,angle:22.5,sq:false}, 296:{gw:0.367,gh:0.433,angle:45,sq:true},297:{gw:0.967,gh:0.45,angle:45,sq:true}, 299:{gw:1,gh:1,angle:45,sq:false},301:{gw:2,gh:1,angle:22.5,sq:false}, + 305:{gw:1,gh:1,angle:45,sq:false},307:{gw:2,gh:1,angle:22.5,sq:false}, + 349:{gw:1,gh:1,angle:45,sq:false},351:{gw:2,gh:1,angle:22.5,sq:false}, + 673:{gw:1,gh:1,angle:45,sq:false},674:{gw:2,gh:1,angle:22.5,sq:false}, 309:{gw:1,gh:1,angle:45,sq:false},311:{gw:2,gh:1,angle:22.5,sq:false}, 315:{gw:1,gh:1,angle:45,sq:false},317:{gw:2,gh:1,angle:22.5,sq:false}, 321:{gw:1,gh:1,angle:45,sq:false},323:{gw:2,gh:1,angle:22.5,sq:false}, @@ -355,20 +360,91 @@ function _resolveSlopeOrientation(objectDef, levelObj) { return { dir, flipY }; } +function _gdSlopeType(flipX, flipY, rotationDegrees) { + const rot = (parseInt(rotationDegrees, 10) || 0) % 360; + const fx = !!flipX, fy = !!flipY; + const r0 = rot === 0; + const r180 = Math.abs(rot) === 180; + const r90 = rot === 90 || rot === -270; + const r270 = rot === -90 || rot === 270; + if (!fx && !fy) return r0 ? 0 : r90 ? 4 : r180 ? 3 : r270 ? 5 : null; + if (!fx && fy) return r0 ? 1 : r90 ? 7 : r180 ? 2 : r270 ? 6 : null; + if (fx && !fy) return r0 ? 2 : r90 ? 6 : r180 ? 1 : r270 ? 7 : null; + return r0 ? 3 : r90 ? 5 : r180 ? 0 : r270 ? 4 : null; +} + +function _gdSlopeSolidAbove(type) { + return type !== null && type < 7 && ((0x6a >> type) & 1) !== 0; +} +function _gdSlopeWallOnLeft(type) { + return type !== null && ((type - 2) >>> 0 < 3 || type === 6); +} + +const _SLOPE_DECO_RANGES = [ + [681, 708], [713, 716], [730, 763], [771, 772], [826, 829], [877, 878], + [888, 961], [969, 970], [1014, 1015], [1033, 1034], [1037, 1038], + [1041, 1092], [1108, 1188], [1198, 1199], [1256, 1306], [1316, 1317], + [1758, 1900] +]; + +function _isDecorationSlopeId(id) { + const n = parseInt(id, 10); + if (!Number.isFinite(n)) return false; + for (const [lo, hi] of _SLOPE_DECO_RANGES) { + if (n >= lo && n <= hi) return true; + } + return false; +} + +function rotateSlopePoint(localX, localY, rotDeg) { + const theta = -rotDeg * Math.PI / 180; + const cosT = Math.cos(theta), sinT = Math.sin(theta); + return { x: localX * cosT - localY * sinT, y: localX * sinT + localY * cosT }; +} + function _createSlopeCollider(levelObj, objectDef, worldX, worldY) { const slopeData = _SLOPE_DATA[levelObj.id]; if (!slopeData) return null; - const rot90 = Math.round((((levelObj.rot || 0) % 360) + 360) % 360 / 90) % 4; - const steep = rot90 === 1 || rot90 === 3; - const w = (steep ? slopeData.gh : slopeData.gw) * a; - const h = (steep ? slopeData.gw : slopeData.gh) * a; + if (slopeData.sq) return null; + if (_isDecorationSlopeId(levelObj.id)) return null; + const _scaleRaw = Number(levelObj.scale); + const _scale = Number.isFinite(_scaleRaw) && _scaleRaw > 0 ? _scaleRaw : 1; + const hw0 = (slopeData.gw * a * _scale) / 2; + const hh0 = (slopeData.gh * a * _scale) / 2; + let vRight = { x: hw0, y: -hh0 }; + let vLo = { x: -hw0, y: -hh0 }; + let vHi = { x: hw0, y: hh0 }; + const fx = levelObj.flipX ? -1 : 1; + const fy = levelObj.flipY ? -1 : 1; + const flip = pt => ({ x: pt.x * fx, y: pt.y * fy }); + vRight = flip(vRight); vLo = flip(vLo); vHi = flip(vHi); + const rotDeg = levelObj.rot || 0; + vRight = rotateSlopePoint(vRight.x, vRight.y, rotDeg); + vLo = rotateSlopePoint(vLo.x, vLo.y, rotDeg); + vHi = rotateSlopePoint(vHi.x, vHi.y, rotDeg); + const xs = [vRight.x, vLo.x, vHi.x]; + const ys = [vRight.y, vLo.y, vHi.y]; + const w = Math.max(...xs) - Math.min(...xs); + const h = Math.max(...ys) - Math.min(...ys); const collider = new Collider(slopeType, worldX, worldY, w, h, 0); collider.objid = levelObj.id; + collider.hypoAx = vLo.x; collider.hypoAy = vLo.y; + collider.hypoBx = vHi.x; collider.hypoBy = vHi.y; + collider.rightAx = vRight.x; collider.rightAy = vRight.y; + const hypoDx = vHi.x - vLo.x; + collider.slopeSolidBelow = Math.abs(hypoDx) < 0.01 + ? vRight.x < vLo.x + : vRight.y < (vLo.y + ((vRight.x - vLo.x) / hypoDx) * (vHi.y - vLo.y)); + const rot90 = Math.round(((rotDeg % 360) + 360) % 360 / 90) % 4; + const steep = rot90 === 1 || rot90 === 3; collider.slopeAngleDeg = steep ? (90 - slopeData.angle) : slopeData.angle; - const { dir, flipY } = _resolveSlopeOrientation(objectDef, levelObj); + const { dir } = _resolveSlopeOrientation(objectDef, levelObj); collider.slopeDir = dir; - collider.slopeFlipY = flipY; + collider.slopeFlipY = !collider.slopeSolidBelow; collider.slopeIsFilled = slopeData.sq; + collider.slopeType = _gdSlopeType(levelObj.flipX, levelObj.flipY, levelObj.rot); + collider.gdSolidAbove = _gdSlopeSolidAbove(collider.slopeType); + collider.gdWallOnLeft = _gdSlopeWallOnLeft(collider.slopeType); return collider; } diff --git a/assets/scripts/core/loading-screen.js b/assets/scripts/core/loading-screen.js index c28c1e53..6b931f18 100644 --- a/assets/scripts/core/loading-screen.js +++ b/assets/scripts/core/loading-screen.js @@ -187,7 +187,9 @@ class BootScene extends Phaser.Scene { "It is only game...", "Unlock new icons and colors by completing achievements", "y=mx+b", - "Nicest game ever!" + "Nicest game ever!", + "S-slopes!~", + "Anything but slopes" ]; const sliderOriginX = cx - 105; const sliderOriginY = cy + 110; diff --git a/assets/scripts/core/player.js b/assets/scripts/core/player.js index a3b54228..dcec6032 100644 --- a/assets/scripts/core/player.js +++ b/assets/scripts/core/player.js @@ -408,6 +408,12 @@ class PlayerObject { this._prevSlopeObj = null; this._prevSlopeAngle = null; this._slopeEntryX = null; + this._slopeRiding = false; + this._slopeContactFrames = 0; + this._slopeExitVel = null; + this._slopeExitGrace = 0; + this._slopeRidePush = false; + this._skipSlopeExit = false; this.rotateActionActive = false; this.rotateActionTime = 0; this.rotateActionDuration = 0; @@ -2283,8 +2289,8 @@ if (this.p.isFlying || this.p.isUfo) { } this._setGamemodeFlyBounds(false, 0); } - hitGround() { - const _0x4a38a5 = !this.p.onGround; + hitGround(skipBurst = false) { + const _0x4a38a5 = !skipBurst && !this.p.onGround; if (!this.p.isFlying && !this.p.isWave && !this.p.isUfo) { this.p.lastGroundY = this.p.y; } @@ -2913,194 +2919,249 @@ if (this.p.isFlying || this.p.isUfo) { _getSlopeHitSize(playerSize, waveHitSize) { return this.p.isWave ? waveHitSize : playerSize; } - _isOnSlopeSurface(surfaceY, footProbe, headProbe, hitSize, useFloorLanding) { - const tolerance = Math.max(14, hitSize * 0.45); - if (useFloorLanding) { - return Math.abs(footProbe - surfaceY) < tolerance && footProbe >= surfaceY - 2; - } - return Math.abs(headProbe - surfaceY) < tolerance && headProbe <= surfaceY + 2; - } - _landOnSlopeFloor(gameObj, surfaceY, hitSize, footProbe, lastFootProbe, playersY) { - const margin = Math.max(8, Math.abs(footProbe - lastFootProbe) + 4); - if (!this.p.gravityFlipped && - (footProbe >= surfaceY || lastFootProbe >= surfaceY) && - (this.p.yVelocity <= 0 || this.p.onGround) && - playersY <= surfaceY + hitSize + margin) { - return { y: surfaceY + hitSize, onCeiling: false, collideBottom: surfaceY, collideTop: null, slopeAngle: gameObj.getSlopeAngleRad() }; - } - return null; - } - _landOnSlopeCeiling(gameObj, surfaceY, hitSize, headProbe, lastHeadProbe, playersY) { - const margin = Math.max(8, Math.abs(headProbe - lastHeadProbe) + 4); - if ((headProbe <= surfaceY || lastHeadProbe <= surfaceY) && - (this.p.yVelocity >= 0 || this.p.onGround) && - playersY >= surfaceY - hitSize - margin) { - return { y: surfaceY - hitSize, onCeiling: true, collideBottom: null, collideTop: surfaceY, slopeAngle: gameObj.getSlopeAngleRad() }; - } - return null; - } - _landOnSlopeCeilingFlipped(gameObj, surfaceY, hitSize, footProbe, lastFootProbe, playersY) { - const margin = Math.max(8, Math.abs(footProbe - lastFootProbe) + 4); - if (this.p.gravityFlipped && - (footProbe >= surfaceY || lastFootProbe >= surfaceY) && - (this.p.yVelocity <= 0 || this.p.onGround) && - playersY <= surfaceY + hitSize + margin) { - return { y: surfaceY + hitSize, onCeiling: true, collideBottom: null, collideTop: surfaceY, slopeAngle: gameObj.getSlopeAngleRad() }; - } - return null; - } - _trySnapToSlopeSurface(gameObj, pieceWidth, playersY, hitSize, gamemodeAddition, useFloorLanding) { - const surfaceY = gameObj.getSlopeSurfaceY(pieceWidth); - if (surfaceY === null) return null; - const isFlyMode = this.p.isFlying || this.p.isUfo; - const pad = isFlyMode ? Math.max(10, hitSize * 0.4) : 9; - const footProbe = playersY - hitSize + gamemodeAddition; - const headProbe = playersY + hitSize - gamemodeAddition; - if (useFloorLanding) { - if (footProbe < surfaceY - pad) return null; - if (footProbe > surfaceY + pad) return null; - return { y: surfaceY + hitSize, onCeiling: false, collideBottom: surfaceY, collideTop: null, slopeAngle: gameObj.getSlopeAngleRad() }; - } - if (headProbe < surfaceY - pad) return null; - if (headProbe > surfaceY + pad) return null; - return { y: surfaceY - hitSize, onCeiling: true, collideBottom: null, collideTop: surfaceY, slopeAngle: gameObj.getSlopeAngleRad() }; - } - _trySlopeVelocityLanding(gameObj, surfaceY, hitSize, footProbe, lastFootProbe, headProbe, lastHeadProbe, playersY, useFloorLanding) { - if (useFloorLanding) { - if (this.p.gravityFlipped) { - return this._landOnSlopeCeilingFlipped(gameObj, surfaceY, hitSize, footProbe, lastFootProbe, playersY); - } - return this._landOnSlopeFloor(gameObj, surfaceY, hitSize, footProbe, lastFootProbe, playersY); - } - return this._landOnSlopeCeiling(gameObj, surfaceY, hitSize, headProbe, lastHeadProbe, playersY); - } - _handleSlopeCollision(gameObj, pieceWidth, playersY, playersLastY, left, right, top, bottom, playerSize, waveHitSize, gamemodeAddition) { + _slopeRideY(gameObj, surfaceY, angle, playerSize, above) { + return above ? surfaceY + playerSize : surfaceY - playerSize; + } + _slopeReached(pEdge, surfaceY, gamemodeAddition, above) { + if (window.slopeReachGuard === false) return true; + if (this.p.onGround) return true; + return above ? pEdge <= surfaceY + gamemodeAddition + : pEdge >= surfaceY - gamemodeAddition; + } + _slopeBackWallHit(gameObj, pieceWidth, playersY, surfaceY) { + if (window.slopeBackWall === false) return false; + const ty = gameObj.slopeType; + if (ty === null || ty === undefined) return false; + const halfW = (gameObj.w || 0) / 2; + const halfH = (gameObj.h || 0) / 2; + if (!(halfW > 0) || !(halfH > 0)) return false; + if (surfaceY !== null && surfaceY !== undefined) { + const insideSolid = gameObj.slopeSolidBelow + ? (playersY < surfaceY) + : (playersY > surfaceY); + if (!insideSolid) return false; + } + const wallW = 2; + const wallLeft = gameObj.gdWallOnLeft + ? (gameObj.x - halfW) + : (gameObj.x + halfW - wallW); + const wallRight = wallLeft + wallW; + const boxBot = gameObj.y - halfH; + const boxTop = gameObj.y + halfH; + const inner = 9; + return (pieceWidth + inner > wallLeft) && (pieceWidth - inner < wallRight) + && (playersY + inner > boxBot) && (playersY - inner < boxTop); + } + _slopeFlatCapLand(gameObj, pieceWidth, playersY, playersLastY, playerSize, gamemodeAddition) { + if (window.slopeFlatCap === false) return null; + const ty = gameObj.slopeType; + if (ty === null || ty === undefined) return null; + if (this.p.isWave) return null; + const halfW = (gameObj.w || 0) / 2; + const halfH = (gameObj.h || 0) / 2; + if (!(halfW > 0) || !(halfH > 0)) return null; + if (pieceWidth <= gameObj.x - halfW || pieceWidth >= gameObj.x + halfW) return null; + const boxTop = gameObj.y + halfH; + const boxBot = gameObj.y - halfH; + const capAtTop = !!gameObj.gdSolidAbove; + const gFlip = this.p.gravityFlipped; + if (capAtTop === gFlip) return null; + const face = capAtTop ? boxTop : boxBot; + const feet = capAtTop + ? playersY - playerSize + gamemodeAddition + : playersY + playerSize - gamemodeAddition; + const lastFeet = capAtTop + ? playersLastY - playerSize + gamemodeAddition + : playersLastY + playerSize - gamemodeAddition; + const falling = capAtTop ? (this.p.yVelocity <= 0) : (this.p.yVelocity >= 0); + if (!falling && !this.p.onGround) return null; + const crossed = capAtTop + ? (feet <= face && (lastFeet >= face || this.p.onGround)) + : (feet >= face && (lastFeet <= face || this.p.onGround)); + if (!crossed) return null; + if (Math.abs(face - feet) > playerSize) return null; + return { landed: true, died: false, candidate: { + y: capAtTop ? face + playerSize : face - playerSize, + onCeiling: !capAtTop, + collideBottom: capAtTop ? face : null, + collideTop: capAtTop ? null : face, + slopeAngle: 0, blocked: true } }; + } + _handleSlopeCollision(gameObj, pieceWidth, playersY, playersLastY, left, right, top, bottom, playerSize, waveHitSize, gamemodeAddition, previousWorldX) { const surfaceY = gameObj.getSlopeSurfaceY(pieceWidth); if (surfaceY === null) return { landed: false, died: false }; - const hitSize = this._getSlopeHitSize(playerSize, waveHitSize); - const footProbe = playersY - hitSize + gamemodeAddition; - const lastFootProbe = playersLastY - hitSize + gamemodeAddition; - const headProbe = playersY + hitSize - gamemodeAddition; - const lastHeadProbe = playersLastY + hitSize - gamemodeAddition; - const tightPad = this.p.isWave ? 5 : 9; - const inXRange = pieceWidth + hitSize - 5 > left && pieceWidth - hitSize + 5 < right; - const useFloorLanding = gameObj.usesFloorLanding(this.p.gravityFlipped); - const onSlopeSurface = this._isOnSlopeSurface(surfaceY, footProbe, headProbe, hitSize, useFloorLanding); - - let candidate = null; - if (inXRange) { - candidate = this._trySlopeVelocityLanding(gameObj, surfaceY, hitSize, footProbe, lastFootProbe, headProbe, lastHeadProbe, playersY, useFloorLanding); - if (!candidate) { - candidate = this._trySnapToSlopeSurface(gameObj, pieceWidth, playersY, hitSize, gamemodeAddition, useFloorLanding); + const lastSurfaceY = previousWorldX === undefined + ? surfaceY + : (gameObj.getSlopeSurfaceY(previousWorldX) ?? surfaceY); + if (this._slopeBackWallHit(gameObj, pieceWidth, playersY, surfaceY)) { + return { landed: false, died: true, immediate: true, reason: 'slope-backwall' }; + } + const capLand = this._slopeFlatCapLand( + gameObj, pieceWidth, playersY, playersLastY, playerSize, gamemodeAddition); + if (capLand) return capLand; + const angle = gameObj.getSlopeAngleRad(); + const isCeilSlope = !gameObj.slopeSolidBelow; + + if (this.p.isWave) { + const waveHalf = this.p.isMini ? 6 : 9; + const insideSolid = gameObj.slopeSolidBelow + ? (playersY - waveHalf < surfaceY) + : (playersY + waveHalf > surfaceY); + if (insideSolid) return { landed: false, died: true, immediate: true }; + return { landed: false, died: false }; + } + + const pLow = playersY - playerSize + gamemodeAddition; + const pHigh = playersY + playerSize - gamemodeAddition; + const pLastLow = playersLastY - playerSize + gamemodeAddition; + const pLastHigh = playersLastY + playerSize - gamemodeAddition; + const _tanSpeed = playerSpeed * (window.slopeTangentD !== undefined ? window.slopeTangentD : d); + const tangent = Math.tan(angle) * _tanSpeed; + + if (this.p.isFlying && !this.p.isUfo) { + const gFlip = this.p.gravityFlipped; + const actsAsFloor = (!isCeilSlope && !gFlip) || (isCeilSlope && gFlip); + const snapAbove = actsAsFloor !== gFlip; + const stickRest = this.p.onGround && !this.p.upKeyDown; + const stickPush = this.p.onGround && this.p.upKeyDown; + if (snapAbove) { + const crossedDown = pLastLow >= surfaceY - gamemodeAddition && pLow < surfaceY; + if ((this.p.yVelocity <= 0 || (gFlip ? stickPush : stickRest) || crossedDown) && + pLow >= surfaceY - playerSize && pLow <= surfaceY + gamemodeAddition) { + if (this._slopeRiding && this._slopeExitVel > 0 && tangent < 0) { + return { landed: false, died: false }; + } + return { landed: true, died: false, candidate: { + y: this._slopeRideY(gameObj, surfaceY, angle, playerSize, true), + onCeiling: false, collideBottom: surfaceY, collideTop: null, + slopeAngle: angle, tangent: tangent, ride: true, ridePush: gFlip } }; + } + return { landed: false, died: false }; } - if (!candidate && onSlopeSurface) { - if (useFloorLanding) { - candidate = { - y: surfaceY + hitSize, - onCeiling: this.p.gravityFlipped, - collideBottom: this.p.gravityFlipped ? null : surfaceY, - collideTop: this.p.gravityFlipped ? surfaceY : null, - slopeAngle: gameObj.getSlopeAngleRad() - }; - } else { - candidate = { - y: surfaceY - hitSize, - onCeiling: true, - collideBottom: null, - collideTop: surfaceY, - slopeAngle: gameObj.getSlopeAngleRad() - }; + const crossedUp = pLastHigh <= surfaceY + gamemodeAddition && pHigh > surfaceY; + if ((this.p.yVelocity >= 0 || (gFlip ? stickRest : stickPush) || crossedUp) && + pHigh >= surfaceY - playerSize * 1.5 && pHigh <= surfaceY + playerSize) { + if (this._slopeRiding && this._slopeExitVel < 0 && tangent > 0) { + return { landed: false, died: false }; } + return { landed: true, died: false, candidate: { + y: this._slopeRideY(gameObj, surfaceY, angle, playerSize, false), + onCeiling: true, collideBottom: null, collideTop: surfaceY, + slopeAngle: angle, tangent: tangent, ride: true, ridePush: !gFlip } }; } + return { landed: false, died: false }; } - if (candidate) { - candidate.gameObj = gameObj; - return { landed: true, died: false, candidate }; - } + const rideable = isCeilSlope === this.p.gravityFlipped; - const overlapping = pieceWidth + tightPad > left && pieceWidth - tightPad < right && - playersY + tightPad > top && playersY - tightPad < bottom; - if (this.p.isWave && overlapping) { - return { landed: false, died: true }; - } - if (overlapping && !onSlopeSurface) { - if (!gameObj.slopeIsFilled && gameObj.isSlopeSolidAt(pieceWidth, playersY, this.p.gravityFlipped)) { - if (window.noClip) this.p.diedThisFrame = true; - else return { landed: false, died: true }; - } else { - const backSide = gameObj.getSlopeBackWallSide(this.p.gravityFlipped); - const wallX = backSide === "left" ? left : right; - const wallSurface = gameObj.getSlopeSurfaceY(wallX); - let hitBackWall = false; - if (useFloorLanding) { - if (backSide === "left") { - hitBackWall = pieceWidth < wallX + tightPad && pieceWidth + hitSize > wallX && - footProbe < wallSurface - 2 && headProbe > top + 2; - } else { - hitBackWall = pieceWidth > wallX - tightPad && pieceWidth - hitSize < wallX && - footProbe < wallSurface - 2 && headProbe > top + 2; - } - } else if (backSide === "left") { - hitBackWall = pieceWidth < wallX + tightPad && pieceWidth + hitSize > wallX && - headProbe > wallSurface + 2 && footProbe < bottom - 2; - } else { - hitBackWall = pieceWidth > wallX - tightPad && pieceWidth - hitSize < wallX && - headProbe > wallSurface + 2 && footProbe < bottom - 2; + if (!isCeilSlope) { + if (!rideable) { + if (pLow < surfaceY && pLow >= surfaceY - playerSize) { + return { landed: true, died: false, candidate: { + y: this._slopeRideY(gameObj, surfaceY, angle, playerSize, true), + onCeiling: false, collideBottom: surfaceY, collideTop: null, + slopeAngle: angle, blocked: true } }; } - if (hitBackWall || gameObj.slopeIsFilled) { - if (window.noClip) this.p.diedThisFrame = true; - else return { landed: false, died: true }; + if (pLow < surfaceY - playerSize && pLastLow < surfaceY - playerSize) { + return { landed: false, died: true }; } + return { landed: false, died: false }; + } + if ((pLastLow >= lastSurfaceY || this.p.onGround) && (this.p.yVelocity <= 0 || this.p.onGround)) { + if (pLow >= surfaceY - playerSize && this._slopeReached(pLow, surfaceY, gamemodeAddition, true)) { + if (this._slopeRiding && this._slopeExitVel > 0 && tangent < 0) { + return { landed: false, died: false }; + } + return { landed: true, died: false, candidate: { + y: this._slopeRideY(gameObj, surfaceY, angle, playerSize, true), + onCeiling: false, collideBottom: surfaceY, collideTop: null, + slopeAngle: angle, tangent: tangent, ride: true } }; + } + return { landed: false, died: false }; + } + if (pLow < surfaceY - playerSize && pLastLow < surfaceY - playerSize) { + return { landed: false, died: true }; } + return { landed: false, died: false }; } + if (!rideable) { + if (pHigh > surfaceY && pHigh <= surfaceY + playerSize) { + return { landed: true, died: false, candidate: { + y: this._slopeRideY(gameObj, surfaceY, angle, playerSize, false), + onCeiling: true, collideBottom: null, collideTop: surfaceY, + slopeAngle: angle, blocked: true } }; + } + if (pHigh > surfaceY + playerSize && pLastHigh > surfaceY + playerSize) { + return { landed: false, died: true }; + } + return { landed: false, died: false }; + } + if ((pLastHigh <= lastSurfaceY || this.p.onGround) && (this.p.yVelocity >= 0 || this.p.onGround)) { + if (pHigh <= surfaceY + playerSize && this._slopeReached(pHigh, surfaceY, gamemodeAddition, false)) { + if (this._slopeRiding && this._slopeExitVel < 0 && tangent > 0) { + return { landed: false, died: false }; + } + return { landed: true, died: false, candidate: { + y: this._slopeRideY(gameObj, surfaceY, angle, playerSize, false), + onCeiling: true, collideBottom: null, collideTop: surfaceY, + slopeAngle: angle, tangent: tangent, ride: true } }; + } + return { landed: false, died: false }; + } + if (pHigh > surfaceY + playerSize && pLastHigh > surfaceY + playerSize) { + return { landed: false, died: true }; + } return { landed: false, died: false }; } - static SLOPE_BOUNCE_SCALE = { - 291: 1, - }; - _applySlopeExitBounce(slopeObj, slopeAngle, pieceWidth) { - if (!slopeObj || slopeAngle === null || slopeAngle === undefined) return; - if (this.p.isJumping && !this.p.onGround) return; - const angleRad = Math.abs(slopeAngle); - if (angleRad < 0.05) return; - const halfW = slopeObj.w / 2; - const left = slopeObj.x - halfW; - const right = slopeObj.x + halfW; - const exitMargin = 14; - let exitHighEnd = slopeObj.slopeDir > 0 - ? pieceWidth >= right - exitMargin - : pieceWidth <= left + exitMargin; - if (slopeObj.slopeFlipY) exitHighEnd = !exitHighEnd; - if (!exitHighEnd) return; - let highEndIsRight = slopeObj.slopeDir > 0; - if (slopeObj.slopeFlipY) highEndIsRight = !highEndIsRight; - const highEndX = highEndIsRight ? right : left; - const lowEndX = highEndIsRight ? left : right; - const slopeSpan = Math.abs(right - left) || 1; - const entryX = Number.isFinite(this._slopeEntryX) ? this._slopeEntryX : lowEndX; - const traveledFraction = Math.min(1, Math.max(0, Math.abs(highEndX - entryX) / slopeSpan)); - let bounceVel = Math.tan(angleRad) * playerSpeed * this.flipMod(); - const idScale = PlayerObject.SLOPE_BOUNCE_SCALE[slopeObj.objid]; - if (idScale !== undefined) bounceVel *= idScale; - bounceVel *= traveledFraction; - if (slopeObj.slopeFlipY) bounceVel = -bounceVel; - if (this.p.isWave) bounceVel *= 0.85; - else if (this.p.isMini) bounceVel *= 0.8; - if (Math.abs(bounceVel) < 0.5) return; - this.p.yVelocity = bounceVel; + _slopeLaunchMultiplier(angleRad) { + const num = window.slopeLaunchNum !== undefined ? window.slopeLaunchNum : 0.8; + const cap = window.slopeLaunchCap !== undefined ? window.slopeLaunchCap : 1.1; + const gain = window.slopeLaunchGain !== undefined ? window.slopeLaunchGain : 1.4; + const acute = Math.atan(Math.abs(Math.tan(angleRad || 0))); + if (!(acute > 1e-6)) return cap * gain; + return Math.min(num / acute, cap) * gain; + } + _slopeExitScale() { + const rampTime = window.slopeRampTime !== undefined ? window.slopeRampTime : 0.1; + const rampFloor = window.slopeRampFloor !== undefined ? window.slopeRampFloor : 0.4; + const held = (this._slopeContactFrames || 0) / 240; + const ramp = held < rampTime ? Math.max(held / rampTime, rampFloor) : 1; + const modeScale = (this.p.isFlying || this.p.isUfo || this.p.isWave + || this.p.isSwing || this.p.isBall) ? 0.75 : 1; + return this._slopeLaunchMultiplier(this._prevSlopeAngle) * modeScale * ramp; + } + _applySlopeExitVelocity() { + if (this._slopeExitVel === null) return; + if (this._skipSlopeExit) return; + if (this.p.isFlying && !this.p.isUfo) { + if (!this.p.upKeyDown || this._slopeRidePush) { + const shipCap = window.slopeShipCap !== undefined ? window.slopeShipCap : 16; + const shipVel = this._slopeExitVel * this._slopeExitScale(); + const shipDir = shipVel >= 0 ? 1 : -1; + if (shipVel * shipDir <= this.p.yVelocity * shipDir) return; + this.p.yVelocity = Math.max(-shipCap, Math.min(shipCap, Math.round(shipVel * 1000) / 1000)); + } + return; + } + if (this.p.isJumping) return; + const exitScale = this._slopeExitScale(); + const exitCap = this.p.isSwing + ? (window.slopeSwingCap !== undefined ? window.slopeSwingCap : 13) + : (window.slopeExitCap !== undefined ? window.slopeExitCap : 30); + const exitVel = this._slopeExitVel * exitScale; + const dir = exitVel >= 0 ? 1 : -1; + if (exitVel * dir <= this.p.yVelocity * dir) return; + this.p.yVelocity = Math.max(-exitCap, Math.min(exitCap, Math.round(exitVel * 1000) / 1000)); this.p.onGround = false; this.p.canJump = false; - if (this.p.isFlying) { - this.p._slopeBounceActive = true; - } - if (!this.p.isFlying && !this.p.isWave && !this.p.isUfo && !this.p.isSpider && !this.p.isRobot) { - this.p.isJumping = true; - this._rotation = slopeAngle; - this.stopRotation(); + const rising = this.p.gravityFlipped ? this.p.yVelocity < 0 : this.p.yVelocity > 0; + const launchedClear = Math.abs(this.p.yVelocity) > p * 2; + if (rising && launchedClear && !this.p.isWave && !this.p.isUfo && !this.p.isSpider && !this.p.isRobot && !this.rotateActionActive) { + this.runRotateAction(); } } + _checkSnapJump(_0x1f801b) { const _0x483058 = [{ dx: 240, @@ -3281,6 +3342,29 @@ if (this.p.isFlying || this.p.isUfo) { return this.p.yVelocity < p; } } + _gravBaseForSpeed() { + if (window.speedGravity === false) return p; + if (typeof SpeedPortal === 'undefined' || typeof playerSpeed !== 'number') return p; + const sp = playerSpeed; + if (sp <= (SpeedPortal.HALF + SpeedPortal.ONE_TIMES) / 2) return 1.880398; + if (sp <= (SpeedPortal.ONE_TIMES + SpeedPortal.TWO_TIMES) / 2) return p; + if (sp <= (SpeedPortal.TWO_TIMES + SpeedPortal.THREE_TIMES) / 2) return 1.914398; + return 1.922398; + } + _applySlopeJumpBoost() { + if (window.slopeJumpBoost === false) return; + if (this._slopeExitVel === null || this._slopeExitVel === undefined) return; + const base = this.p.yVelocity; + if (!base) return; + const dir = base >= 0 ? 1 : -1; + const contact = this._slopeExitVel * this._slopeExitScale(); + if (contact * dir <= 0) return; + const cap = base * 1.4; + let v = base + contact * 0.25; + if (dir > 0) { if (v > cap) v = cap; } + else { if (v < cap) v = cap; } + this.p.yVelocity = v; + } updateJump(_0x3d1c6f) { if (this.p.pendingVelocity !== null) { this.p.yVelocity = this.p.pendingVelocity; @@ -3314,9 +3398,10 @@ if (this.p.isFlying || this.p.isUfo) { this.p.upKeyPressed = false; this.p.queuedHold = false; this.p.yVelocity = this.flipMod() * 22.360064 * (this.p.isMini ? 0.8 : 1); + this._applySlopeJumpBoost(); this.runRotateAction(); } else if (this.p.isJumping) { - this.p.yVelocity -= p * _0x3d1c6f * this.flipMod(); + this.p.yVelocity -= this._gravBaseForSpeed() * _0x3d1c6f * this.flipMod(); if (this.playerIsFalling()) { this.p.isJumping = false; this.p.onGround = false; @@ -3325,7 +3410,7 @@ if (this.p.isFlying || this.p.isUfo) { if (this.playerIsFalling()) { this.p.canJump = false; } - this.p.yVelocity -= p * _0x3d1c6f * this.flipMod(); + this.p.yVelocity -= this._gravBaseForSpeed() * _0x3d1c6f * this.flipMod(); if (this.p.gravityFlipped) { this.p.yVelocity = Math.min(this.p.yVelocity, 30); } else { @@ -3444,9 +3529,10 @@ if (this.p.isFlying || this.p.isUfo) { this.p.onGround = false; this.p.canJump = false; this.p.yVelocity *= 0.6; + this._skipSlopeExit = true; return; } - + if (this.playerIsFalling()) { this.p.canJump = false; } @@ -3503,8 +3589,8 @@ if (this.p.isFlying || this.p.isUfo) { const robotJumpInit = 10.25 * robotMiniJumpScale; const robotHoldMax = 15.5; const robotHoldForce = p * 0.2 * robotMiniJumpScale; - const robotGravityHold = p * 0.15; - const robotGravityFall = p * 0.81; + const robotGravityHold = this._gravBaseForSpeed() * (window.robotGravHold !== undefined ? window.robotGravHold : 0.15); + const robotGravityFall = this._gravBaseForSpeed() * (window.robotGravMult !== undefined ? window.robotGravMult : 0.9); const robotReleaseCut = 0.9; const robotReleaseMinTime = 0; const robotMaxFall = 28; @@ -3517,6 +3603,7 @@ if (this.p.isFlying || this.p.isUfo) { this.p.canJump = false; this.p.queuedHold = false; this.p.yVelocity = this.flipMod() * robotJumpInit; + this._applySlopeJumpBoost(); this.p._robotHold = true; this.p._robotHoldTimer = 0; this._robotJumpFlameActive = true; @@ -3574,9 +3661,12 @@ if (this.p.isFlying || this.p.isUfo) { } _updateUfoJump(_dt) { const _ufoJump = this.p.isMini ? 13.296 : 13.742; + const _ufoMini = window.ufoMiniDiv !== undefined ? window.ufoMiniDiv : 0.85; + const _ufoFastBase = window.ufoFastGrav !== undefined ? window.ufoFastGrav : 0.6; + const _ufoSlowBase = window.ufoSlowGrav !== undefined ? window.ufoSlowGrav : 0.5; const _ufoThreshold = 3.832796; - const _ufoFastGrav = this.p.isMini ? 0.634524 : 0.540121; - const _ufoSlowGrav = this.p.isMini ? 0.421624 : 0.359973; + const _ufoFastGrav = this.p.isMini ? _ufoFastBase / _ufoMini : _ufoFastBase; + const _ufoSlowGrav = this.p.isMini ? _ufoSlowBase / _ufoMini : _ufoSlowBase; const _ufoUpVel = this.p.yVelocity * this.flipMod(); const _ufoGrav = _ufoUpVel > _ufoThreshold ? _ufoFastGrav : _ufoSlowGrav; this.p.yVelocity -= p * _ufoGrav * _dt * this.flipMod(); @@ -3968,6 +4058,10 @@ if (this.p.isFlying || this.p.isUfo) { let bestSlopeCandidate = null; let bestSlopeGameObj = null; const preferHighestSlopeSurface = !this.p.gravityFlipped; + let _slopeKillPending = false; + let _snappedSlopeThisFrame = false; + let _slopeTangentThisFrame = 0; + let _slopeBlockedThisStep = false; const _0x198534 = this._gameLayer.getNearbySectionObjects(pieceWidth); for (let gameObj of _0x198534) { let left = gameObj.x - gameObj.w / 2; @@ -4522,11 +4616,19 @@ if (this.p.isFlying || this.p.isUfo) { return; } else if (_colType === slopeType) { const slopeResult = this._handleSlopeCollision( - gameObj, pieceWidth, playersY, playersLastY, left, right, top, bottom, playerSize, waveHitSize, gamemodeAddition + gameObj, pieceWidth, playersY, playersLastY, left, right, top, bottom, playerSize, waveHitSize, gamemodeAddition, previousWorldX ); if (slopeResult.died) { - this.killPlayer(); - return; + if (slopeResult.immediate) { + if (window.noClip) { + this.p.diedThisFrame = true; + continue; + } + this.killPlayer(); + return; + } + _slopeKillPending = true; + continue; } if (slopeResult.landed && slopeResult.candidate) { const cand = slopeResult.candidate; @@ -4562,7 +4664,10 @@ if (this.p.isFlying || this.p.isUfo) { const _0xLandBot = (this.p.yVelocity <= 0 || this.p.onGround) && (_0x146a97 >= bottom || _0x869e42 >= bottom); const _0xLandTop = (this.p.yVelocity >= 0 || this.p.onGround) && (_0x3e7199 <= top || _0x135a9d <= top); const isstandingOnAPlatform = this.p.gravityFlipped ? _0xLandTop : _0xLandBot; - if (iscolliding && !isstandingOnAPlatform) { + const _slopeLeadIn = this._slopeRiding && (this.p.gravityFlipped + ? _0x3e7199 <= top + playerSize + : _0x146a97 >= bottom - playerSize); + if (iscolliding && !isstandingOnAPlatform && !_slopeLeadIn) { if (window.noClip) { continue; @@ -4573,8 +4678,17 @@ if (this.p.isFlying || this.p.isUfo) { this.killPlayer(); return; } + const _sDist = Math.abs(playersY - playersLastY); + const _normalLandBot = _0x146a97 >= bottom && _0x146a97 - bottom <= _sDist + gamemodeAddition; + const _coyoteBot = this.p.onGround && !this._slopeRiding && this._slopeExitGrace === 0 && _0x146a97 >= bottom && _0x146a97 - bottom < playerSize + gamemodeAddition; + const _skipBot = _0x146a97 < bottom && _0x869e42 >= bottom; + const _landBot = _normalLandBot || _coyoteBot || _skipBot; + const _normalLandTop = _0x3e7199 <= top && top - _0x3e7199 <= _sDist + gamemodeAddition; + const _coyoteTop = this.p.onGround && !this._slopeRiding && this._slopeExitGrace === 0 && _0x3e7199 <= top && top - _0x3e7199 < playerSize + gamemodeAddition; + const _skipTop = _0x3e7199 > top && _0x135a9d <= top; + const _landTop = _normalLandTop || _coyoteTop || _skipTop; if (pieceWidth + playerSize - 5 > left && pieceWidth - playerSize + 5 < right) { - if (!this.p.gravityFlipped && (_0x146a97 >= bottom || _0x869e42 >= bottom) && (this.p.yVelocity <= 0 || this.p.onGround)) { + if (!this.p.gravityFlipped && _landBot && (this.p.yVelocity <= 0 || this.p.onGround)) { this.p.y = bottom + playerSize; this.hitGround(); _0x30410f = true; @@ -4584,7 +4698,7 @@ if (this.p.isFlying || this.p.isUfo) { } continue; } - if (this.p.gravityFlipped && !this.p.isFlying && (_0x3e7199 <= top || _0x135a9d <= top) && (this.p.yVelocity >= 0 || this.p.onGround)) { + if (this.p.gravityFlipped && !this.p.isFlying && _landTop && (this.p.yVelocity >= 0 || this.p.onGround)) { this.p.y = top - playerSize; this.hitGround(); _0x30410f = true; @@ -4596,14 +4710,14 @@ if (this.p.isFlying || this.p.isUfo) { continue; } if (this.p.isUfo) { - if (!this.p.gravityFlipped && (_0x3e7199 <= top || _0x135a9d <= top) && (this.p.yVelocity >= 0 || this.p.onGround)) { + if (!this.p.gravityFlipped && _landTop && (this.p.yVelocity >= 0 || this.p.onGround)) { this.p.y = top - playerSize; this.hitGround(); this.p.onCeiling = true; this.p.collideTop = top; continue; } - if (this.p.gravityFlipped && (_0x146a97 >= bottom || _0x869e42 >= bottom) && (this.p.yVelocity <= 0 || this.p.onGround)) { + if (this.p.gravityFlipped && _landBot && (this.p.yVelocity <= 0 || this.p.onGround)) { this.p.y = bottom + playerSize; this.hitGround(); _0x30410f = true; @@ -4613,7 +4727,7 @@ if (this.p.isFlying || this.p.isUfo) { } continue; } - if ((_0x3e7199 <= top || _0x135a9d <= top) && (this.p.yVelocity >= 0 || this.p.onGround) && this.p.isFlying) { + if (_landTop && (this.p.yVelocity >= 0 || this.p.onGround) && this.p.isFlying) { this.p.y = top - playerSize; this.hitGround(); this.p.onCeiling = true; @@ -4630,7 +4744,7 @@ if (this.p.isFlying || this.p.isUfo) { } continue; } - if (this.p.gravityFlipped && (_0x146a97 >= bottom || _0x869e42 >= bottom) && (this.p.yVelocity <= 0 || this.p.onGround) && this.p.isFlying) { + if (this.p.gravityFlipped && _landBot && (this.p.yVelocity <= 0 || this.p.onGround) && this.p.isFlying) { this.p.y = bottom + playerSize; this.hitGround(); _0x30410f = true; @@ -4642,9 +4756,14 @@ if (this.p.isFlying || this.p.isUfo) { } } } + if (bestSlopeCandidate && _boostedThisStep && window.slopeBoostSkipSnap !== false) { + this._skipSlopeExit = true; + bestSlopeCandidate = null; + } if (bestSlopeCandidate) { + const _wasJumpingSlope = this.p.isJumping; this.p.y = bestSlopeCandidate.y; - this.hitGround(); + this.hitGround(true); this.p.onCeiling = !!bestSlopeCandidate.onCeiling; if (bestSlopeCandidate.collideBottom !== null) this.p.collideBottom = bestSlopeCandidate.collideBottom; if (bestSlopeCandidate.collideTop !== null) this.p.collideTop = bestSlopeCandidate.collideTop; @@ -4655,10 +4774,27 @@ if (this.p.isFlying || this.p.isUfo) { this._activeSlopeObj = bestSlopeGameObj; this._activeSlopeAngle = bestSlopeCandidate.slopeAngle; _0x30410f = true; + if (bestSlopeCandidate.blocked) { + _slopeBlockedThisStep = true; + } else if (!_wasJumpingSlope) { + _snappedSlopeThisFrame = true; + _slopeTangentThisFrame = bestSlopeCandidate.tangent; + if (bestSlopeCandidate.ridePush !== undefined) { + this._slopeRidePush = bestSlopeCandidate.ridePush; + } + } if (!this.p.isFlying && !this.p.isWave) { this._checkSnapJump(bestSlopeGameObj); } } + if (_slopeKillPending && !bestSlopeCandidate && !_0x30410f) { + if (window.noClip) { + this.p.diedThisFrame = true; + } else { + this.killPlayer(); + return; + } + } if (this.p.collideTop !== 0 && this.p.collideBottom !== 0) { if (Math.abs(this.p.collideTop - this.p.collideBottom) < 48) { if (window.noClip) this.p.diedThisFrame = true; @@ -4735,25 +4871,21 @@ if (this.p.isFlying || this.p.isUfo) { if (this._ignoreTeleportUntilClear && !_touchingTeleportDuringRespawnIgnore) { this._ignoreTeleportUntilClear = false; } - if (bestSlopeCandidate) { - this.p.y = bestSlopeCandidate.y; - this.hitGround(); - this.p.onCeiling = !!bestSlopeCandidate.onCeiling; - if (bestSlopeCandidate.collideBottom !== null) this.p.collideBottom = bestSlopeCandidate.collideBottom; - if (bestSlopeCandidate.collideTop !== null) this.p.collideTop = bestSlopeCandidate.collideTop; - this._onSlopeAngle = bestSlopeCandidate.slopeAngle; - if (this._prevSlopeObj !== bestSlopeGameObj) { - this._slopeEntryX = pieceWidth; - } - this._activeSlopeObj = bestSlopeGameObj; - this._activeSlopeAngle = bestSlopeCandidate.slopeAngle; - _0x30410f = true; - if (!this.p.isFlying && !this.p.isWave) { - this._checkSnapJump(bestSlopeGameObj); - } - } - if (this._prevSlopeObj && !this._activeSlopeObj) { - this._applySlopeExitBounce(this._prevSlopeObj, this._prevSlopeAngle, pieceWidth); + if (_snappedSlopeThisFrame) { + this._slopeContactFrames = this._slopeRiding ? (this._slopeContactFrames || 0) + 1 : 0; + this._slopeRiding = true; + this._slopeExitVel = _slopeTangentThisFrame; + this._slopeExitGrace = 20; + } else if (this._slopeRiding) { + this._slopeRiding = false; + if (!_slopeBlockedThisStep) { + this._applySlopeExitVelocity(); + } + this._slopeExitVel = null; + } + this._skipSlopeExit = false; + if (this._slopeExitGrace > 0 && !this._slopeRiding) { + this._slopeExitGrace--; } if (this.p.isFlying || this.p.isWave || this.p.isUfo || this.p.isSpider) { const _0x354b7c = this.p.y <= _0x3020c8 + _effectiveSize; diff --git a/index.html b/index.html index b099db67..8d033615 100644 --- a/index.html +++ b/index.html @@ -45,7 +45,7 @@ - +