diff --git a/Assets/Img/Garden/Plants/Rose/Red/Seed.png b/Assets/Img/Garden/Plants/Rose/Red/1.png
similarity index 100%
rename from Assets/Img/Garden/Plants/Rose/Red/Seed.png
rename to Assets/Img/Garden/Plants/Rose/Red/1.png
diff --git a/Assets/Img/Garden/Plants/Rose/Red/Sprout.png b/Assets/Img/Garden/Plants/Rose/Red/2.png
similarity index 100%
rename from Assets/Img/Garden/Plants/Rose/Red/Sprout.png
rename to Assets/Img/Garden/Plants/Rose/Red/2.png
diff --git a/Assets/Img/Garden/Plants/Rose/Red/Mature.png b/Assets/Img/Garden/Plants/Rose/Red/3.png
similarity index 100%
rename from Assets/Img/Garden/Plants/Rose/Red/Mature.png
rename to Assets/Img/Garden/Plants/Rose/Red/3.png
diff --git a/Build.php b/Build.php
index 1254647..1f2bce5 100644
--- a/Build.php
+++ b/Build.php
@@ -103,6 +103,220 @@ function RenderPage(
);
}
+function GeneratePlantImageManifest(
+ string $RootDirectory,
+ string $OutputDirectory
+): void {
+ $PlantsDirectory =
+ $RootDirectory .
+ "/Assets/Img/Garden/Plants";
+
+ $Manifest = [];
+
+ if (!is_dir($PlantsDirectory)) {
+ return;
+ }
+
+ foreach (
+ new DirectoryIterator(
+ $PlantsDirectory
+ )
+ as $PlantDirectory
+ ) {
+ if (
+ $PlantDirectory->isDot() ||
+ !$PlantDirectory->isDir()
+ ) {
+ continue;
+ }
+
+ $PlantName =
+ $PlantDirectory->getFilename();
+
+ $Manifest[$PlantName] = [];
+
+
+ foreach (
+ new DirectoryIterator(
+ $PlantDirectory->getPathname()
+ )
+ as $VariantDirectory
+ ) {
+ if (
+ $VariantDirectory->isDot() ||
+ !$VariantDirectory->isDir()
+ ) {
+ continue;
+ }
+
+ $VariantName =
+ $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 (
+ count(
+ $Manifest[$PlantName]
+ ) === 0
+ ) {
+ unset(
+ $Manifest[$PlantName]
+ );
+ } else {
+ ksort(
+ $Manifest[$PlantName],
+ SORT_NATURAL |
+ SORT_FLAG_CASE
+ );
+ }
+ }
+
+
+ ksort(
+ $Manifest,
+ SORT_NATURAL |
+ SORT_FLAG_CASE
+ );
+
+
+ $ManifestDirectory =
+ $OutputDirectory .
+ "/Scripts/Garden";
+
+ if (!is_dir($ManifestDirectory)) {
+ mkdir(
+ $ManifestDirectory,
+ 0777,
+ true
+ );
+ }
+
+
+ $Json = json_encode(
+ $Manifest,
+ JSON_PRETTY_PRINT |
+ JSON_UNESCAPED_SLASHES |
+ JSON_UNESCAPED_UNICODE |
+ JSON_THROW_ON_ERROR
+ );
+
+
+ file_put_contents(
+ $ManifestDirectory .
+ "/PlantImages.js",
+
+ "const PlantImages = " .
+ $Json .
+ ";" .
+ PHP_EOL
+ );
+
+
+ echo
+ "Generated Scripts/Garden/PlantImages.js" .
+ PHP_EOL;
+}
/*
* Remove the previous build.
@@ -134,6 +348,10 @@ foreach ($StaticDirectories as $Directory) {
);
}
+GeneratePlantImageManifest(
+ $RootDirectory,
+ $OutputDirectory
+);
/*
* Copy index.html.
@@ -145,7 +363,6 @@ foreach ($StaticDirectories as $Directory) {
"./"
);
-
/*
* Build PHP pages.
*/
diff --git a/Pages/Garden.php b/Pages/Garden.php
index bdf4d97..62606ef 100644
--- a/Pages/Garden.php
+++ b/Pages/Garden.php
@@ -103,6 +103,7 @@ $PageTitle = "Garden";
+
diff --git a/Scripts/Garden/Garden.js b/Scripts/Garden/Garden.js
index feee651..f6599eb 100644
--- a/Scripts/Garden/Garden.js
+++ b/Scripts/Garden/Garden.js
@@ -179,6 +179,11 @@ function CreatePlotElement(
Button.type = "button";
+
+ /*
+ * Empty plot.
+ */
+
if (Plot === null) {
Button.classList.add(
"GardenPlotEmpty"
@@ -210,38 +215,47 @@ function CreatePlotElement(
}
- const Stage =
- GetPlantStage(
+ /*
+ * Plant image.
+ */
+
+ const ImagePath =
+ GetPlantImage(
Plot,
Plant
);
- const Image =
- document.createElement(
- "img"
+ if (ImagePath !== null) {
+ const Image =
+ document.createElement(
+ "img"
+ );
+
+ Image.className =
+ "GardenPlant";
+
+ Image.alt =
+ Plant.Name;
+
+ Image.src =
+ ImagePath;
+
+ Button.appendChild(
+ Image
);
-
- Image.className =
- "GardenPlant";
-
- Image.alt =
- Plant.Name +
- " - " +
- Stage;
-
- SetPlantImage(
- Image,
- Plot,
- Plant,
- Stage
- );
-
- Button.appendChild(
- Image
- );
+ }
- if (Stage === "Mature") {
+ /*
+ * Mature plants can be harvested.
+ */
+
+ if (
+ IsPlantMature(
+ Plot,
+ Plant
+ )
+ ) {
Button.classList.add(
"GardenPlotMature"
);
@@ -340,10 +354,10 @@ async function HarvestPlant(
Plants[Plot.Plant];
if (
- GetPlantStage(
+ !IsPlantMature(
Plot,
Plant
- ) !== "Mature"
+ )
) {
return;
}
@@ -413,36 +427,16 @@ function GetPlantGrowthProgress(
Date.now() -
Plot.PlantedAt;
- return Math.min(
- Age /
- Plant.GrowthTime,
- 1
+ return Math.max(
+ 0,
+ Math.min(
+ Age /
+ Plant.GrowthTime,
+ 1
+ )
);
}
-
-function GetPlantStage(
- Plot,
- Plant
-) {
- const Progress =
- GetPlantGrowthProgress(
- Plot,
- Plant
- );
-
- if (Progress < 1 / 3) {
- return "Seed";
- }
-
- if (Progress < 2 / 3) {
- return "Sprout";
- }
-
- return "Mature";
-}
-
-
function GetPlantTags(
Plot,
Plant
@@ -500,65 +494,130 @@ function GetPlantVariant(
}
-function SetPlantImage(
- Image,
+function IsPlantMature(
Plot,
- Plant,
- Stage
+ Plant
) {
+ return (
+ GetPlantGrowthProgress(
+ Plot,
+ Plant
+ ) >= 1
+ );
+}
+
+
+function GetPlantImages(
+ Plot,
+ Plant
+) {
+ const Variants =
+ PlantImages[
+ Plot.Plant
+ ];
+
+ if (Variants === undefined) {
+ return [];
+ }
+
+
const Variant =
GetPlantVariant(
Plot,
Plant
);
- const BasePath =
- "/Assets/Img/Garden/Plants/" +
- Plot.Plant +
- "/" +
- Variant +
- "/";
+
+ /*
+ * Preferred variant.
+ */
+
+ if (
+ Array.isArray(
+ Variants[Variant]
+ ) &&
+ Variants[Variant].length > 0
+ ) {
+ return Variants[
+ Variant
+ ];
+ }
/*
- * Try the requested stage first.
- *
- * If that image doesn't exist,
- * Mature.png becomes the fallback.
+ * Default visual fallback.
*/
- const Candidates = [
- BasePath + Stage + ".png",
- BasePath + "Mature.png"
- ];
+ if (
+ Array.isArray(
+ Variants.Default
+ ) &&
+ Variants.Default.length > 0
+ ) {
+ return Variants.Default;
+ }
- let CandidateIndex = 0;
- Image.src =
- Candidates[
- CandidateIndex
- ];
+ /*
+ * Absolute fallback:
+ * use the first variant that has images.
+ */
- Image.addEventListener(
- "error",
- () => {
- CandidateIndex++;
-
- if (
- CandidateIndex <
- Candidates.length
- ) {
- Image.src =
- Candidates[
- CandidateIndex
- ];
-
- return;
- }
-
- Image.remove();
+ for (
+ const Images
+ of Object.values(
+ Variants
+ )
+ ) {
+ if (
+ Array.isArray(Images) &&
+ Images.length > 0
+ ) {
+ return Images;
}
- );
+ }
+
+
+ return [];
+}
+
+
+function GetPlantImage(
+ Plot,
+ Plant
+) {
+ const Images =
+ GetPlantImages(
+ Plot,
+ Plant
+ );
+
+ if (Images.length === 0) {
+ return null;
+ }
+
+
+ const Progress =
+ GetPlantGrowthProgress(
+ Plot,
+ Plant
+ );
+
+
+ const ImageIndex =
+ Math.min(
+ Math.floor(
+ Progress *
+ Images.length
+ ),
+
+ Images.length - 1
+ );
+
+
+ return Images[
+ ImageIndex
+ ];
}
diff --git a/Styles/Style.css b/Styles/Style.css
index b2f071d..20c49ec 100644
--- a/Styles/Style.css
+++ b/Styles/Style.css
@@ -631,16 +631,12 @@ select {
width: 100%;
overflow-x: auto;
-
- border: 3px solid var(--Surface2);
-
- box-shadow:
- 3px 3px 0
- var(--Crust);
}
.LeaderboardTable {
- width: 100%;
+ width: auto;
+ min-width: 24rem;
+ margin: 0 auto;
border-collapse: collapse;
@@ -648,6 +644,12 @@ select {
background-color: var(--Base);
font-size: 1.2rem;
+
+ border: 3px solid var(--Surface2);
+
+ box-shadow:
+ 3px 3px 0
+ var(--Crust);
}
.LeaderboardTable th {
@@ -686,6 +688,15 @@ select {
text-align: center;
}
+.LeaderboardUsername {
+ min-width: 12rem;
+}
+
+.LeaderboardDewColumn,
+.LeaderboardDew {
+ min-width: 7rem;
+}
+
.LeaderboardDew {
color: var(--Green);
}