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); }