mirror of
https://git.srhtnin.garden/sarah/srhtnin.github.io.git
synced 2026-10-13 18:38:11 +02:00
3135 lines
55 KiB
JavaScript
3135 lines
55 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;
|
||
|
||
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() {
|
||
RenderGardenSelectorView({
|
||
Gardens: GameSave.Gardens,
|
||
GardenIndex:
|
||
GameSave.ActiveGardenIndex,
|
||
PreviousButton:
|
||
document.getElementById(
|
||
"PreviousGardenButton"
|
||
),
|
||
NameInput:
|
||
document.getElementById(
|
||
"GardenNameInput"
|
||
),
|
||
NextButton:
|
||
document.getElementById(
|
||
"NextGardenButton"
|
||
),
|
||
PreserveFocusedName: true
|
||
});
|
||
}
|
||
|
||
|
||
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
|
||
};
|
||
}
|
||
|
||
|
||
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 Plant =
|
||
Plot === null
|
||
? null
|
||
: Plants[Plot.Plant];
|
||
|
||
let Progress = 0;
|
||
let CooldownState = null;
|
||
let BorderProgress = 0;
|
||
let ImagePath = null;
|
||
let TimerText = "";
|
||
|
||
if (
|
||
Plot !== null &&
|
||
Plant !== undefined
|
||
) {
|
||
Progress =
|
||
GetPlantGrowthProgress(
|
||
Plot,
|
||
Plant
|
||
);
|
||
|
||
CooldownState =
|
||
Progress >= 1 &&
|
||
typeof GetFunctionalPlantCooldownDisplayState ===
|
||
"function"
|
||
? GetFunctionalPlantCooldownDisplayState(
|
||
Plot,
|
||
Plant,
|
||
GetSimulationTime(
|
||
GameSave
|
||
)
|
||
)
|
||
: null;
|
||
|
||
BorderProgress =
|
||
CooldownState === null
|
||
? Progress
|
||
: CooldownState.Progress;
|
||
|
||
ImagePath =
|
||
GetPlantImage(
|
||
Plot,
|
||
Plant
|
||
);
|
||
|
||
TimerText =
|
||
GetGardenPlotTimerText(
|
||
Plot,
|
||
Plant,
|
||
Progress,
|
||
GetSimulationTime(
|
||
GameSave
|
||
),
|
||
CooldownState
|
||
);
|
||
}
|
||
|
||
const View =
|
||
CreateGardenPlotView({
|
||
Plot,
|
||
Plant,
|
||
Progress,
|
||
BorderProgress,
|
||
DisplaySettings,
|
||
ImagePath,
|
||
TimerText,
|
||
FunctionalCooldown:
|
||
CooldownState !== null,
|
||
ShowRotation:
|
||
Plot !== null &&
|
||
DisplaySettings
|
||
.ShowPlotRotation &&
|
||
!IsPreview,
|
||
OnRotate: () => {
|
||
RotateGardenPlot(
|
||
PlotIndex
|
||
);
|
||
}
|
||
});
|
||
|
||
const Tile = View.Tile;
|
||
const MainButton =
|
||
View.MainButton;
|
||
|
||
|
||
if (Plot === null) {
|
||
MainButton.title =
|
||
IsPreview
|
||
? "Future Sight: empty plot"
|
||
: "Empty plot";
|
||
|
||
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) {
|
||
MainButton.disabled = true;
|
||
return Tile;
|
||
}
|
||
|
||
|
||
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 ===
|
||
"MagicTrowel"
|
||
) {
|
||
if (Progress < 1) {
|
||
MainButton.title =
|
||
Plant.Name +
|
||
" - " +
|
||
Math.floor(
|
||
Progress * 100
|
||
) +
|
||
"% grown";
|
||
|
||
MainButton.disabled = true;
|
||
return Tile;
|
||
}
|
||
|
||
Tile.classList.add(
|
||
"GardenPlotMature",
|
||
"GardenPlotMagicPlant"
|
||
);
|
||
|
||
MainButton.title =
|
||
"Harvest all mature " +
|
||
Plant.Name +
|
||
" plants in this Garden";
|
||
|
||
MainButton.addEventListener(
|
||
"click",
|
||
() => {
|
||
HarvestManyPlants(
|
||
Plot.Plant
|
||
);
|
||
}
|
||
);
|
||
|
||
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;
|
||
}
|
||
|
||
|
||
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;
|
||
}
|
||
|
||
|
||
const PlantResult =
|
||
PlantGardenPlot(
|
||
GameSave,
|
||
GameSave.ActiveGardenIndex,
|
||
PlotIndex,
|
||
PlantKey,
|
||
ActionTime,
|
||
{
|
||
ConsumeSeed: true,
|
||
Rotation:
|
||
GardenPlotDefaultRotation
|
||
}
|
||
);
|
||
|
||
if (!PlantResult.Planted) {
|
||
EnsureSelectedSeed();
|
||
RenderSeeds();
|
||
return;
|
||
}
|
||
|
||
|
||
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;
|
||
}
|
||
|
||
|
||
let PlantedCount = 0;
|
||
|
||
for (
|
||
let Index = 0;
|
||
Index < PlantCount;
|
||
Index++
|
||
) {
|
||
const PlantResult =
|
||
PlantGardenPlot(
|
||
GameSave,
|
||
GameSave.ActiveGardenIndex,
|
||
EmptyPlotIndexes[Index],
|
||
PlantKey,
|
||
ActionTime,
|
||
{
|
||
ConsumeSeed: true,
|
||
Rotation:
|
||
GardenPlotDefaultRotation
|
||
}
|
||
);
|
||
|
||
if (!PlantResult.Planted) {
|
||
break;
|
||
}
|
||
|
||
PlantedCount++;
|
||
}
|
||
|
||
if (PlantedCount <= 0) {
|
||
EnsureSelectedSeed();
|
||
RenderSeeds();
|
||
return;
|
||
}
|
||
|
||
|
||
SetGardenMessage(
|
||
"Magic trowel planted " +
|
||
PlantedCount.toLocaleString() +
|
||
" " +
|
||
Plant.Name +
|
||
(PlantedCount === 1
|
||
? " seed."
|
||
: " seeds.")
|
||
);
|
||
|
||
CheckGardenMutations(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
RenderGame();
|
||
|
||
await SaveGame(
|
||
GameSave
|
||
);
|
||
}
|
||
|
||
|
||
async function HarvestManyPlants(
|
||
PlantKey
|
||
) {
|
||
const ActionTime = Date.now();
|
||
|
||
AdvanceGameSimulation(
|
||
GameSave,
|
||
ActionTime
|
||
);
|
||
|
||
const Plant =
|
||
Plants[PlantKey];
|
||
|
||
if (Plant === undefined) {
|
||
return;
|
||
}
|
||
|
||
|
||
const MatchingPlotIndexes = [];
|
||
|
||
GameSave.Garden.Plots.forEach(
|
||
(Plot, PlotIndex) => {
|
||
if (
|
||
Plot !== null &&
|
||
Plot.Plant === PlantKey &&
|
||
IsPlantMature(
|
||
Plot,
|
||
Plant
|
||
)
|
||
) {
|
||
MatchingPlotIndexes.push(
|
||
PlotIndex
|
||
);
|
||
}
|
||
}
|
||
);
|
||
|
||
if (MatchingPlotIndexes.length === 0) {
|
||
SetGardenMessage(
|
||
"There aren't any mature " +
|
||
Plant.Name +
|
||
" plants to harvest."
|
||
);
|
||
|
||
RenderGame();
|
||
return;
|
||
}
|
||
|
||
|
||
let HarvestedCount = 0;
|
||
let RewardAmount = 0;
|
||
let DisabledCount = 0;
|
||
let RewardUnavailableCount = 0;
|
||
|
||
for (
|
||
const PlotIndex
|
||
of MatchingPlotIndexes
|
||
) {
|
||
const HarvestResult =
|
||
HarvestGardenPlot(
|
||
GameSave,
|
||
GameSave.ActiveGardenIndex,
|
||
PlotIndex,
|
||
ActionTime
|
||
);
|
||
|
||
if (!HarvestResult.Harvested) {
|
||
if (HarvestResult.Disabled) {
|
||
DisabledCount++;
|
||
} else if (
|
||
HarvestResult.RewardUnavailable
|
||
) {
|
||
RewardUnavailableCount++;
|
||
}
|
||
|
||
continue;
|
||
}
|
||
|
||
HarvestedCount++;
|
||
RewardAmount +=
|
||
HarvestResult.RewardAmount;
|
||
}
|
||
|
||
|
||
if (HarvestedCount <= 0) {
|
||
if (DisabledCount > 0) {
|
||
SetGardenMessage(
|
||
Plant.Name +
|
||
" can't be harvested right now."
|
||
);
|
||
} else if (
|
||
RewardUnavailableCount > 0
|
||
) {
|
||
SetGardenMessage(
|
||
"Couldn't calculate the harvest reward for " +
|
||
Plant.Name +
|
||
"."
|
||
);
|
||
}
|
||
|
||
RenderGame();
|
||
return;
|
||
}
|
||
|
||
|
||
SetGardenMessage(
|
||
"Magic trowel harvested " +
|
||
HarvestedCount.toLocaleString() +
|
||
" " +
|
||
Plant.Name +
|
||
(HarvestedCount === 1
|
||
? " plant"
|
||
: " plants") +
|
||
" for " +
|
||
RewardAmount.toLocaleString() +
|
||
" Dew."
|
||
);
|
||
|
||
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 HarvestResult =
|
||
HarvestGardenPlot(
|
||
GameSave,
|
||
GameSave.ActiveGardenIndex,
|
||
PlotIndex,
|
||
ActionTime
|
||
);
|
||
|
||
if (!HarvestResult.Harvested) {
|
||
if (HarvestResult.Disabled) {
|
||
SetGardenMessage(
|
||
Plant.Name +
|
||
" can't be harvested right now."
|
||
);
|
||
} else if (
|
||
HarvestResult.RewardUnavailable
|
||
) {
|
||
SetGardenMessage(
|
||
"Couldn't calculate the harvest reward for " +
|
||
Plant.Name +
|
||
"."
|
||
);
|
||
}
|
||
|
||
RenderGame();
|
||
return;
|
||
}
|
||
|
||
|
||
SetGardenMessage(
|
||
"Harvested " +
|
||
Plant.Name +
|
||
" for " +
|
||
HarvestResult.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
|
||
);
|