const MutationTimeFeePerMinute = 3; const PlantKeysById = new Map(); const PlantsById = new Map(); const MutationsById = new Map(); let IndexedPlants = null; let IndexedMutations = null; function EnsureEconomyIndexes() { if ( IndexedPlants === Plants && IndexedMutations === MutationSets ) { return; } PlantKeysById.clear(); PlantsById.clear(); MutationsById.clear(); 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 ); } IndexedPlants = Plants; IndexedMutations = MutationSets; } function GetPlantKeyById( PlantId ) { EnsureEconomyIndexes(); return PlantKeysById.get( Number(PlantId) ) ?? null; } function GetPlantById( PlantId ) { EnsureEconomyIndexes(); return PlantsById.get( Number(PlantId) ) ?? null; } function GetMutationById( MutationId ) { EnsureEconomyIndexes(); 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?.ShopPlant === 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?.ShopPlant === 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" || Matcher === "Any" ) { 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 / 60000 * MutationTimeFeePerMinute ); } function GetPlantHarvestReward( SaveData, PlantId ) { const Cost = GetPlantShopCost( SaveData, PlantId ); if (Cost === null) { return null; } return Math.ceil( Cost * 1.5 ); } function GetPlantDewPerHour( SaveData, PlantId ) { const Plant = GetPlantById( PlantId ); if (Plant === null) { return null; } const GrowthTime = Number( Plant.GrowthTime ); if ( !Number.isFinite( GrowthTime ) || GrowthTime <= 0 ) { return null; } const Cost = GetPlantShopCost( SaveData, PlantId ); const Reward = GetPlantHarvestReward( SaveData, PlantId ); if ( Cost === null || Reward === null ) { return null; } const Profit = Reward - Cost; return ( Profit * 3600000 / GrowthTime ); } function FormatDewPerHour( DewPerHour ) { if ( DewPerHour === null || !Number.isFinite( DewPerHour ) ) { return "Unavailable"; } return ( DewPerHour.toLocaleString( undefined, { maximumFractionDigits: 2 } ) + " Dew" ); }