Added multiple gardens per user upgrade. Pretty cool!!!!

This commit is contained in:
sarah committed 2026-09-03 18:07:19 +02:00
1 parent c7b5494385
commit d72045d615
7 files changed
+930 -103

No files matched your search

+32
View File
@@ -78,6 +78,38 @@ $PageSection = "Garden";
</div>
</div>
<div
id="GardenSelector"
class="GardenSelectorPanel"
>
<button
id="PreviousGardenButton"
class="ActionButton GardenSelectorButton"
type="button"
aria-label="Previous Garden"
>
&lt;
</button>
<input
id="GardenNameInput"
class="GardenNameInput"
type="text"
maxlength="32"
value="Garden"
aria-label="Garden name"
>
<button
id="NextGardenButton"
class="ActionButton GardenSelectorButton"
type="button"
aria-label="Next Garden"
>
&gt;
</button>
</div>
<div class="GardenLayout">
<section id="Plots">
<h2>Plots</h2>
+2 -2
View File
@@ -47,8 +47,8 @@ $PageSection = "Shop";
<h2>Permanent upgrades</h2>
<p>
Permanently expand your Garden or unlock
extra information and quality-of-life features.
Expand your active Garden, buy additional Gardens,
or unlock information and quality-of-life features.
</p>
<div
+245 -2
View File
@@ -27,10 +27,11 @@ async function StartGame() {
GameSave = await LoadGame();
BindGardenTools();
BindGardenSelector();
EnsureSelectedSeed();
const InitialMutationResult =
CheckGardenMutations(
CheckAllGardenMutations(
GameSave
);
@@ -57,12 +58,235 @@ function RenderGame() {
RenderCurrency();
RenderSeeds();
RenderTools();
RenderGardenSelector();
RenderGarden();
RenderNextHarvest();
RenderGardenOverview();
}
function BindGardenSelector() {
const PreviousButton =
document.getElementById(
"PreviousGardenButton"
);
const NextButton =
document.getElementById(
"NextGardenButton"
);
const NameInput =
document.getElementById(
"GardenNameInput"
);
PreviousButton?.addEventListener(
"click",
() => {
SwitchGarden(-1);
}
);
NextButton?.addEventListener(
"click",
() => {
SwitchGarden(1);
}
);
NameInput?.addEventListener(
"change",
async () => {
const GardenIndex =
GameSave.ActiveGardenIndex;
const GardenName =
RenameGarden(
GameSave,
GardenIndex,
NameInput.value
);
NameInput.value =
GardenName;
SetGardenMessage(
"Garden renamed to " +
GardenName +
"."
);
await SaveGame(
GameSave
);
}
);
NameInput?.addEventListener(
"keydown",
Event => {
if (Event.key !== "Enter") {
return;
}
Event.preventDefault();
NameInput.blur();
}
);
}
async function SwitchGarden(
Direction
) {
const GardenCount =
GameSave.Gardens.length;
if (GardenCount <= 1) {
return;
}
const NewIndex =
GameSave.ActiveGardenIndex +
Direction;
if (
NewIndex < 0 ||
NewIndex >= GardenCount
) {
return;
}
SetActiveGardenIndex(
GameSave,
NewIndex
);
const MutationResult =
CheckGardenMutations(
GameSave
);
RenderGame();
SetGardenMessage(
"Switched to " +
GameSave.Garden.Name +
"."
);
await SaveGame(
GameSave
);
if (MutationResult.Attempted) {
RenderGame();
}
}
function RenderGardenSelector() {
const PreviousButton =
document.getElementById(
"PreviousGardenButton"
);
const NextButton =
document.getElementById(
"NextGardenButton"
);
const NameInput =
document.getElementById(
"GardenNameInput"
);
if (
PreviousButton === null ||
NextButton === null ||
NameInput === null
) {
return;
}
const GardenCount =
GameSave.Gardens.length;
const GardenIndex =
GameSave.ActiveGardenIndex;
PreviousButton.disabled =
GardenIndex <= 0;
NextButton.disabled =
GardenIndex >=
GardenCount - 1;
if (
document.activeElement !==
NameInput
) {
NameInput.value =
GameSave.Garden.Name;
}
NameInput.title =
"Garden " +
(GardenIndex + 1) +
" of " +
GardenCount;
}
function CheckAllGardenMutations(
SaveData
) {
const OriginalGardenIndex =
SaveData.ActiveGardenIndex;
const Result = {
Attempted: false,
Changed: false
};
for (
let GardenIndex = 0;
GardenIndex <
SaveData.Gardens.length;
GardenIndex++
) {
SetActiveGardenIndex(
SaveData,
GardenIndex
);
const GardenResult =
CheckGardenMutations(
SaveData
);
Result.Attempted =
Result.Attempted ||
GardenResult.Attempted;
Result.Changed =
Result.Changed ||
GardenResult.Changed;
}
SetActiveGardenIndex(
SaveData,
OriginalGardenIndex
);
return Result;
}
function BindGardenTools() {
const ShovelButton =
document.getElementById(
@@ -326,6 +550,25 @@ function RenderGarden() {
GameSave.Garden.Width
);
const GardenBorderColours = [
"var(--Lavender)",
"var(--Mauve)",
"var(--Pink)",
"var(--Blue)",
"var(--Teal)",
"var(--Peach)",
"var(--Yellow)",
"var(--Green)"
];
GardenGrid.style.setProperty(
"--GardenBorderColour",
GardenBorderColours[
GameSave.ActiveGardenIndex %
GardenBorderColours.length
]
);
GardenGrid.replaceChildren();
@@ -954,7 +1197,7 @@ async function GardenTick() {
try {
const MutationResult =
CheckGardenMutations(
CheckAllGardenMutations(
GameSave
);
+395 -98
View File
@@ -10,6 +10,357 @@ const StartingSeeds = {
};
const DefaultGardenName = "Garden";
const MaximumGardenNameLength = 32;
const StartingGardenWidth = 3;
const StartingGardenHeight = 3;
function CreateGardenData(
Name = DefaultGardenName
) {
return {
Name: NormalizeGardenName(
Name
),
Width: StartingGardenWidth,
Height: StartingGardenHeight,
Plots: Array(
StartingGardenWidth *
StartingGardenHeight
).fill(null),
MutationCooldowns: {}
};
}
function NormalizeGardenName(
Value
) {
if (
typeof Value !== "string"
) {
return DefaultGardenName;
}
const Name = Value
.trim()
.slice(
0,
MaximumGardenNameLength
);
return Name.length > 0
? Name
: DefaultGardenName;
}
function NormalizeGardenData(
Garden,
LegacyMutationCooldowns = null
) {
if (
Garden === null ||
typeof Garden !== "object" ||
Array.isArray(Garden)
) {
Garden = CreateGardenData();
}
Garden.Name = NormalizeGardenName(
Garden.Name
);
Garden.Width = Math.max(
1,
Math.floor(
Number(
Garden.Width ??
StartingGardenWidth
) || StartingGardenWidth
)
);
Garden.Height = Math.max(
1,
Math.floor(
Number(
Garden.Height ??
StartingGardenHeight
) || StartingGardenHeight
)
);
if (!Array.isArray(Garden.Plots)) {
Garden.Plots = [];
}
for (
let PlotIndex = 0;
PlotIndex < Garden.Plots.length;
PlotIndex++
) {
const Plot =
Garden.Plots[PlotIndex];
if (Plot === null) {
continue;
}
if (
typeof Plot !== "object" ||
Array.isArray(Plot)
) {
Garden.Plots[PlotIndex] = null;
continue;
}
if (
Plot.Plant === "Rose"
) {
Plot.Plant = "RedRose";
}
if (
typeof Plot.Plant ===
"number"
) {
const PlantKey =
ResolvePlantKeyById(
Plot.Plant
);
if (PlantKey !== null) {
Plot.Plant = PlantKey;
}
}
Plot.PlantedAt = Number(
Plot.PlantedAt ??
Date.now()
);
delete Plot.VisualVariant;
delete Plot.AddedTags;
}
const RequiredPlots =
Garden.Width *
Garden.Height;
while (
Garden.Plots.length <
RequiredPlots
) {
Garden.Plots.push(null);
}
if (
Garden.Plots.length >
RequiredPlots
) {
Garden.Plots.length =
RequiredPlots;
}
if (
Garden.MutationCooldowns ===
undefined &&
LegacyMutationCooldowns !== null &&
typeof LegacyMutationCooldowns ===
"object" &&
!Array.isArray(
LegacyMutationCooldowns
)
) {
Garden.MutationCooldowns =
LegacyMutationCooldowns;
}
if (
Garden.MutationCooldowns === null ||
typeof Garden.MutationCooldowns !==
"object" ||
Array.isArray(
Garden.MutationCooldowns
)
) {
Garden.MutationCooldowns = {};
}
return Garden;
}
function GetActiveGarden(
SaveData
) {
if (
!Array.isArray(
SaveData.Gardens
) ||
SaveData.Gardens.length === 0
) {
SaveData.Gardens = [
CreateGardenData()
];
}
const GardenIndex = Math.min(
SaveData.Gardens.length - 1,
Math.max(
0,
Math.floor(
Number(
SaveData.ActiveGardenIndex ??
0
) || 0
)
)
);
SaveData.ActiveGardenIndex =
GardenIndex;
return SaveData.Gardens[
GardenIndex
];
}
function SetActiveGardenIndex(
SaveData,
GardenIndex
) {
if (
!Array.isArray(
SaveData.Gardens
) ||
SaveData.Gardens.length === 0
) {
return false;
}
const NormalizedIndex = Math.min(
SaveData.Gardens.length - 1,
Math.max(
0,
Math.floor(
Number(
GardenIndex
) || 0
)
)
);
const Changed =
SaveData.ActiveGardenIndex !==
NormalizedIndex;
SaveData.ActiveGardenIndex =
NormalizedIndex;
return Changed;
}
function RenameGarden(
SaveData,
GardenIndex,
Name
) {
if (
!Array.isArray(
SaveData.Gardens
) ||
SaveData.Gardens[GardenIndex] ===
undefined
) {
return DefaultGardenName;
}
const NormalizedName =
NormalizeGardenName(
Name
);
SaveData.Gardens[
GardenIndex
].Name = NormalizedName;
return NormalizedName;
}
function DefineActiveGardenAliases(
SaveData
) {
delete SaveData.Garden;
delete SaveData.MutationCooldowns;
Object.defineProperty(
SaveData,
"Garden",
{
configurable: true,
enumerable: false,
get() {
return GetActiveGarden(
SaveData
);
},
set(Value) {
const GardenIndex =
SaveData.ActiveGardenIndex ??
0;
SaveData.Gardens[
GardenIndex
] = NormalizeGardenData(
Value
);
}
}
);
Object.defineProperty(
SaveData,
"MutationCooldowns",
{
configurable: true,
enumerable: false,
get() {
return GetActiveGarden(
SaveData
).MutationCooldowns;
},
set(Value) {
const Garden =
GetActiveGarden(
SaveData
);
Garden.MutationCooldowns =
Value !== null &&
typeof Value === "object" &&
!Array.isArray(Value)
? Value
: {};
}
}
);
}
function CreateNewSave() {
const SeedInventory = {};
const StartingPlants = [];
@@ -40,8 +391,8 @@ function CreateNewSave() {
}
return {
Version: 2,
return NormalizeSaveData({
Version: 3,
Revision: 0,
LastSavedAt: Date.now(),
@@ -83,19 +434,17 @@ function CreateNewSave() {
ShowReadyPlots: true
},
Garden: {
Width: 3,
Height: 3,
Plots: Array(9).fill(null)
},
Gardens: [
CreateGardenData()
],
ActiveGardenIndex: 0,
Discoveries: {
Plants: StartingPlants,
Mutations: []
},
MutationCooldowns: {}
};
}
});
}
@@ -106,7 +455,7 @@ function NormalizeSaveData(
Number(
SaveData.Version ?? 1
),
2
3
);
SaveData.Revision = Number(
@@ -301,100 +650,48 @@ function NormalizeSaveData(
false;
SaveData.Garden ??= {
Width: 3,
Height: 3,
Plots: []
};
const LegacyGarden =
SaveData.Garden ??
null;
SaveData.Garden.Width = Math.max(
1,
Math.floor(
Number(
SaveData.Garden.Width ?? 3
) || 3
)
);
const LegacyMutationCooldowns =
SaveData.MutationCooldowns ??
null;
SaveData.Garden.Height = Math.max(
1,
Math.floor(
Number(
SaveData.Garden.Height ?? 3
) || 3
)
);
SaveData.Garden.Plots ??= [];
for (
let PlotIndex = 0;
PlotIndex <
SaveData.Garden.Plots.length;
PlotIndex++
) {
const Plot =
SaveData.Garden.Plots[
PlotIndex
];
if (Plot === null) {
continue;
}
if (
Plot.Plant === "Rose"
) {
Plot.Plant =
"RedRose";
}
if (
typeof Plot.Plant ===
"number"
) {
const PlantKey =
ResolvePlantKeyById(
Plot.Plant
);
if (PlantKey !== null) {
Plot.Plant = PlantKey;
}
}
Plot.PlantedAt = Number(
Plot.PlantedAt ??
Date.now()
);
delete Plot.VisualVariant;
delete Plot.AddedTags;
}
const RequiredPlots =
SaveData.Garden.Width *
SaveData.Garden.Height;
while (
SaveData.Garden.Plots.length <
RequiredPlots
) {
SaveData.Garden.Plots.push(null);
}
if (
SaveData.Garden.Plots.length >
RequiredPlots
!Array.isArray(
SaveData.Gardens
) ||
SaveData.Gardens.length === 0
) {
SaveData.Garden.Plots.length =
RequiredPlots;
SaveData.Gardens = [
LegacyGarden ??
CreateGardenData()
];
}
SaveData.MutationCooldowns ??= {};
SaveData.Gardens =
SaveData.Gardens.map(
(Garden, GardenIndex) =>
NormalizeGardenData(
Garden,
GardenIndex === 0
? LegacyMutationCooldowns
: null
)
);
SetActiveGardenIndex(
SaveData,
SaveData.ActiveGardenIndex ?? 0
);
DefineActiveGardenAliases(
SaveData
);
return SaveData;
+168
View File
@@ -124,6 +124,7 @@ function RenderGardenUpgrades() {
CreateGardenExpansionCard(
"Row"
),
CreateNewGardenCard(),
CreatePlantInformationUpgradeCard(),
CreateGardenOverviewUpgradeCard(),
CreateMutationHintsUpgradeCard()
@@ -210,6 +211,10 @@ function CreateGardenExpansionCard(
"ShopItemDetails";
Details.append(
CreateShopStat(
"Garden",
ShopSave.Garden.Name
),
CreateShopStat(
"Current size",
ShopSave.Garden.Width +
@@ -273,6 +278,115 @@ function CreateGardenExpansionCard(
}
function CreateNewGardenCard() {
const Cost =
GetNewGardenCost(
ShopSave
);
const GardensOwned =
ShopSave.Gardens.length;
const Card =
document.createElement(
"article"
);
Card.className =
"Panel ShopItem";
const Header =
document.createElement(
"div"
);
Header.className =
"ShopItemHeader";
const Name =
document.createElement(
"h3"
);
Name.textContent =
"Buy new Garden";
Header.appendChild(Name);
const Description =
document.createElement(
"p"
);
Description.textContent =
"Buy another independent 3×3 Garden. New Gardens can be named and expanded separately.";
const Details =
document.createElement(
"div"
);
Details.className =
"ShopItemDetails";
Details.append(
CreateShopStat(
"Gardens owned",
GardensOwned.toLocaleString()
),
CreateShopStat(
"New Garden size",
"3×3 (9 plots)"
),
CreateShopStat(
"Price scaling",
"+50% per Garden"
)
);
const BuyButton =
document.createElement(
"button"
);
BuyButton.className =
"ActionButton ShopBuyButton";
BuyButton.type = "button";
BuyButton.textContent =
"Buy - " +
Cost.toLocaleString() +
" Dew";
BuyButton.disabled =
ShopSave.Currency.Dew < Cost ||
ShopPurchasePending;
BuyButton.addEventListener(
"click",
BuyNewGarden
);
Card.append(
Header,
Description,
Details,
BuyButton
);
return Card;
}
function CreatePlantInformationUpgradeCard() {
const Cost =
GetPlantInformationUpgradeCost();
@@ -1188,6 +1302,60 @@ async function BuyGardenExpansion(
}
async function BuyNewGarden() {
if (ShopPurchasePending) {
return;
}
const Cost =
GetNewGardenCost(
ShopSave
);
if (
ShopSave.Currency.Dew <
Cost
) {
SetShopMessage(
"You don't have enough Dew for a new Garden."
);
return;
}
ShopPurchasePending = true;
try {
ShopSave.Currency.Dew -=
Cost;
ShopSave.Statistics.CurrencySpent.Dew +=
Cost;
AddNewGarden(
ShopSave
);
SetShopMessage(
"Bought a new 3×3 Garden for " +
Cost.toLocaleString() +
" Dew. It is now your active Garden."
);
RenderShop();
await SaveGame(
ShopSave
);
} finally {
ShopPurchasePending = false;
RenderShop();
}
}
async function BuyPlantInformationUpgrade() {
if (
ShopPurchasePending ||
+48
View File
@@ -2,6 +2,9 @@ const GardenExpansionBaseCost = 1000;
const PlantInformationUpgradeCost = 500;
const GardenOverviewUpgradeCost = 1000;
const MutationHintsUpgradeCost = 1500;
const NewGardenBaseCost = 30000;
const NewGardenCostMultiplier = 1.5;
const NewGardenCostRounding = 1000;
function GetGardenColumnUpgradeCost(
@@ -112,6 +115,51 @@ function ResetGardenMutationCooldowns(
}
function GetNewGardenCost(
SaveData
) {
const GardensOwned = Math.max(
1,
SaveData.Gardens?.length ?? 1
);
let Cost =
NewGardenBaseCost;
for (
let GardenNumber = 1;
GardenNumber < GardensOwned;
GardenNumber++
) {
Cost = Math.round(
Cost *
NewGardenCostMultiplier /
NewGardenCostRounding
) * NewGardenCostRounding;
}
return Cost;
}
function AddNewGarden(
SaveData
) {
SaveData.Gardens ??= [];
SaveData.Gardens.push(
CreateGardenData()
);
SetActiveGardenIndex(
SaveData,
SaveData.Gardens.length - 1
);
return SaveData.Garden;
}
function HasPlantInformationUpgrade(
SaveData
) {
+40 -1
View File
@@ -313,6 +313,45 @@ input, button, textarea, select {
image-rendering: auto;
}
.GardenSelectorPanel {
display: grid;
grid-template-columns: 3rem minmax(0, 1fr) 3rem;
gap: 0.5rem;
margin-bottom: 1rem;
padding: 0.5rem;
background-color: var(--Mantle);
border: 3px solid var(--Surface2);
}
.GardenSelectorButton {
width: 100%;
min-width: 0;
padding-right: 0;
padding-left: 0;
font-size: 1.25rem;
}
.GardenNameInput {
width: 100%;
min-width: 0;
padding: 0.6rem 0.75rem;
color: var(--Text);
background-color: var(--Surface0);
border: 3px solid var(--Surface2);
border-radius: 0;
text-align: center;
box-shadow: 3px 3px 0 var(--Crust);
}
.GardenNameInput:hover {
border-color: var(--Sky);
}
.GardenNameInput:focus {
outline: 3px solid var(--Yellow);
outline-offset: 3px;
}
.GardenLayout {
display: grid;
grid-template-columns: minmax(0, 1fr) 12rem;
@@ -327,7 +366,7 @@ input, button, textarea, select {
gap: 0.5rem;
padding: 0.75rem;
background-color: var(--Mantle);
border: 4px solid var(--Lavender);
border: 4px solid var(--GardenBorderColour, var(--Lavender));
}
.GardenPlot {