diff --git a/Assets/Img/Guide.png b/Assets/Img/Guide.png index d2f5431..94478d7 100644 Binary files a/Assets/Img/Guide.png and b/Assets/Img/Guide.png differ diff --git a/Assets/Img/Plants.png b/Assets/Img/Plants.png new file mode 100644 index 0000000..f8fa048 Binary files /dev/null and b/Assets/Img/Plants.png differ diff --git a/Pages/Bits/Navbar.php b/Pages/Bits/Navbar.php index 2f665cf..2ff4219 100644 --- a/Pages/Bits/Navbar.php +++ b/Pages/Bits/Navbar.php @@ -5,6 +5,7 @@ $NavbarPages = [ "Sarahtonin" => "/Pages/Sarahtonin.html", "Garden" => "/Pages/Garden.html", "Shop" => "/Pages/Shop.html", + "Plants" => "/Pages/Plants.html", "Guide" => "/Pages/Guide.html", "Profile" => "/Pages/Profile.html" ]; diff --git a/Pages/Guide.php b/Pages/Guide.php index f574616..7b653bc 100644 --- a/Pages/Guide.php +++ b/Pages/Guide.php @@ -71,7 +71,7 @@ $PageSection = "Guide"; id="Harvesting" class="GuideSection" > -

Harvesting

+

Harvesting

Click a mature plant to harvest it. Harvesting gives @@ -178,7 +178,7 @@ $PageSection = "Guide"; id="Discoveries" class="GuideSection" > -

Discoveries

+

Discoveries

Discovering a new plant or mutation is saved with diff --git a/Pages/Plants.php b/Pages/Plants.php new file mode 100644 index 0000000..52a97c1 --- /dev/null +++ b/Pages/Plants.php @@ -0,0 +1,48 @@ + + + + + + + + + + +

+

Plant encyclopedia

+ +

+ Every plant you discover is recorded here. + Undiscovered plants stay hidden until you find + them in your Garden. +

+ +
+ +

+ Loading discovered plants... +

