Files
srhtnin.github.io/Scripts/Garden/Garden.js
T
2026-09-02 22:40:58 +02:00

1312 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
let GameSave;
let SelectedSeedId = null;
let SelectedGardenTool = "Seed";
let MutationCheckPending = false;
const GardenUpdateInterval = 1000;
async function StartGame() {
try {
await LoadGameContent();
} catch (Error) {
console.error(
"Couldn't load Garden content:",
Error
);
SetGardenMessage(
"Couldn't load plant or mutation data."
);
return;
}
GameSave = await LoadGame();
BindGardenTools();
EnsureSelectedSeed();
const InitialMutationResult =
CheckGardenMutations(
GameSave
);
RenderGame();
if (InitialMutationResult.Attempted) {
await SaveGame(
GameSave
);
}
setInterval(
GardenTick,
GardenUpdateInterval
);
await RenderUser();
await RenderLeaderboard();
}
function RenderGame() {
EnsureSelectedSeed();
RenderCurrency();
RenderSeeds();
RenderTools();
RenderGarden();
RenderNextHarvest();
}
function BindGardenTools() {
const ShovelButton =
document.getElementById(
"ShovelToolButton"
);
if (ShovelButton === null) {
return;
}
ShovelButton.addEventListener(
"click",
() => {
if (
SelectedGardenTool ===
"Shovel"
) {
SelectedGardenTool =
null;
} else {
SelectedGardenTool =
"Shovel";
}
RenderSeeds();
RenderTools();
RenderGarden();
}
);
}
function RenderTools() {
const ShovelButton =
document.getElementById(
"ShovelToolButton"
);
if (ShovelButton === null) {
return;
}
if (
SelectedGardenTool ===
"Shovel"
) {
ShovelButton.setAttribute(
"aria-current",
"true"
);
} else {
ShovelButton.removeAttribute(
"aria-current"
);
}
}
function RenderCurrency() {
const DewAmount =
document.getElementById(
"DewAmount"
);
DewAmount.textContent =
GameSave.Currency.Dew
.toLocaleString();
}
function EnsureSelectedSeed() {
if (
SelectedSeedId !== null &&
GetSeedCount(
GameSave,
SelectedSeedId
) > 0
) {
return;
}
SelectedSeedId = null;
const SeedIds = Object.keys(
GameSave.Inventory.Seeds
)
.map(Number)
.filter(
PlantId =>
GetSeedCount(
GameSave,
PlantId
) > 0 &&
GetPlantById(
PlantId
) !== null
)
.sort(
(A, B) => A - B
);
if (SeedIds.length > 0) {
SelectedSeedId =
SeedIds[0];
return;
}
if (
SelectedGardenTool ===
"Seed"
) {
SelectedGardenTool = null;
}
}
function RenderSeeds() {
const SeedList =
document.getElementById(
"SeedList"
);
SeedList.replaceChildren();
const SeedIds = Object.keys(
GameSave.Inventory.Seeds
)
.map(Number)
.filter(
PlantId =>
GetSeedCount(
GameSave,
PlantId
) > 0 &&
GetPlantById(
PlantId
) !== null
)
.sort(
(A, B) => A - B
);
if (SeedIds.length === 0) {
const EmptyMessage =
document.createElement(
"p"
);
EmptyMessage.className =
"SeedEmpty";
EmptyMessage.append(
"You don't have any seeds. Visit the "
);
const ShopLink =
document.createElement(
"a"
);
ShopLink.href =
"/Pages/Shop.html";
ShopLink.textContent =
"shop";
EmptyMessage.append(
ShopLink,
"."
);
SeedList.appendChild(
EmptyMessage
);
return;
}
for (
const PlantId
of SeedIds
) {
const Plant =
GetPlantById(
PlantId
);
if (Plant === null) {
continue;
}
const Button =
document.createElement(
"button"
);
Button.className =
"ActionButton SeedButton";
Button.type = "button";
Button.textContent =
Plant.Name +
" ×" +
GetSeedCount(
GameSave,
PlantId
);
if (
SelectedGardenTool ===
"Seed" &&
PlantId ===
SelectedSeedId
) {
Button.setAttribute(
"aria-current",
"true"
);
}
Button.addEventListener(
"click",
() => {
SelectedGardenTool =
"Seed";
SelectedSeedId =
PlantId;
RenderSeeds();
RenderTools();
RenderGarden();
SetGardenMessage(
"Selected " +
Plant.Name +
"."
);
}
);
SeedList.appendChild(
Button
);
}
}
function RenderGarden() {
const GardenGrid =
document.getElementById(
"GardenGrid"
);
GardenGrid.style.setProperty(
"--GardenWidth",
GameSave.Garden.Width
);
GardenGrid.replaceChildren();
const DisplaySettings =
GetGardenDisplaySettings();
GameSave.Garden.Plots.forEach(
(Plot, PlotIndex) => {
const PlotElement =
CreatePlotElement(
Plot,
PlotIndex,
DisplaySettings
);
GardenGrid.appendChild(
PlotElement
);
}
);
}
function GetGardenDisplaySettings() {
const HasPlantInformation =
HasPlantInformationUpgrade(
GameSave
);
return {
ShowPlantNames:
HasPlantInformation &&
GameSave.Preferences
.ShowPlantNames !==
false,
ShowGrowthTimers:
HasPlantInformation &&
GameSave.Preferences
.ShowGrowthTimers !==
false
};
}
function CreatePlotElement(
Plot,
PlotIndex,
DisplaySettings
) {
const Button =
document.createElement(
"button"
);
Button.className =
"PlantTile GardenPlot";
Button.type = "button";
if (
DisplaySettings.ShowPlantNames
) {
Button.classList.add(
"GardenPlotWithName"
);
}
if (
DisplaySettings.ShowGrowthTimers
) {
Button.classList.add(
"GardenPlotWithTimer"
);
}
const Plant =
Plot === null
? null
: Plants[Plot.Plant];
if (
DisplaySettings.ShowPlantNames
) {
Button.appendChild(
CreateGardenPlotName(
Plot,
Plant
)
);
}
const Visual =
document.createElement(
"span"
);
Visual.className =
"GardenPlotVisual";
Button.appendChild(
Visual
);
if (Plot === null) {
Button.classList.add(
"GardenPlotEmpty"
);
Button.title =
"Empty plot";
if (
DisplaySettings
.ShowGrowthTimers
) {
Button.appendChild(
CreateGardenPlotTimer(
null,
null,
0
)
);
}
if (
SelectedGardenTool ===
"Seed"
) {
Button.addEventListener(
"click",
() => {
PlantSeed(
PlotIndex
);
}
);
}
return Button;
}
if (Plant === undefined) {
Visual.textContent = "?";
if (
DisplaySettings
.ShowGrowthTimers
) {
Button.appendChild(
CreateGardenPlotTimer(
Plot,
null,
0
)
);
}
return Button;
}
const Progress =
GetPlantGrowthProgress(
Plot,
Plant
);
Button.style.setProperty(
"--GrowthProgress",
`${Progress * 100}%`
);
const ImagePath =
GetPlantImage(
Plot,
Plant
);
if (ImagePath !== null) {
const Image =
document.createElement(
"img"
);
Image.className =
"PlantSprite";
Image.alt =
Plant.Name;
Image.src =
ImagePath;
Visual.appendChild(
Image
);
} else {
const MissingImage =
document.createElement(
"span"
);
MissingImage.className =
"GardenPlantMissing";
MissingImage.textContent =
Plant.Name;
Visual.appendChild(
MissingImage
);
}
if (
DisplaySettings.ShowGrowthTimers
) {
Button.appendChild(
CreateGardenPlotTimer(
Plot,
Plant,
Progress
)
);
}
if (
SelectedGardenTool ===
"Shovel"
) {
Button.classList.add(
"GardenPlotRemove"
);
Button.title =
"Remove " +
Plant.Name;
Button.addEventListener(
"click",
() => {
RemovePlant(
PlotIndex
);
}
);
return Button;
}
if (
Progress >= 1
) {
Button.classList.add(
"GardenPlotMature"
);
Button.title =
"Harvest " +
Plant.Name;
Button.addEventListener(
"click",
() => {
HarvestPlant(
PlotIndex
);
}
);
} else {
Button.title =
Plant.Name +
" - " +
Math.floor(
Progress * 100
) +
"% grown";
}
return Button;
}
function CreateGardenPlotName(
Plot,
Plant
) {
const Name =
document.createElement(
"span"
);
Name.className =
"GardenPlotName";
if (Plot === null) {
Name.textContent =
"Empty";
} else if (Plant === undefined) {
Name.textContent =
"Unknown";
} else {
Name.textContent =
Plant.Name;
Name.title =
Plant.Name;
}
return Name;
}
function CreateGardenPlotTimer(
Plot,
Plant,
Progress
) {
const Timer =
document.createElement(
"span"
);
Timer.className =
"GardenPlotTimer";
if (
Plot === null ||
Plant === null ||
Plant === undefined
) {
Timer.textContent = "";
return Timer;
}
if (Progress >= 1) {
Timer.textContent =
"Ready";
return Timer;
}
const RemainingTime =
Math.max(
0,
Plant.GrowthTime -
(
Date.now() -
Plot.PlantedAt
)
);
Timer.textContent =
FormatGardenRemainingTime(
RemainingTime
);
return Timer;
}
function FormatGardenRemainingTime(
Milliseconds
) {
const TotalSeconds =
Math.max(
0,
Math.ceil(
Number(
Milliseconds
) / 1000
)
);
const Hours =
Math.floor(
TotalSeconds / 3600
);
const Minutes =
Math.floor(
(
TotalSeconds % 3600
) / 60
);
const Seconds =
TotalSeconds % 60;
if (Hours > 0) {
return (
Hours +
":" +
String(Minutes).padStart(
2,
"0"
) +
":" +
String(Seconds).padStart(
2,
"0"
)
);
}
return (
Minutes +
":" +
String(Seconds).padStart(
2,
"0"
)
);
}
async function PlantSeed(
PlotIndex
) {
if (
GameSave.Garden.Plots[
PlotIndex
] !== null
) {
return;
}
EnsureSelectedSeed();
if (SelectedSeedId === null) {
SetGardenMessage(
"You don't have any seeds to plant."
);
return;
}
const Plant =
GetPlantById(
SelectedSeedId
);
const PlantKey =
GetPlantKeyById(
SelectedSeedId
);
if (
Plant === null ||
PlantKey === null
) {
return;
}
if (
!TakeSeed(
GameSave,
SelectedSeedId
)
) {
EnsureSelectedSeed();
RenderSeeds();
return;
}
GameSave.Garden.Plots[
PlotIndex
] = {
Plant: PlantKey,
PlantedAt: Date.now()
};
SetGardenMessage(
"Planted " +
Plant.Name +
"."
);
CheckGardenMutations(
GameSave
);
RenderGame();
await SaveGame(
GameSave
);
}
async function HarvestPlant(
PlotIndex
) {
const Plot =
GameSave.Garden.Plots[
PlotIndex
];
if (Plot === null) {
return;
}
const Plant =
Plants[Plot.Plant];
if (
Plant === undefined ||
!IsPlantMature(
Plot,
Plant
)
) {
return;
}
const RewardAmount =
GetPlantHarvestReward(
GameSave,
Plant.Id
);
if (RewardAmount === null) {
SetGardenMessage(
"Couldn't calculate the harvest reward for " +
Plant.Name +
"."
);
return;
}
GiveReward({
Currency: "Dew",
Amount: RewardAmount
});
GameSave.Garden.Plots[
PlotIndex
] = null;
SetGardenMessage(
"Harvested " +
Plant.Name +
" for " +
RewardAmount +
" Dew."
);
CheckGardenMutations(
GameSave
);
RenderGame();
await SaveGame(
GameSave
);
}
async function RemovePlant(
PlotIndex
) {
if (
GameSave.Garden.Plots[
PlotIndex
] === null
) {
return;
}
GameSave.Garden.Plots[
PlotIndex
] = null;
CheckGardenMutations(
GameSave
);
RenderGame();
await SaveGame(
GameSave
);
}
async function GardenTick() {
RenderGarden();
RenderNextHarvest();
if (MutationCheckPending) {
return;
}
MutationCheckPending = true;
try {
const MutationResult =
CheckGardenMutations(
GameSave
);
if (MutationResult.Attempted) {
RenderGame();
await SaveGame(
GameSave
);
}
} finally {
MutationCheckPending = false;
}
}
function GiveReward(
Reward
) {
GameSave.Currency[
Reward.Currency
] ??= 0;
GameSave.Currency[
Reward.Currency
] += Reward.Amount;
GameSave.Statistics ??= {};
GameSave.Statistics.CurrencyEarned ??= {};
GameSave.Statistics.CurrencyEarned[
Reward.Currency
] ??= 0;
GameSave.Statistics.CurrencyEarned[
Reward.Currency
] += Reward.Amount;
}
function GetPlantGrowthProgress(
Plot,
Plant
) {
if (
Plant.GrowthTime <= 0
) {
return 1;
}
const Age =
Date.now() -
Plot.PlantedAt;
return Math.max(
0,
Math.min(
Age /
Plant.GrowthTime,
1
)
);
}
function GetPlantTags(
Plot,
Plant
) {
return [
...(Plant.Tags ?? [])
];
}
function IsPlantMature(
Plot,
Plant
) {
return (
GetPlantGrowthProgress(
Plot,
Plant
) >= 1
);
}
function GetPlantImages(
Plot,
Plant
) {
return PlantImages[
Plot.Plant
] ?? [];
}
function GetPlantImage(
Plot,
Plant
) {
const Images =
GetPlantImages(
Plot,
Plant
);
if (Images.length === 0) {
return null;
}
if (Images.length === 1) {
return Images[0];
}
const Progress =
GetPlantGrowthProgress(
Plot,
Plant
);
if (Progress >= 1) {
return Images[
Images.length - 1
];
}
const GrowingImageCount =
Images.length - 1;
const ImageIndex =
Math.min(
Math.floor(
Progress *
GrowingImageCount
),
GrowingImageCount - 1
);
return Images[
ImageIndex
];
}
function SetGardenMessage(
Message
) {
document.getElementById(
"GardenMessage"
).textContent = Message;
}
function GetNextHarvestTime() {
let NextHarvestTime = null;
for (
const Plot
of GameSave.Garden.Plots
) {
if (Plot === null) {
continue;
}
const Plant =
Plants[Plot.Plant];
if (Plant === undefined) {
continue;
}
const HarvestTime =
Plot.PlantedAt +
Plant.GrowthTime;
if (
HarvestTime <=
Date.now()
) {
return Date.now();
}
if (
NextHarvestTime === null ||
HarvestTime <
NextHarvestTime
) {
NextHarvestTime =
HarvestTime;
}
}
return NextHarvestTime;
}
function RenderNextHarvest() {
const Element =
document.getElementById(
"NextHarvest"
);
if (Element === null) {
return;
}
const HarvestTime =
GetNextHarvestTime();
if (HarvestTime === null) {
Element.textContent =
"Nothing planted";
Element.removeAttribute(
"title"
);
return;
}
const Remaining =
HarvestTime -
Date.now();
if (Remaining <= 0) {
Element.textContent =
"Ready now!";
Element.removeAttribute(
"title"
);
return;
}
Element.textContent =
FormatRemainingTime(
Remaining
);
const HarvestDate =
new Date(
HarvestTime
);
Element.title =
"Ready at " +
HarvestDate.toLocaleTimeString(
[],
{
hour: "2-digit",
minute: "2-digit"
}
);
}
function FormatRemainingTime(
Milliseconds
) {
const TotalSeconds =
Math.ceil(
Milliseconds /
1000
);
const Days =
Math.floor(
TotalSeconds /
86400
);
const Hours =
Math.floor(
(
TotalSeconds %
86400
) /
3600
);
const Minutes =
Math.floor(
(
TotalSeconds %
3600
) /
60
);
const Seconds =
TotalSeconds %
60;
if (Days > 0) {
return (
Days +
"d " +
Hours +
"h"
);
}
if (Hours > 0) {
return (
Hours +
"h " +
Minutes +
"m"
);
}
if (Minutes > 0) {
return (
Minutes +
"m " +
Seconds +
"s"
);
}
return (
Seconds +
"s"
);
}
document.addEventListener(
"DOMContentLoaded",
StartGame
);