mirror of
https://git.srhtnin.garden/sarah/srhtnin.github.io.git
synced 2026-10-13 18:38:11 +02:00
3415 lines
59 KiB
JavaScript
3415 lines
59 KiB
JavaScript
let GameSave;
|
||
let SelectedSeedId = null;
|
||
let SelectedGardenTool = "Trowel";
|
||
let FutureSightPreview = null;
|
||
|
||
let MutationCheckPending = false;
|
||
let QuickBuyPurchasePending = false;
|
||
let QuickBuyAmountMode = "One";
|
||
|
||
const GardenUpdateInterval = 1000;
|
||
|
||
const GardenBorderColours = [
|
||
"var(--Rosewater)",
|
||
"var(--Flamingo)",
|
||
"var(--Pink)",
|
||
"var(--Mauve)",
|
||
"var(--Red)",
|
||
"var(--Maroon)",
|
||
"var(--Peach)",
|
||
"var(--Yellow)",
|
||
"var(--Green)",
|
||
"var(--Teal)",
|
||
"var(--Sky)",
|
||
"var(--Sapphire)",
|
||
"var(--Blue)",
|
||
"var(--Lavender)"
|
||
];
|
||
|
||
const GardenPlotDefaultRotation = "East";
|
||
const GardenPlotRotations = [
|
||
"North",
|
||
"East",
|
||
"South",
|
||
"West"
|
||
];
|
||
const GardenPlotRotationLabels = {
|
||
North: "^",
|
||
East: "->",
|
||
South: "v",
|
||
West: "<-"
|
||
};
|
||
|
||
|
||
function GetGardenBorderColour(
|
||
GardenIndex
|
||
) {
|
||
const ColourCount =
|
||
GardenBorderColours.length;
|
||
|
||
const NormalizedIndex =
|
||
(
|
||
Number(GardenIndex) %
|
||
ColourCount +
|
||
ColourCount
|
||
) % ColourCount;
|
||
|
||
return GardenBorderColours[
|
||
NormalizedIndex
|
||
];
|
||
}
|
||
|
||
|
||
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();
|
||
|
||
const InitialSimulationResult =
|
||
AdvanceGameSimulation(
|
||
GameSave,
|
||
Date.now()
|
||
);
|
||
|
||
BindGardenTools();
|
||
BindGardenSelector();
|
||
BindQuickBuyControls();
|
||
EnsureSelectedSeed();
|
||
|
||
RenderGame();
|
||
|
||
if (
|
||
InitialSimulationResult.Advanced ||
|
||
InitialSimulationResult.Dirty
|
||
) {
|
||
await SaveGame(
|
||
GameSave
|
||
);
|
||
}
|
||
|
||
setInterval(
|
||
GardenTick,
|
||
GardenUpdateInterval
|
||
);
|
||
|
||
await RenderUser();
|
||
}
|
||
|
||
|
||
function RenderGame() {
|
||
EnsureSelectedGardenTool();
|
||
EnsureSelectedSeed();
|
||
RenderCurrency();
|
||
RenderQuickBuy();
|
||
RenderSeeds();
|
||
RenderTools();
|
||
RenderGardenSelector();
|
||
RenderGarden();
|
||
RenderNextHarvest();
|
||
RenderGardenOverview();
|
||
RenderGardenEconomy();
|
||
RenderGardenInfoLayout();
|
||
}
|
||
|
||
|
||
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 ActionTime = Date.now();
|
||
|
||
AdvanceGameSimulation(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
const GardenCount =
|
||
GameSave.Gardens.length;
|
||
|
||
if (GardenCount <= 1) {
|
||
return;
|
||
}
|
||
|
||
const NewIndex =
|
||
GameSave.ActiveGardenIndex +
|
||
Direction;
|
||
|
||
if (
|
||
NewIndex < 0 ||
|
||
NewIndex >= GardenCount
|
||
) {
|
||
return;
|
||
}
|
||
|
||
SetActiveGardenIndex(
|
||
GameSave,
|
||
NewIndex
|
||
);
|
||
|
||
FutureSightPreview = null;
|
||
|
||
if (
|
||
SelectedGardenTool ===
|
||
"FutureSight"
|
||
) {
|
||
SelectedGardenTool =
|
||
"Trowel";
|
||
}
|
||
|
||
const MutationResult =
|
||
CheckGardenMutations(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
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;
|
||
|
||
const HasPreviousGarden =
|
||
GardenIndex > 0;
|
||
|
||
const HasNextGarden =
|
||
GardenIndex <
|
||
GardenCount - 1;
|
||
|
||
PreviousButton.disabled =
|
||
!HasPreviousGarden;
|
||
|
||
NextButton.disabled =
|
||
!HasNextGarden;
|
||
|
||
|
||
PreviousButton.style.setProperty(
|
||
"--GardenSelectorBorderColour",
|
||
HasPreviousGarden
|
||
? GetGardenBorderColour(
|
||
GardenIndex - 1
|
||
)
|
||
: "var(--Surface2)"
|
||
);
|
||
|
||
NameInput.style.setProperty(
|
||
"--GardenSelectorBorderColour",
|
||
GetGardenBorderColour(
|
||
GardenIndex
|
||
)
|
||
);
|
||
|
||
NextButton.style.setProperty(
|
||
"--GardenSelectorBorderColour",
|
||
HasNextGarden
|
||
? GetGardenBorderColour(
|
||
GardenIndex + 1
|
||
)
|
||
: "var(--Surface2)"
|
||
);
|
||
|
||
|
||
PreviousButton.setAttribute(
|
||
"aria-label",
|
||
HasPreviousGarden
|
||
? "Previous Garden: " +
|
||
GameSave.Gardens[
|
||
GardenIndex - 1
|
||
].Name
|
||
: "No previous Garden"
|
||
);
|
||
|
||
NextButton.setAttribute(
|
||
"aria-label",
|
||
HasNextGarden
|
||
? "Next Garden: " +
|
||
GameSave.Gardens[
|
||
GardenIndex + 1
|
||
].Name
|
||
: "No next Garden"
|
||
);
|
||
|
||
|
||
if (
|
||
document.activeElement !==
|
||
NameInput
|
||
) {
|
||
NameInput.value =
|
||
GameSave.Garden.Name;
|
||
}
|
||
|
||
NameInput.title =
|
||
"Garden " +
|
||
(GardenIndex + 1) +
|
||
" of " +
|
||
GardenCount;
|
||
}
|
||
|
||
|
||
function CheckAllGardenMutations(
|
||
SaveData,
|
||
AtTime = GetSimulationTime(
|
||
SaveData
|
||
)
|
||
) {
|
||
return CheckAllGardenMutationsAtTime(
|
||
SaveData,
|
||
AtTime
|
||
);
|
||
}
|
||
|
||
|
||
function BindGardenTools() {
|
||
const ToolList =
|
||
document.getElementById(
|
||
"ToolList"
|
||
);
|
||
|
||
if (ToolList === null) {
|
||
return;
|
||
}
|
||
|
||
|
||
ToolList.addEventListener(
|
||
"click",
|
||
Event => {
|
||
const Button =
|
||
Event.target.closest(
|
||
"[data-garden-tool]"
|
||
);
|
||
|
||
if (
|
||
Button === null ||
|
||
!ToolList.contains(
|
||
Button
|
||
)
|
||
) {
|
||
return;
|
||
}
|
||
|
||
const Tool =
|
||
Button.dataset
|
||
.gardenTool;
|
||
|
||
SelectGardenTool(
|
||
Tool
|
||
);
|
||
}
|
||
);
|
||
}
|
||
|
||
|
||
async function SelectGardenTool(
|
||
Tool
|
||
) {
|
||
if (
|
||
!IsGardenToolUnlocked(
|
||
GameSave,
|
||
Tool
|
||
)
|
||
) {
|
||
return;
|
||
}
|
||
|
||
|
||
if (Tool === "FutureSight") {
|
||
if (
|
||
!IsFutureSightReady(
|
||
GameSave
|
||
) &&
|
||
FutureSightPreview === null
|
||
) {
|
||
SetGardenMessage(
|
||
"Future Sight is still recharging for " +
|
||
FormatGardenRemainingTime(
|
||
GetFutureSightCooldownRemaining(
|
||
GameSave
|
||
)
|
||
) +
|
||
"."
|
||
);
|
||
|
||
RenderTools();
|
||
|
||
return;
|
||
}
|
||
|
||
SelectedGardenTool =
|
||
"FutureSight";
|
||
|
||
if (FutureSightPreview === null) {
|
||
await UseFutureSight();
|
||
} else {
|
||
RenderTools();
|
||
RenderGarden();
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
SelectedGardenTool = Tool;
|
||
FutureSightPreview = null;
|
||
|
||
RenderSeeds();
|
||
RenderTools();
|
||
RenderGarden();
|
||
}
|
||
|
||
|
||
function EnsureSelectedGardenTool() {
|
||
if (
|
||
!IsGardenToolUnlocked(
|
||
GameSave,
|
||
SelectedGardenTool
|
||
)
|
||
) {
|
||
SelectedGardenTool =
|
||
"Trowel";
|
||
|
||
FutureSightPreview = null;
|
||
}
|
||
}
|
||
|
||
|
||
function RenderTools() {
|
||
const ToolList =
|
||
document.getElementById(
|
||
"ToolList"
|
||
);
|
||
|
||
if (ToolList === null) {
|
||
return;
|
||
}
|
||
|
||
|
||
EnsureSelectedGardenTool();
|
||
|
||
const ToolDefinitions = [
|
||
[
|
||
"Trowel",
|
||
"Trowel"
|
||
],
|
||
[
|
||
"MagicTrowel",
|
||
"Magic trowel"
|
||
],
|
||
[
|
||
"Shovel",
|
||
"Shovel"
|
||
],
|
||
[
|
||
"Fertilizer",
|
||
"Fertilizer"
|
||
],
|
||
[
|
||
"FutureSight",
|
||
"Future Sight"
|
||
]
|
||
];
|
||
|
||
|
||
ToolList.replaceChildren();
|
||
|
||
|
||
for (
|
||
const [Tool, Name]
|
||
of ToolDefinitions
|
||
) {
|
||
if (
|
||
!IsGardenToolUnlocked(
|
||
GameSave,
|
||
Tool
|
||
)
|
||
) {
|
||
continue;
|
||
}
|
||
|
||
|
||
const Button =
|
||
document.createElement(
|
||
"button"
|
||
);
|
||
|
||
Button.className =
|
||
"ActionButton ToolButton ToolButton" +
|
||
Tool;
|
||
|
||
Button.type = "button";
|
||
|
||
Button.dataset.gardenTool =
|
||
Tool;
|
||
|
||
|
||
let Label = Name;
|
||
|
||
if (Tool === "Fertilizer") {
|
||
Label +=
|
||
" ×" +
|
||
Number(
|
||
GameSave.Inventory
|
||
.Fertilizer ?? 0
|
||
).toLocaleString();
|
||
}
|
||
|
||
if (Tool === "FutureSight") {
|
||
const Remaining =
|
||
GetFutureSightCooldownRemaining(
|
||
GameSave
|
||
);
|
||
|
||
if (Remaining > 0) {
|
||
Label +=
|
||
" (" +
|
||
FormatGardenRemainingTime(
|
||
Remaining
|
||
) +
|
||
")";
|
||
}
|
||
|
||
Button.disabled =
|
||
Remaining > 0 &&
|
||
!(
|
||
SelectedGardenTool ===
|
||
"FutureSight" &&
|
||
FutureSightPreview !==
|
||
null
|
||
);
|
||
}
|
||
|
||
|
||
Button.textContent = Label;
|
||
|
||
|
||
if (
|
||
SelectedGardenTool ===
|
||
Tool
|
||
) {
|
||
Button.setAttribute(
|
||
"aria-current",
|
||
"true"
|
||
);
|
||
}
|
||
|
||
|
||
ToolList.appendChild(
|
||
Button
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
function BindQuickBuyControls() {
|
||
const Controls = [
|
||
[
|
||
"QuickBuyOneButton",
|
||
"One"
|
||
],
|
||
[
|
||
"QuickBuyTenButton",
|
||
"Ten"
|
||
],
|
||
[
|
||
"QuickBuyGardenButton",
|
||
"Garden"
|
||
]
|
||
];
|
||
|
||
|
||
for (
|
||
const [ButtonId, Mode]
|
||
of Controls
|
||
) {
|
||
const Button =
|
||
document.getElementById(
|
||
ButtonId
|
||
);
|
||
|
||
Button?.addEventListener(
|
||
"click",
|
||
() => {
|
||
QuickBuyAmountMode =
|
||
Mode;
|
||
|
||
RenderQuickBuy();
|
||
}
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
function GetQuickBuyAmount() {
|
||
if (
|
||
QuickBuyAmountMode ===
|
||
"Ten"
|
||
) {
|
||
return 10;
|
||
}
|
||
|
||
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 TenButton =
|
||
document.getElementById(
|
||
"QuickBuyTenButton"
|
||
);
|
||
|
||
const GardenButton =
|
||
document.getElementById(
|
||
"QuickBuyGardenButton"
|
||
);
|
||
|
||
|
||
const AmountButtons = [
|
||
[OneButton, "One", 1],
|
||
[TenButton, "Ten", 10],
|
||
[
|
||
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
|
||
);
|
||
|
||
if (
|
||
GameSave.Preferences
|
||
.SelectQuickBoughtPlant !==
|
||
false
|
||
) {
|
||
SelectInventoryPlant(
|
||
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(
|
||
"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;
|
||
}
|
||
|
||
}
|
||
|
||
|
||
function SelectInventoryPlant(
|
||
PlantId
|
||
) {
|
||
if (
|
||
GetPlantById(
|
||
PlantId
|
||
) === null ||
|
||
GetSeedCount(
|
||
GameSave,
|
||
PlantId
|
||
) <= 0
|
||
) {
|
||
return false;
|
||
}
|
||
|
||
|
||
SelectedSeedId =
|
||
Number(PlantId);
|
||
|
||
if (
|
||
GameSave.Preferences
|
||
.SelectTrowelWithInventoryPlant !==
|
||
false
|
||
) {
|
||
SelectedGardenTool =
|
||
"Trowel";
|
||
|
||
FutureSightPreview = null;
|
||
}
|
||
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
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 (
|
||
PlantId ===
|
||
SelectedSeedId
|
||
) {
|
||
Button.setAttribute(
|
||
"aria-current",
|
||
"true"
|
||
);
|
||
}
|
||
|
||
Button.addEventListener(
|
||
"click",
|
||
() => {
|
||
SelectInventoryPlant(
|
||
PlantId
|
||
);
|
||
|
||
RenderSeeds();
|
||
RenderTools();
|
||
RenderGarden();
|
||
|
||
SetGardenMessage(
|
||
"Selected " +
|
||
Plant.Name +
|
||
"."
|
||
);
|
||
}
|
||
);
|
||
|
||
SeedList.appendChild(
|
||
Button
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
function RenderGarden() {
|
||
const GardenGrid =
|
||
document.getElementById(
|
||
"GardenGrid"
|
||
);
|
||
|
||
const GardenPanel =
|
||
document.getElementById(
|
||
"Plots"
|
||
);
|
||
|
||
GardenGrid.style.setProperty(
|
||
"--GardenWidth",
|
||
GameSave.Garden.Width
|
||
);
|
||
|
||
GardenPanel.style.setProperty(
|
||
"--GardenBorderColour",
|
||
GetGardenBorderColour(
|
||
GameSave.ActiveGardenIndex
|
||
)
|
||
);
|
||
|
||
GardenPanel.classList.toggle(
|
||
"GardenPlotsFutureSight",
|
||
SelectedGardenTool ===
|
||
"FutureSight" &&
|
||
FutureSightPreview !== null
|
||
);
|
||
|
||
GardenGrid.replaceChildren();
|
||
|
||
|
||
const DisplaySettings =
|
||
GetGardenDisplaySettings();
|
||
|
||
const IsFutureSightPreview =
|
||
SelectedGardenTool ===
|
||
"FutureSight" &&
|
||
FutureSightPreview !== null;
|
||
|
||
|
||
GameSave.Garden.Plots.forEach(
|
||
(Plot, PlotIndex) => {
|
||
const DisplayPlot =
|
||
IsFutureSightPreview
|
||
? FutureSightPreview
|
||
.Plots[PlotIndex]
|
||
: Plot;
|
||
|
||
const PlotElement =
|
||
CreatePlotElement(
|
||
DisplayPlot,
|
||
PlotIndex,
|
||
DisplaySettings,
|
||
IsFutureSightPreview
|
||
);
|
||
|
||
if (IsFutureSightPreview) {
|
||
PlotElement.classList.add(
|
||
"GardenPlotFutureSight"
|
||
);
|
||
|
||
if (
|
||
!AreGardenPlotsEqual(
|
||
Plot,
|
||
DisplayPlot
|
||
)
|
||
) {
|
||
PlotElement.classList.add(
|
||
"GardenPlotFutureSightChanged"
|
||
);
|
||
}
|
||
}
|
||
|
||
GardenGrid.appendChild(
|
||
PlotElement
|
||
);
|
||
}
|
||
);
|
||
}
|
||
|
||
|
||
function AreGardenPlotsEqual(
|
||
A,
|
||
B
|
||
) {
|
||
if (A === null || B === null) {
|
||
return A === B;
|
||
}
|
||
|
||
return (
|
||
A.Plant === B.Plant &&
|
||
Number(A.PlantedAt) ===
|
||
Number(B.PlantedAt) &&
|
||
GetGardenPlotRotation(A) ===
|
||
GetGardenPlotRotation(B)
|
||
);
|
||
}
|
||
|
||
|
||
function GetGardenDisplaySettings() {
|
||
const HasPlantInformation =
|
||
HasPlantInformationUpgrade(
|
||
GameSave
|
||
);
|
||
|
||
return {
|
||
ShowPlantNames:
|
||
HasPlantInformation &&
|
||
GameSave.Preferences
|
||
.ShowPlantNames !==
|
||
false,
|
||
|
||
ShowGrowthTimers:
|
||
HasPlantInformation &&
|
||
GameSave.Preferences
|
||
.ShowGrowthTimers !==
|
||
false,
|
||
|
||
ShowPlotRotation:
|
||
HasRotationUpgrade(
|
||
GameSave
|
||
) &&
|
||
GameSave.Preferences
|
||
.ShowPlotRotation !==
|
||
false
|
||
};
|
||
}
|
||
|
||
|
||
function GetGardenPlotRotation(
|
||
Plot
|
||
) {
|
||
return GardenPlotRotations.includes(
|
||
Plot?.Rotation
|
||
)
|
||
? Plot.Rotation
|
||
: GardenPlotDefaultRotation;
|
||
}
|
||
|
||
|
||
function GetGardenPlotRotationLabel(
|
||
Plot
|
||
) {
|
||
return GardenPlotRotationLabels[
|
||
GetGardenPlotRotation(
|
||
Plot
|
||
)
|
||
] ?? "->";
|
||
}
|
||
|
||
|
||
async function RotateGardenPlot(
|
||
PlotIndex
|
||
) {
|
||
if (
|
||
!HasRotationUpgrade(
|
||
GameSave
|
||
)
|
||
) {
|
||
return;
|
||
}
|
||
|
||
const ActionTime = Date.now();
|
||
|
||
AdvanceGameSimulation(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
const Plot =
|
||
GameSave.Garden.Plots[
|
||
PlotIndex
|
||
];
|
||
|
||
if (
|
||
Plot === null ||
|
||
typeof Plot !== "object"
|
||
) {
|
||
return;
|
||
}
|
||
|
||
const CurrentIndex =
|
||
GardenPlotRotations.indexOf(
|
||
GetGardenPlotRotation(
|
||
Plot
|
||
)
|
||
);
|
||
|
||
Plot.Rotation =
|
||
GardenPlotRotations[
|
||
(CurrentIndex + 1) %
|
||
GardenPlotRotations.length
|
||
];
|
||
|
||
const Plant =
|
||
Plants[Plot.Plant];
|
||
|
||
SetGardenMessage(
|
||
"Rotated " +
|
||
(Plant?.Name ?? "plot") +
|
||
" " +
|
||
Plot.Rotation.toLowerCase() +
|
||
"."
|
||
);
|
||
|
||
RenderGame();
|
||
|
||
await SaveGame(
|
||
GameSave
|
||
);
|
||
}
|
||
|
||
|
||
function CreatePlotElement(
|
||
Plot,
|
||
PlotIndex,
|
||
DisplaySettings,
|
||
IsPreview = false
|
||
) {
|
||
const Tile =
|
||
document.createElement(
|
||
"div"
|
||
);
|
||
|
||
Tile.className =
|
||
"PlantTile GardenPlot";
|
||
|
||
const MainButton =
|
||
document.createElement(
|
||
"button"
|
||
);
|
||
|
||
MainButton.className =
|
||
"GardenPlotMain";
|
||
MainButton.type = "button";
|
||
|
||
|
||
if (
|
||
DisplaySettings.ShowPlantNames
|
||
) {
|
||
Tile.classList.add(
|
||
"GardenPlotWithName"
|
||
);
|
||
}
|
||
|
||
|
||
const Plant =
|
||
Plot === null
|
||
? null
|
||
: Plants[Plot.Plant];
|
||
|
||
if (
|
||
DisplaySettings.ShowPlantNames
|
||
) {
|
||
MainButton.appendChild(
|
||
CreateGardenPlotName(
|
||
Plot,
|
||
Plant
|
||
)
|
||
);
|
||
}
|
||
|
||
|
||
const Visual =
|
||
document.createElement(
|
||
"span"
|
||
);
|
||
|
||
Visual.className =
|
||
"GardenPlotVisual";
|
||
|
||
MainButton.appendChild(
|
||
Visual
|
||
);
|
||
|
||
Tile.appendChild(
|
||
MainButton
|
||
);
|
||
|
||
|
||
if (Plot === null) {
|
||
Tile.classList.add(
|
||
"GardenPlotEmpty"
|
||
);
|
||
|
||
MainButton.title =
|
||
IsPreview
|
||
? "Future Sight: empty plot"
|
||
: "Empty plot";
|
||
|
||
AppendGardenPlotFooter(
|
||
Tile,
|
||
null,
|
||
null,
|
||
0,
|
||
PlotIndex,
|
||
DisplaySettings,
|
||
IsPreview
|
||
);
|
||
|
||
if (IsPreview) {
|
||
MainButton.disabled = true;
|
||
return Tile;
|
||
}
|
||
|
||
if (
|
||
SelectedGardenTool ===
|
||
"Trowel"
|
||
) {
|
||
MainButton.addEventListener(
|
||
"click",
|
||
() => {
|
||
PlantSeed(
|
||
PlotIndex
|
||
);
|
||
}
|
||
);
|
||
} else if (
|
||
SelectedGardenTool ===
|
||
"MagicTrowel"
|
||
) {
|
||
Tile.classList.add(
|
||
"GardenPlotMagicPlant"
|
||
);
|
||
|
||
MainButton.title =
|
||
"Fill empty plots with the selected seed";
|
||
|
||
MainButton.addEventListener(
|
||
"click",
|
||
PlantManySeeds
|
||
);
|
||
} else {
|
||
MainButton.disabled = true;
|
||
}
|
||
|
||
return Tile;
|
||
}
|
||
|
||
|
||
if (Plant === undefined) {
|
||
Visual.textContent = "?";
|
||
|
||
AppendGardenPlotFooter(
|
||
Tile,
|
||
Plot,
|
||
null,
|
||
0,
|
||
PlotIndex,
|
||
DisplaySettings,
|
||
IsPreview
|
||
);
|
||
|
||
MainButton.disabled = true;
|
||
return Tile;
|
||
}
|
||
|
||
|
||
const Progress =
|
||
GetPlantGrowthProgress(
|
||
Plot,
|
||
Plant
|
||
);
|
||
|
||
Tile.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
|
||
);
|
||
}
|
||
|
||
|
||
AppendGardenPlotFooter(
|
||
Tile,
|
||
Plot,
|
||
Plant,
|
||
Progress,
|
||
PlotIndex,
|
||
DisplaySettings,
|
||
IsPreview
|
||
);
|
||
|
||
|
||
if (IsPreview) {
|
||
MainButton.title =
|
||
"Future Sight: " +
|
||
Plant.Name;
|
||
|
||
MainButton.disabled = true;
|
||
return Tile;
|
||
}
|
||
|
||
|
||
if (
|
||
SelectedGardenTool ===
|
||
"Shovel"
|
||
) {
|
||
Tile.classList.add(
|
||
"GardenPlotRemove"
|
||
);
|
||
|
||
MainButton.title =
|
||
"Remove " +
|
||
Plant.Name;
|
||
|
||
MainButton.addEventListener(
|
||
"click",
|
||
() => {
|
||
RemovePlant(
|
||
PlotIndex
|
||
);
|
||
}
|
||
);
|
||
|
||
return Tile;
|
||
}
|
||
|
||
|
||
if (
|
||
SelectedGardenTool ===
|
||
"Fertilizer"
|
||
) {
|
||
if (Progress >= 1) {
|
||
MainButton.title =
|
||
Plant.Name +
|
||
" is already fully grown";
|
||
|
||
MainButton.disabled = true;
|
||
return Tile;
|
||
}
|
||
|
||
Tile.classList.add(
|
||
"GardenPlotFertilize"
|
||
);
|
||
|
||
MainButton.title =
|
||
"Fertilize " +
|
||
Plant.Name +
|
||
" (" +
|
||
GetFertilizerGrowthMinutes(
|
||
GameSave
|
||
) +
|
||
" minutes)";
|
||
|
||
MainButton.addEventListener(
|
||
"click",
|
||
() => {
|
||
FertilizePlant(
|
||
PlotIndex
|
||
);
|
||
}
|
||
);
|
||
|
||
return Tile;
|
||
}
|
||
|
||
|
||
if (
|
||
SelectedGardenTool !==
|
||
"Trowel"
|
||
) {
|
||
MainButton.title =
|
||
Plant.Name +
|
||
" - " +
|
||
Math.floor(
|
||
Progress * 100
|
||
) +
|
||
"% grown";
|
||
|
||
MainButton.disabled = true;
|
||
return Tile;
|
||
}
|
||
|
||
|
||
if (
|
||
Progress >= 1
|
||
) {
|
||
Tile.classList.add(
|
||
"GardenPlotMature"
|
||
);
|
||
|
||
MainButton.title =
|
||
"Harvest " +
|
||
Plant.Name;
|
||
|
||
MainButton.addEventListener(
|
||
"click",
|
||
() => {
|
||
HarvestPlant(
|
||
PlotIndex
|
||
);
|
||
}
|
||
);
|
||
} else {
|
||
MainButton.title =
|
||
Plant.Name +
|
||
" - " +
|
||
Math.floor(
|
||
Progress * 100
|
||
) +
|
||
"% grown";
|
||
|
||
MainButton.disabled = true;
|
||
}
|
||
|
||
|
||
return Tile;
|
||
}
|
||
|
||
|
||
function AppendGardenPlotFooter(
|
||
Tile,
|
||
Plot,
|
||
Plant,
|
||
Progress,
|
||
PlotIndex,
|
||
DisplaySettings,
|
||
IsPreview
|
||
) {
|
||
const ShowTimer =
|
||
DisplaySettings.ShowGrowthTimers;
|
||
|
||
const ShowRotation =
|
||
Plot !== null &&
|
||
DisplaySettings.ShowPlotRotation &&
|
||
!IsPreview;
|
||
|
||
if (
|
||
!ShowTimer &&
|
||
!ShowRotation
|
||
) {
|
||
return;
|
||
}
|
||
|
||
Tile.classList.add(
|
||
"GardenPlotWithFooter"
|
||
);
|
||
|
||
const Footer =
|
||
document.createElement(
|
||
"span"
|
||
);
|
||
|
||
Footer.className =
|
||
"GardenPlotFooter";
|
||
|
||
if (
|
||
ShowTimer &&
|
||
ShowRotation
|
||
) {
|
||
Footer.classList.add(
|
||
"GardenPlotFooterSplit"
|
||
);
|
||
}
|
||
|
||
if (ShowTimer) {
|
||
Footer.appendChild(
|
||
CreateGardenPlotTimer(
|
||
Plot,
|
||
Plant,
|
||
Progress
|
||
)
|
||
);
|
||
}
|
||
|
||
if (ShowRotation) {
|
||
const RotateButton =
|
||
document.createElement(
|
||
"button"
|
||
);
|
||
|
||
RotateButton.className =
|
||
"GardenPlotRotationButton";
|
||
RotateButton.type = "button";
|
||
RotateButton.textContent =
|
||
GetGardenPlotRotationLabel(
|
||
Plot
|
||
);
|
||
RotateButton.title =
|
||
"Rotate plot clockwise";
|
||
RotateButton.setAttribute(
|
||
"aria-label",
|
||
"Rotate " +
|
||
(Plant?.Name ?? "plot") +
|
||
" clockwise"
|
||
);
|
||
|
||
RotateButton.addEventListener(
|
||
"click",
|
||
() => {
|
||
RotateGardenPlot(
|
||
PlotIndex
|
||
);
|
||
}
|
||
);
|
||
|
||
Footer.appendChild(
|
||
RotateButton
|
||
);
|
||
}
|
||
|
||
Tile.appendChild(Footer);
|
||
}
|
||
|
||
|
||
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 -
|
||
(
|
||
GetSimulationTime(
|
||
GameSave
|
||
) -
|
||
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
|
||
) {
|
||
const ActionTime = Date.now();
|
||
|
||
AdvanceGameSimulation(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
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: ActionTime,
|
||
Rotation: GardenPlotDefaultRotation
|
||
};
|
||
|
||
|
||
SetGardenMessage(
|
||
"Planted " +
|
||
Plant.Name +
|
||
"."
|
||
);
|
||
|
||
CheckGardenMutations(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
RenderGame();
|
||
|
||
await SaveGame(
|
||
GameSave
|
||
);
|
||
}
|
||
|
||
|
||
async function PlantManySeeds() {
|
||
const ActionTime = Date.now();
|
||
|
||
AdvanceGameSimulation(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
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;
|
||
}
|
||
|
||
|
||
const EmptyPlotIndexes = [];
|
||
|
||
GameSave.Garden.Plots.forEach(
|
||
(Plot, PlotIndex) => {
|
||
if (Plot === null) {
|
||
EmptyPlotIndexes.push(
|
||
PlotIndex
|
||
);
|
||
}
|
||
}
|
||
);
|
||
|
||
if (EmptyPlotIndexes.length === 0) {
|
||
SetGardenMessage(
|
||
"There aren't any empty plots to fill."
|
||
);
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
const SeedCount =
|
||
GetSeedCount(
|
||
GameSave,
|
||
SelectedSeedId
|
||
);
|
||
|
||
const PlantCount = Math.min(
|
||
SeedCount,
|
||
EmptyPlotIndexes.length
|
||
);
|
||
|
||
if (PlantCount <= 0) {
|
||
EnsureSelectedSeed();
|
||
RenderGame();
|
||
|
||
SetGardenMessage(
|
||
"You don't have any of that seed left."
|
||
);
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
if (
|
||
!TakeSeed(
|
||
GameSave,
|
||
SelectedSeedId,
|
||
PlantCount
|
||
)
|
||
) {
|
||
return;
|
||
}
|
||
|
||
|
||
const PlantedAt = ActionTime;
|
||
|
||
for (
|
||
let Index = 0;
|
||
Index < PlantCount;
|
||
Index++
|
||
) {
|
||
GameSave.Garden.Plots[
|
||
EmptyPlotIndexes[Index]
|
||
] = {
|
||
Plant: PlantKey,
|
||
PlantedAt: PlantedAt,
|
||
Rotation: GardenPlotDefaultRotation
|
||
};
|
||
}
|
||
|
||
|
||
SetGardenMessage(
|
||
"Magic trowel planted " +
|
||
PlantCount.toLocaleString() +
|
||
" " +
|
||
Plant.Name +
|
||
(PlantCount === 1
|
||
? " seed."
|
||
: " seeds.")
|
||
);
|
||
|
||
CheckGardenMutations(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
RenderGame();
|
||
|
||
await SaveGame(
|
||
GameSave
|
||
);
|
||
}
|
||
|
||
|
||
async function HarvestPlant(
|
||
PlotIndex
|
||
) {
|
||
const ActionTime = Date.now();
|
||
|
||
AdvanceGameSimulation(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
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,
|
||
ActionTime
|
||
);
|
||
|
||
RenderGame();
|
||
|
||
await SaveGame(
|
||
GameSave
|
||
);
|
||
}
|
||
|
||
|
||
async function RemovePlant(
|
||
PlotIndex
|
||
) {
|
||
const ActionTime = Date.now();
|
||
|
||
AdvanceGameSimulation(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
const Plot =
|
||
GameSave.Garden.Plots[
|
||
PlotIndex
|
||
];
|
||
|
||
if (Plot === null) {
|
||
return;
|
||
}
|
||
|
||
|
||
const Plant =
|
||
Plants[Plot.Plant];
|
||
|
||
let ReturnMessage = "";
|
||
|
||
|
||
if (
|
||
Plant !== undefined &&
|
||
DoesShovelReturnSeed(
|
||
GameSave
|
||
)
|
||
) {
|
||
AddSeed(
|
||
GameSave,
|
||
Plant.Id,
|
||
1
|
||
);
|
||
|
||
ReturnMessage =
|
||
" The seed was returned.";
|
||
} else if (
|
||
Plant !== undefined
|
||
) {
|
||
const RefundRate =
|
||
GetShovelRefundRate(
|
||
GameSave
|
||
);
|
||
|
||
const SeedCost =
|
||
GetPlantShopCost(
|
||
GameSave,
|
||
Plant.Id
|
||
);
|
||
|
||
const Refund =
|
||
SeedCost === null
|
||
? 0
|
||
: Math.floor(
|
||
SeedCost *
|
||
RefundRate
|
||
);
|
||
|
||
if (Refund > 0) {
|
||
GameSave.Currency.Dew +=
|
||
Refund;
|
||
|
||
ReturnMessage =
|
||
" Recovered " +
|
||
Refund.toLocaleString() +
|
||
" Dew.";
|
||
}
|
||
}
|
||
|
||
|
||
GameSave.Garden.Plots[
|
||
PlotIndex
|
||
] = null;
|
||
|
||
|
||
SetGardenMessage(
|
||
"Removed " +
|
||
(Plant?.Name ?? "plant") +
|
||
"." +
|
||
ReturnMessage
|
||
);
|
||
|
||
CheckGardenMutations(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
RenderGame();
|
||
|
||
await SaveGame(
|
||
GameSave
|
||
);
|
||
}
|
||
|
||
|
||
async function FertilizePlant(
|
||
PlotIndex
|
||
) {
|
||
const ActionTime = Date.now();
|
||
|
||
AdvanceGameSimulation(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
const Plot =
|
||
GameSave.Garden.Plots[
|
||
PlotIndex
|
||
];
|
||
|
||
if (Plot === null) {
|
||
return;
|
||
}
|
||
|
||
|
||
const Plant =
|
||
Plants[Plot.Plant];
|
||
|
||
if (
|
||
Plant === undefined ||
|
||
IsPlantMature(
|
||
Plot,
|
||
Plant
|
||
)
|
||
) {
|
||
return;
|
||
}
|
||
|
||
|
||
if (
|
||
!TakeFertilizerUse(
|
||
GameSave
|
||
)
|
||
) {
|
||
SetGardenMessage(
|
||
"You're out of Fertilizer. Buy more in the Shop."
|
||
);
|
||
|
||
RenderTools();
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
const Minutes =
|
||
GetFertilizerGrowthMinutes(
|
||
GameSave
|
||
);
|
||
|
||
Plot.PlantedAt -=
|
||
Minutes * 60 * 1000;
|
||
|
||
|
||
SetGardenMessage(
|
||
"Fertilized " +
|
||
Plant.Name +
|
||
", advancing its growth by " +
|
||
Minutes +
|
||
(Minutes === 1
|
||
? " minute."
|
||
: " minutes.")
|
||
);
|
||
|
||
CheckGardenMutations(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
RenderGame();
|
||
|
||
await SaveGame(
|
||
GameSave
|
||
);
|
||
}
|
||
|
||
|
||
async function UseFutureSight() {
|
||
const ActionTime = Date.now();
|
||
|
||
AdvanceGameSimulation(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
if (
|
||
!IsGardenToolUnlocked(
|
||
GameSave,
|
||
"FutureSight"
|
||
) ||
|
||
!IsFutureSightReady(
|
||
GameSave
|
||
)
|
||
) {
|
||
return;
|
||
}
|
||
|
||
|
||
const Preview =
|
||
CreateFutureSightPreview();
|
||
|
||
if (Preview === null) {
|
||
SelectedGardenTool =
|
||
"Trowel";
|
||
|
||
SetGardenMessage(
|
||
"Future Sight can't see a mutation from the current Garden."
|
||
);
|
||
|
||
RenderTools();
|
||
RenderGarden();
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
FutureSightPreview = Preview;
|
||
|
||
StartFutureSightCooldown(
|
||
GameSave
|
||
);
|
||
|
||
|
||
SetGardenMessage(
|
||
"Future Sight: highlighted plots show the next mutation succeeding."
|
||
);
|
||
|
||
RenderSeeds();
|
||
RenderTools();
|
||
RenderGarden();
|
||
|
||
await SaveGame(
|
||
GameSave
|
||
);
|
||
}
|
||
|
||
|
||
function CreateFutureSightPreview() {
|
||
const PreviewSave =
|
||
NormalizeSaveData(
|
||
JSON.parse(
|
||
JSON.stringify(
|
||
GameSave
|
||
)
|
||
)
|
||
);
|
||
|
||
const Candidates =
|
||
FindGardenMutationCandidates(
|
||
PreviewSave
|
||
)
|
||
.filter(
|
||
Candidate =>
|
||
!IsMutationCandidateOnCooldown(
|
||
PreviewSave,
|
||
Candidate
|
||
)
|
||
)
|
||
.sort(
|
||
CompareMutationCandidates
|
||
);
|
||
|
||
if (Candidates.length === 0) {
|
||
return null;
|
||
}
|
||
|
||
|
||
const Candidate =
|
||
Candidates[0];
|
||
|
||
ApplyMutationResult(
|
||
PreviewSave,
|
||
Candidate,
|
||
Candidate.Success
|
||
);
|
||
|
||
|
||
return {
|
||
MutationId:
|
||
Candidate.Mutation.Id,
|
||
|
||
Plots:
|
||
PreviewSave.Garden.Plots.map(
|
||
Plot =>
|
||
CloneMutationPlot(
|
||
Plot
|
||
)
|
||
)
|
||
};
|
||
}
|
||
|
||
|
||
async function GardenTick() {
|
||
if (MutationCheckPending) {
|
||
return;
|
||
}
|
||
|
||
MutationCheckPending = true;
|
||
|
||
try {
|
||
const SimulationResult =
|
||
AdvanceGameSimulation(
|
||
GameSave,
|
||
Date.now()
|
||
);
|
||
|
||
if (
|
||
SimulationResult.Attempted &&
|
||
FutureSightPreview !== null
|
||
) {
|
||
FutureSightPreview = null;
|
||
|
||
if (
|
||
SelectedGardenTool ===
|
||
"FutureSight"
|
||
) {
|
||
SelectedGardenTool =
|
||
"Trowel";
|
||
}
|
||
}
|
||
|
||
RenderGame();
|
||
|
||
if (SimulationResult.Dirty) {
|
||
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 =
|
||
GetSimulationTime(
|
||
GameSave
|
||
) -
|
||
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
|
||
) {
|
||
const Direction =
|
||
Plot !== null &&
|
||
Plant?.DirectionalSprites === true
|
||
? GetGardenPlotRotation(
|
||
Plot
|
||
)
|
||
: null;
|
||
|
||
return GetPlantImageSources(
|
||
Plant ?? Plot.Plant,
|
||
Direction
|
||
);
|
||
}
|
||
|
||
|
||
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 GetNextHarvestInfo() {
|
||
let NextHarvest = 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 (
|
||
NextHarvest === null ||
|
||
HarvestTime <
|
||
NextHarvest.Time
|
||
) {
|
||
NextHarvest = {
|
||
Time: HarvestTime,
|
||
Plant
|
||
};
|
||
}
|
||
}
|
||
|
||
|
||
return NextHarvest;
|
||
}
|
||
|
||
|
||
function RenderNextHarvest() {
|
||
const Line =
|
||
document.getElementById(
|
||
"NextHarvestLine"
|
||
);
|
||
|
||
const Element =
|
||
document.getElementById(
|
||
"NextHarvest"
|
||
);
|
||
|
||
if (
|
||
Line === null ||
|
||
Element === null
|
||
) {
|
||
return;
|
||
}
|
||
|
||
|
||
const HasOverview =
|
||
HasGardenOverviewUpgrade(
|
||
GameSave
|
||
);
|
||
|
||
const ShowNextHarvest =
|
||
!HasOverview ||
|
||
GameSave.Preferences
|
||
.ShowNextHarvest !== false;
|
||
|
||
Line.hidden = !ShowNextHarvest;
|
||
|
||
if (!ShowNextHarvest) {
|
||
return;
|
||
}
|
||
|
||
|
||
const Harvest =
|
||
GetNextHarvestInfo();
|
||
|
||
if (Harvest === null) {
|
||
Element.textContent =
|
||
"Nothing planted";
|
||
|
||
Element.removeAttribute(
|
||
"title"
|
||
);
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
const Remaining =
|
||
Harvest.Time -
|
||
GetSimulationTime(
|
||
GameSave
|
||
);
|
||
|
||
const IncludePlantName =
|
||
HasOverview;
|
||
|
||
|
||
if (Remaining <= 0) {
|
||
Element.textContent =
|
||
IncludePlantName
|
||
? Harvest.Plant.Name +
|
||
" is ready!"
|
||
: "Ready now!";
|
||
|
||
Element.removeAttribute(
|
||
"title"
|
||
);
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
const RemainingText =
|
||
FormatRemainingTime(
|
||
Remaining
|
||
);
|
||
|
||
Element.textContent =
|
||
IncludePlantName
|
||
? Harvest.Plant.Name +
|
||
" in " +
|
||
RemainingText
|
||
: RemainingText;
|
||
|
||
|
||
const HarvestDate =
|
||
new Date(
|
||
Harvest.Time
|
||
);
|
||
|
||
Element.title =
|
||
"Ready at " +
|
||
HarvestDate.toLocaleTimeString(
|
||
[],
|
||
{
|
||
hour: "2-digit",
|
||
minute: "2-digit"
|
||
}
|
||
);
|
||
}
|
||
|
||
|
||
function RenderGardenOverview() {
|
||
const Overview =
|
||
document.getElementById(
|
||
"GardenOverviewDetails"
|
||
);
|
||
|
||
if (Overview === null) {
|
||
return;
|
||
}
|
||
|
||
|
||
if (
|
||
!HasGardenOverviewUpgrade(
|
||
GameSave
|
||
)
|
||
) {
|
||
Overview.hidden = true;
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
const TotalPlots =
|
||
GameSave.Garden.Width *
|
||
GameSave.Garden.Height;
|
||
|
||
let PlantedPlots = 0;
|
||
let GrowingPlots = 0;
|
||
let ReadyPlots = 0;
|
||
|
||
|
||
for (
|
||
const Plot
|
||
of GameSave.Garden.Plots
|
||
) {
|
||
if (Plot === null) {
|
||
continue;
|
||
}
|
||
|
||
PlantedPlots++;
|
||
|
||
const Plant =
|
||
Plants[Plot.Plant];
|
||
|
||
if (Plant === undefined) {
|
||
continue;
|
||
}
|
||
|
||
if (
|
||
IsPlantMature(
|
||
Plot,
|
||
Plant
|
||
)
|
||
) {
|
||
ReadyPlots++;
|
||
} else {
|
||
GrowingPlots++;
|
||
}
|
||
}
|
||
|
||
|
||
const EmptyPlots =
|
||
Math.max(
|
||
0,
|
||
TotalPlots -
|
||
PlantedPlots
|
||
);
|
||
|
||
const Lines = [
|
||
{
|
||
LineId:
|
||
"GardenSizeOverviewLine",
|
||
ValueId:
|
||
"GardenSizeOverview",
|
||
Preference:
|
||
"ShowGardenSize",
|
||
Value:
|
||
GameSave.Garden.Width +
|
||
"×" +
|
||
GameSave.Garden.Height +
|
||
" (" +
|
||
TotalPlots.toLocaleString() +
|
||
")"
|
||
},
|
||
{
|
||
LineId:
|
||
"EmptyPlotsOverviewLine",
|
||
ValueId:
|
||
"EmptyPlotsOverview",
|
||
Preference:
|
||
"ShowEmptyPlots",
|
||
Value:
|
||
EmptyPlots.toLocaleString() +
|
||
"/" +
|
||
TotalPlots.toLocaleString()
|
||
},
|
||
{
|
||
LineId:
|
||
"PlantedPlotsOverviewLine",
|
||
ValueId:
|
||
"PlantedPlotsOverview",
|
||
Preference:
|
||
"ShowPlantedPlots",
|
||
Value:
|
||
PlantedPlots.toLocaleString() +
|
||
"/" +
|
||
TotalPlots.toLocaleString()
|
||
},
|
||
{
|
||
LineId:
|
||
"GrowingPlotsOverviewLine",
|
||
ValueId:
|
||
"GrowingPlotsOverview",
|
||
Preference:
|
||
"ShowGrowingPlots",
|
||
Value:
|
||
GrowingPlots.toLocaleString() +
|
||
"/" +
|
||
TotalPlots.toLocaleString()
|
||
},
|
||
{
|
||
LineId:
|
||
"ReadyPlotsOverviewLine",
|
||
ValueId:
|
||
"ReadyPlotsOverview",
|
||
Preference:
|
||
"ShowReadyPlots",
|
||
Value:
|
||
ReadyPlots.toLocaleString() +
|
||
"/" +
|
||
TotalPlots.toLocaleString()
|
||
}
|
||
];
|
||
|
||
let VisibleLines = 0;
|
||
|
||
|
||
for (
|
||
const Line
|
||
of Lines
|
||
) {
|
||
const LineElement =
|
||
document.getElementById(
|
||
Line.LineId
|
||
);
|
||
|
||
const ValueElement =
|
||
document.getElementById(
|
||
Line.ValueId
|
||
);
|
||
|
||
if (
|
||
LineElement === null ||
|
||
ValueElement === null
|
||
) {
|
||
continue;
|
||
}
|
||
|
||
const IsVisible =
|
||
GameSave.Preferences[
|
||
Line.Preference
|
||
] !== false;
|
||
|
||
LineElement.hidden =
|
||
!IsVisible;
|
||
|
||
ValueElement.textContent =
|
||
Line.Value;
|
||
|
||
if (IsVisible) {
|
||
VisibleLines++;
|
||
}
|
||
}
|
||
|
||
|
||
Overview.hidden =
|
||
VisibleLines === 0;
|
||
}
|
||
|
||
|
||
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"
|
||
);
|
||
}
|
||
|
||
|
||
function RenderGardenEconomy() {
|
||
const Details =
|
||
document.getElementById(
|
||
"GardenEconomyDetails"
|
||
);
|
||
|
||
if (Details === null) {
|
||
return;
|
||
}
|
||
|
||
|
||
if (
|
||
!HasGardenEconomyUpgrade(
|
||
GameSave
|
||
)
|
||
) {
|
||
Details.hidden = true;
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
Details.hidden = false;
|
||
|
||
const Summary =
|
||
GetGardenEconomySummary(
|
||
GameSave
|
||
);
|
||
|
||
const Values = {
|
||
GardenDewInvested:
|
||
Summary === null
|
||
? "Unavailable"
|
||
: Summary.DewInvested
|
||
.toLocaleString(),
|
||
|
||
GardenHarvestValue:
|
||
Summary === null
|
||
? "Unavailable"
|
||
: Summary.HarvestValue
|
||
.toLocaleString(),
|
||
|
||
GardenNetProfit:
|
||
Summary === null
|
||
? "Unavailable"
|
||
: Summary.NetProfit
|
||
.toLocaleString(),
|
||
|
||
GardenDewPerHour:
|
||
Summary === null
|
||
? "Unavailable"
|
||
: FormatDewPerHour(
|
||
Summary.DewPerHour
|
||
)
|
||
};
|
||
|
||
|
||
for (
|
||
const [ElementId, Value]
|
||
of Object.entries(Values)
|
||
) {
|
||
const Element =
|
||
document.getElementById(
|
||
ElementId
|
||
);
|
||
|
||
if (Element !== null) {
|
||
Element.textContent =
|
||
Value;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
function RenderGardenInfoLayout() {
|
||
const DetailRow =
|
||
document.getElementById(
|
||
"GardenInfoDetailRow"
|
||
);
|
||
|
||
const OverviewSection =
|
||
document.getElementById(
|
||
"GardenOverviewSection"
|
||
);
|
||
|
||
const EconomySection =
|
||
document.getElementById(
|
||
"GardenEconomySection"
|
||
);
|
||
|
||
if (
|
||
DetailRow === null ||
|
||
OverviewSection === null ||
|
||
EconomySection === null
|
||
) {
|
||
return;
|
||
}
|
||
|
||
|
||
const HasOverview =
|
||
HasGardenOverviewUpgrade(
|
||
GameSave
|
||
);
|
||
|
||
const HasEconomy =
|
||
HasGardenEconomyUpgrade(
|
||
GameSave
|
||
);
|
||
|
||
DetailRow.hidden =
|
||
!HasOverview &&
|
||
!HasEconomy;
|
||
|
||
OverviewSection.classList.toggle(
|
||
"GardenInfoSectionEmpty",
|
||
!HasOverview
|
||
);
|
||
|
||
EconomySection.classList.toggle(
|
||
"GardenInfoSectionEmpty",
|
||
!HasEconomy
|
||
);
|
||
}
|
||
|
||
document.addEventListener(
|
||
"DOMContentLoaded",
|
||
StartGame
|
||
);
|