+
+ + + + + + + + + + diff --git a/Scripts/Garden/PlantEncyclopedia.js b/Scripts/Garden/PlantEncyclopedia.js new file mode 100644 index 0000000..763c281 --- /dev/null +++ b/Scripts/Garden/PlantEncyclopedia.js @@ -0,0 +1,673 @@ +let PlantEncyclopediaSave; + + +async function StartPlantEncyclopedia() { + try { + await LoadGameContent(); + PlantEncyclopediaSave = + await LoadGame(); + + RenderPlantEncyclopedia(); + } catch (Error) { + console.error( + "Couldn't load Plant encyclopedia:", + Error + ); + + SetPlantEncyclopediaMessage( + "Couldn't load your discovered plants." + ); + } +} + + +function RenderPlantEncyclopedia() { + const List = + document.getElementById( + "PlantEncyclopediaList" + ); + + List.replaceChildren(); + + + const DiscoveredPlants = + Object.values(Plants) + .filter( + Plant => + HasDiscoveredPlant( + PlantEncyclopediaSave, + Plant.Id + ) + ) + .sort( + (A, B) => + A.Id - B.Id + ); + + + if ( + DiscoveredPlants.length === 0 + ) { + SetPlantEncyclopediaMessage( + "No plants discovered yet." + ); + + return; + } + + + for ( + const Plant + of DiscoveredPlants + ) { + List.appendChild( + CreatePlantEncyclopediaCard( + Plant + ) + ); + } + + + SetPlantEncyclopediaMessage( + DiscoveredPlants.length === 1 + ? "1 plant discovered." + : DiscoveredPlants.length + .toLocaleString() + + " plants discovered." + ); +} + + +function CreatePlantEncyclopediaCard( + Plant +) { + const Card = + document.createElement( + "article" + ); + + Card.className = + "Panel PlantEncyclopediaCard"; + + Card.id = + "Plant-" + Plant.Id; + + + const Header = + document.createElement( + "header" + ); + + Header.className = + "PlantEncyclopediaHeader"; + + + const Number = + document.createElement( + "span" + ); + + Number.className = + "PlantEncyclopediaNumber"; + + Number.textContent = + String( + Plant.Id + ).padStart( + 3, + "0" + ); + + + const Name = + document.createElement( + "h2" + ); + + Name.textContent = + Plant.Name; + + + Header.append( + Number, + Name + ); + + + const Body = + document.createElement( + "div" + ); + + Body.className = + "PlantEncyclopediaBody"; + + + Body.append( + CreatePlantEncyclopediaVisual( + Plant + ), + CreatePlantEncyclopediaDetails( + Plant + ), + CreatePlantEncyclopediaRelations( + Plant + ) + ); + + + Card.append( + Header, + Body + ); + + + return Card; +} + + +function CreatePlantEncyclopediaVisual( + Plant +) { + const Visual = + document.createElement( + "div" + ); + + Visual.className = + "PlantTile PlantEncyclopediaVisual"; + + + const PlantKey = + GetPlantKeyById( + Plant.Id + ); + + const Images = + PlantKey === null + ? [] + : PlantImages[ + PlantKey + ] ?? []; + + + if (Images.length === 0) { + const Missing = + document.createElement( + "span" + ); + + Missing.className = + "PlantEncyclopediaMissing"; + + Missing.textContent = + "No image"; + + Visual.appendChild( + Missing + ); + + return Visual; + } + + + const Image = + document.createElement( + "img" + ); + + Image.className = + "PlantSprite"; + + Image.src = + Images[ + Images.length - 1 + ]; + + Image.alt = + Plant.Name; + + + Visual.appendChild( + Image + ); + + + return Visual; +} + + +function CreatePlantEncyclopediaDetails( + Plant +) { + const Details = + document.createElement( + "div" + ); + + Details.className = + "PlantEncyclopediaDetails"; + + + const Description = + document.createElement( + "p" + ); + + Description.className = + "PlantEncyclopediaDescription"; + + Description.textContent = + Plant.Description || + "No description."; + + + Details.appendChild( + Description + ); + + + const Tags = + Array.isArray( + Plant.Tags + ) + ? Plant.Tags + : []; + + Details.appendChild( + CreatePlantEncyclopediaStat( + "Tags", + Tags.length === 0 + ? "None" + : Tags.join(", ") + ) + ); + + + Details.appendChild( + CreatePlantEncyclopediaStat( + "Growth", + FormatPlantEncyclopediaTime( + Plant.GrowthTime + ) + ) + ); + + + const Reward = + GetPlantHarvestReward( + PlantEncyclopediaSave, + Plant.Id + ); + + Details.appendChild( + CreatePlantEncyclopediaStat( + "Reward", + Reward === null + ? "Unavailable" + : Reward.toLocaleString() + + " Dew" + ) + ); + + + const RecipeProgress = + GetPlantRecipeProgress( + Plant.Id + ); + + Details.appendChild( + CreatePlantEncyclopediaStat( + "Found recipes", + RecipeProgress.Found + .toLocaleString() + + "/" + + RecipeProgress.Total + .toLocaleString() + ) + ); + + + return Details; +} + + +function CreatePlantEncyclopediaStat( + Name, + Value +) { + const Row = + document.createElement( + "div" + ); + + Row.className = + "PlantEncyclopediaStat"; + + + const NameElement = + document.createElement( + "span" + ); + + NameElement.className = + "PlantEncyclopediaStatName"; + + NameElement.textContent = + Name; + + + const ValueElement = + document.createElement( + "span" + ); + + ValueElement.textContent = + Value; + + + Row.append( + NameElement, + ValueElement + ); + + + return Row; +} + + +function CreatePlantEncyclopediaRelations( + Plant +) { + const Relations = + document.createElement( + "div" + ); + + Relations.className = + "PlantEncyclopediaRelations"; + + + Relations.append( + CreatePlantMutationRelationGroup( + "Created by", + GetPlantRelatedMutations( + Plant.Id, + "PlantsCreated" + ) + ), + CreatePlantMutationRelationGroup( + "Used in", + GetPlantRelatedMutations( + Plant.Id, + "PlantsUsed" + ) + ) + ); + + + return Relations; +} + + +function CreatePlantMutationRelationGroup( + Heading, + Mutations +) { + const Group = + document.createElement( + "section" + ); + + Group.className = + "PlantEncyclopediaRelationGroup"; + + + const Title = + document.createElement( + "h3" + ); + + Title.textContent = + Heading; + + + const List = + document.createElement( + "ul" + ); + + List.className = + "PlantEncyclopediaRelationList"; + + + if (Mutations.length === 0) { + const Empty = + document.createElement( + "li" + ); + + Empty.className = + "PlantEncyclopediaRelationEmpty"; + + Empty.textContent = + "None"; + + List.appendChild( + Empty + ); + } else { + for ( + const Mutation + of Mutations + ) { + List.appendChild( + CreatePlantMutationRelation( + Mutation + ) + ); + } + } + + + Group.append( + Title, + List + ); + + + return Group; +} + + +function CreatePlantMutationRelation( + Mutation +) { + const Item = + document.createElement( + "li" + ); + + + if ( + !HasDiscoveredMutation( + PlantEncyclopediaSave, + Mutation.Id + ) + ) { + Item.className = + "PlantEncyclopediaRelationUnknown"; + + Item.textContent = + "????????"; + + return Item; + } + + + const Link = + document.createElement( + "a" + ); + + Link.href = + "/Pages/Mutations.html#Mutation-" + + Mutation.Id; + + Link.textContent = + Mutation.Name; + + + Item.appendChild( + Link + ); + + + return Item; +} + + +function GetPlantRelatedMutations( + PlantId, + RelationName +) { + return Object.values( + MutationSets + ) + .filter( + Mutation => + Mutation.Relations?.[ + RelationName + ]?.includes( + Number(PlantId) + ) + ) + .sort( + (A, B) => + A.Id - B.Id + ); +} + + +function GetPlantRecipeProgress( + PlantId +) { + const RelatedMutationIds = + new Set(); + + + for ( + const Mutation + of Object.values( + MutationSets + ) + ) { + const Relations = + Mutation.Relations ?? {}; + + const IsRelated = + Relations.PlantsCreated + ?.includes( + Number(PlantId) + ) || + Relations.PlantsUsed + ?.includes( + Number(PlantId) + ); + + if (IsRelated) { + RelatedMutationIds.add( + Mutation.Id + ); + } + } + + + let Found = 0; + + for ( + const MutationId + of RelatedMutationIds + ) { + if ( + HasDiscoveredMutation( + PlantEncyclopediaSave, + MutationId + ) + ) { + Found++; + } + } + + + return { + Found: Found, + Total: RelatedMutationIds.size + }; +} + + +function FormatPlantEncyclopediaTime( + Milliseconds +) { + const TotalMinutes = + Math.max( + 1, + Math.ceil( + Number( + Milliseconds + ) / + 60000 + ) + ); + + const Hours = + Math.floor( + TotalMinutes / + 60 + ); + + const Minutes = + TotalMinutes % + 60; + + + if ( + Hours > 0 && + Minutes > 0 + ) { + return ( + Hours + + "h " + + Minutes + + "m" + ); + } + + if (Hours > 0) { + return Hours + "h"; + } + + return Minutes + "m"; +} + + +function SetPlantEncyclopediaMessage( + Message +) { + const Element = + document.getElementById( + "PlantEncyclopediaMessage" + ); + + if (Element !== null) { + Element.textContent = + Message; + } +} + + +document.addEventListener( + "DOMContentLoaded", + StartPlantEncyclopedia +); diff --git a/Styles/Style.css b/Styles/Style.css index 3fd96f4..0f2de74 100644 --- a/Styles/Style.css +++ b/Styles/Style.css @@ -1299,3 +1299,179 @@ select { height: 3rem; } } + +/* + * Plant encyclopedia + */ + +.PlantEncyclopediaList { + display: flex; + flex-direction: column; + gap: 1rem; + + margin-top: 1.5rem; +} + +.PlantEncyclopediaCard { + overflow: hidden; + + box-shadow: 3px 3px 0 var(--Crust); +} + +.PlantEncyclopediaHeader { + display: flex; + gap: 0.75rem; + + align-items: baseline; + + padding: 0.75rem 1rem; + + background-color: var(--Surface0); + border-bottom: 3px solid var(--Surface2); +} + +.PlantEncyclopediaHeader h2 { + margin: 0; + + color: var(--Green); +} + +.PlantEncyclopediaNumber { + flex: 0 0 auto; + + color: var(--Overlay2); + + font-size: 1.1rem; +} + +.PlantEncyclopediaBody { + display: grid; + grid-template-columns: + 7rem + minmax(0, 1fr) + minmax(10rem, 0.8fr); + gap: 1rem; + + padding: 1rem; + + align-items: start; +} + +.PlantEncyclopediaVisual { + display: flex; + + width: 7rem; + height: 7rem; + + align-items: center; + justify-content: center; + + background-color: var(--Base); +} + +.PlantEncyclopediaMissing { + padding: 0.5rem; + + color: var(--Overlay1); + + text-align: center; +} + +.PlantEncyclopediaDescription { + margin-bottom: 0.75rem; +} + +.PlantEncyclopediaStat { + display: flex; + justify-content: space-between; + gap: 0.75rem; + + padding: 0.35rem 0; + + border-bottom: 2px solid var(--Surface0); +} + +.PlantEncyclopediaStat:last-child { + border-bottom: 0; +} + +.PlantEncyclopediaStatName { + flex: 0 0 auto; + + color: var(--Subtext1); +} + +.PlantEncyclopediaStat > :last-child { + min-width: 0; + + text-align: right; + overflow-wrap: anywhere; +} + +.PlantEncyclopediaRelations { + min-width: 0; + + background-color: var(--Base); + border: 3px solid var(--Surface2); +} + +.PlantEncyclopediaRelationGroup { + padding: 0.65rem 0.75rem; +} + +.PlantEncyclopediaRelationGroup + +.PlantEncyclopediaRelationGroup { + border-top: 3px solid var(--Surface2); +} + +.PlantEncyclopediaRelationGroup h3 { + margin-bottom: 0.5rem; + + color: var(--Peach); + + font-size: 1rem; +} + +.PlantEncyclopediaRelationList { + max-height: 7rem; + margin: 0; + padding-left: 1.25rem; + + overflow-y: auto; +} + +.PlantEncyclopediaRelationList li { + margin-bottom: 0.35rem; +} + +.PlantEncyclopediaRelationList li:last-child { + margin-bottom: 0; +} + +.PlantEncyclopediaRelationUnknown, +.PlantEncyclopediaRelationEmpty { + color: var(--Overlay1); +} + +@media (max-width: 700px) { + .PlantEncyclopediaBody { + grid-template-columns: 1fr; + } + + .PlantEncyclopediaVisual { + width: min(8rem, 100%); + height: auto; + aspect-ratio: 1; + + justify-self: center; + } + + .PlantEncyclopediaStat > :last-child { + text-align: left; + } + + .PlantEncyclopediaStat { + flex-direction: column; + gap: 0.2rem; + } +}