diff --git a/Pages/Mutations.php b/Pages/Mutations.php index ee3bc8a..3a33d9c 100644 --- a/Pages/Mutations.php +++ b/Pages/Mutations.php @@ -20,7 +20,9 @@ $PageSection = "Mutations";

Every mutation you discover is recorded here. Once found, its recipe and result stay visible so - you can recreate it whenever you want. + you can recreate it whenever you want. Mutation + hints may also appear here for recipes you are + currently capable of discovering.

+ diff --git a/Scripts/Garden/Content.js b/Scripts/Garden/Content.js index 7ab3c2a..3aaa9ce 100644 --- a/Scripts/Garden/Content.js +++ b/Scripts/Garden/Content.js @@ -1,7 +1,7 @@ const GameContentCacheName = "SarahtoninGardenContent"; -const GameContentCacheSchemaVersion = 1; +const GameContentCacheSchemaVersion = 2; /* * Once content has been checked, page loads for the next diff --git a/Scripts/Garden/MutationEncyclopedia.js b/Scripts/Garden/MutationEncyclopedia.js index d46b882..f987d30 100644 --- a/Scripts/Garden/MutationEncyclopedia.js +++ b/Scripts/Garden/MutationEncyclopedia.js @@ -31,31 +31,54 @@ function RenderMutationEncyclopedia() { List.replaceChildren(); - const DiscoveredMutations = + const AllMutations = Object.values( MutationSets ) - .filter( - Mutation => - HasDiscoveredMutation( - MutationEncyclopediaSave, - Mutation.Id - ) - ) .sort( (A, B) => A.Id - B.Id ); - - if ( - DiscoveredMutations.length === 0 - ) { - SetMutationEncyclopediaMessage( - "No mutations discovered yet." + const DiscoveredMutations = + AllMutations.filter( + Mutation => + HasDiscoveredMutation( + MutationEncyclopediaSave, + Mutation.Id + ) ); - return; + const AvailableHints = + HasMutationHintsUpgrade( + MutationEncyclopediaSave + ) + ? AllMutations.filter( + Mutation => + !HasDiscoveredMutation( + MutationEncyclopediaSave, + Mutation.Id + ) && + HasUsableMutationHint( + Mutation + ) && + CanAttemptHintedMutation( + Mutation, + MutationEncyclopediaSave + ) + ) + : []; + + + for ( + const Mutation + of AvailableHints + ) { + List.appendChild( + CreateMutationHintCard( + Mutation.Hint + ) + ); } @@ -71,13 +94,332 @@ function RenderMutationEncyclopedia() { } - SetMutationEncyclopediaMessage( + if ( + DiscoveredMutations.length === 0 && + AvailableHints.length === 0 + ) { + SetMutationEncyclopediaMessage( + "No mutations discovered yet." + ); + + return; + } + + + const MessageParts = []; + + MessageParts.push( DiscoveredMutations.length === 1 ? "1 mutation discovered." : DiscoveredMutations.length .toLocaleString() + " mutations discovered." ); + + if (AvailableHints.length > 0) { + MessageParts.push( + AvailableHints.length === 1 + ? "1 hint available." + : AvailableHints.length + .toLocaleString() + + " hints available." + ); + } + + SetMutationEncyclopediaMessage( + MessageParts.join( + " " + ) + ); +} + + +function HasUsableMutationHint( + Mutation +) { + return ( + typeof Mutation.Hint === + "string" && + Mutation.Hint.trim().length > 0 + ); +} + + +function CanAttemptHintedMutation( + Mutation, + SaveData +) { + if ( + !CanMutationPatternFitGarden( + Mutation, + SaveData + ) + ) { + return false; + } + + + if (!Array.isArray(Mutation.Pattern)) { + return false; + } + + + return Mutation.Pattern.every( + Row => + Array.isArray(Row) && + Row.every( + Matcher => + IsHintMatcherAvailable( + Matcher, + SaveData + ) + ) + ); +} + + +function CanMutationPatternFitGarden( + Mutation, + SaveData +) { + const Pattern = + Mutation.Pattern; + + if ( + !Array.isArray(Pattern) || + Pattern.length === 0 + ) { + return false; + } + + + const PatternHeight = + Pattern.length; + + const PatternWidth = + Math.max( + 0, + ...Pattern.map( + Row => + Array.isArray(Row) + ? Row.length + : 0 + ) + ); + + if (PatternWidth === 0) { + return false; + } + + + const GardenWidth = + Number( + SaveData.Garden?.Width ?? 0 + ); + + const GardenHeight = + Number( + SaveData.Garden?.Height ?? 0 + ); + + const FitsNormally = + PatternWidth <= GardenWidth && + PatternHeight <= GardenHeight; + + if (FitsNormally) { + return true; + } + + + return ( + Mutation.Rotation === "Any" && + PatternHeight <= GardenWidth && + PatternWidth <= GardenHeight + ); +} + + +function IsHintMatcherAvailable( + Matcher, + SaveData +) { + if ( + Matcher === null || + Matcher === "Any" || + Matcher === "Empty" + ) { + return true; + } + + + if (typeof Matcher === "string") { + const Plant = + Plants[Matcher]; + + return ( + Plant !== undefined && + HasDiscoveredPlant( + SaveData, + Plant.Id + ) + ); + } + + + if ( + Matcher === null || + typeof Matcher !== "object" || + Array.isArray(Matcher) + ) { + return false; + } + + + return Object.entries( + Plants + ).some( + ([PlantKey, Plant]) => + HasDiscoveredPlant( + SaveData, + Plant.Id + ) && + DoesPlantMatchHintMatcher( + PlantKey, + Plant, + Matcher + ) + ); +} + + +function DoesPlantMatchHintMatcher( + PlantKey, + Plant, + Matcher +) { + if ( + typeof Matcher.Plant === + "string" && + Matcher.Plant !== PlantKey + ) { + return false; + } + + + const Tags = + Array.isArray(Plant.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 CreateMutationHintCard( + Hint +) { + const Card = + document.createElement( + "article" + ); + + Card.className = + "Panel MutationEncyclopediaCard MutationHintCard"; + + + const Header = + document.createElement( + "header" + ); + + Header.className = + "MutationEncyclopediaHeader"; + + + const Name = + document.createElement( + "h2" + ); + + Name.textContent = + "Mutation hint"; + + Header.appendChild(Name); + + + const Body = + document.createElement( + "div" + ); + + Body.className = + "MutationEncyclopediaBody"; + + + const HintText = + document.createElement( + "p" + ); + + HintText.className = + "MutationEncyclopediaDescription"; + + HintText.textContent = + Hint.trim(); + + Body.appendChild( + HintText + ); + + + Card.append( + Header, + Body + ); + + + return Card; } diff --git a/Scripts/Garden/Save.js b/Scripts/Garden/Save.js index d04d858..b577eec 100644 --- a/Scripts/Garden/Save.js +++ b/Scripts/Garden/Save.js @@ -68,7 +68,8 @@ function CreateNewSave() { Upgrades: { PlotSize: 0, PlantInformation: false, - GardenOverview: false + GardenOverview: false, + MutationHints: false }, Preferences: { @@ -260,6 +261,10 @@ function NormalizeSaveData( SaveData.Upgrades.GardenOverview === true; + SaveData.Upgrades.MutationHints = + SaveData.Upgrades.MutationHints === + true; + SaveData.Preferences ??= {}; diff --git a/Scripts/Garden/Shop.js b/Scripts/Garden/Shop.js index 2312895..23ac82e 100644 --- a/Scripts/Garden/Shop.js +++ b/Scripts/Garden/Shop.js @@ -125,7 +125,8 @@ function RenderGardenUpgrades() { "Row" ), CreatePlantInformationUpgradeCard(), - CreateGardenOverviewUpgradeCard() + CreateGardenOverviewUpgradeCard(), + CreateMutationHintsUpgradeCard() ); } @@ -504,6 +505,120 @@ function CreateGardenOverviewUpgradeCard() { } +function CreateMutationHintsUpgradeCard() { + const Cost = + GetMutationHintsUpgradeCost(); + + const IsOwned = + HasMutationHintsUpgrade( + ShopSave + ); + + + const Card = + document.createElement( + "article" + ); + + Card.className = + "Panel ShopItem"; + + + const Header = + document.createElement( + "div" + ); + + Header.className = + "ShopItemHeader"; + + + const Name = + document.createElement( + "h3" + ); + + Name.textContent = + "Mutation hints"; + + Header.appendChild(Name); + + + const Description = + document.createElement( + "p" + ); + + Description.textContent = + "Show subtle hints for undiscovered mutations once you've discovered the plants needed to attempt them. Hints appear in the Mutation encyclopedia without revealing the recipe."; + + + const Details = + document.createElement( + "div" + ); + + Details.className = + "ShopItemDetails"; + + Details.append( + CreateShopStat( + "Mutation hints", + IsOwned + ? "Unlocked" + : "Locked" + ), + CreateShopStat( + "Requirements", + "Required plants discovered" + ) + ); + + + const BuyButton = + document.createElement( + "button" + ); + + BuyButton.className = + "ActionButton ShopBuyButton"; + + BuyButton.type = "button"; + + if (IsOwned) { + BuyButton.textContent = + "Purchased"; + + BuyButton.disabled = true; + } else { + BuyButton.textContent = + "Buy - " + + Cost.toLocaleString() + + " Dew"; + + BuyButton.disabled = + ShopSave.Currency.Dew < Cost || + ShopPurchasePending; + + BuyButton.addEventListener( + "click", + BuyMutationHintsUpgrade + ); + } + + + Card.append( + Header, + Description, + Details, + BuyButton + ); + + + return Card; +} + + function CreateShopSeedCard( Plant ) { @@ -1189,6 +1304,64 @@ async function BuyGardenOverviewUpgrade() { } +async function BuyMutationHintsUpgrade() { + if ( + ShopPurchasePending || + HasMutationHintsUpgrade( + ShopSave + ) + ) { + return; + } + + + const Cost = + GetMutationHintsUpgradeCost(); + + if ( + ShopSave.Currency.Dew < + Cost + ) { + SetShopMessage( + "You don't have enough Dew for Mutation hints." + ); + + return; + } + + + ShopPurchasePending = true; + + try { + ShopSave.Currency.Dew -= + Cost; + + ShopSave.Statistics.CurrencySpent.Dew += + Cost; + + UnlockMutationHints( + ShopSave + ); + + + SetShopMessage( + "Bought Mutation hints for " + + Cost.toLocaleString() + + " Dew. Eligible hints are now visible in the Mutation encyclopedia." + ); + + RenderShop(); + + await SaveGame( + ShopSave + ); + } finally { + ShopPurchasePending = false; + RenderShop(); + } +} + + function SetShopMessage( Message ) { diff --git a/Scripts/Garden/Upgrades.js b/Scripts/Garden/Upgrades.js index eb1e10d..fa9a698 100644 --- a/Scripts/Garden/Upgrades.js +++ b/Scripts/Garden/Upgrades.js @@ -1,6 +1,7 @@ const GardenExpansionBaseCost = 1000; const PlantInformationUpgradeCost = 500; const GardenOverviewUpgradeCost = 1000; +const MutationHintsUpgradeCost = 1000; function GetGardenColumnUpgradeCost( @@ -189,3 +190,29 @@ function UnlockGardenOverview( SaveData.Preferences.ShowReadyPlots = true; } + + +function HasMutationHintsUpgrade( + SaveData +) { + return ( + SaveData + ?.Upgrades + ?.MutationHints === + true + ); +} + + +function GetMutationHintsUpgradeCost() { + return MutationHintsUpgradeCost; +} + + +function UnlockMutationHints( + SaveData +) { + SaveData.Upgrades ??= {}; + SaveData.Upgrades.MutationHints = + true; +} diff --git a/Styles/Style.css b/Styles/Style.css index bafa984..8599e77 100644 --- a/Styles/Style.css +++ b/Styles/Style.css @@ -1292,6 +1292,19 @@ input, button, textarea, select { margin-bottom: 1rem; } +.MutationHintCard { + border-color: var(--Yellow); +} + +.MutationHintCard .MutationEncyclopediaHeader h2 { + color: var(--Yellow); +} + +.MutationHintCard .MutationEncyclopediaDescription { + margin-bottom: 0; + color: var(--Subtext1); +} + .MutationEncyclopediaStats { display: grid; grid-template-columns: repeat( 2, minmax(0, 1fr) );