Files

1406 lines
24 KiB
JavaScript

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.Archived === true) {
return false;
}
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
) {
if (
Plant.Shop.BaseCost === null ||
Plant.Shop.BaseCost === undefined ||
Plant.Shop.BaseCost === ""
) {
return null;
}
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 (Mutation.Archived === true) {
continue;
}
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;
const ListReferenceCounts =
GetEconomyMutationListReferenceCounts(
Mutation.Pattern
);
for (
const Row
of Mutation.Pattern ?? []
) {
for (
const Matcher
of Row
) {
if (
Matcher === null ||
Matcher === "Empty" ||
Matcher === "Any" ||
GetEconomyMutationListReference(
Matcher
) !== null
) {
continue;
}
const MatcherCost =
GetMatcherSeedCost(
SaveData,
Matcher,
Cache,
Visiting
);
if (MatcherCost === null) {
return null;
}
TotalCost +=
MatcherCost;
}
}
for (
const [
ListNumber,
ReferenceCount
]
of ListReferenceCounts
) {
const List =
Mutation.Lists?.[
ListNumber - 1
];
const ListCost =
GetMutationListSeedCost(
List,
ReferenceCount,
Item =>
GetMutationListItemSeedCost(
Item,
Plants,
Plant =>
GetPlantShopCost(
SaveData,
Plant.Id,
Cache,
Visiting
),
Plant =>
IsPlantAvailableInShop(
SaveData,
Plant
)
)
);
if (ListCost === null) {
return null;
}
TotalCost += ListCost;
}
return TotalCost;
}
function GetEconomyMutationListReference(
Matcher
) {
if (typeof Matcher !== "string") {
return null;
}
const Match =
/^List:([1-9][0-9]*)$/.exec(
Matcher
);
return Match === null
? null
: Number(Match[1]);
}
function GetEconomyMutationListReferenceCounts(
Pattern
) {
const Counts = new Map();
for (const Row of Pattern ?? []) {
if (!Array.isArray(Row)) {
continue;
}
for (const Matcher of Row) {
const ListNumber =
GetEconomyMutationListReference(
Matcher
);
if (ListNumber === null) {
continue;
}
Counts.set(
ListNumber,
(Counts.get(ListNumber) ?? 0) + 1
);
}
}
return Counts;
}
function GetMutationListSeedCost(
List,
ReferenceCount,
GetItemCost
) {
if (
List === null ||
typeof List !== "object" ||
!Array.isArray(List.Items) ||
List.Items.length === 0 ||
!Number.isInteger(ReferenceCount) ||
ReferenceCount <= 0
) {
return null;
}
const ItemCosts = [];
for (const Item of List.Items) {
const Cost = GetItemCost(Item);
if (
Cost === null ||
!Number.isFinite(Cost) ||
Cost < 0
) {
continue;
}
ItemCosts.push(Cost);
}
if (ItemCosts.length === 0) {
return null;
}
if (List.Mode !== "Once") {
return (
Math.min(...ItemCosts) *
ReferenceCount
);
}
if (ItemCosts.length < ReferenceCount) {
return null;
}
ItemCosts.sort(
(A, B) => A - B
);
return ItemCosts
.slice(0, ReferenceCount)
.reduce(
(Total, Cost) =>
Total + Cost,
0
);
}
function GetMutationListItemSeedCost(
Item,
PlantCatalogue,
GetPlantCost,
IsPlantCandidateAvailable
) {
if (
Item === null ||
typeof Item !== "object" ||
Array.isArray(Item)
) {
return null;
}
if (
Item.Type === "Any" ||
Item.Type === "Empty"
) {
return 0;
}
if (Item.Type === "Plant") {
const Plant =
PlantCatalogue[Item.Value];
if (
Plant === undefined ||
Plant.Archived === true
) {
return null;
}
return GetPlantCost(Plant);
}
if (Item.Type !== "Tag") {
return null;
}
let CheapestCost = null;
for (
const Plant
of Object.values(
PlantCatalogue
)
) {
if (
!IsPlantCandidateAvailable(
Plant
) ||
!Array.isArray(Plant.Tags) ||
!Plant.Tags.includes(
Item.Value
)
) {
continue;
}
const Cost = GetPlantCost(Plant);
if (Cost === null) {
continue;
}
if (
CheapestCost === null ||
Cost < CheapestCost
) {
CheapestCost = Cost;
}
}
return CheapestCost;
}
function GetMatcherSeedCost(
SaveData,
Matcher,
Cache,
Visiting
) {
if (typeof Matcher === "string") {
const Plant =
Plants[Matcher];
if (
Plant === undefined ||
Plant.Archived === true
) {
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 ||
Plant.Archived === true
) {
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 GetPlantHarvestMultiplier(
PlantId
) {
const Plant =
typeof PlantId === "object" &&
PlantId !== null
? PlantId
: GetPlantById(
PlantId
);
if (Plant === null) {
return 1.5;
}
const Multiplier = Number(
Plant.HarvestMultiplier ??
1.5
);
return (
Number.isFinite(Multiplier) &&
Multiplier >= 0
)
? Multiplier
: 1.5;
}
function GetPlantHarvestReward(
SaveData,
PlantId
) {
const Cost =
GetPlantShopCost(
SaveData,
PlantId
);
if (Cost === null) {
return null;
}
return Math.ceil(
Cost *
GetPlantHarvestMultiplier(
PlantId
)
);
}
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 GetGardenEconomySummary(
SaveData,
Garden = SaveData?.Garden
) {
if (
Garden === null ||
Garden === undefined ||
!Array.isArray(
Garden.Plots
)
) {
return null;
}
let DewInvested = 0;
let HarvestValue = 0;
let NetProfit = 0;
let DewPerHour = 0;
for (
const Plot
of Garden.Plots
) {
if (Plot === null) {
continue;
}
const Plant =
Plants[Plot.Plant];
if (Plant === undefined) {
return null;
}
const Cost =
GetPlantShopCost(
SaveData,
Plant.Id
);
const Reward =
GetPlantHarvestReward(
SaveData,
Plant.Id
);
const PlantDewPerHour =
GetPlantDewPerHour(
SaveData,
Plant.Id
);
if (
Cost === null ||
Reward === null ||
PlantDewPerHour === null
) {
return null;
}
DewInvested += Cost;
HarvestValue += Reward;
NetProfit +=
Reward - Cost;
DewPerHour +=
PlantDewPerHour;
}
return {
DewInvested,
HarvestValue,
NetProfit,
DewPerHour
};
}
function FormatDewPerHour(
DewPerHour
) {
if (
DewPerHour === null ||
!Number.isFinite(
DewPerHour
)
) {
return "Unavailable";
}
return (
DewPerHour.toLocaleString(
undefined,
{
maximumFractionDigits: 2
}
) +
" Dew"
);
}
function GetCataloguePlantById(
PlantId,
PlantCatalogue = Plants
) {
PlantId = Number(PlantId);
for (
const Plant
of Object.values(
PlantCatalogue
)
) {
if (Number(Plant.Id) === PlantId) {
return Plant;
}
}
return null;
}
function GetCataloguePlantCostInfo(
PlantId,
PlantCatalogue = Plants,
MutationCatalogue = MutationSets,
Cache = new Map(),
Visiting = new Set()
) {
PlantId = Number(PlantId);
if (Cache.has(PlantId)) {
return Cache.get(PlantId);
}
const Plant =
GetCataloguePlantById(
PlantId,
PlantCatalogue
);
if (Plant === null) {
return null;
}
if (Plant.Shop?.ShopPlant === true) {
if (
Plant.Shop.BaseCost === null ||
Plant.Shop.BaseCost === undefined ||
Plant.Shop.BaseCost === ""
) {
return null;
}
const BaseCost = Number(
Plant.Shop.BaseCost
);
if (
!Number.isFinite(BaseCost) ||
BaseCost < 0
) {
return null;
}
const Info = {
Cost: Math.ceil(BaseCost),
SourceType: "Base",
SourceMutation: null
};
Cache.set(PlantId, Info);
return Info;
}
if (Visiting.has(PlantId)) {
return null;
}
Visiting.add(PlantId);
let CheapestInfo = null;
for (
const [MutationKey, Mutation]
of Object.entries(
MutationCatalogue
)
) {
if (Mutation.Archived === true) {
continue;
}
if (
!Mutation.Relations
?.PlantsCreated
?.includes(PlantId)
) {
continue;
}
const IngredientCost =
GetCatalogueMutationIngredientCost(
Mutation,
PlantCatalogue,
MutationCatalogue,
Cache,
Visiting
);
if (IngredientCost === null) {
continue;
}
const Cost =
IngredientCost +
GetMutationTimeFee(
Mutation
);
if (
CheapestInfo === null ||
Cost < CheapestInfo.Cost
) {
CheapestInfo = {
Cost: Cost,
SourceType: "Mutation",
SourceMutation: {
Id: Number(
Mutation.Id
),
MutationKey: MutationKey,
Name:
Mutation.Name ??
MutationKey
}
};
}
}
Visiting.delete(PlantId);
Cache.set(PlantId, CheapestInfo);
return CheapestInfo;
}
function GetCatalogueMutationIngredientCost(
Mutation,
PlantCatalogue,
MutationCatalogue,
Cache,
Visiting
) {
let TotalCost = 0;
const ListReferenceCounts =
GetEconomyMutationListReferenceCounts(
Mutation.Pattern
);
for (const Row of Mutation.Pattern ?? []) {
for (const Matcher of Row) {
if (
Matcher === null ||
Matcher === "Empty" ||
Matcher === "Any" ||
GetEconomyMutationListReference(
Matcher
) !== null
) {
continue;
}
const MatcherCost =
GetCatalogueMatcherSeedCost(
Matcher,
PlantCatalogue,
MutationCatalogue,
Cache,
Visiting
);
if (MatcherCost === null) {
return null;
}
TotalCost += MatcherCost;
}
}
for (
const [
ListNumber,
ReferenceCount
]
of ListReferenceCounts
) {
const List =
Mutation.Lists?.[
ListNumber - 1
];
const ListCost =
GetMutationListSeedCost(
List,
ReferenceCount,
Item =>
GetMutationListItemSeedCost(
Item,
PlantCatalogue,
Plant =>
GetCataloguePlantCostInfo(
Plant.Id,
PlantCatalogue,
MutationCatalogue,
Cache,
Visiting
)?.Cost ?? null,
Plant =>
Plant.Archived !== true
)
);
if (ListCost === null) {
return null;
}
TotalCost += ListCost;
}
return TotalCost;
}
function GetCatalogueMatcherSeedCost(
Matcher,
PlantCatalogue,
MutationCatalogue,
Cache,
Visiting
) {
if (typeof Matcher === "string") {
const Plant =
PlantCatalogue[Matcher];
if (
Plant === undefined ||
Plant.Archived === true
) {
return null;
}
return GetCataloguePlantCostInfo(
Plant.Id,
PlantCatalogue,
MutationCatalogue,
Cache,
Visiting
)?.Cost ?? null;
}
if (
typeof Matcher !== "object" ||
Matcher === null
) {
return null;
}
if (typeof Matcher.Plant === "string") {
const Plant =
PlantCatalogue[Matcher.Plant];
if (
Plant === undefined ||
Plant.Archived === true
) {
return null;
}
return GetCataloguePlantCostInfo(
Plant.Id,
PlantCatalogue,
MutationCatalogue,
Cache,
Visiting
)?.Cost ?? null;
}
let CheapestCost = null;
for (
const Plant
of Object.values(
PlantCatalogue
)
) {
if (Plant.Archived === true) {
continue;
}
if (
!DoesPlantMatchRecipeMatcher(
Plant,
Matcher
)
) {
continue;
}
const Cost =
GetCataloguePlantCostInfo(
Plant.Id,
PlantCatalogue,
MutationCatalogue,
Cache,
Visiting
)?.Cost ?? null;
if (Cost === null) {
continue;
}
if (
CheapestCost === null ||
Cost < CheapestCost
) {
CheapestCost = Cost;
}
}
return CheapestCost;
}