diff --git a/Readme.md b/Readme.md
index ef35c66..76191f2 100644
--- a/Readme.md
+++ b/Readme.md
@@ -24,6 +24,7 @@ There's also an easy mode for kids (slower ball).
# Next
+- stop scrolling back to top in menu
- render next level behind upgrade picker again
- sturdy bricks: map of remaining hits
@@ -76,7 +77,7 @@ There's also an easy mode for kids (slower ball).
- the white outline on bricks associated with picky eater kinda works but i feel it's more distracting than anything. maybe try something different ? put a cross on matching coloured bricks, or the contrary, grey out other bricks.
# New perks ideas
-
+- bricks are invisible, but ..
- second puck (symmetric to the first one)
- offer next level choice after upgrade pick
- ban 3 random perks from pool, doesn't tell you which ones, gain 2 upgrades
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index e05b412..fc71482 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -11,8 +11,8 @@ android {
applicationId = "me.lecaro.breakout"
minSdk = 21
targetSdk = 34
- versionCode = 29040298
- versionName = "29040298"
+ versionCode = 29041544
+ versionName = "29041544"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html
index e26198d..a0e280d 100644
--- a/app/src/main/assets/index.html
+++ b/app/src/main/assets/index.html
@@ -1 +1 @@
-
Breakout 71
\ No newline at end of file
+Breakout 71
\ No newline at end of file
diff --git a/dist/index.html b/dist/index.html
index 04375df..4e1a17d 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -1,6 +1,6 @@
-
+
@@ -31,7 +31,7 @@ body {
height: 100vh;
height: calc(var(--vh, 1vh) * 100);
width: 100vw;
- position: absolute;
+ position: fixed;
top: 0;
left: 0;
}
@@ -72,16 +72,31 @@ body {
left: 0;
}
-.popup {
- z-index: 10;
- background: #000000e6;
+body.has-alert-open {
+ height: auto;
+ overflow: visible;
+}
+
+body:not(.has-alert-open) #popup {
+ display: none;
+}
+
+#popup {
display: flex;
- position: fixed;
- inset: 0;
overflow: auto;
}
-.popup > div {
+#popup:before {
+ z-index: 10;
+ content: "";
+ background: #000000e6;
+ display: block;
+ position: fixed;
+ inset: 0;
+}
+
+#popup > div {
+ z-index: 11;
transform-origin: center;
flex-direction: column;
align-items: stretch;
@@ -90,25 +105,26 @@ body {
margin: auto;
padding: 20px 10px;
display: flex;
+ position: relative;
}
-.popup > div > * {
+#popup > div > * {
margin: 0;
padding: 0;
}
-.popup > div > h2, .popup > div > p {
+#popup > div > h2, #popup > div > p {
margin-bottom: 20px;
}
-.popup > div > section {
+#popup > div > section {
flex-direction: column;
align-items: stretch;
margin-top: 20px;
display: flex;
}
-.popup > div > section button {
+#popup > div > section button {
font: inherit;
color: #fff;
cursor: pointer;
@@ -121,47 +137,48 @@ body {
display: flex;
}
-.popup > div > section button:not([disabled]):hover, .popup > div > section button:not([disabled]):focus {
+#popup > div > section button:not([disabled]):hover, #popup > div > section button:not([disabled]):focus {
z-index: 1;
border-color: #f1d33b;
position: relative;
}
-.popup > div > section button[disabled] {
+#popup > div > section button[disabled] {
opacity: .5;
filter: saturate(0);
pointer-events: none;
}
-.popup > div > section button > div {
+#popup > div > section button > div {
flex-grow: 1;
}
-.popup > div > section button > div > em {
+#popup > div > section button > div > em {
opacity: .8;
display: block;
}
-.popup > div > section button.grey-out-unless-hovered:not(:hover) {
+#popup > div > section button.grey-out-unless-hovered:not(:hover) {
opacity: .6;
}
-.popup > div > section button.grey-out-unless-hovered:not(:hover) img {
+#popup > div > section button.grey-out-unless-hovered:not(:hover) img {
filter: saturate(0);
}
-.popup.actionsAsGrid > div {
+#popup.actionsAsGrid > div {
max-width: 800px;
}
-.popup.actionsAsGrid > div section {
+#popup.actionsAsGrid > div section {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
display: grid;
}
-.popup button.close-modale {
+#popup button#close-modale {
color: #fff;
cursor: pointer;
+ z-index: 11;
background: none;
border: none;
width: 60px;
@@ -172,7 +189,7 @@ body {
overflow: hidden;
}
-.popup button.close-modale:before {
+#popup button#close-modale:before {
content: "+";
font-size: 80px;
display: inline-block;
@@ -182,20 +199,20 @@ body {
transform: translate(-50%, -50%)rotate(45deg);
}
-.popup button.close-modale:hover {
+#popup button#close-modale:hover {
background: #000;
font-weight: bold;
}
-.popup .textAfterButtons {
+#popup .textAfterButtons {
color: #ffffff94;
}
-.popup a[href] {
+#popup a[href] {
color: inherit;
}
-.popup a[href]:hover, .popup a[href]:focus {
+#popup a[href]:hover, #popup a[href]:focus {
color: #fff;
}
@@ -257,15 +274,6 @@ body {
box-shadow: 2px 2px 5px #000;
}
-@media (width >= 1200px) {
- #level-recording-container {
- max-width: calc(50vw - 305px);
- position: absolute;
- top: 40px;
- left: 40px;
- }
-}
-
.histogram {
align-items: stretch;
gap: 10px;
@@ -326,6 +334,9 @@ h2.histogram-title strong {
+
diff --git a/src/PWA/sw-b71.js b/src/PWA/sw-b71.js
index 5f284a4..7bf2092 100644
--- a/src/PWA/sw-b71.js
+++ b/src/PWA/sw-b71.js
@@ -1,5 +1,5 @@
// The version of the cache.
-const VERSION = "29040298";
+const VERSION = "29041544";
// The name of the cache
const CACHE_NAME = `breakout-71-${VERSION}`;
diff --git a/src/asyncAlert.ts b/src/asyncAlert.ts
index 9c3ec93..4ab1109 100644
--- a/src/asyncAlert.ts
+++ b/src/asyncAlert.ts
@@ -12,7 +12,20 @@ export type AsyncAlertAction = {
className?: string;
};
-export function asyncAlert({
+const popupWrap = document.getElementById("popup") as HTMLDivElement;
+const closeModaleButton = document.getElementById(
+ "close-modale",
+) as HTMLButtonElement;
+closeModaleButton.addEventListener("click", (e) => {
+ e.preventDefault();
+ if (closeModal) closeModal();
+});
+
+closeModaleButton.title = t("play.close_modale_window_tooltip");
+
+let lastClickedItemIndex = -1;
+
+export async function asyncAlert({
title,
text,
actions,
@@ -27,36 +40,27 @@ export function asyncAlert({
allowClose?: boolean;
actionsAsGrid?: boolean;
}): Promise {
- alertsOpen++;
+ updateAlertsOpen(+1);
return new Promise((resolve) => {
- const popupWrap = document.createElement("div");
- document.body.appendChild(popupWrap);
- popupWrap.className = "popup " + (actionsAsGrid ? "actionsAsGrid " : "");
+ popupWrap.className = actionsAsGrid ? " " : "";
+ closeModaleButton.style.display = allowClose ? "" : "none";
+ const popup = document.createElement("div");
function closeWithResult(value: t | undefined) {
+ document.body.style.minHeight = document.body.scrollHeight + "px";
+ setTimeout(() => (document.body.style.minHeight = ""), 100);
+ popup.remove();
resolve(value);
- // Doing this async lets the menu scroll persist if it's shown a second time
- setTimeout(() => {
- document.body.removeChild(popupWrap);
- });
}
if (allowClose) {
- const closeButton = document.createElement("button");
- closeButton.title = t("play.close_modale_window_tooltip");
- closeButton.className = "close-modale";
- closeButton.addEventListener("click", (e) => {
- e.preventDefault();
- closeWithResult(undefined);
- });
closeModal = () => {
closeWithResult(undefined);
};
- popupWrap.appendChild(closeButton);
+ } else {
+ closeModal = null;
}
- const popup = document.createElement("div");
-
if (title) {
const p = document.createElement("h2");
p.innerHTML = title;
@@ -70,32 +74,39 @@ export function asyncAlert({
}
const buttons = document.createElement("section");
+ buttons.className = "actions";
popup.appendChild(buttons);
actions
?.filter((i) => i)
- .forEach(({ text, value, help, disabled, className = "", icon = "" }) => {
- const button = document.createElement("button");
+ .forEach(
+ ({ text, value, help, disabled, className = "", icon = "" }, index) => {
+ const button = document.createElement("button");
- button.innerHTML = `
+ button.innerHTML = `
${icon}
${text}
${help || ""}
`;
- if (disabled) {
- button.setAttribute("disabled", "disabled");
- } else {
- button.addEventListener("click", (e) => {
- e.preventDefault();
- closeWithResult(value);
- });
- }
- button.className = className;
- buttons.appendChild(button);
- });
+ if (disabled) {
+ button.setAttribute("disabled", "disabled");
+ } else {
+ button.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ closeWithResult(value);
+ // Focus "same" button if it's still there
+ lastClickedItemIndex = index;
+ });
+ }
+ button.className =
+ className + (lastClickedItemIndex === index ? " needs-focus" : "");
+ buttons.appendChild(button);
+ },
+ );
if (textAfterButtons) {
const p = document.createElement("div");
p.className = "textAfterButtons";
@@ -105,17 +116,28 @@ ${icon}
popupWrap.appendChild(popup);
(
- popup.querySelector("button:not([disabled])") as HTMLButtonElement
+ popupWrap.querySelector(
+ `section.actions > button.needs-focus`,
+ ) as HTMLButtonElement
)?.focus();
+ lastClickedItemIndex = -1;
}).then(
(v: unknown) => {
- alertsOpen--;
+ updateAlertsOpen(-1);
closeModal = null;
return v as t | undefined;
},
() => {
closeModal = null;
- alertsOpen--;
+ updateAlertsOpen(-1);
},
);
}
+
+function updateAlertsOpen(delta: number) {
+ alertsOpen += delta;
+ if (alertsOpen > 1) {
+ throw new Error("Cannot open two alerts at once");
+ }
+ document.body.classList[alertsOpen ? "add" : "remove"]("has-alert-open");
+}
diff --git a/src/data/version.json b/src/data/version.json
index e13484e..dd5c354 100644
--- a/src/data/version.json
+++ b/src/data/version.json
@@ -1 +1 @@
-"29040298"
+"29041544"
diff --git a/src/game.less b/src/game.less
index fe41149..299c5f3 100644
--- a/src/game.less
+++ b/src/game.less
@@ -23,7 +23,7 @@ body {
}
#game {
- position: absolute;
+ position: fixed;
top: 0;
left: 0;
height: 100vh;
@@ -66,16 +66,29 @@ body {
#menu {
left: 0;
}
+body.has-alert-open {
+ height: auto;
+ overflow: visible;
+}
+body:not(.has-alert-open) #popup {
+ display: none;
+}
+#popup {
+ &::before {
+ z-index: 10;
+ content: "";
+ display: block;
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.9);
+ }
-.popup {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.9);
- z-index: 10;
display: flex;
overflow: auto;
& > div {
+ z-index: 11;
+ position: relative;
margin: auto;
padding: 20px 10px;
transform-origin: center;
@@ -157,7 +170,7 @@ body {
}
}
- button.close-modale {
+ button#close-modale {
color: white;
position: absolute;
top: 0;
@@ -168,6 +181,7 @@ body {
border: none;
cursor: pointer;
overflow: hidden;
+ z-index: 11;
&:before {
content: "+";
@@ -259,13 +273,6 @@ body {
margin: 20px auto;
}
}
-
- @media (min-width: 1200px) {
- position: absolute;
- top: 40px;
- left: 40px;
- max-width: calc((100vw - 450px) / 2 - 80px);
- }
}
.histogram {
diff --git a/src/game.ts b/src/game.ts
index cefd415..3486be9 100644
--- a/src/game.ts
+++ b/src/game.ts
@@ -61,7 +61,8 @@ export function play() {
startRecordingGame(gameState);
getAudioContext()?.resume();
resumeRecording();
- document.body.className = gameState.running ? " running " : " paused ";
+
+ // document.body.classList[gameState.running ? 'add' : 'remove']('running')
}
export function pause(playerAskedForPause: boolean) {
@@ -78,7 +79,7 @@ export function pause(playerAskedForPause: boolean) {
pauseRecording();
gameState.pauseTimeout = null;
- document.body.className = gameState.running ? " running " : " paused ";
+ // document.body.className = gameState.running ? " running " : " paused ";
scoreDisplay.className = "";
gameState.needsRender = true;
},
@@ -373,7 +374,9 @@ window.addEventListener("visibilitychange", () => {
scoreDisplay.addEventListener("click", (e) => {
e.preventDefault();
- openScorePanel();
+ if (!alertsOpen) {
+ openScorePanel();
+ }
});
document.addEventListener("visibilitychange", () => {
@@ -420,7 +423,9 @@ async function openScorePanel() {
"click",
(e) => {
e.preventDefault();
- openSettingsPanel();
+ if (!alertsOpen) {
+ openSettingsPanel();
+ }
},
);
diff --git a/src/gameStateMutators.ts b/src/gameStateMutators.ts
index 9e08aeb..0ad2977 100644
--- a/src/gameStateMutators.ts
+++ b/src/gameStateMutators.ts
@@ -501,10 +501,15 @@ export async function setLevel(gameState: GameState, l: number) {
gameState.runStatistics.levelsPlayed++;
// Reset combo silently
- const finalCombo=gameState.combo
+ const finalCombo = gameState.combo;
gameState.combo = baseCombo(gameState);
if (!gameState.perks.shunt) {
- gameState.combo += Math.round(Math.max(0,(finalCombo-gameState.combo)*25*gameState.perks.shunt/100))
+ gameState.combo += Math.round(
+ Math.max(
+ 0,
+ ((finalCombo - gameState.combo) * 25 * gameState.perks.shunt) / 100,
+ ),
+ );
}
gameState.combo += gameState.perks.hot_start * 15;
@@ -1225,6 +1230,14 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
if (gameState.perks.top_is_lava && borderHitCode >= 2) {
resetCombo(gameState, ball.x, ball.y + gameState.ballSize);
}
+ if (gameState.perks.trampoline && borderHitCode >= 2) {
+ decreaseCombo(
+ gameState,
+ gameState.perks.trampoline,
+ ball.x,
+ ball.y + gameState.ballSize,
+ );
+ }
schedulGameSound(gameState, "wallBeep", ball.x, 1);
gameState.levelWallBounces++;
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 76d1c55..94f989b 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -230,7 +230,7 @@
"upgrades.top_is_lava.help": "More coins if you don't touch the top.",
"upgrades.top_is_lava.name": "Sky is the limit",
"upgrades.trampoline.fullHelp": "One of the rare combo upgrades that don't add a reset condition",
- "upgrades.trampoline.help": "+{{lvl}} combo per puck bounce",
+ "upgrades.trampoline.help": "+{{lvl}} combo per puck bounce,-{{lvl}} combo per ceiling bounce",
"upgrades.trampoline.name": "Trampoline",
"upgrades.unbounded.fullHelp": "I hope you've found a way to keep your ball on screen",
"upgrades.unbounded.help": "+1 combo per brick, no more sides",
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index ca448e6..e595929 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -230,7 +230,7 @@
"upgrades.top_is_lava.help": "Plus de pièces si vous ne touchez pas le haut de la zone de jeu",
"upgrades.top_is_lava.name": "Icare ",
"upgrades.trampoline.fullHelp": "Une des rares améliorations à ne pas avoir de condition de remise à zéro",
- "upgrades.trampoline.help": "+{{lvl}} combo à chaque rebond d'une balle sur le palet",
+ "upgrades.trampoline.help": "+{{lvl}} combo à chaque rebond d'une balle sur le palet,-{{lvl}} combo à chaque rebond au plafond",
"upgrades.trampoline.name": "Trampoline",
"upgrades.unbounded.fullHelp": "J'espère que vous avez prévu un moyen de récupérer vos balles",
"upgrades.unbounded.help": "+1 combo par brique, plus de cotés",
diff --git a/src/index.html b/src/index.html
index b2b6c72..2559d13 100644
--- a/src/index.html
+++ b/src/index.html
@@ -23,6 +23,9 @@
+
diff --git a/src/upgrades.ts b/src/upgrades.ts
index 3dfc880..7c9aa06 100644
--- a/src/upgrades.ts
+++ b/src/upgrades.ts
@@ -3,6 +3,7 @@ import { t } from "./i18n/i18n";
export const rawUpgrades = [
{
requires: "",
+ rejects: "",
threshold: 0,
giftable: false,
id: "extra_life",
@@ -16,6 +17,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 0,
id: "streak_shots",
giftable: true,
@@ -27,6 +29,7 @@ export const rawUpgrades = [
{
requires: "",
+ rejects: "",
threshold: 0,
id: "base_combo",
giftable: true,
@@ -38,6 +41,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 0,
giftable: false,
id: "slow_down",
@@ -48,6 +52,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 0,
giftable: false,
id: "bigger_puck",
@@ -58,6 +63,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 0,
giftable: false,
id: "viscosity",
@@ -69,6 +75,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 0,
id: "left_is_lava",
giftable: true,
@@ -80,6 +87,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 0,
id: "right_is_lava",
giftable: true,
@@ -90,6 +98,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 0,
id: "top_is_lava",
giftable: true,
@@ -100,6 +109,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 0,
giftable: false,
id: "skip_last",
@@ -113,6 +123,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 500,
id: "telekinesis",
giftable: true,
@@ -126,6 +137,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 1000,
giftable: false,
id: "coin_magnet",
@@ -139,6 +151,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 1500,
id: "multiball",
giftable: true,
@@ -149,6 +162,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 2000,
giftable: false,
id: "smaller_puck",
@@ -162,6 +176,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 3000,
id: "pierce",
giftable: true,
@@ -172,6 +187,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 4000,
id: "picky_eater",
giftable: true,
@@ -182,6 +198,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 5000,
giftable: false,
id: "metamorphosis",
@@ -192,6 +209,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 6000,
id: "compound_interest",
giftable: true,
@@ -202,6 +220,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 7000,
id: "hot_start",
giftable: true,
@@ -216,6 +235,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 9000,
id: "sapper",
giftable: true,
@@ -229,6 +249,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 11000,
id: "bigger_explosions",
giftable: false,
@@ -239,6 +260,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 13000,
giftable: false,
id: "extra_levels",
@@ -249,6 +271,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 15000,
giftable: false,
id: "pierce_color",
@@ -259,6 +282,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 18000,
giftable: false,
id: "soft_reset",
@@ -295,6 +319,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 30000,
giftable: false,
id: "puck_repulse_ball",
@@ -308,6 +333,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 35000,
giftable: false,
id: "wind",
@@ -319,6 +345,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 40000,
giftable: false,
id: "sturdy_bricks",
@@ -332,6 +359,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 45000,
giftable: false,
id: "respawn",
@@ -343,6 +371,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 50000,
giftable: false,
id: "one_more_choice",
@@ -353,6 +382,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 55000,
giftable: false,
id: "instant_upgrade",
@@ -363,6 +393,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 60000,
giftable: false,
id: "concave_puck",
@@ -373,6 +404,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 65000,
giftable: false,
id: "helium",
@@ -383,6 +415,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 70000,
giftable: false,
id: "asceticism",
@@ -393,6 +426,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 75000,
giftable: false,
id: "unbounded",
@@ -403,16 +437,18 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 80000,
giftable: false,
id: "shunt",
max: 3,
name: t("upgrades.shunt.name"),
- help: (lvl: number) => t("upgrades.shunt.help",{percent:lvl*25}),
+ help: (lvl: number) => t("upgrades.shunt.help", { percent: lvl * 25 }),
fullHelp: t("upgrades.shunt.fullHelp"),
},
{
requires: "",
+ rejects: "",
threshold: 85000,
giftable: false,
id: "yoyo",
@@ -423,6 +459,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 90000,
giftable: false,
id: "nbricks",
@@ -433,6 +470,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 95000,
giftable: false,
id: "etherealcoins",
@@ -453,6 +491,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 105000,
giftable: false,
id: "zen",
@@ -484,6 +523,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 120000,
giftable: false,
id: "ghost_coins",
@@ -504,6 +544,7 @@ export const rawUpgrades = [
},
{
requires: "",
+ rejects: "",
threshold: 130000,
giftable: false,
id: "ball_attracts_coins",