mirror of
https://git.srhtnin.garden/sarah/srhtnin.github.io.git
synced 2026-10-14 02:38:12 +02:00
Added shop, inventory, fixed plant and mutation definitions, and more..
This commit is contained in:
1 parent
cd2759ce4e
commit
97204f2d3f
15 files changed
+2510
-351
No files matched your search
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -117,6 +117,7 @@ function GeneratePlantImageManifest(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
foreach (
|
foreach (
|
||||||
new DirectoryIterator(
|
new DirectoryIterator(
|
||||||
$PlantsDirectory
|
$PlantsDirectory
|
||||||
@@ -133,143 +134,78 @@ function GeneratePlantImageManifest(
|
|||||||
$PlantName =
|
$PlantName =
|
||||||
$PlantDirectory->getFilename();
|
$PlantDirectory->getFilename();
|
||||||
|
|
||||||
$Manifest[$PlantName] = [];
|
$Images = [];
|
||||||
|
|
||||||
|
|
||||||
foreach (
|
foreach (
|
||||||
new DirectoryIterator(
|
new DirectoryIterator(
|
||||||
$PlantDirectory->getPathname()
|
$PlantDirectory->getPathname()
|
||||||
)
|
)
|
||||||
as $VariantDirectory
|
as $Image
|
||||||
|
) {
|
||||||
|
if (!$Image->isFile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
preg_match(
|
||||||
|
"/^([0-9]+)\\.png$/i",
|
||||||
|
$Image->getFilename(),
|
||||||
|
$Matches
|
||||||
|
) !== 1
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ImageNumber =
|
||||||
|
(int) $Matches[1];
|
||||||
|
|
||||||
|
if ($ImageNumber < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$Images[$ImageNumber] =
|
||||||
|
"/Assets/Img/Garden/Plants/" .
|
||||||
|
$PlantName .
|
||||||
|
"/" .
|
||||||
|
$Image->getFilename();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ksort(
|
||||||
|
$Images,
|
||||||
|
SORT_NUMERIC
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
$ExpectedNumber = 1;
|
||||||
|
|
||||||
|
foreach (
|
||||||
|
array_keys($Images)
|
||||||
|
as $ImageNumber
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
$VariantDirectory->isDot() ||
|
$ImageNumber !==
|
||||||
!$VariantDirectory->isDir()
|
$ExpectedNumber
|
||||||
) {
|
) {
|
||||||
continue;
|
throw new RuntimeException(
|
||||||
|
"Missing plant image " .
|
||||||
|
$ExpectedNumber .
|
||||||
|
".png in " .
|
||||||
|
$PlantName
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$VariantName =
|
$ExpectedNumber++;
|
||||||
$VariantDirectory->getFilename();
|
|
||||||
|
|
||||||
$Images = [];
|
|
||||||
|
|
||||||
|
|
||||||
foreach (
|
|
||||||
new DirectoryIterator(
|
|
||||||
$VariantDirectory->getPathname()
|
|
||||||
)
|
|
||||||
as $Image
|
|
||||||
) {
|
|
||||||
if (!$Image->isFile()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
preg_match(
|
|
||||||
"/^([0-9]+)\.png$/i",
|
|
||||||
$Image->getFilename(),
|
|
||||||
$Matches
|
|
||||||
) !== 1
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$ImageNumber =
|
|
||||||
(int) $Matches[1];
|
|
||||||
|
|
||||||
if ($ImageNumber < 1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$Images[$ImageNumber] =
|
|
||||||
"/Assets/Img/Garden/Plants/" .
|
|
||||||
$PlantName .
|
|
||||||
"/" .
|
|
||||||
$VariantName .
|
|
||||||
"/" .
|
|
||||||
$Image->getFilename();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sort numerically:
|
|
||||||
*
|
|
||||||
* 1.png
|
|
||||||
* 2.png
|
|
||||||
* ...
|
|
||||||
* 10.png
|
|
||||||
*
|
|
||||||
* instead of:
|
|
||||||
*
|
|
||||||
* 1.png
|
|
||||||
* 10.png
|
|
||||||
* 2.png
|
|
||||||
*/
|
|
||||||
|
|
||||||
ksort(
|
|
||||||
$Images,
|
|
||||||
SORT_NUMERIC
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Require consecutive numbering.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$ExpectedNumber = 1;
|
|
||||||
|
|
||||||
foreach (
|
|
||||||
array_keys($Images)
|
|
||||||
as $ImageNumber
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
$ImageNumber !==
|
|
||||||
$ExpectedNumber
|
|
||||||
) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Missing plant image " .
|
|
||||||
$ExpectedNumber .
|
|
||||||
".png in " .
|
|
||||||
$PlantName .
|
|
||||||
"/" .
|
|
||||||
$VariantName
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$ExpectedNumber++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (count($Images) === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$Manifest[
|
|
||||||
$PlantName
|
|
||||||
][
|
|
||||||
$VariantName
|
|
||||||
] = array_values(
|
|
||||||
$Images
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (
|
if (count($Images) === 0) {
|
||||||
count(
|
continue;
|
||||||
$Manifest[$PlantName]
|
|
||||||
) === 0
|
|
||||||
) {
|
|
||||||
unset(
|
|
||||||
$Manifest[$PlantName]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ksort(
|
|
||||||
$Manifest[$PlantName],
|
|
||||||
SORT_NATURAL |
|
|
||||||
SORT_FLAG_CASE
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$Manifest[$PlantName] =
|
||||||
|
array_values($Images);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -305,7 +241,6 @@ function GeneratePlantImageManifest(
|
|||||||
file_put_contents(
|
file_put_contents(
|
||||||
$ManifestDirectory .
|
$ManifestDirectory .
|
||||||
"/PlantImages.js",
|
"/PlantImages.js",
|
||||||
|
|
||||||
"const PlantImages = " .
|
"const PlantImages = " .
|
||||||
$Json .
|
$Json .
|
||||||
";" .
|
";" .
|
||||||
|
|||||||
@@ -35,6 +35,17 @@ $SubNavbarCurrent ??= null;
|
|||||||
ENT_QUOTES,
|
ENT_QUOTES,
|
||||||
"UTF-8"
|
"UTF-8"
|
||||||
) ?>"
|
) ?>"
|
||||||
|
<?php if (
|
||||||
|
$SubNavbarCurrent ===
|
||||||
|
$Name
|
||||||
|
): ?>
|
||||||
|
aria-current="<?= str_starts_with(
|
||||||
|
$Url,
|
||||||
|
"#"
|
||||||
|
)
|
||||||
|
? "location"
|
||||||
|
: "page" ?>"
|
||||||
|
<?php endif; ?>
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="SubNavbarPrefix"
|
class="SubNavbarPrefix"
|
||||||
|
|||||||
+8
-1
@@ -1,15 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$PageTitle = "Garden";
|
$PageTitle = "Garden";
|
||||||
|
$PageSection = "Garden";
|
||||||
|
|
||||||
$SubNavbarItems = [
|
$SubNavbarItems = [
|
||||||
|
"Garden" => "/Pages/Garden.html",
|
||||||
|
"Shop" => "/Pages/Shop.html",
|
||||||
"Plots" => "#Plots",
|
"Plots" => "#Plots",
|
||||||
"Seeds" => "#Seeds",
|
"Seeds" => "#Seeds",
|
||||||
"Player" => "#Player",
|
"Player" => "#Player",
|
||||||
"Leaderboard" => "#Leaderboard"
|
"Leaderboard" => "#Leaderboard"
|
||||||
];
|
];
|
||||||
|
|
||||||
$SubNavbarCurrent = "Plots";
|
$SubNavbarCurrent = "Garden";
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@@ -28,6 +31,9 @@ $SubNavbarCurrent = "Plots";
|
|||||||
<div class="GardenStatus">
|
<div class="GardenStatus">
|
||||||
<strong>Dew:</strong>
|
<strong>Dew:</strong>
|
||||||
<span id="DewAmount">0</span>
|
<span id="DewAmount">0</span>
|
||||||
|
<br>
|
||||||
|
<strong>Next harvest:</strong>
|
||||||
|
<span id="NextHarvest">Nothing planted</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="GardenLayout">
|
<div class="GardenLayout">
|
||||||
@@ -127,6 +133,7 @@ $SubNavbarCurrent = "Plots";
|
|||||||
<script src="/Scripts/Garden/Plants.js"></script>
|
<script src="/Scripts/Garden/Plants.js"></script>
|
||||||
<script src="/Scripts/Garden/Mutations.js"></script>
|
<script src="/Scripts/Garden/Mutations.js"></script>
|
||||||
<script src="/Scripts/Garden/Save.js"></script>
|
<script src="/Scripts/Garden/Save.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Economy.js"></script>
|
||||||
<script src="/Scripts/Garden/Users.js"></script>
|
<script src="/Scripts/Garden/Users.js"></script>
|
||||||
<script src="/Scripts/Garden/Leaderboard.js"></script>
|
<script src="/Scripts/Garden/Leaderboard.js"></script>
|
||||||
<script src="/Scripts/Garden/Garden.js"></script>
|
<script src="/Scripts/Garden/Garden.js"></script>
|
||||||
|
|||||||
@@ -261,6 +261,8 @@ $SubNavbarCurrent = "Player";
|
|||||||
|
|
||||||
|
|
||||||
<script src="/Scripts/SubNavbar.js"></script>
|
<script src="/Scripts/SubNavbar.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Plants.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Mutations.js"></script>
|
||||||
<script src="/Scripts/Garden/Save.js"></script>
|
<script src="/Scripts/Garden/Save.js"></script>
|
||||||
<script src="/Scripts/Garden/Users.js"></script>
|
<script src="/Scripts/Garden/Users.js"></script>
|
||||||
<script src="/Scripts/Garden/Profile.js"></script>
|
<script src="/Scripts/Garden/Profile.js"></script>
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$PageTitle = "Shop";
|
||||||
|
$PageSection = "Garden";
|
||||||
|
|
||||||
|
$SubNavbarItems = [
|
||||||
|
"Garden" => "/Pages/Garden.html",
|
||||||
|
"Shop" => "/Pages/Shop.html",
|
||||||
|
"Seeds" => "#Seeds"
|
||||||
|
];
|
||||||
|
|
||||||
|
$SubNavbarCurrent = "Shop";
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="en" data-palette="CatppuccinMocha">
|
||||||
|
<?php require __DIR__ . "/Bits/Head.php"; ?>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<?php require __DIR__ . "/Bits/Navbar.php"; ?>
|
||||||
|
<?php require __DIR__ . "/Bits/SubNavbar.php"; ?>
|
||||||
|
|
||||||
|
<main class="MainContainer">
|
||||||
|
<h1>Garden shop</h1>
|
||||||
|
|
||||||
|
<div class="ShopStatus">
|
||||||
|
<strong>Dew:</strong>
|
||||||
|
<span id="ShopDewAmount">0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Buy seeds here, then take them to the Garden
|
||||||
|
to plant them. Seeds are consumable: planting
|
||||||
|
one removes it from your inventory.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Plants discovered through mutations become
|
||||||
|
purchasable automatically. Their seed price is
|
||||||
|
based on the cheapest recipe you've discovered,
|
||||||
|
plus 10 Dew per hour of mutation time.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Harvesting a plant returns 150% of its current
|
||||||
|
seed price, rounded up.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="Seeds"
|
||||||
|
class="ShopSection"
|
||||||
|
>
|
||||||
|
<h2>Seeds</h2>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="ShopSeedList"
|
||||||
|
class="ShopSeedList"
|
||||||
|
></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<p id="ShopMessage">
|
||||||
|
Choose a seed to buy.
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="/Scripts/SubNavbar.js"></script>
|
||||||
|
<script src="/Scripts/Garden/PlantImages.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Plants.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Mutations.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Save.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Economy.js"></script>
|
||||||
|
<script src="/Scripts/Garden/Shop.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,578 @@
|
|||||||
|
const MutationTimeFeePerHour = 10;
|
||||||
|
|
||||||
|
const PlantKeysById = new Map();
|
||||||
|
const PlantsById = new Map();
|
||||||
|
const MutationsById = new Map();
|
||||||
|
|
||||||
|
|
||||||
|
for (
|
||||||
|
const [PlantKey, Plant]
|
||||||
|
of Object.entries(Plants)
|
||||||
|
) {
|
||||||
|
if (PlantsById.has(Plant.Id)) {
|
||||||
|
throw new Error(
|
||||||
|
"Duplicate plant ID: " +
|
||||||
|
Plant.Id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlantKeysById.set(
|
||||||
|
Plant.Id,
|
||||||
|
PlantKey
|
||||||
|
);
|
||||||
|
|
||||||
|
PlantsById.set(
|
||||||
|
Plant.Id,
|
||||||
|
Plant
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (
|
||||||
|
const Mutation
|
||||||
|
of Object.values(MutationSets)
|
||||||
|
) {
|
||||||
|
if (MutationsById.has(Mutation.Id)) {
|
||||||
|
throw new Error(
|
||||||
|
"Duplicate mutation ID: " +
|
||||||
|
Mutation.Id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MutationsById.set(
|
||||||
|
Mutation.Id,
|
||||||
|
Mutation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetPlantKeyById(
|
||||||
|
PlantId
|
||||||
|
) {
|
||||||
|
return PlantKeysById.get(
|
||||||
|
Number(PlantId)
|
||||||
|
) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetPlantById(
|
||||||
|
PlantId
|
||||||
|
) {
|
||||||
|
return PlantsById.get(
|
||||||
|
Number(PlantId)
|
||||||
|
) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetMutationById(
|
||||||
|
MutationId
|
||||||
|
) {
|
||||||
|
return MutationsById.get(
|
||||||
|
Number(MutationId)
|
||||||
|
) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function HasDiscoveredPlant(
|
||||||
|
SaveData,
|
||||||
|
PlantId
|
||||||
|
) {
|
||||||
|
return SaveData.Discoveries.Plants.includes(
|
||||||
|
Number(PlantId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function HasDiscoveredMutation(
|
||||||
|
SaveData,
|
||||||
|
MutationId
|
||||||
|
) {
|
||||||
|
return SaveData.Discoveries.Mutations.includes(
|
||||||
|
Number(MutationId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function DiscoverPlant(
|
||||||
|
SaveData,
|
||||||
|
PlantId
|
||||||
|
) {
|
||||||
|
PlantId = Number(PlantId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!HasDiscoveredPlant(
|
||||||
|
SaveData,
|
||||||
|
PlantId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
SaveData.Discoveries.Plants.push(
|
||||||
|
PlantId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function DiscoverMutation(
|
||||||
|
SaveData,
|
||||||
|
MutationId
|
||||||
|
) {
|
||||||
|
MutationId = Number(
|
||||||
|
MutationId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!HasDiscoveredMutation(
|
||||||
|
SaveData,
|
||||||
|
MutationId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
SaveData.Discoveries.Mutations.push(
|
||||||
|
MutationId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function IsPlantAvailableInShop(
|
||||||
|
SaveData,
|
||||||
|
Plant
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
Plant.Shop?.StartingPlant ===
|
||||||
|
true
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HasDiscoveredPlant(
|
||||||
|
SaveData,
|
||||||
|
Plant.Id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetSeedCount(
|
||||||
|
SaveData,
|
||||||
|
PlantId
|
||||||
|
) {
|
||||||
|
return Number(
|
||||||
|
SaveData.Inventory.Seeds[
|
||||||
|
String(PlantId)
|
||||||
|
] ?? 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function AddSeed(
|
||||||
|
SaveData,
|
||||||
|
PlantId,
|
||||||
|
Amount = 1
|
||||||
|
) {
|
||||||
|
const InventoryKey =
|
||||||
|
String(PlantId);
|
||||||
|
|
||||||
|
SaveData.Inventory.Seeds[
|
||||||
|
InventoryKey
|
||||||
|
] ??= 0;
|
||||||
|
|
||||||
|
SaveData.Inventory.Seeds[
|
||||||
|
InventoryKey
|
||||||
|
] += Amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function TakeSeed(
|
||||||
|
SaveData,
|
||||||
|
PlantId,
|
||||||
|
Amount = 1
|
||||||
|
) {
|
||||||
|
const CurrentAmount =
|
||||||
|
GetSeedCount(
|
||||||
|
SaveData,
|
||||||
|
PlantId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (CurrentAmount < Amount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveData.Inventory.Seeds[
|
||||||
|
String(PlantId)
|
||||||
|
] = CurrentAmount - Amount;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetPlantShopCost(
|
||||||
|
SaveData,
|
||||||
|
PlantId,
|
||||||
|
Cache = new Map(),
|
||||||
|
Visiting = new Set()
|
||||||
|
) {
|
||||||
|
PlantId = Number(PlantId);
|
||||||
|
|
||||||
|
if (Cache.has(PlantId)) {
|
||||||
|
return Cache.get(PlantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Plant =
|
||||||
|
GetPlantById(
|
||||||
|
PlantId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Plant === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Plant.Shop?.StartingPlant ===
|
||||||
|
true
|
||||||
|
) {
|
||||||
|
const BaseCost = Number(
|
||||||
|
Plant.Shop.BaseCost
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Number.isFinite(BaseCost) ||
|
||||||
|
BaseCost < 0
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Cost =
|
||||||
|
Math.ceil(BaseCost);
|
||||||
|
|
||||||
|
Cache.set(
|
||||||
|
PlantId,
|
||||||
|
Cost
|
||||||
|
);
|
||||||
|
|
||||||
|
return Cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!HasDiscoveredPlant(
|
||||||
|
SaveData,
|
||||||
|
PlantId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Visiting.has(PlantId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Visiting.add(
|
||||||
|
PlantId
|
||||||
|
);
|
||||||
|
|
||||||
|
let CheapestCost = null;
|
||||||
|
|
||||||
|
|
||||||
|
for (
|
||||||
|
const Mutation
|
||||||
|
of Object.values(
|
||||||
|
MutationSets
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!HasDiscoveredMutation(
|
||||||
|
SaveData,
|
||||||
|
Mutation.Id
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Mutation.Relations
|
||||||
|
?.PlantsCreated
|
||||||
|
?.includes(
|
||||||
|
PlantId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IngredientCost =
|
||||||
|
GetMutationIngredientCost(
|
||||||
|
SaveData,
|
||||||
|
Mutation,
|
||||||
|
Cache,
|
||||||
|
Visiting
|
||||||
|
);
|
||||||
|
|
||||||
|
if (IngredientCost === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MutationCost =
|
||||||
|
IngredientCost +
|
||||||
|
GetMutationTimeFee(
|
||||||
|
Mutation
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
CheapestCost === null ||
|
||||||
|
MutationCost < CheapestCost
|
||||||
|
) {
|
||||||
|
CheapestCost =
|
||||||
|
MutationCost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Visiting.delete(
|
||||||
|
PlantId
|
||||||
|
);
|
||||||
|
|
||||||
|
Cache.set(
|
||||||
|
PlantId,
|
||||||
|
CheapestCost
|
||||||
|
);
|
||||||
|
|
||||||
|
return CheapestCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetMutationIngredientCost(
|
||||||
|
SaveData,
|
||||||
|
Mutation,
|
||||||
|
Cache,
|
||||||
|
Visiting
|
||||||
|
) {
|
||||||
|
let TotalCost = 0;
|
||||||
|
|
||||||
|
|
||||||
|
for (
|
||||||
|
const Row
|
||||||
|
of Mutation.Pattern ?? []
|
||||||
|
) {
|
||||||
|
for (
|
||||||
|
const Matcher
|
||||||
|
of Row
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
Matcher === null ||
|
||||||
|
Matcher === "Empty"
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MatcherCost =
|
||||||
|
GetMatcherSeedCost(
|
||||||
|
SaveData,
|
||||||
|
Matcher,
|
||||||
|
Cache,
|
||||||
|
Visiting
|
||||||
|
);
|
||||||
|
|
||||||
|
if (MatcherCost === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
TotalCost +=
|
||||||
|
MatcherCost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return TotalCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetMatcherSeedCost(
|
||||||
|
SaveData,
|
||||||
|
Matcher,
|
||||||
|
Cache,
|
||||||
|
Visiting
|
||||||
|
) {
|
||||||
|
if (typeof Matcher === "string") {
|
||||||
|
const Plant =
|
||||||
|
Plants[Matcher];
|
||||||
|
|
||||||
|
if (Plant === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetPlantShopCost(
|
||||||
|
SaveData,
|
||||||
|
Plant.Id,
|
||||||
|
Cache,
|
||||||
|
Visiting
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof Matcher !== "object" ||
|
||||||
|
Matcher === null
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof Matcher.Plant ===
|
||||||
|
"string"
|
||||||
|
) {
|
||||||
|
const Plant =
|
||||||
|
Plants[Matcher.Plant];
|
||||||
|
|
||||||
|
if (Plant === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetPlantShopCost(
|
||||||
|
SaveData,
|
||||||
|
Plant.Id,
|
||||||
|
Cache,
|
||||||
|
Visiting
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let CheapestCost = null;
|
||||||
|
|
||||||
|
|
||||||
|
for (
|
||||||
|
const Plant
|
||||||
|
of Object.values(Plants)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!IsPlantAvailableInShop(
|
||||||
|
SaveData,
|
||||||
|
Plant
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!DoesPlantMatchRecipeMatcher(
|
||||||
|
Plant,
|
||||||
|
Matcher
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Cost =
|
||||||
|
GetPlantShopCost(
|
||||||
|
SaveData,
|
||||||
|
Plant.Id,
|
||||||
|
Cache,
|
||||||
|
Visiting
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Cost === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
CheapestCost === null ||
|
||||||
|
Cost < CheapestCost
|
||||||
|
) {
|
||||||
|
CheapestCost = Cost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return CheapestCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function DoesPlantMatchRecipeMatcher(
|
||||||
|
Plant,
|
||||||
|
Matcher
|
||||||
|
) {
|
||||||
|
const Tags =
|
||||||
|
Plant.Tags ?? [];
|
||||||
|
|
||||||
|
if (
|
||||||
|
Array.isArray(
|
||||||
|
Matcher.Tags
|
||||||
|
) &&
|
||||||
|
!Matcher.Tags.every(
|
||||||
|
Tag =>
|
||||||
|
Tags.includes(Tag)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Array.isArray(
|
||||||
|
Matcher.TagsAny
|
||||||
|
) &&
|
||||||
|
Matcher.TagsAny.length > 0 &&
|
||||||
|
!Matcher.TagsAny.some(
|
||||||
|
Tag =>
|
||||||
|
Tags.includes(Tag)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Array.isArray(
|
||||||
|
Matcher.TagsNot
|
||||||
|
) &&
|
||||||
|
Matcher.TagsNot.some(
|
||||||
|
Tag =>
|
||||||
|
Tags.includes(Tag)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetMutationTimeFee(
|
||||||
|
Mutation
|
||||||
|
) {
|
||||||
|
const MutationTime = Number(
|
||||||
|
Mutation.MutationTime ??
|
||||||
|
Mutation.Cooldown ??
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Number.isFinite(
|
||||||
|
MutationTime
|
||||||
|
) ||
|
||||||
|
MutationTime <= 0
|
||||||
|
) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.ceil(
|
||||||
|
MutationTime /
|
||||||
|
3600000 *
|
||||||
|
MutationTimeFeePerHour
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetPlantHarvestReward(
|
||||||
|
SaveData,
|
||||||
|
PlantId
|
||||||
|
) {
|
||||||
|
const Cost =
|
||||||
|
GetPlantShopCost(
|
||||||
|
SaveData,
|
||||||
|
PlantId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Cost === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.ceil(
|
||||||
|
Cost * 1.5
|
||||||
|
);
|
||||||
|
}
|
||||||
+398
-191
@@ -1,62 +1,34 @@
|
|||||||
let GameSave;
|
let GameSave;
|
||||||
let SelectedSeed = "Rose";
|
let SelectedSeedId = null;
|
||||||
|
|
||||||
const GardenUpdateInterval = 1000;
|
const GardenUpdateInterval = 1000;
|
||||||
|
|
||||||
|
|
||||||
async function StartGame() {
|
async function StartGame() {
|
||||||
GameSave = await LoadGame();
|
GameSave = await LoadGame();
|
||||||
NormalizeSave();
|
|
||||||
|
|
||||||
|
EnsureSelectedSeed();
|
||||||
RenderGame();
|
RenderGame();
|
||||||
|
|
||||||
setInterval(
|
setInterval(
|
||||||
RenderGarden,
|
() => {
|
||||||
|
RenderGarden();
|
||||||
|
RenderNextHarvest();
|
||||||
|
},
|
||||||
GardenUpdateInterval
|
GardenUpdateInterval
|
||||||
);
|
);
|
||||||
|
|
||||||
await RenderUser();
|
await RenderUser();
|
||||||
await RenderLeaderboard();
|
await RenderLeaderboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function NormalizeSave() {
|
|
||||||
GameSave.Currency ??= {};
|
|
||||||
GameSave.Currency.Dew ??= 0;
|
|
||||||
|
|
||||||
GameSave.Seeds ??= {};
|
|
||||||
GameSave.Seeds.Unlocked ??= [
|
|
||||||
"Rose"
|
|
||||||
];
|
|
||||||
|
|
||||||
GameSave.Statistics ??= {};
|
|
||||||
GameSave.Statistics.CurrencyEarned ??= {};
|
|
||||||
|
|
||||||
GameSave.Statistics.CurrencyEarned.Dew ??=
|
|
||||||
GameSave.Currency.Dew;
|
|
||||||
|
|
||||||
GameSave.Garden ??= {
|
|
||||||
Width: 3,
|
|
||||||
Height: 3,
|
|
||||||
Plots: []
|
|
||||||
};
|
|
||||||
|
|
||||||
const RequiredPlots =
|
|
||||||
GameSave.Garden.Width *
|
|
||||||
GameSave.Garden.Height;
|
|
||||||
|
|
||||||
while (
|
|
||||||
GameSave.Garden.Plots.length <
|
|
||||||
RequiredPlots
|
|
||||||
) {
|
|
||||||
GameSave.Garden.Plots.push(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function RenderGame() {
|
function RenderGame() {
|
||||||
|
EnsureSelectedSeed();
|
||||||
RenderCurrency();
|
RenderCurrency();
|
||||||
RenderSeeds();
|
RenderSeeds();
|
||||||
RenderGarden();
|
RenderGarden();
|
||||||
|
RenderNextHarvest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -67,7 +39,46 @@ function RenderCurrency() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
DewAmount.textContent =
|
DewAmount.textContent =
|
||||||
GameSave.Currency.Dew;
|
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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -79,14 +90,72 @@ function RenderSeeds() {
|
|||||||
|
|
||||||
SeedList.replaceChildren();
|
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 (
|
for (
|
||||||
const PlantId
|
const PlantId
|
||||||
of GameSave.Seeds.Unlocked
|
of SeedIds
|
||||||
) {
|
) {
|
||||||
const Plant =
|
const Plant =
|
||||||
Plants[PlantId];
|
GetPlantById(
|
||||||
|
PlantId
|
||||||
|
);
|
||||||
|
|
||||||
if (Plant === undefined) {
|
if (Plant === null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,11 +170,16 @@ function RenderSeeds() {
|
|||||||
Button.type = "button";
|
Button.type = "button";
|
||||||
|
|
||||||
Button.textContent =
|
Button.textContent =
|
||||||
Plant.Name;
|
Plant.Name +
|
||||||
|
" ×" +
|
||||||
|
GetSeedCount(
|
||||||
|
GameSave,
|
||||||
|
PlantId
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
PlantId ===
|
PlantId ===
|
||||||
SelectedSeed
|
SelectedSeedId
|
||||||
) {
|
) {
|
||||||
Button.setAttribute(
|
Button.setAttribute(
|
||||||
"aria-current",
|
"aria-current",
|
||||||
@@ -116,7 +190,7 @@ function RenderSeeds() {
|
|||||||
Button.addEventListener(
|
Button.addEventListener(
|
||||||
"click",
|
"click",
|
||||||
() => {
|
() => {
|
||||||
SelectedSeed =
|
SelectedSeedId =
|
||||||
PlantId;
|
PlantId;
|
||||||
|
|
||||||
RenderSeeds();
|
RenderSeeds();
|
||||||
@@ -180,10 +254,6 @@ function CreatePlotElement(
|
|||||||
Button.type = "button";
|
Button.type = "button";
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Empty plot.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (Plot === null) {
|
if (Plot === null) {
|
||||||
Button.classList.add(
|
Button.classList.add(
|
||||||
"GardenPlotEmpty"
|
"GardenPlotEmpty"
|
||||||
@@ -215,10 +285,6 @@ function CreatePlotElement(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Plant image.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const ImagePath =
|
const ImagePath =
|
||||||
GetPlantImage(
|
GetPlantImage(
|
||||||
Plot,
|
Plot,
|
||||||
@@ -243,13 +309,24 @@ function CreatePlotElement(
|
|||||||
Button.appendChild(
|
Button.appendChild(
|
||||||
Image
|
Image
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
const MissingImage =
|
||||||
|
document.createElement(
|
||||||
|
"span"
|
||||||
|
);
|
||||||
|
|
||||||
|
MissingImage.className =
|
||||||
|
"GardenPlantMissing";
|
||||||
|
|
||||||
|
MissingImage.textContent =
|
||||||
|
Plant.Name;
|
||||||
|
|
||||||
|
Button.appendChild(
|
||||||
|
MissingImage
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Mature plants can be harvested.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
IsPlantMature(
|
IsPlantMature(
|
||||||
Plot,
|
Plot,
|
||||||
@@ -288,6 +365,7 @@ function CreatePlotElement(
|
|||||||
"% grown";
|
"% grown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return Button;
|
return Button;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,34 +381,63 @@ async function PlantSeed(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsureSelectedSeed();
|
||||||
|
|
||||||
|
if (SelectedSeedId === null) {
|
||||||
|
SetGardenMessage(
|
||||||
|
"You don't have any seeds to plant."
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Plant =
|
||||||
|
GetPlantById(
|
||||||
|
SelectedSeedId
|
||||||
|
);
|
||||||
|
|
||||||
|
const PlantKey =
|
||||||
|
GetPlantKeyById(
|
||||||
|
SelectedSeedId
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!GameSave.Seeds.Unlocked
|
Plant === null ||
|
||||||
.includes(
|
PlantKey === null
|
||||||
SelectedSeed
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
!TakeSeed(
|
||||||
|
GameSave,
|
||||||
|
SelectedSeedId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
EnsureSelectedSeed();
|
||||||
|
RenderSeeds();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
GameSave.Garden.Plots[
|
GameSave.Garden.Plots[
|
||||||
PlotIndex
|
PlotIndex
|
||||||
] = {
|
] = {
|
||||||
Plant: SelectedSeed,
|
Plant: PlantKey,
|
||||||
|
|
||||||
AddedTags: [],
|
|
||||||
|
|
||||||
VisualVariant: null,
|
|
||||||
|
|
||||||
PlantedAt: Date.now()
|
PlantedAt: Date.now()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
SetGardenMessage(
|
SetGardenMessage(
|
||||||
"Planted " +
|
"Planted " +
|
||||||
Plants[SelectedSeed].Name +
|
Plant.Name +
|
||||||
"."
|
"."
|
||||||
);
|
);
|
||||||
|
|
||||||
RenderGarden();
|
RenderGame();
|
||||||
|
|
||||||
await SaveGame(
|
await SaveGame(
|
||||||
GameSave
|
GameSave
|
||||||
@@ -354,6 +461,7 @@ async function HarvestPlant(
|
|||||||
Plants[Plot.Plant];
|
Plants[Plot.Plant];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
Plant === undefined ||
|
||||||
!IsPlantMature(
|
!IsPlantMature(
|
||||||
Plot,
|
Plot,
|
||||||
Plant
|
Plant
|
||||||
@@ -362,22 +470,40 @@ async function HarvestPlant(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GiveReward(
|
|
||||||
Plant.Reward
|
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[
|
GameSave.Garden.Plots[
|
||||||
PlotIndex
|
PlotIndex
|
||||||
] = null;
|
] = null;
|
||||||
|
|
||||||
|
|
||||||
SetGardenMessage(
|
SetGardenMessage(
|
||||||
"Harvested " +
|
"Harvested " +
|
||||||
Plant.Name +
|
Plant.Name +
|
||||||
" for " +
|
" for " +
|
||||||
Plant.Reward.Amount +
|
RewardAmount +
|
||||||
" " +
|
" Dew."
|
||||||
Plant.Reward.Currency +
|
|
||||||
"."
|
|
||||||
);
|
);
|
||||||
|
|
||||||
RenderGame();
|
RenderGame();
|
||||||
@@ -437,63 +563,17 @@ function GetPlantGrowthProgress(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function GetPlantTags(
|
function GetPlantTags(
|
||||||
Plot,
|
Plot,
|
||||||
Plant
|
Plant
|
||||||
) {
|
) {
|
||||||
return [
|
return [
|
||||||
...Plant.Tags,
|
...(Plant.Tags ?? [])
|
||||||
...(Plot.AddedTags ?? [])
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function GetPlantVariant(
|
|
||||||
Plot,
|
|
||||||
Plant
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
Plot.VisualVariant !==
|
|
||||||
null
|
|
||||||
) {
|
|
||||||
return Plot.VisualVariant;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Tags =
|
|
||||||
GetPlantTags(
|
|
||||||
Plot,
|
|
||||||
Plant
|
|
||||||
);
|
|
||||||
|
|
||||||
const Variants = [
|
|
||||||
...Plant.Variants
|
|
||||||
].sort(
|
|
||||||
(A, B) =>
|
|
||||||
B.Priority -
|
|
||||||
A.Priority
|
|
||||||
);
|
|
||||||
|
|
||||||
for (
|
|
||||||
const Variant
|
|
||||||
of Variants
|
|
||||||
) {
|
|
||||||
const Matches =
|
|
||||||
Variant.Tags.every(
|
|
||||||
Tag =>
|
|
||||||
Tags.includes(
|
|
||||||
Tag
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Matches) {
|
|
||||||
return Variant.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Default";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function IsPlantMature(
|
function IsPlantMature(
|
||||||
Plot,
|
Plot,
|
||||||
Plant
|
Plant
|
||||||
@@ -511,74 +591,9 @@ function GetPlantImages(
|
|||||||
Plot,
|
Plot,
|
||||||
Plant
|
Plant
|
||||||
) {
|
) {
|
||||||
const Variants =
|
return PlantImages[
|
||||||
PlantImages[
|
Plot.Plant
|
||||||
Plot.Plant
|
] ?? [];
|
||||||
];
|
|
||||||
|
|
||||||
if (Variants === undefined) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const Variant =
|
|
||||||
GetPlantVariant(
|
|
||||||
Plot,
|
|
||||||
Plant
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Preferred variant.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (
|
|
||||||
Array.isArray(
|
|
||||||
Variants[Variant]
|
|
||||||
) &&
|
|
||||||
Variants[Variant].length > 0
|
|
||||||
) {
|
|
||||||
return Variants[
|
|
||||||
Variant
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Default visual fallback.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (
|
|
||||||
Array.isArray(
|
|
||||||
Variants.Default
|
|
||||||
) &&
|
|
||||||
Variants.Default.length > 0
|
|
||||||
) {
|
|
||||||
return Variants.Default;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Absolute fallback:
|
|
||||||
* use the first variant that has images.
|
|
||||||
*/
|
|
||||||
|
|
||||||
for (
|
|
||||||
const Images
|
|
||||||
of Object.values(
|
|
||||||
Variants
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
Array.isArray(Images) &&
|
|
||||||
Images.length > 0
|
|
||||||
) {
|
|
||||||
return Images;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -642,6 +657,198 @@ function SetGardenMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetNextHarvestTime() {
|
||||||
|
let NextHarvestTime = null;
|
||||||
|
|
||||||
|
|
||||||
|
for (
|
||||||
|
const Plot
|
||||||
|
of GameSave.Garden.Plots
|
||||||
|
) {
|
||||||
|
if (Plot === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Plant =
|
||||||
|
Plants[Plot.Plant];
|
||||||
|
|
||||||
|
if (Plant === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const HarvestTime =
|
||||||
|
Plot.PlantedAt +
|
||||||
|
Plant.GrowthTime;
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
HarvestTime <=
|
||||||
|
Date.now()
|
||||||
|
) {
|
||||||
|
return Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
NextHarvestTime === null ||
|
||||||
|
HarvestTime <
|
||||||
|
NextHarvestTime
|
||||||
|
) {
|
||||||
|
NextHarvestTime =
|
||||||
|
HarvestTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return NextHarvestTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RenderNextHarvest() {
|
||||||
|
const Element =
|
||||||
|
document.getElementById(
|
||||||
|
"NextHarvest"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Element === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const HarvestTime =
|
||||||
|
GetNextHarvestTime();
|
||||||
|
|
||||||
|
|
||||||
|
if (HarvestTime === null) {
|
||||||
|
Element.textContent =
|
||||||
|
"Nothing planted";
|
||||||
|
|
||||||
|
Element.removeAttribute(
|
||||||
|
"title"
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Remaining =
|
||||||
|
HarvestTime -
|
||||||
|
Date.now();
|
||||||
|
|
||||||
|
|
||||||
|
if (Remaining <= 0) {
|
||||||
|
Element.textContent =
|
||||||
|
"Ready now!";
|
||||||
|
|
||||||
|
Element.removeAttribute(
|
||||||
|
"title"
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Element.textContent =
|
||||||
|
FormatRemainingTime(
|
||||||
|
Remaining
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const HarvestDate =
|
||||||
|
new Date(
|
||||||
|
HarvestTime
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
Element.title =
|
||||||
|
"Ready at " +
|
||||||
|
HarvestDate.toLocaleTimeString(
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function FormatRemainingTime(
|
||||||
|
Milliseconds
|
||||||
|
) {
|
||||||
|
const TotalSeconds =
|
||||||
|
Math.ceil(
|
||||||
|
Milliseconds /
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
|
||||||
|
const Days =
|
||||||
|
Math.floor(
|
||||||
|
TotalSeconds /
|
||||||
|
86400
|
||||||
|
);
|
||||||
|
|
||||||
|
const Hours =
|
||||||
|
Math.floor(
|
||||||
|
(
|
||||||
|
TotalSeconds %
|
||||||
|
86400
|
||||||
|
) /
|
||||||
|
3600
|
||||||
|
);
|
||||||
|
|
||||||
|
const Minutes =
|
||||||
|
Math.floor(
|
||||||
|
(
|
||||||
|
TotalSeconds %
|
||||||
|
3600
|
||||||
|
) /
|
||||||
|
60
|
||||||
|
);
|
||||||
|
|
||||||
|
const Seconds =
|
||||||
|
TotalSeconds %
|
||||||
|
60;
|
||||||
|
|
||||||
|
|
||||||
|
if (Days > 0) {
|
||||||
|
return (
|
||||||
|
Days +
|
||||||
|
"d " +
|
||||||
|
Hours +
|
||||||
|
"h"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (Hours > 0) {
|
||||||
|
return (
|
||||||
|
Hours +
|
||||||
|
"h " +
|
||||||
|
Minutes +
|
||||||
|
"m"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (Minutes > 0) {
|
||||||
|
return (
|
||||||
|
Minutes +
|
||||||
|
"m " +
|
||||||
|
Seconds +
|
||||||
|
"s"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
Seconds +
|
||||||
|
"s"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"DOMContentLoaded",
|
"DOMContentLoaded",
|
||||||
StartGame
|
StartGame
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
const MutationSets = {
|
const MutationSets = {
|
||||||
DarkRose: {
|
RoseDarkening: {
|
||||||
Name: "Dark Rose",
|
Id: 1,
|
||||||
|
Name: "Rose darkening",
|
||||||
|
|
||||||
|
Description:
|
||||||
|
"A mutation that seems to corrupt roses.",
|
||||||
|
|
||||||
Chance: 0.1,
|
Chance: 0.1,
|
||||||
Cooldown: 120000,
|
Cooldown: 120000,
|
||||||
|
|
||||||
|
Relations: {
|
||||||
|
PlantsUsed: [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
|
||||||
|
PlantsCreated: [
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
Pattern: [
|
Pattern: [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -35,6 +49,60 @@ const MutationSets = {
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
|
Failure: "Keep"
|
||||||
|
},
|
||||||
|
|
||||||
|
RoseDyePurple: {
|
||||||
|
Id: 2,
|
||||||
|
Name: "Rose dying purple",
|
||||||
|
|
||||||
|
Description:
|
||||||
|
"A colourful mutation that makes a purple rose.",
|
||||||
|
|
||||||
|
Chance: 1.0,
|
||||||
|
Cooldown: 100000,
|
||||||
|
|
||||||
|
Relations: {
|
||||||
|
PlantsUsed: [
|
||||||
|
1,
|
||||||
|
3
|
||||||
|
],
|
||||||
|
|
||||||
|
PlantsCreated: [
|
||||||
|
4
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
Pattern: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
Tags: [
|
||||||
|
"Rose",
|
||||||
|
"Red"
|
||||||
|
],
|
||||||
|
Capture: "ParentA"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Empty",
|
||||||
|
|
||||||
|
{
|
||||||
|
Tags: [
|
||||||
|
"Rose",
|
||||||
|
"Blue"
|
||||||
|
],
|
||||||
|
Capture: "ParentB"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
|
Success: [
|
||||||
|
[
|
||||||
|
"Empty",
|
||||||
|
"PurpleRose",
|
||||||
|
"Empty"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
Failure: "Keep"
|
Failure: "Keep"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
+71
-22
@@ -1,6 +1,10 @@
|
|||||||
const Plants = {
|
const Plants = {
|
||||||
Rose: {
|
RedRose: {
|
||||||
Name: "Rose",
|
Id: 1,
|
||||||
|
Name: "Red Rose",
|
||||||
|
|
||||||
|
Description:
|
||||||
|
"A common red rose.",
|
||||||
|
|
||||||
Tags: [
|
Tags: [
|
||||||
"Rose",
|
"Rose",
|
||||||
@@ -8,29 +12,74 @@ const Plants = {
|
|||||||
"Red"
|
"Red"
|
||||||
],
|
],
|
||||||
|
|
||||||
GrowthTime: 60000,
|
GrowthTime: 3600000,
|
||||||
|
|
||||||
Variants: [
|
|
||||||
{
|
|
||||||
Name: "Red",
|
|
||||||
Tags: [
|
|
||||||
"Red"
|
|
||||||
],
|
|
||||||
Priority: 10
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Name: "Default",
|
|
||||||
Tags: [],
|
|
||||||
Priority: 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
Effects: [],
|
Effects: [],
|
||||||
|
|
||||||
Reward: {
|
Shop: {
|
||||||
Currency: "Dew",
|
StartingPlant: true,
|
||||||
Amount: 5
|
BaseCost: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
DarkRose: {
|
||||||
|
Id: 2,
|
||||||
|
Name: "Dark Rose",
|
||||||
|
|
||||||
|
Description:
|
||||||
|
"A jet-black rose.",
|
||||||
|
|
||||||
|
Tags: [
|
||||||
|
"Rose",
|
||||||
|
"Flower",
|
||||||
|
"Black"
|
||||||
|
],
|
||||||
|
|
||||||
|
GrowthTime: 4000000,
|
||||||
|
|
||||||
|
Effects: [],
|
||||||
|
|
||||||
|
Shop: {
|
||||||
|
StartingPlant: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
BlueRose: {
|
||||||
|
Id: 3,
|
||||||
|
Name: "Blue Rose",
|
||||||
|
|
||||||
|
Tags: [
|
||||||
|
"Rose",
|
||||||
|
"Flower",
|
||||||
|
"Blue"
|
||||||
|
],
|
||||||
|
|
||||||
|
GrowthTime: 3600000,
|
||||||
|
|
||||||
|
Effects: [],
|
||||||
|
|
||||||
|
Shop: {
|
||||||
|
StartingPlant: true,
|
||||||
|
BaseCost: 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
PurpleRose: {
|
||||||
|
Id: 4,
|
||||||
|
Name: "Purple Rose",
|
||||||
|
|
||||||
|
Tags: [
|
||||||
|
"Rose",
|
||||||
|
"Flower",
|
||||||
|
"Purple"
|
||||||
|
],
|
||||||
|
|
||||||
|
GrowthTime: 3600000,
|
||||||
|
|
||||||
|
Effects: [],
|
||||||
|
|
||||||
|
Shop: {
|
||||||
|
StartingPlant: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
+500
-12
@@ -5,8 +5,45 @@ const ApiUrl = "https://api.srhtnin.garden";
|
|||||||
|
|
||||||
|
|
||||||
function CreateNewSave() {
|
function CreateNewSave() {
|
||||||
|
const StartingSeeds = {};
|
||||||
|
const StartingPlants = [];
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof Plants !==
|
||||||
|
"undefined"
|
||||||
|
) {
|
||||||
|
for (
|
||||||
|
const Plant
|
||||||
|
of Object.values(Plants)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
Plant.Shop
|
||||||
|
?.StartingPlant !==
|
||||||
|
true
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartingSeeds[
|
||||||
|
String(Plant.Id)
|
||||||
|
] = 1;
|
||||||
|
|
||||||
|
StartingPlants.push(
|
||||||
|
Plant.Id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
StartingPlants.length === 0
|
||||||
|
) {
|
||||||
|
StartingSeeds["1"] = 1;
|
||||||
|
StartingPlants.push(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Version: 1,
|
Version: 2,
|
||||||
Revision: 0,
|
Revision: 0,
|
||||||
LastSavedAt: Date.now(),
|
LastSavedAt: Date.now(),
|
||||||
|
|
||||||
@@ -17,13 +54,17 @@ function CreateNewSave() {
|
|||||||
Statistics: {
|
Statistics: {
|
||||||
CurrencyEarned: {
|
CurrencyEarned: {
|
||||||
Dew: 0
|
Dew: 0
|
||||||
}
|
},
|
||||||
|
|
||||||
|
CurrencySpent: {
|
||||||
|
Dew: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
SeedsPurchased: 0
|
||||||
},
|
},
|
||||||
|
|
||||||
Seeds: {
|
Inventory: {
|
||||||
Unlocked: [
|
Seeds: StartingSeeds
|
||||||
"Rose"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Upgrades: {
|
Upgrades: {
|
||||||
@@ -37,10 +78,7 @@ function CreateNewSave() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Discoveries: {
|
Discoveries: {
|
||||||
Plants: [
|
Plants: StartingPlants,
|
||||||
"Rose"
|
|
||||||
],
|
|
||||||
|
|
||||||
Mutations: []
|
Mutations: []
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -49,6 +87,445 @@ function CreateNewSave() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function NormalizeSaveData(
|
||||||
|
SaveData
|
||||||
|
) {
|
||||||
|
SaveData.Version = Math.max(
|
||||||
|
Number(
|
||||||
|
SaveData.Version ?? 1
|
||||||
|
),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
|
||||||
|
SaveData.Revision = Number(
|
||||||
|
SaveData.Revision ?? 0
|
||||||
|
);
|
||||||
|
|
||||||
|
SaveData.LastSavedAt = Number(
|
||||||
|
SaveData.LastSavedAt ??
|
||||||
|
Date.now()
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
SaveData.Currency ??= {};
|
||||||
|
SaveData.Currency.Dew = Number(
|
||||||
|
SaveData.Currency.Dew ?? 0
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
SaveData.Statistics ??= {};
|
||||||
|
SaveData.Statistics.CurrencyEarned ??= {};
|
||||||
|
SaveData.Statistics.CurrencySpent ??= {};
|
||||||
|
|
||||||
|
SaveData.Statistics.CurrencyEarned.Dew ??=
|
||||||
|
SaveData.Currency.Dew;
|
||||||
|
|
||||||
|
SaveData.Statistics.CurrencySpent.Dew ??= 0;
|
||||||
|
SaveData.Statistics.SeedsPurchased ??= 0;
|
||||||
|
|
||||||
|
|
||||||
|
const HadSeedInventory =
|
||||||
|
SaveData.Inventory
|
||||||
|
?.Seeds !== undefined;
|
||||||
|
|
||||||
|
SaveData.Inventory ??= {};
|
||||||
|
SaveData.Inventory.Seeds ??= {};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Migrate the old infinite seed unlock list
|
||||||
|
* into the new finite seed inventory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (
|
||||||
|
const LegacyPlant
|
||||||
|
of SaveData.Seeds
|
||||||
|
?.Unlocked ?? []
|
||||||
|
) {
|
||||||
|
const PlantId =
|
||||||
|
ResolveLegacyPlantId(
|
||||||
|
LegacyPlant
|
||||||
|
);
|
||||||
|
|
||||||
|
if (PlantId === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveData.Inventory.Seeds[
|
||||||
|
String(PlantId)
|
||||||
|
] ??= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Existing saves predate inventory, so give them
|
||||||
|
* one seed for each starting plant as a migration
|
||||||
|
* safety net. New saves already contain these.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!HadSeedInventory) {
|
||||||
|
for (
|
||||||
|
const Plant
|
||||||
|
of GetStartingPlantDefinitions()
|
||||||
|
) {
|
||||||
|
SaveData.Inventory.Seeds[
|
||||||
|
String(Plant.Id)
|
||||||
|
] ??= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (
|
||||||
|
const InventoryKey
|
||||||
|
of Object.keys(
|
||||||
|
SaveData.Inventory.Seeds
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const Amount = Math.max(
|
||||||
|
0,
|
||||||
|
Math.floor(
|
||||||
|
Number(
|
||||||
|
SaveData.Inventory.Seeds[
|
||||||
|
InventoryKey
|
||||||
|
] ?? 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
SaveData.Inventory.Seeds[
|
||||||
|
InventoryKey
|
||||||
|
] = Amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
delete SaveData.Seeds;
|
||||||
|
|
||||||
|
|
||||||
|
SaveData.Discoveries ??= {};
|
||||||
|
|
||||||
|
SaveData.Discoveries.Plants =
|
||||||
|
NormalizePlantDiscoveries(
|
||||||
|
SaveData.Discoveries.Plants ?? []
|
||||||
|
);
|
||||||
|
|
||||||
|
SaveData.Discoveries.Mutations =
|
||||||
|
NormalizeMutationDiscoveries(
|
||||||
|
SaveData.Discoveries.Mutations ?? []
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
for (
|
||||||
|
const Plant
|
||||||
|
of GetStartingPlantDefinitions()
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!SaveData.Discoveries.Plants
|
||||||
|
.includes(
|
||||||
|
Plant.Id
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
SaveData.Discoveries.Plants.push(
|
||||||
|
Plant.Id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveData.Discoveries.Plants.sort(
|
||||||
|
(A, B) => A - B
|
||||||
|
);
|
||||||
|
|
||||||
|
SaveData.Discoveries.Mutations.sort(
|
||||||
|
(A, B) => A - B
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
SaveData.Upgrades ??= {};
|
||||||
|
SaveData.Upgrades.PlotSize ??= 0;
|
||||||
|
|
||||||
|
|
||||||
|
SaveData.Garden ??= {
|
||||||
|
Width: 3,
|
||||||
|
Height: 3,
|
||||||
|
Plots: []
|
||||||
|
};
|
||||||
|
|
||||||
|
SaveData.Garden.Width = Math.max(
|
||||||
|
1,
|
||||||
|
Number(
|
||||||
|
SaveData.Garden.Width ?? 3
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
SaveData.Garden.Height = Math.max(
|
||||||
|
1,
|
||||||
|
Number(
|
||||||
|
SaveData.Garden.Height ?? 3
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
SaveData.Garden.Plots ??= [];
|
||||||
|
|
||||||
|
|
||||||
|
for (
|
||||||
|
let PlotIndex = 0;
|
||||||
|
PlotIndex <
|
||||||
|
SaveData.Garden.Plots.length;
|
||||||
|
PlotIndex++
|
||||||
|
) {
|
||||||
|
const Plot =
|
||||||
|
SaveData.Garden.Plots[
|
||||||
|
PlotIndex
|
||||||
|
];
|
||||||
|
|
||||||
|
if (Plot === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Plot.Plant === "Rose"
|
||||||
|
) {
|
||||||
|
Plot.Plant =
|
||||||
|
"RedRose";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof Plot.Plant ===
|
||||||
|
"number"
|
||||||
|
) {
|
||||||
|
const PlantKey =
|
||||||
|
ResolvePlantKeyById(
|
||||||
|
Plot.Plant
|
||||||
|
);
|
||||||
|
|
||||||
|
if (PlantKey !== null) {
|
||||||
|
Plot.Plant = PlantKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plot.PlantedAt = Number(
|
||||||
|
Plot.PlantedAt ??
|
||||||
|
Date.now()
|
||||||
|
);
|
||||||
|
|
||||||
|
delete Plot.VisualVariant;
|
||||||
|
delete Plot.AddedTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const RequiredPlots =
|
||||||
|
SaveData.Garden.Width *
|
||||||
|
SaveData.Garden.Height;
|
||||||
|
|
||||||
|
while (
|
||||||
|
SaveData.Garden.Plots.length <
|
||||||
|
RequiredPlots
|
||||||
|
) {
|
||||||
|
SaveData.Garden.Plots.push(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
SaveData.Garden.Plots.length >
|
||||||
|
RequiredPlots
|
||||||
|
) {
|
||||||
|
SaveData.Garden.Plots.length =
|
||||||
|
RequiredPlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SaveData.MutationCooldowns ??= {};
|
||||||
|
|
||||||
|
|
||||||
|
return SaveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetStartingPlantDefinitions() {
|
||||||
|
if (
|
||||||
|
typeof Plants ===
|
||||||
|
"undefined"
|
||||||
|
) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.values(Plants)
|
||||||
|
.filter(
|
||||||
|
Plant =>
|
||||||
|
Plant.Shop
|
||||||
|
?.StartingPlant ===
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ResolveLegacyPlantId(
|
||||||
|
Value
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
typeof Value ===
|
||||||
|
"number"
|
||||||
|
) {
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof Value !==
|
||||||
|
"string"
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LegacyPlantIds = {
|
||||||
|
Rose: 1,
|
||||||
|
RedRose: 1,
|
||||||
|
DarkRose: 2,
|
||||||
|
BlueRose: 3,
|
||||||
|
PurpleRose: 4
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
LegacyPlantIds[Value] !==
|
||||||
|
undefined
|
||||||
|
) {
|
||||||
|
return LegacyPlantIds[Value];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof Plants !==
|
||||||
|
"undefined" &&
|
||||||
|
Plants[Value] !== undefined
|
||||||
|
) {
|
||||||
|
return Plants[Value].Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NumericValue =
|
||||||
|
Number(Value);
|
||||||
|
|
||||||
|
if (
|
||||||
|
Number.isInteger(
|
||||||
|
NumericValue
|
||||||
|
) &&
|
||||||
|
NumericValue > 0
|
||||||
|
) {
|
||||||
|
return NumericValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ResolvePlantKeyById(
|
||||||
|
PlantId
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
typeof Plants ===
|
||||||
|
"undefined"
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (
|
||||||
|
const [PlantKey, Plant]
|
||||||
|
of Object.entries(Plants)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
Plant.Id ===
|
||||||
|
Number(PlantId)
|
||||||
|
) {
|
||||||
|
return PlantKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function NormalizePlantDiscoveries(
|
||||||
|
Values
|
||||||
|
) {
|
||||||
|
const PlantIds = [];
|
||||||
|
|
||||||
|
for (
|
||||||
|
const Value
|
||||||
|
of Values
|
||||||
|
) {
|
||||||
|
const PlantId =
|
||||||
|
ResolveLegacyPlantId(
|
||||||
|
Value
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
PlantId !== null &&
|
||||||
|
!PlantIds.includes(
|
||||||
|
PlantId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
PlantIds.push(
|
||||||
|
PlantId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlantIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function NormalizeMutationDiscoveries(
|
||||||
|
Values
|
||||||
|
) {
|
||||||
|
const MutationIds = [];
|
||||||
|
|
||||||
|
for (
|
||||||
|
const Value
|
||||||
|
of Values
|
||||||
|
) {
|
||||||
|
let MutationId = null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof Value ===
|
||||||
|
"number"
|
||||||
|
) {
|
||||||
|
MutationId = Value;
|
||||||
|
} else if (
|
||||||
|
typeof Value ===
|
||||||
|
"string" &&
|
||||||
|
typeof MutationSets !==
|
||||||
|
"undefined" &&
|
||||||
|
MutationSets[Value] !==
|
||||||
|
undefined
|
||||||
|
) {
|
||||||
|
MutationId =
|
||||||
|
MutationSets[Value].Id;
|
||||||
|
} else {
|
||||||
|
const NumericValue =
|
||||||
|
Number(Value);
|
||||||
|
|
||||||
|
if (
|
||||||
|
Number.isInteger(
|
||||||
|
NumericValue
|
||||||
|
) &&
|
||||||
|
NumericValue > 0
|
||||||
|
) {
|
||||||
|
MutationId =
|
||||||
|
NumericValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
MutationId !== null &&
|
||||||
|
!MutationIds.includes(
|
||||||
|
MutationId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
MutationIds.push(
|
||||||
|
MutationId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MutationIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function GetSaveKey() {
|
function GetSaveKey() {
|
||||||
let SaveKey = localStorage.getItem(
|
let SaveKey = localStorage.getItem(
|
||||||
SaveKeyName
|
SaveKeyName
|
||||||
@@ -96,7 +573,9 @@ function ReadLocalSave() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return JSON.parse(RawSave);
|
return NormalizeSaveData(
|
||||||
|
JSON.parse(RawSave)
|
||||||
|
);
|
||||||
} catch (Error) {
|
} catch (Error) {
|
||||||
console.error(
|
console.error(
|
||||||
"Couldn't read local save:",
|
"Couldn't read local save:",
|
||||||
@@ -185,10 +664,17 @@ async function ReadRemoteSave(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.SaveData;
|
return NormalizeSaveData(
|
||||||
|
Result.SaveData
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function SaveGame(SaveData) {
|
async function SaveGame(SaveData) {
|
||||||
|
NormalizeSaveData(
|
||||||
|
SaveData
|
||||||
|
);
|
||||||
|
|
||||||
SaveData.Revision++;
|
SaveData.Revision++;
|
||||||
SaveData.LastSavedAt = Date.now();
|
SaveData.LastSavedAt = Date.now();
|
||||||
|
|
||||||
@@ -304,5 +790,7 @@ async function LoadGame() {
|
|||||||
return RemoteSave;
|
return RemoteSave;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WriteLocalSave(LocalSave);
|
||||||
|
|
||||||
return LocalSave;
|
return LocalSave;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,504 @@
|
|||||||
|
let ShopSave;
|
||||||
|
let ShopPurchasePending = false;
|
||||||
|
|
||||||
|
|
||||||
|
async function StartShop() {
|
||||||
|
ShopSave = await LoadGame();
|
||||||
|
|
||||||
|
RenderShop();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RenderShop() {
|
||||||
|
RenderShopCurrency();
|
||||||
|
RenderShopSeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RenderShopCurrency() {
|
||||||
|
const DewAmount =
|
||||||
|
document.getElementById(
|
||||||
|
"ShopDewAmount"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (DewAmount === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DewAmount.textContent =
|
||||||
|
ShopSave.Currency.Dew
|
||||||
|
.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RenderShopSeeds() {
|
||||||
|
const SeedList =
|
||||||
|
document.getElementById(
|
||||||
|
"ShopSeedList"
|
||||||
|
);
|
||||||
|
|
||||||
|
SeedList.replaceChildren();
|
||||||
|
|
||||||
|
|
||||||
|
const AvailablePlants =
|
||||||
|
Object.values(Plants)
|
||||||
|
.filter(
|
||||||
|
Plant =>
|
||||||
|
IsPlantAvailableInShop(
|
||||||
|
ShopSave,
|
||||||
|
Plant
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.sort(
|
||||||
|
(A, B) =>
|
||||||
|
A.Id - B.Id
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
AvailablePlants.length === 0
|
||||||
|
) {
|
||||||
|
const EmptyMessage =
|
||||||
|
document.createElement(
|
||||||
|
"p"
|
||||||
|
);
|
||||||
|
|
||||||
|
EmptyMessage.className =
|
||||||
|
"ShopEmpty";
|
||||||
|
|
||||||
|
EmptyMessage.textContent =
|
||||||
|
"No seeds are available yet.";
|
||||||
|
|
||||||
|
SeedList.appendChild(
|
||||||
|
EmptyMessage
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (
|
||||||
|
const Plant
|
||||||
|
of AvailablePlants
|
||||||
|
) {
|
||||||
|
SeedList.appendChild(
|
||||||
|
CreateShopSeedCard(
|
||||||
|
Plant
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function CreateShopSeedCard(
|
||||||
|
Plant
|
||||||
|
) {
|
||||||
|
const Card =
|
||||||
|
document.createElement(
|
||||||
|
"article"
|
||||||
|
);
|
||||||
|
|
||||||
|
Card.className =
|
||||||
|
"ShopItem";
|
||||||
|
|
||||||
|
|
||||||
|
const Header =
|
||||||
|
document.createElement(
|
||||||
|
"div"
|
||||||
|
);
|
||||||
|
|
||||||
|
Header.className =
|
||||||
|
"ShopItemHeader";
|
||||||
|
|
||||||
|
|
||||||
|
const Number =
|
||||||
|
document.createElement(
|
||||||
|
"span"
|
||||||
|
);
|
||||||
|
|
||||||
|
Number.className =
|
||||||
|
"ShopItemNumber";
|
||||||
|
|
||||||
|
Number.textContent =
|
||||||
|
"No. " +
|
||||||
|
String(Plant.Id).padStart(
|
||||||
|
3,
|
||||||
|
"0"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const Name =
|
||||||
|
document.createElement(
|
||||||
|
"h3"
|
||||||
|
);
|
||||||
|
|
||||||
|
Name.textContent =
|
||||||
|
Plant.Name;
|
||||||
|
|
||||||
|
|
||||||
|
Header.append(
|
||||||
|
Number,
|
||||||
|
Name
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const Body =
|
||||||
|
document.createElement(
|
||||||
|
"div"
|
||||||
|
);
|
||||||
|
|
||||||
|
Body.className =
|
||||||
|
"ShopItemBody";
|
||||||
|
|
||||||
|
|
||||||
|
const Visual =
|
||||||
|
CreateShopPlantVisual(
|
||||||
|
Plant
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const Details =
|
||||||
|
document.createElement(
|
||||||
|
"div"
|
||||||
|
);
|
||||||
|
|
||||||
|
Details.className =
|
||||||
|
"ShopItemDetails";
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof Plant.Description ===
|
||||||
|
"string" &&
|
||||||
|
Plant.Description.length > 0
|
||||||
|
) {
|
||||||
|
const Description =
|
||||||
|
document.createElement(
|
||||||
|
"p"
|
||||||
|
);
|
||||||
|
|
||||||
|
Description.textContent =
|
||||||
|
Plant.Description;
|
||||||
|
|
||||||
|
Details.appendChild(
|
||||||
|
Description
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Cost =
|
||||||
|
GetPlantShopCost(
|
||||||
|
ShopSave,
|
||||||
|
Plant.Id
|
||||||
|
);
|
||||||
|
|
||||||
|
const Reward =
|
||||||
|
GetPlantHarvestReward(
|
||||||
|
ShopSave,
|
||||||
|
Plant.Id
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
Details.appendChild(
|
||||||
|
CreateShopStat(
|
||||||
|
"Seed cost",
|
||||||
|
Cost === null
|
||||||
|
? "Unavailable"
|
||||||
|
: Cost.toLocaleString() +
|
||||||
|
" Dew"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Details.appendChild(
|
||||||
|
CreateShopStat(
|
||||||
|
"Harvest reward",
|
||||||
|
Reward === null
|
||||||
|
? "Unavailable"
|
||||||
|
: Reward.toLocaleString() +
|
||||||
|
" Dew"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Details.appendChild(
|
||||||
|
CreateShopStat(
|
||||||
|
"Inventory",
|
||||||
|
GetSeedCount(
|
||||||
|
ShopSave,
|
||||||
|
Plant.Id
|
||||||
|
).toLocaleString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
Body.append(
|
||||||
|
Visual,
|
||||||
|
Details
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const BuyButton =
|
||||||
|
document.createElement(
|
||||||
|
"button"
|
||||||
|
);
|
||||||
|
|
||||||
|
BuyButton.className =
|
||||||
|
"ShopBuyButton";
|
||||||
|
|
||||||
|
BuyButton.type = "button";
|
||||||
|
|
||||||
|
BuyButton.textContent =
|
||||||
|
Cost === null
|
||||||
|
? "Unavailable"
|
||||||
|
: "Buy seed - " +
|
||||||
|
Cost.toLocaleString() +
|
||||||
|
" Dew";
|
||||||
|
|
||||||
|
BuyButton.disabled =
|
||||||
|
Cost === null ||
|
||||||
|
ShopSave.Currency.Dew <
|
||||||
|
Cost ||
|
||||||
|
ShopPurchasePending;
|
||||||
|
|
||||||
|
BuyButton.addEventListener(
|
||||||
|
"click",
|
||||||
|
() => {
|
||||||
|
BuyPlantSeed(
|
||||||
|
Plant.Id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
Card.append(
|
||||||
|
Header,
|
||||||
|
Body,
|
||||||
|
BuyButton
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
return Card;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function CreateShopPlantVisual(
|
||||||
|
Plant
|
||||||
|
) {
|
||||||
|
const Visual =
|
||||||
|
document.createElement(
|
||||||
|
"div"
|
||||||
|
);
|
||||||
|
|
||||||
|
Visual.className =
|
||||||
|
"ShopPlantVisual";
|
||||||
|
|
||||||
|
|
||||||
|
const PlantKey =
|
||||||
|
GetPlantKeyById(
|
||||||
|
Plant.Id
|
||||||
|
);
|
||||||
|
|
||||||
|
const Images =
|
||||||
|
PlantKey === null
|
||||||
|
? []
|
||||||
|
: PlantImages[
|
||||||
|
PlantKey
|
||||||
|
] ?? [];
|
||||||
|
|
||||||
|
|
||||||
|
if (Images.length === 0) {
|
||||||
|
const Missing =
|
||||||
|
document.createElement(
|
||||||
|
"span"
|
||||||
|
);
|
||||||
|
|
||||||
|
Missing.className =
|
||||||
|
"ShopPlantMissing";
|
||||||
|
|
||||||
|
Missing.textContent =
|
||||||
|
"No image";
|
||||||
|
|
||||||
|
Visual.appendChild(
|
||||||
|
Missing
|
||||||
|
);
|
||||||
|
|
||||||
|
return Visual;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Image =
|
||||||
|
document.createElement(
|
||||||
|
"img"
|
||||||
|
);
|
||||||
|
|
||||||
|
Image.className =
|
||||||
|
"ShopPlantImage";
|
||||||
|
|
||||||
|
Image.src =
|
||||||
|
Images[
|
||||||
|
Images.length - 1
|
||||||
|
];
|
||||||
|
|
||||||
|
Image.alt =
|
||||||
|
Plant.Name;
|
||||||
|
|
||||||
|
Visual.appendChild(
|
||||||
|
Image
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
return Visual;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function CreateShopStat(
|
||||||
|
Name,
|
||||||
|
Value
|
||||||
|
) {
|
||||||
|
const Row =
|
||||||
|
document.createElement(
|
||||||
|
"p"
|
||||||
|
);
|
||||||
|
|
||||||
|
Row.className =
|
||||||
|
"ShopStat";
|
||||||
|
|
||||||
|
|
||||||
|
const Label =
|
||||||
|
document.createElement(
|
||||||
|
"span"
|
||||||
|
);
|
||||||
|
|
||||||
|
Label.className =
|
||||||
|
"ShopStatName";
|
||||||
|
|
||||||
|
Label.textContent =
|
||||||
|
Name + ":";
|
||||||
|
|
||||||
|
|
||||||
|
const Amount =
|
||||||
|
document.createElement(
|
||||||
|
"strong"
|
||||||
|
);
|
||||||
|
|
||||||
|
Amount.textContent =
|
||||||
|
Value;
|
||||||
|
|
||||||
|
|
||||||
|
Row.append(
|
||||||
|
Label,
|
||||||
|
Amount
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
return Row;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function BuyPlantSeed(
|
||||||
|
PlantId
|
||||||
|
) {
|
||||||
|
if (ShopPurchasePending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Plant =
|
||||||
|
GetPlantById(
|
||||||
|
PlantId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
Plant === null ||
|
||||||
|
!IsPlantAvailableInShop(
|
||||||
|
ShopSave,
|
||||||
|
Plant
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Cost =
|
||||||
|
GetPlantShopCost(
|
||||||
|
ShopSave,
|
||||||
|
PlantId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Cost === null) {
|
||||||
|
SetShopMessage(
|
||||||
|
"That seed doesn't have a purchasable recipe yet."
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
ShopSave.Currency.Dew <
|
||||||
|
Cost
|
||||||
|
) {
|
||||||
|
SetShopMessage(
|
||||||
|
"You don't have enough Dew for " +
|
||||||
|
Plant.Name +
|
||||||
|
"."
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ShopPurchasePending = true;
|
||||||
|
|
||||||
|
ShopSave.Currency.Dew -=
|
||||||
|
Cost;
|
||||||
|
|
||||||
|
ShopSave.Statistics.CurrencySpent.Dew +=
|
||||||
|
Cost;
|
||||||
|
|
||||||
|
ShopSave.Statistics.SeedsPurchased++;
|
||||||
|
|
||||||
|
AddSeed(
|
||||||
|
ShopSave,
|
||||||
|
PlantId
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
SetShopMessage(
|
||||||
|
"Bought a " +
|
||||||
|
Plant.Name +
|
||||||
|
" seed for " +
|
||||||
|
Cost +
|
||||||
|
" Dew."
|
||||||
|
);
|
||||||
|
|
||||||
|
RenderShop();
|
||||||
|
|
||||||
|
|
||||||
|
await SaveGame(
|
||||||
|
ShopSave
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
ShopPurchasePending = false;
|
||||||
|
RenderShop();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function SetShopMessage(
|
||||||
|
Message
|
||||||
|
) {
|
||||||
|
const Element =
|
||||||
|
document.getElementById(
|
||||||
|
"ShopMessage"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Element !== null) {
|
||||||
|
Element.textContent =
|
||||||
|
Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
"DOMContentLoaded",
|
||||||
|
StartShop
|
||||||
|
);
|
||||||
@@ -1060,3 +1060,237 @@ select {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.SeedEmpty {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
color: var(--Subtext1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.GardenPlantMissing {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0.35rem;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
color: var(--Subtext1);
|
||||||
|
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopStatus {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
|
||||||
|
background-color: var(--Mantle);
|
||||||
|
border: 3px solid var(--Surface2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopSection {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopSeedList {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
repeat(
|
||||||
|
auto-fit,
|
||||||
|
minmax(15rem, 1fr)
|
||||||
|
);
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopItem {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
|
||||||
|
padding: 0.75rem;
|
||||||
|
|
||||||
|
background-color: var(--Mantle);
|
||||||
|
border: 4px solid var(--Mauve);
|
||||||
|
|
||||||
|
box-shadow:
|
||||||
|
3px 3px 0
|
||||||
|
var(--Crust);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopItemHeader {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
|
||||||
|
border-bottom:
|
||||||
|
3px solid
|
||||||
|
var(--Surface2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopItemHeader h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopItemNumber {
|
||||||
|
color: var(--Overlay2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopItemBody {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 5rem minmax(0, 1fr);
|
||||||
|
gap: 0.75rem;
|
||||||
|
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopPlantVisual {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
width: 5rem;
|
||||||
|
height: 5rem;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
background-color: var(--Base);
|
||||||
|
border: 3px solid var(--Surface2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopPlantImage {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
object-fit: contain;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopPlantMissing {
|
||||||
|
padding: 0.35rem;
|
||||||
|
|
||||||
|
color: var(--Overlay1);
|
||||||
|
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopItemDetails p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopStat {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.75rem;
|
||||||
|
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopStatName {
|
||||||
|
color: var(--Subtext1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopBuyButton {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.65rem 0.8rem;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
color: var(--Text);
|
||||||
|
background-color: var(--Surface0);
|
||||||
|
|
||||||
|
border: 3px solid var(--Surface2);
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
box-shadow:
|
||||||
|
3px 3px 0
|
||||||
|
var(--Crust);
|
||||||
|
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopBuyButton:hover:not(:disabled) {
|
||||||
|
color: var(--Green);
|
||||||
|
background-color: var(--Surface1);
|
||||||
|
|
||||||
|
border-color: var(--Green);
|
||||||
|
|
||||||
|
transform: translate(1px, 1px);
|
||||||
|
|
||||||
|
box-shadow:
|
||||||
|
2px 2px 0
|
||||||
|
var(--Crust);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopBuyButton:active:not(:disabled) {
|
||||||
|
transform: translate(2px, 2px);
|
||||||
|
|
||||||
|
box-shadow:
|
||||||
|
1px 1px 0
|
||||||
|
var(--Crust);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopBuyButton:focus-visible {
|
||||||
|
outline: 3px solid var(--Yellow);
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopBuyButton:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
color: var(--Overlay1);
|
||||||
|
background-color: var(--Surface0);
|
||||||
|
|
||||||
|
border-color: var(--Surface1);
|
||||||
|
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopEmpty {
|
||||||
|
margin: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
color: var(--Subtext1);
|
||||||
|
background-color: var(--Mantle);
|
||||||
|
|
||||||
|
border: 3px solid var(--Surface2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ShopMessage {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding: 0.75rem;
|
||||||
|
|
||||||
|
color: var(--Subtext1);
|
||||||
|
background-color: var(--Mantle);
|
||||||
|
|
||||||
|
border-left:
|
||||||
|
4px solid
|
||||||
|
var(--Blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.ShopSeedList {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopItemBody {
|
||||||
|
grid-template-columns: 4.5rem minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ShopPlantVisual {
|
||||||
|
width: 4.5rem;
|
||||||
|
height: 4.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user