Fix gravity reset and add spawn cap for swirl arena

This commit is contained in:
Daddy32
2025-12-13 23:21:27 +01:00
parent 91ed6c6202
commit ea2446e36b
5 changed files with 186 additions and 13 deletions

View File

@@ -64,6 +64,8 @@
const BALL_BASELINE = 680; // reference height used for relative ball sizing
const engine = Engine.create();
const defaultGravityScale = engine.gravity.scale;
const defaultTimeScale = engine.timing.timeScale || 1;
engine.gravity.y = config.gravity;
const world = engine.world;
@@ -125,6 +127,7 @@
const balls = [];
const blobConstraints = new Map();
let spawnTimer = null;
let spawnCount = 0;
let score = 0;
let highScore = 0;
let clearedCount = 0;
@@ -165,7 +168,16 @@
const prevRadius = config.ballRadius;
Object.assign(config, next.config);
config.link = { ...next.config.link };
engine.gravity.scale =
typeof next.config.gravityScale === "number"
? next.config.gravityScale
: defaultGravityScale;
engine.timing.timeScale =
typeof next.config.timeScale === "number"
? next.config.timeScale
: defaultTimeScale;
updateBallRadius(prevRadius);
engine.gravity.x = 0;
engine.gravity.y = config.gravity;
clearedCount = 0;
levelWon = false;
@@ -221,16 +233,39 @@
const spawnBall = () => {
if (gameOver) return;
const spawnLimit = currentScene?.config?.spawnLimit;
if (Number.isFinite(spawnLimit) && spawnCount >= spawnLimit) {
stopSpawner();
return;
}
const color =
config.palette[Math.floor(Math.random() * config.palette.length)];
const x = Math.max(
config.ballRadius + 10,
Math.min(width - config.ballRadius - 10, Math.random() * width),
);
const spawnOrigin = currentScene?.config?.spawnOrigin || "edge";
const spawnJitter =
currentScene?.config?.spawnJitter ?? config.ballRadius * 3;
const centerSpawn =
spawnOrigin === "center"
? {
x:
width / 2 +
(Math.random() - 0.5) * Math.max(spawnJitter, config.ballRadius),
y:
height / 2 +
(Math.random() - 0.5) * Math.max(spawnJitter, config.ballRadius),
}
: null;
const x =
centerSpawn?.x ??
Math.max(
config.ballRadius + 10,
Math.min(width - config.ballRadius - 10, Math.random() * width),
);
const spawnFromBottom = currentScene?.config?.spawnFrom === "bottom";
const y = spawnFromBottom
? height + config.ballRadius * 2
: -config.ballRadius * 2;
const y =
centerSpawn?.y ??
(spawnFromBottom
? height + config.ballRadius * 2
: -config.ballRadius * 2);
const batchMin = currentScene?.config?.spawnBatchMin ?? 1;
const batchMax = currentScene?.config?.spawnBatchMax ?? 1;
const batchCount =
@@ -276,6 +311,7 @@
World.add(world, blob.constraints);
}
}
spawnCount += batchCount;
};
const startSpawner = () => {
@@ -292,6 +328,14 @@
}
};
const spawnInitialBurst = () => {
const initialCount = currentScene?.config?.initialSpawnCount || 0;
if (!initialCount || initialCount <= 0) return;
for (let i = 0; i < initialCount; i += 1) {
spawnBall();
}
};
const cleanupBall = (ball) => {
if (ball.plugin && ball.plugin.entryCheckId) {
clearTimeout(ball.plugin.entryCheckId);
@@ -371,6 +415,7 @@
gameOver = false;
isPaused = false;
levelWon = false;
spawnCount = 0;
score = 0;
clearedCount = 0;
clearedByColor = {};
@@ -393,6 +438,7 @@
ui.hideGameOver();
ui.hideWin();
ui.setPauseState(false);
engine.gravity.x = 0;
engine.gravity.y = config.gravity;
engine.timing.timeScale = 1;
startRunner();
@@ -400,6 +446,7 @@
if (isGridScene()) {
spawnGridBalls();
} else {
spawnInitialBurst();
startSpawner();
}
};
@@ -534,7 +581,20 @@
const finishChain = (releasePoint) => {
if (!chain.active || gameOver || isPaused) return;
if (chain.bodies.length >= config.minChain) {
const gain = 10 * Math.pow(chain.bodies.length, 2);
const baseGain = 10 * Math.pow(chain.bodies.length, 2);
const negativeColors = (
currentScene?.config?.negativeScoreColors || []
).map(normalizeColor);
const negativeProgressColors = (
currentScene?.config?.negativeProgressColors || []
).map(normalizeColor);
const isNegative =
chain.color &&
negativeColors.includes(normalizeColor(chain.color || ""));
const isNegativeProgress =
chain.color &&
negativeProgressColors.includes(normalizeColor(chain.color || ""));
const gain = isNegative ? -baseGain : baseGain;
score += gain;
clearedCount += chain.bodies.length;
if (score > highScore) {
@@ -576,6 +636,16 @@
balls.splice(i, 1);
}
}
if (isNegativeProgress) {
const winCond = currentScene?.config?.winCondition;
if (winCond?.type === "colorClear" && Array.isArray(winCond.targets)) {
winCond.targets.forEach((target) => {
const key = normalizeColor(target.color);
const current = clearedByColor[key] || 0;
clearedByColor[key] = Math.max(0, current - chain.bodies.length);
});
}
}
} else {
chain.constraints.forEach((c) => World.remove(world, c));
chain.bodies.forEach((b) => setHighlight(b, false));
@@ -886,10 +956,11 @@
const totalTarget = targets.reduce((sum, t) => sum + t.count, 0);
let totalAchieved = 0;
const parts = targets.map((t) => {
const got = Math.min(
t.count,
const achieved = Math.max(
0,
clearedByColor[normalizeColor(t.color)] || 0,
);
const got = Math.min(t.count, achieved);
totalAchieved += got;
const remaining = Math.max(0, t.count - got);
return `${got}/${t.count} (${remaining} left)`;
@@ -972,8 +1043,16 @@
ball.plugin.hasEntered = true;
const spawnFromBottom = currentScene?.config?.spawnFrom === "bottom";
Matter.Body.setPosition(ball, {
x: Math.random() * width,
y: spawnFromBottom ? height + 40 : -40,
x:
currentScene?.config?.spawnOrigin === "center"
? width / 2
: Math.random() * width,
y:
currentScene?.config?.spawnOrigin === "center"
? height / 2
: spawnFromBottom
? height + 40
: -40,
});
Matter.Body.setVelocity(ball, { x: 0, y: 0 });
}
@@ -982,6 +1061,9 @@
Events.on(engine, "beforeUpdate", () => {
// Rope-like constraint handling: allow shortening without push-back, tension when stretched.
if (typeof currentScene?.config?.onBeforeUpdate === "function") {
currentScene.config.onBeforeUpdate({ engine, width, height });
}
chain.constraints.forEach((c) => {
if (!c.plugin || !c.plugin.rope) return;
const current = Vector.magnitude(