Added quick-buy and bulk-buying qol features to both shop and garden.

This commit is contained in:
sarah committed 2026-09-03 21:00:30 +02:00
1 parent cce44b1b0e
commit a055a74148
4 files changed
+769 -58

No files matched your search

+44
View File
@@ -164,6 +164,50 @@ $PageSection = "Garden";
</button>
</div>
<section
id="QuickBuy"
class="Panel GardenQuickBuyPanel"
>
<div class="GardenQuickBuyHeader">
<h2>Quick-buy</h2>
<div
class="QuickBuyAmountList"
role="group"
aria-label="Quick-buy amount"
>
<button
id="QuickBuyOneButton"
class="QuickBuyAmountButton"
type="button"
>
×1
</button>
<button
id="QuickBuyTwoButton"
class="QuickBuyAmountButton"
type="button"
>
×2
</button>
<button
id="QuickBuyGardenButton"
class="QuickBuyAmountButton"
type="button"
>
×9
</button>
</div>
</div>
<div
id="QuickBuyPlantList"
class="QuickBuyPlantList"
></div>
</section>
<div class="GardenLayout">
<section
id="Plots"
+452
View File
@@ -3,6 +3,8 @@ let SelectedSeedId = null;
let SelectedGardenTool = "Seed";
let MutationCheckPending = false;
let QuickBuyPurchasePending = false;
let QuickBuyAmountMode = "One";
const GardenUpdateInterval = 1000;
@@ -64,6 +66,7 @@ async function StartGame() {
BindGardenTools();
BindGardenSelector();
BindQuickBuyControls();
EnsureSelectedSeed();
const InitialMutationResult =
@@ -92,6 +95,7 @@ async function StartGame() {
function RenderGame() {
EnsureSelectedSeed();
RenderCurrency();
RenderQuickBuy();
RenderSeeds();
RenderTools();
RenderGardenSelector();
@@ -437,6 +441,454 @@ function RenderTools() {
}
function BindQuickBuyControls() {
const Controls = [
[
"QuickBuyOneButton",
"One"
],
[
"QuickBuyTwoButton",
"Two"
],
[
"QuickBuyGardenButton",
"Garden"
]
];
for (
const [ButtonId, Mode]
of Controls
) {
const Button =
document.getElementById(
ButtonId
);
Button?.addEventListener(
"click",
() => {
QuickBuyAmountMode =
Mode;
RenderQuickBuy();
}
);
}
}
function GetQuickBuyAmount() {
if (
QuickBuyAmountMode ===
"Two"
) {
return 2;
}
if (
QuickBuyAmountMode ===
"Garden"
) {
return Math.max(
1,
GameSave.Garden.Width *
GameSave.Garden.Height
);
}
return 1;
}
function RenderQuickBuy() {
const PlantList =
document.getElementById(
"QuickBuyPlantList"
);
if (PlantList === null) {
return;
}
const Amount =
GetQuickBuyAmount();
const OneButton =
document.getElementById(
"QuickBuyOneButton"
);
const TwoButton =
document.getElementById(
"QuickBuyTwoButton"
);
const GardenButton =
document.getElementById(
"QuickBuyGardenButton"
);
const AmountButtons = [
[OneButton, "One", 1],
[TwoButton, "Two", 2],
[
GardenButton,
"Garden",
Math.max(
1,
GameSave.Garden.Width *
GameSave.Garden.Height
)
]
];
for (
const [Button, Mode, ButtonAmount]
of AmountButtons
) {
if (Button === null) {
continue;
}
Button.textContent =
"×" +
ButtonAmount.toLocaleString();
Button.disabled =
QuickBuyPurchasePending;
if (
QuickBuyAmountMode ===
Mode
) {
Button.setAttribute(
"aria-current",
"true"
);
} else {
Button.removeAttribute(
"aria-current"
);
}
}
PlantList.replaceChildren();
const AvailablePlants =
Object.values(Plants)
.filter(
Plant => {
if (
!IsPlantAvailableInShop(
GameSave,
Plant
)
) {
return false;
}
const Cost =
GetPlantShopCost(
GameSave,
Plant.Id
);
return (
Cost !== null &&
GameSave.Currency.Dew >=
Cost * Amount
);
}
)
.sort(
(A, B) =>
A.Id - B.Id
);
if (
AvailablePlants.length === 0
) {
const EmptyMessage =
document.createElement(
"p"
);
EmptyMessage.className =
"QuickBuyEmpty";
EmptyMessage.textContent =
Amount === 1
? "You can't afford any available seeds."
: "You can't afford this many of any available seed.";
PlantList.appendChild(
EmptyMessage
);
return;
}
for (
const Plant
of AvailablePlants
) {
PlantList.appendChild(
CreateQuickBuyPlantButton(
Plant,
Amount
)
);
}
}
function CreateQuickBuyPlantButton(
Plant,
Amount
) {
const UnitCost =
GetPlantShopCost(
GameSave,
Plant.Id
);
const TotalCost =
UnitCost * Amount;
const Button =
document.createElement(
"button"
);
Button.className =
"PlantTile QuickBuyPlantButton";
Button.type = "button";
Button.disabled =
QuickBuyPurchasePending;
Button.title =
"Buy " +
Amount.toLocaleString() +
(Amount === 1
? " seed of "
: " seeds of ") +
Plant.Name +
" for " +
TotalCost.toLocaleString() +
" Dew";
const Name =
document.createElement(
"span"
);
Name.className =
"QuickBuyPlantName";
Name.textContent =
Plant.Name;
const Visual =
document.createElement(
"span"
);
Visual.className =
"QuickBuyPlantVisual";
const MatureImage =
GetPlantMatureImageSource(
Plant
);
if (MatureImage === null) {
const Missing =
document.createElement(
"span"
);
Missing.className =
"QuickBuyPlantMissing";
Missing.textContent =
"No image";
Visual.appendChild(
Missing
);
} else {
const Image =
document.createElement(
"img"
);
Image.className =
"PlantSprite";
Image.src =
MatureImage;
Image.alt =
Plant.Name;
Visual.appendChild(
Image
);
}
Button.append(
Name,
Visual
);
Button.addEventListener(
"click",
() => {
BuyQuickBuyPlantSeed(
Plant.Id
);
}
);
return Button;
}
async function BuyQuickBuyPlantSeed(
PlantId
) {
if (QuickBuyPurchasePending) {
return;
}
const Plant =
GetPlantById(
PlantId
);
if (
Plant === null ||
!IsPlantAvailableInShop(
GameSave,
Plant
)
) {
return;
}
const Amount =
GetQuickBuyAmount();
const UnitCost =
GetPlantShopCost(
GameSave,
PlantId
);
if (UnitCost === null) {
SetGardenMessage(
"That seed doesn't have a purchasable recipe yet."
);
return;
}
const TotalCost =
UnitCost * Amount;
if (
GameSave.Currency.Dew <
TotalCost
) {
SetGardenMessage(
"You don't have enough Dew for " +
Amount.toLocaleString() +
(Amount === 1
? " seed of "
: " seeds of ") +
Plant.Name +
"."
);
RenderQuickBuy();
return;
}
QuickBuyPurchasePending = true;
try {
GameSave.Currency.Dew -=
TotalCost;
GameSave.Statistics.CurrencySpent.Dew +=
TotalCost;
GameSave.Statistics.SeedsPurchased +=
Amount;
AddSeed(
GameSave,
PlantId,
Amount
);
DiscoverPlant(
GameSave,
PlantId
);
SetGardenMessage(
"Bought " +
Amount.toLocaleString() +
" " +
Plant.Name +
(Amount === 1
? " seed for "
: " seeds for ") +
TotalCost.toLocaleString() +
" Dew."
);
RenderGame();
await SaveGame(
GameSave
);
} finally {
QuickBuyPurchasePending =
false;
RenderGame();
}
}
function RenderCurrency() {
const DewAmount =
document.getElementById(
+132 -58
View File
@@ -1001,43 +1001,95 @@ function CreateShopSeedCard(
);
const BuyButton =
const BuyOptions =
document.createElement(
"button"
"div"
);
BuyButton.className =
"ActionButton ShopBuyButton";
BuyOptions.className =
"ShopSeedBuyOptions";
BuyButton.type = "button";
BuyButton.textContent =
Cost === null
? "Unavailable"
: "Buy seed - " +
Cost.toLocaleString() +
" Dew";
const GardenSize =
Math.max(
1,
ShopSave.Garden.Width *
ShopSave.Garden.Height
);
BuyButton.disabled =
Cost === null ||
ShopSave.Currency.Dew <
Cost ||
ShopPurchasePending;
BuyButton.addEventListener(
"click",
() => {
BuyPlantSeed(
Plant.Id
);
const PurchaseOptions = [
{
Amount: 1,
Label: "Buy seed"
},
{
Amount: 10,
Label: "Buy 10 seeds"
},
{
Amount: GardenSize,
Label:
"Buy " +
GardenSize.toLocaleString() +
" seeds"
}
);
];
for (
const Option
of PurchaseOptions
) {
const BuyButton =
document.createElement(
"button"
);
BuyButton.className =
"ActionButton ShopBuyButton";
BuyButton.type = "button";
const TotalCost =
Cost === null
? null
: Cost * Option.Amount;
BuyButton.textContent =
TotalCost === null
? "Unavailable"
: Option.Label +
" - " +
TotalCost.toLocaleString() +
" Dew";
BuyButton.disabled =
TotalCost === null ||
ShopSave.Currency.Dew <
TotalCost ||
ShopPurchasePending;
BuyButton.addEventListener(
"click",
() => {
BuyPlantSeed(
Plant.Id,
Option.Amount
);
}
);
BuyOptions.appendChild(
BuyButton
);
}
Card.append(
Header,
Body,
BuyButton
BuyOptions
);
@@ -1225,13 +1277,22 @@ function CreateShopStat(
async function BuyPlantSeed(
PlantId
PlantId,
Amount = 1
) {
if (ShopPurchasePending) {
return;
}
Amount = Math.max(
1,
Math.floor(
Number(Amount) || 1
)
);
const Plant =
GetPlantById(
PlantId
@@ -1248,13 +1309,13 @@ async function BuyPlantSeed(
}
const Cost =
const UnitCost =
GetPlantShopCost(
ShopSave,
PlantId
);
if (Cost === null) {
if (UnitCost === null) {
SetShopMessage(
"That seed doesn't have a purchasable recipe yet."
);
@@ -1263,12 +1324,19 @@ async function BuyPlantSeed(
}
const TotalCost =
UnitCost * Amount;
if (
ShopSave.Currency.Dew <
Cost
TotalCost
) {
SetShopMessage(
"You don't have enough Dew for " +
Amount.toLocaleString() +
(Amount === 1
? " seed of "
: " seeds of ") +
Plant.Name +
"."
);
@@ -1279,45 +1347,51 @@ async function BuyPlantSeed(
ShopPurchasePending = true;
ShopSave.Currency.Dew -=
Cost;
try {
ShopSave.Currency.Dew -=
TotalCost;
ShopSave.Statistics.CurrencySpent.Dew +=
Cost;
ShopSave.Statistics.CurrencySpent.Dew +=
TotalCost;
ShopSave.Statistics.SeedsPurchased++;
ShopSave.Statistics.SeedsPurchased +=
Amount;
AddSeed(
ShopSave,
PlantId
);
AddSeed(
ShopSave,
PlantId,
Amount
);
DiscoverPlant(
ShopSave,
PlantId
);
SetShopMessage(
"Bought a " +
Plant.Name +
" seed for " +
Cost +
" Dew."
);
RenderShop();
DiscoverPlant(
ShopSave,
PlantId
);
await SaveGame(
ShopSave
);
SetShopMessage(
"Bought " +
Amount.toLocaleString() +
" " +
Plant.Name +
(Amount === 1
? " seed for "
: " seeds for ") +
TotalCost.toLocaleString() +
" Dew."
);
RenderShop();
ShopPurchasePending = false;
RenderShop();
await SaveGame(
ShopSave
);
} finally {
ShopPurchasePending = false;
RenderShop();
}
}
async function BuyGardenExpansion(
Direction
) {
+141
View File
@@ -415,6 +415,140 @@ input, button, textarea, select {
}
}
.GardenQuickBuyPanel {
margin-bottom: 1rem;
padding: 0.75rem;
}
.GardenQuickBuyHeader {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.GardenQuickBuyHeader h2 {
margin: 0;
}
.QuickBuyAmountList {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.QuickBuyAmountButton {
min-width: 3.25rem;
padding: 0.4rem 0.55rem;
cursor: pointer;
color: var(--Text);
background-color: var(--Surface0);
border: 3px solid var(--Surface2);
border-radius: 0;
box-shadow: 2px 2px 0 var(--Crust);
appearance: none;
}
.QuickBuyAmountButton:hover:not(:disabled) {
color: var(--Green);
border-color: var(--Green);
}
.QuickBuyAmountButton[aria-current="true"] {
color: var(--Green);
background-color: var(--Surface1);
border-color: var(--Green);
}
.QuickBuyAmountButton:disabled {
cursor: not-allowed;
}
.QuickBuyAmountButton:focus-visible,
.QuickBuyPlantButton:focus-visible {
outline: 3px solid var(--Yellow);
outline-offset: 3px;
}
.QuickBuyPlantList {
display: flex;
gap: 0.65rem;
padding-bottom: 0.45rem;
overflow-x: auto;
overflow-y: hidden;
}
.QuickBuyPlantButton {
display: flex;
flex: 0 0 7rem;
width: 7rem;
padding: 0;
flex-direction: column;
cursor: pointer;
color: var(--Text);
border-color: var(--Surface2);
border-radius: 0;
box-shadow: 3px 3px 0 var(--Crust);
appearance: none;
}
.QuickBuyPlantButton:hover:not(:disabled) {
border-color: var(--Green);
transform: translate(1px, 1px);
box-shadow: 2px 2px 0 var(--Crust);
}
.QuickBuyPlantButton:disabled {
cursor: not-allowed;
}
.QuickBuyPlantName {
display: flex;
width: 100%;
min-width: 0;
min-height: 1.7rem;
padding: 0.25rem 0.35rem;
align-items: center;
justify-content: center;
overflow: hidden;
color: var(--Subtext1);
background-color: var(--Mantle);
border-bottom: 2px solid var(--Surface2);
font-size: 0.75rem;
line-height: 1;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
}
.QuickBuyPlantVisual {
display: flex;
width: 100%;
aspect-ratio: 1;
align-items: center;
justify-content: center;
overflow: hidden;
background-color: var(--Surface0);
}
.QuickBuyPlantMissing {
padding: 0.35rem;
color: var(--Overlay1);
font-size: 0.75rem;
text-align: center;
}
.QuickBuyEmpty {
flex: 0 0 100%;
margin: 0;
padding: 0.75rem;
color: var(--Subtext1);
background-color: var(--Mantle);
border: 3px solid var(--Surface2);
}
.GardenLayout {
display: grid;
grid-template-columns: minmax(0, 1fr) 12rem;
@@ -1077,6 +1211,13 @@ input, button, textarea, select {
padding: 0.65rem 0.8rem;
}
.ShopSeedBuyOptions {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-top: auto;
}
.ShopBuyButton:disabled {
cursor: not-allowed;
color: var(--Overlay1);