Files
srhtnin.github.io/Scripts/Garden/FunctionalEffects.js
T

2199 lines
42 KiB
JavaScript

const FunctionalEffectAreas = [
"Front",
"Behind",
"Cardinal",
"Adjacent",
"Farm"
];
const FunctionalEffectActiveTypes = [
"Harvest",
"Duplicate",
"Plant",
"Replant"
];
const FunctionalEffectPassiveTypes = [
"GrowthAdvance",
"DisableMutation",
"DisableHarvest",
"MutationChanceBonus",
"CooldownReduction",
"HarvestYieldBonus"
];
const FunctionalEffectTypes = [
...FunctionalEffectActiveTypes,
...FunctionalEffectPassiveTypes
];
const FunctionalEffectMinimumCooldown = 1000;
const FunctionalEffectHandlers = new Map();
const FunctionalDirectionOffsets = {
North: [0, -1],
East: [1, 0],
South: [0, 1],
West: [-1, 0]
};
const FunctionalCardinalOffsets = [
[0, -1],
[1, 0],
[0, 1],
[-1, 0]
];
const FunctionalAdjacentOffsets = [
[0, -1],
[1, -1],
[1, 0],
[1, 1],
[0, 1],
[-1, 1],
[-1, 0],
[-1, -1]
];
function IsFunctionalPlainObject(
Value
) {
return (
Value !== null &&
typeof Value === "object" &&
!Array.isArray(Value)
);
}
function GetPlantFunctionalEffects(
Plant
) {
const Functional =
Plant?.Effects?.Functional;
if (!Array.isArray(Functional)) {
return [];
}
return Functional.filter(
Effect =>
IsFunctionalPlainObject(
Effect
) &&
typeof Effect.Type ===
"string" &&
Effect.Type.trim().length > 0
);
}
function IsFunctionalPlant(
Plant
) {
return (
GetPlantFunctionalEffects(
Plant
).length > 0
);
}
function IsKnownFunctionalEffectType(
Type
) {
return FunctionalEffectTypes.includes(
String(Type ?? "")
);
}
function IsFunctionalEffectActive(
Effect
) {
return FunctionalEffectActiveTypes.includes(
String(Effect?.Type ?? "")
);
}
function IsFunctionalEffectPassive(
Effect
) {
return FunctionalEffectPassiveTypes.includes(
String(Effect?.Type ?? "")
);
}
function GetFunctionalEffectArea(
Effect,
Field = "Area",
Fallback = "Front"
) {
const Area = String(
Effect?.[Field] ?? Fallback
);
return FunctionalEffectAreas.includes(
Area
)
? Area
: Fallback;
}
function DoesFunctionalEffectRequireMaturity(
Effect
) {
return Effect?.RequireMature !== false;
}
function GetFunctionalEffectCooldown(
Effect
) {
const Cooldown = Number(
Effect?.Cooldown ?? 0
);
if (
!Number.isFinite(Cooldown) ||
Cooldown <= 0
) {
return 0;
}
return Math.max(
FunctionalEffectMinimumCooldown,
Math.floor(Cooldown)
);
}
function GetResolvedFunctionalEffectCooldown(
SaveData,
GardenIndex,
PlotIndex,
Effect,
AtTime
) {
const BaseCooldown =
GetFunctionalEffectCooldown(
Effect
);
if (BaseCooldown <= 0) {
return 0;
}
const Accelerator =
GetStrongestFunctionalEffectAffectingPlot(
SaveData,
GardenIndex,
PlotIndex,
"CooldownReduction",
AtTime
);
const Reduction = Math.max(
0,
Math.min(
1,
GetFunctionalEffectAmount(
Accelerator?.Effect
)
)
);
return Math.max(
FunctionalEffectMinimumCooldown,
Math.floor(
BaseCooldown *
(1 - Reduction)
)
);
}
function GetFunctionalEffectAmount(
Effect,
Fallback = 0
) {
const Amount = Number(
Effect?.Amount
);
return Number.isFinite(Amount)
? Amount
: Fallback;
}
function GetFunctionalEffectKey(
Effect,
EffectIndex
) {
const ExplicitKey =
typeof Effect?.Key === "string"
? Effect.Key.trim()
: "";
if (ExplicitKey.length > 0) {
return ExplicitKey;
}
return (
String(
Effect?.Type ?? "Effect"
) +
":" +
String(EffectIndex)
);
}
function RegisterFunctionalEffectHandler(
Type,
Handler
) {
const NormalizedType =
String(Type ?? "").trim();
if (
NormalizedType.length === 0 ||
typeof Handler !== "function"
) {
return false;
}
FunctionalEffectHandlers.set(
NormalizedType,
Handler
);
return true;
}
function UnregisterFunctionalEffectHandler(
Type
) {
return FunctionalEffectHandlers.delete(
String(Type ?? "")
);
}
function GetFunctionalEffectHandler(
Type
) {
return FunctionalEffectHandlers.get(
String(Type ?? "")
) ?? null;
}
function HasFunctionalEffectHandler(
Type
) {
return (
GetFunctionalEffectHandler(
Type
) !== null
);
}
function GetFunctionalGarden(
SaveData,
GardenIndex
) {
if (
!Array.isArray(
SaveData?.Gardens
)
) {
return null;
}
return SaveData.Gardens[
GardenIndex
] ?? null;
}
function GetFunctionalPlotCoordinates(
Garden,
PlotIndex
) {
const Width = Math.max(
1,
Math.floor(
Number(Garden?.Width) || 1
)
);
return {
X: PlotIndex % Width,
Y: Math.floor(
PlotIndex / Width
)
};
}
function GetFunctionalPlotIndex(
Garden,
X,
Y
) {
const Width = Math.max(
1,
Math.floor(
Number(Garden?.Width) || 1
)
);
const Height = Math.max(
1,
Math.floor(
Number(Garden?.Height) || 1
)
);
if (
X < 0 ||
Y < 0 ||
X >= Width ||
Y >= Height
) {
return null;
}
return Y * Width + X;
}
function GetFunctionalPlotRotation(
Plot
) {
const Rotation = String(
Plot?.Rotation ?? "East"
);
return FunctionalDirectionOffsets[
Rotation
] !== undefined
? Rotation
: "East";
}
function GetFunctionalOffsetIndexes(
Garden,
PlotIndex,
Offsets
) {
const Coordinates =
GetFunctionalPlotCoordinates(
Garden,
PlotIndex
);
const Results = [];
for (const [OffsetX, OffsetY] of Offsets) {
const TargetIndex =
GetFunctionalPlotIndex(
Garden,
Coordinates.X + OffsetX,
Coordinates.Y + OffsetY
);
if (TargetIndex !== null) {
Results.push(TargetIndex);
}
}
return Results;
}
function GetFunctionalAreaPlotIndexes(
SaveData,
GardenIndex,
OriginPlotIndex,
Area,
Rotation = null
) {
const Garden =
GetFunctionalGarden(
SaveData,
GardenIndex
);
if (Garden === null) {
return [];
}
const NormalizedArea =
FunctionalEffectAreas.includes(
Area
)
? Area
: "Front";
if (NormalizedArea === "Farm") {
return Array.from(
{
length:
Garden.Plots?.length ?? 0
},
(_, PlotIndex) => PlotIndex
);
}
if (NormalizedArea === "Cardinal") {
return GetFunctionalOffsetIndexes(
Garden,
OriginPlotIndex,
FunctionalCardinalOffsets
);
}
if (NormalizedArea === "Adjacent") {
return GetFunctionalOffsetIndexes(
Garden,
OriginPlotIndex,
FunctionalAdjacentOffsets
);
}
const Direction =
Rotation === null
? GetFunctionalPlotRotation(
Garden.Plots?.[
OriginPlotIndex
]
)
: GetFunctionalPlotRotation({
Rotation
});
const [DirectionX, DirectionY] =
FunctionalDirectionOffsets[
Direction
];
const Multiplier =
NormalizedArea === "Behind"
? -1
: 1;
return GetFunctionalOffsetIndexes(
Garden,
OriginPlotIndex,
[[
DirectionX * Multiplier,
DirectionY * Multiplier
]]
);
}
function GetFunctionalAreaTargets(
SaveData,
GardenIndex,
OriginPlotIndex,
Area,
Rotation = null
) {
const Garden =
GetFunctionalGarden(
SaveData,
GardenIndex
);
if (Garden === null) {
return [];
}
return GetFunctionalAreaPlotIndexes(
SaveData,
GardenIndex,
OriginPlotIndex,
Area,
Rotation
).map(
PlotIndex => ({
GardenIndex,
PlotIndex,
Plot:
Garden.Plots[
PlotIndex
] ?? null
})
);
}
function IsFunctionalPlantMatureAtTime(
Plot,
Plant,
AtTime
) {
if (
Plot === null ||
Plant === null ||
Plant === undefined
) {
return false;
}
const GrowthTime = Math.max(
0,
Number(
Plant.GrowthTime ?? 0
) || 0
);
return (
Number(AtTime) -
Number(
Plot.PlantedAt ?? 0
) >= GrowthTime
);
}
function EnsureFunctionalCooldowns(
Plot
) {
if (
Plot === null ||
typeof Plot !== "object" ||
Array.isArray(Plot)
) {
return null;
}
if (
!IsFunctionalPlainObject(
Plot.FunctionalCooldowns
)
) {
Plot.FunctionalCooldowns = {};
}
return Plot.FunctionalCooldowns;
}
function NormalizeFunctionalCooldownEntry(
Value
) {
if (
Number.isFinite(
Number(Value)
)
) {
return {
StartedAt: 0,
Until: Math.max(
0,
Math.floor(
Number(Value)
)
)
};
}
if (!IsFunctionalPlainObject(Value)) {
return null;
}
const StartedAt = Math.max(
0,
Math.floor(
Number(
Value.StartedAt ?? 0
) || 0
)
);
const Until = Math.max(
StartedAt,
Math.floor(
Number(
Value.Until ?? 0
) || 0
)
);
if (Until <= 0) {
return null;
}
return {
StartedAt,
Until
};
}
function GetFunctionalEffectCooldownState(
Plot,
Effect,
EffectIndex,
AtTime
) {
const Cooldowns =
EnsureFunctionalCooldowns(
Plot
);
if (Cooldowns === null) {
return null;
}
const EffectKey =
GetFunctionalEffectKey(
Effect,
EffectIndex
);
const Entry =
NormalizeFunctionalCooldownEntry(
Cooldowns[EffectKey]
);
if (Entry === null) {
delete Cooldowns[EffectKey];
return null;
}
Cooldowns[EffectKey] = Entry;
const Duration = Math.max(
1,
Entry.Until -
Entry.StartedAt
);
const Progress = Math.max(
0,
Math.min(
(
Number(AtTime) -
Entry.StartedAt
) / Duration,
1
)
);
return {
Key: EffectKey,
StartedAt: Entry.StartedAt,
Until: Entry.Until,
Remaining: Math.max(
0,
Entry.Until -
Number(AtTime)
),
Progress,
Ready:
Number(AtTime) >=
Entry.Until
};
}
function StartFunctionalEffectCooldown(
Plot,
Effect,
EffectIndex,
AtTime,
ResolvedCooldown = null
) {
const Cooldown =
ResolvedCooldown === null
? GetFunctionalEffectCooldown(
Effect
)
: Math.max(
0,
Math.floor(
Number(
ResolvedCooldown
) || 0
)
);
if (Cooldown <= 0) {
return null;
}
const Cooldowns =
EnsureFunctionalCooldowns(
Plot
);
if (Cooldowns === null) {
return null;
}
const EffectKey =
GetFunctionalEffectKey(
Effect,
EffectIndex
);
const StartedAt = Math.max(
0,
Math.floor(
Number(AtTime) || 0
)
);
Cooldowns[EffectKey] = {
StartedAt,
Until:
StartedAt + Cooldown
};
return GetFunctionalEffectCooldownState(
Plot,
Effect,
EffectIndex,
AtTime
);
}
function IsFunctionalEffectOnCooldown(
Plot,
Effect,
EffectIndex,
AtTime
) {
const State =
GetFunctionalEffectCooldownState(
Plot,
Effect,
EffectIndex,
AtTime
);
return (
State !== null &&
!State.Ready
);
}
function GetFunctionalPlantCooldownDisplayState(
Plot,
Plant,
AtTime
) {
if (
!IsFunctionalPlantMatureAtTime(
Plot,
Plant,
AtTime
)
) {
return null;
}
let BestState = null;
const Effects =
GetPlantFunctionalEffects(
Plant
);
Effects.forEach(
(Effect, EffectIndex) => {
if (
!IsFunctionalEffectActive(
Effect
)
) {
return;
}
const State =
GetFunctionalEffectCooldownState(
Plot,
Effect,
EffectIndex,
AtTime
);
if (
State === null ||
State.Ready
) {
return;
}
if (
BestState === null ||
State.Until <
BestState.Until
) {
BestState = {
...State,
Effect,
EffectIndex
};
}
}
);
return BestState;
}
function GetFunctionalEffectsAffectingPlot(
SaveData,
GardenIndex,
TargetPlotIndex,
Type,
AtTime
) {
const Garden =
GetFunctionalGarden(
SaveData,
GardenIndex
);
if (Garden === null) {
return [];
}
const Results = [];
Garden.Plots.forEach(
(SourcePlot, SourcePlotIndex) => {
if (SourcePlot === null) {
return;
}
const SourcePlant =
Plants?.[
SourcePlot.Plant
];
if (SourcePlant === undefined) {
return;
}
const Effects =
GetPlantFunctionalEffects(
SourcePlant
);
Effects.forEach(
(Effect, EffectIndex) => {
if (
String(
Effect.Type
) !== String(Type)
) {
return;
}
if (
DoesFunctionalEffectRequireMaturity(
Effect
) &&
!IsFunctionalPlantMatureAtTime(
SourcePlot,
SourcePlant,
AtTime
)
) {
return;
}
const Area =
GetFunctionalEffectArea(
Effect
);
const Targets =
GetFunctionalAreaPlotIndexes(
SaveData,
GardenIndex,
SourcePlotIndex,
Area
);
if (
!Targets.includes(
TargetPlotIndex
)
) {
return;
}
Results.push({
GardenIndex,
SourcePlotIndex,
SourcePlot,
SourcePlant,
Effect,
EffectIndex
});
}
);
}
);
return Results;
}
function GetStrongestFunctionalEffectAffectingPlot(
SaveData,
GardenIndex,
TargetPlotIndex,
Type,
AtTime
) {
const Effects =
GetFunctionalEffectsAffectingPlot(
SaveData,
GardenIndex,
TargetPlotIndex,
Type,
AtTime
);
if (Effects.length === 0) {
return null;
}
return Effects.reduce(
(Best, Candidate) =>
Best === null ||
GetFunctionalEffectAmount(
Candidate.Effect
) >
GetFunctionalEffectAmount(
Best.Effect
)
? Candidate
: Best,
null
);
}
function GetStrongestFunctionalEffectAffectingAnyPlot(
SaveData,
GardenIndex,
TargetPlotIndexes,
Type,
AtTime
) {
let Best = null;
for (const PlotIndex of TargetPlotIndexes) {
const Candidate =
GetStrongestFunctionalEffectAffectingPlot(
SaveData,
GardenIndex,
PlotIndex,
Type,
AtTime
);
if (
Candidate !== null &&
(
Best === null ||
GetFunctionalEffectAmount(
Candidate.Effect
) >
GetFunctionalEffectAmount(
Best.Effect
)
)
) {
Best = Candidate;
}
}
return Best;
}
function DoesFunctionalPlantHaveEffectType(
Plant,
Type
) {
return GetPlantFunctionalEffects(
Plant
).some(
Effect =>
String(Effect.Type) ===
String(Type)
);
}
function CanFunctionalPlantUseAssignedSeed(
Plant
) {
return DoesFunctionalPlantHaveEffectType(
Plant,
"Plant"
);
}
function GetFunctionalPercentageAmount(
Effect,
Fallback = 0
) {
return Math.max(
0,
Math.min(
1,
GetFunctionalEffectAmount(
Effect,
Fallback
)
)
);
}
function GetFunctionalRandomItem(
SaveData,
Items
) {
if (!Array.isArray(Items) || Items.length === 0) {
return null;
}
if (Items.length === 1) {
return Items[0];
}
const Random =
typeof GetSimulationRandom === "function"
? GetSimulationRandom(
SaveData
)
: Math.random();
return Items[
Math.min(
Items.length - 1,
Math.floor(
Random * Items.length
)
)
];
}
function AddFunctionalCurrencyReward(
SaveData,
Currency,
Amount
) {
const RewardAmount = Number(
Amount
);
if (
!Number.isFinite(RewardAmount) ||
RewardAmount <= 0
) {
return 0;
}
SaveData.Currency ??= {};
SaveData.Currency[Currency] ??= 0;
SaveData.Currency[Currency] += RewardAmount;
SaveData.Statistics ??= {};
SaveData.Statistics.CurrencyEarned ??= {};
SaveData.Statistics.CurrencyEarned[
Currency
] ??= 0;
SaveData.Statistics.CurrencyEarned[
Currency
] += RewardAmount;
return RewardAmount;
}
function IsGardenPlotHarvestDisabled(
SaveData,
GardenIndex,
PlotIndex,
AtTime
) {
return (
GetStrongestFunctionalEffectAffectingPlot(
SaveData,
GardenIndex,
PlotIndex,
"DisableHarvest",
AtTime
) !== null
);
}
function IsGardenPlotMutationDisabled(
SaveData,
GardenIndex,
PlotIndex,
AtTime
) {
return (
GetStrongestFunctionalEffectAffectingPlot(
SaveData,
GardenIndex,
PlotIndex,
"DisableMutation",
AtTime
) !== null
);
}
function DoesMutationPatternInvolveDisableMutation(
SaveData,
GardenIndex,
OriginX,
OriginY,
Pattern
) {
const Garden =
GetFunctionalGarden(
SaveData,
GardenIndex
);
if (
Garden === null ||
!Array.isArray(Pattern)
) {
return false;
}
for (
let LocalY = 0;
LocalY < Pattern.length;
LocalY++
) {
const Row = Pattern[LocalY];
if (!Array.isArray(Row)) {
continue;
}
for (
let LocalX = 0;
LocalX < Row.length;
LocalX++
) {
const Matcher = Row[LocalX];
if (
Matcher === null ||
Matcher === "Any" ||
Matcher === "Empty"
) {
continue;
}
const PlotIndex =
GetFunctionalPlotIndex(
Garden,
OriginX + LocalX,
OriginY + LocalY
);
if (PlotIndex === null) {
continue;
}
const Plot =
Garden.Plots?.[PlotIndex] ?? null;
if (Plot === null) {
continue;
}
const Plant =
Plants?.[Plot.Plant];
if (
Plant !== undefined &&
DoesFunctionalPlantHaveEffectType(
Plant,
"DisableMutation"
)
) {
return true;
}
}
}
return false;
}
function GetGardenPlotGrowthAdvance(
SaveData,
GardenIndex,
PlotIndex,
AtTime
) {
const Effect =
GetStrongestFunctionalEffectAffectingPlot(
SaveData,
GardenIndex,
PlotIndex,
"GrowthAdvance",
AtTime
);
return GetFunctionalPercentageAmount(
Effect?.Effect
);
}
function GetGardenPlotHarvestYieldBonus(
SaveData,
GardenIndex,
PlotIndex,
AtTime
) {
const Effect =
GetStrongestFunctionalEffectAffectingPlot(
SaveData,
GardenIndex,
PlotIndex,
"HarvestYieldBonus",
AtTime
);
return GetFunctionalPercentageAmount(
Effect?.Effect
);
}
function PlantGardenPlot(
SaveData,
GardenIndex,
PlotIndex,
PlantKey,
AtTime,
Options = {}
) {
const Garden =
GetFunctionalGarden(
SaveData,
GardenIndex
);
const Plant =
Plants?.[PlantKey];
if (
Garden === null ||
Plant === undefined ||
Garden.Plots?.[PlotIndex] !== null
) {
return {
Changed: false,
Planted: false
};
}
const ConsumeSeed =
Options.ConsumeSeed === true;
if (ConsumeSeed) {
if (
typeof TakeSeed !== "function" ||
!TakeSeed(
SaveData,
Plant.Id,
1
)
) {
return {
Changed: false,
Planted: false,
MissingSeed: true
};
}
}
const GrowthAdvance =
Options.ApplyGrowthAdvance === false
? 0
: GetGardenPlotGrowthAdvance(
SaveData,
GardenIndex,
PlotIndex,
AtTime
);
const GrowthTime = Math.max(
0,
Number(
Plant.GrowthTime ?? 0
) || 0
);
const PlantedAt =
Number(AtTime) -
GrowthTime * GrowthAdvance;
const Rotation =
FunctionalDirectionOffsets[
Options.Rotation
] !== undefined
? Options.Rotation
: "East";
Garden.Plots[PlotIndex] = {
Plant: PlantKey,
PlantedAt,
Rotation
};
return {
Changed: true,
Planted: true,
PlantKey,
PlotIndex,
GrowthAdvance,
PlantedAt
};
}
function HarvestGardenPlot(
SaveData,
GardenIndex,
PlotIndex,
AtTime,
Options = {}
) {
const Garden =
GetFunctionalGarden(
SaveData,
GardenIndex
);
const Plot =
Garden?.Plots?.[PlotIndex] ?? null;
if (Plot === null) {
return {
Changed: false,
Harvested: false
};
}
const Plant =
Plants?.[Plot.Plant];
if (
Plant === undefined ||
!IsFunctionalPlantMatureAtTime(
Plot,
Plant,
AtTime
)
) {
return {
Changed: false,
Harvested: false,
Immature: Plant !== undefined
};
}
if (
Options.ExcludeFunctional === true &&
IsFunctionalPlant(
Plant
)
) {
return {
Changed: false,
Harvested: false,
Excluded: true
};
}
if (
IsGardenPlotHarvestDisabled(
SaveData,
GardenIndex,
PlotIndex,
AtTime
)
) {
return {
Changed: false,
Harvested: false,
Disabled: true
};
}
const BaseReward =
typeof GetPlantHarvestReward === "function"
? GetPlantHarvestReward(
SaveData,
Plant.Id
)
: null;
if (BaseReward === null) {
return {
Changed: false,
Harvested: false,
RewardUnavailable: true
};
}
const YieldBonus =
GetGardenPlotHarvestYieldBonus(
SaveData,
GardenIndex,
PlotIndex,
AtTime
);
const RewardAmount = Math.ceil(
BaseReward *
(1 + YieldBonus)
);
AddFunctionalCurrencyReward(
SaveData,
"Dew",
RewardAmount
);
Garden.Plots[PlotIndex] = null;
return {
Changed: true,
Harvested: true,
Plant,
PlantKey: Plot.Plant,
Rotation:
GetFunctionalPlotRotation(
Plot
),
RewardAmount,
YieldBonus
};
}
function GetFunctionalEligibleSourceTargets(
SaveData,
Targets,
AtTime,
RequireMature = true
) {
return Targets.filter(
Target => {
const Plot = Target.Plot;
if (Plot === null) {
return false;
}
const Plant =
Plants?.[Plot.Plant];
if (Plant === undefined) {
return false;
}
return (
!RequireMature ||
IsFunctionalPlantMatureAtTime(
Plot,
Plant,
AtTime
)
);
}
);
}
function SelectFunctionalTarget(
SaveData,
Targets,
Random
) {
if (Targets.length === 0) {
return null;
}
return Random === true
? GetFunctionalRandomItem(
SaveData,
Targets
)
: Targets[0];
}
function HandleFunctionalHarvest(
Context
) {
const Targets =
Context.GetTargets();
let Changed = false;
for (const Target of Targets) {
const Result =
HarvestGardenPlot(
Context.SaveData,
Target.GardenIndex,
Target.PlotIndex,
Context.AtTime,
{
ExcludeFunctional:
Context.Effect.ExcludeFunctional === true
}
);
Changed =
Changed ||
Result.Changed === true;
}
return {Changed};
}
function HandleFunctionalDuplicate(
Context
) {
const SourceArea =
GetFunctionalEffectArea(
Context.Effect,
"Source",
"Behind"
);
const TargetArea =
GetFunctionalEffectArea(
Context.Effect,
"Target",
"Front"
);
const Sources =
GetFunctionalEligibleSourceTargets(
Context.SaveData,
GetFunctionalAreaTargets(
Context.SaveData,
Context.GardenIndex,
Context.PlotIndex,
SourceArea
),
Context.AtTime,
Context.Effect.RequireMatureSource !== false
);
const EmptyTargets =
GetFunctionalAreaTargets(
Context.SaveData,
Context.GardenIndex,
Context.PlotIndex,
TargetArea
).filter(
Target => Target.Plot === null
);
const Source =
SelectFunctionalTarget(
Context.SaveData,
Sources,
Context.Effect.RandomSource === true
);
const Target =
SelectFunctionalTarget(
Context.SaveData,
EmptyTargets,
Context.Effect.RandomTarget === true
);
if (
Source === null ||
Target === null
) {
return {Changed: false};
}
return PlantGardenPlot(
Context.SaveData,
Context.GardenIndex,
Target.PlotIndex,
Source.Plot.Plant,
Context.AtTime,
{
ConsumeSeed: false
}
);
}
function HandleFunctionalPlant(
Context
) {
const AssignedSeed =
typeof Context.Plot.AssignedSeed === "string"
? Context.Plot.AssignedSeed
: null;
if (
AssignedSeed === null ||
Plants?.[AssignedSeed] === undefined
) {
return {Changed: false};
}
const TargetArea =
GetFunctionalEffectArea(
Context.Effect,
"Target",
"Front"
);
const EmptyTargets =
GetFunctionalAreaTargets(
Context.SaveData,
Context.GardenIndex,
Context.PlotIndex,
TargetArea
).filter(
Target => Target.Plot === null
);
const Target =
SelectFunctionalTarget(
Context.SaveData,
EmptyTargets,
Context.Effect.RandomTarget === true
);
if (Target === null) {
return {Changed: false};
}
return PlantGardenPlot(
Context.SaveData,
Context.GardenIndex,
Target.PlotIndex,
AssignedSeed,
Context.AtTime,
{
ConsumeSeed: true
}
);
}
function HandleFunctionalReplant(
Context
) {
const Targets =
Context.GetTargets();
let Changed = false;
for (const Target of Targets) {
const ExistingPlot = Target.Plot;
if (ExistingPlot === null) {
continue;
}
const ExistingPlant =
Plants?.[ExistingPlot.Plant];
if (ExistingPlant === undefined) {
continue;
}
const ConsumeSeed =
Context.Effect.ConsumeSeed !== false;
if (
ConsumeSeed &&
(
typeof GetSeedCount !== "function" ||
GetSeedCount(
Context.SaveData,
ExistingPlant.Id
) < 1
)
) {
continue;
}
const Rotation =
GetFunctionalPlotRotation(
ExistingPlot
);
const HarvestResult =
HarvestGardenPlot(
Context.SaveData,
Target.GardenIndex,
Target.PlotIndex,
Context.AtTime,
{
ExcludeFunctional:
Context.Effect.ExcludeFunctional === true
}
);
if (!HarvestResult.Harvested) {
continue;
}
const PlantResult =
PlantGardenPlot(
Context.SaveData,
Target.GardenIndex,
Target.PlotIndex,
HarvestResult.PlantKey,
Context.AtTime,
{
ConsumeSeed,
Rotation
}
);
Changed =
Changed ||
HarvestResult.Changed === true ||
PlantResult.Changed === true;
}
return {Changed};
}
function SetFunctionalPlantAssignedSeed(
Plot,
PlantKey
) {
if (
Plot === null ||
typeof Plot !== "object" ||
Plants?.[PlantKey] === undefined
) {
return false;
}
Plot.AssignedSeed = PlantKey;
return true;
}
function ClearFunctionalPlantAssignedSeed(
Plot
) {
if (
Plot === null ||
typeof Plot !== "object"
) {
return false;
}
delete Plot.AssignedSeed;
return true;
}
RegisterFunctionalEffectHandler(
"Harvest",
HandleFunctionalHarvest
);
RegisterFunctionalEffectHandler(
"Duplicate",
HandleFunctionalDuplicate
);
RegisterFunctionalEffectHandler(
"Plant",
HandleFunctionalPlant
);
RegisterFunctionalEffectHandler(
"Replant",
HandleFunctionalReplant
);
function CreateFunctionalProcessingResult() {
return {
Attempted: false,
Changed: false,
Activations: []
};
}
function MergeFunctionalHandlerResult(
Result,
HandlerResult
) {
if (HandlerResult === true) {
Result.Changed = true;
return;
}
if (!IsFunctionalPlainObject(HandlerResult)) {
return;
}
Result.Changed =
Result.Changed ||
HandlerResult.Changed === true;
}
function ProcessFunctionalEffectsAtTime(
SaveData,
AtTime
) {
const Result =
CreateFunctionalProcessingResult();
if (
!Array.isArray(
SaveData?.Gardens
)
) {
return Result;
}
for (
let GardenIndex = 0;
GardenIndex < SaveData.Gardens.length;
GardenIndex++
) {
const Garden =
SaveData.Gardens[
GardenIndex
];
for (
let PlotIndex = 0;
PlotIndex < Garden.Plots.length;
PlotIndex++
) {
const Plot =
Garden.Plots[
PlotIndex
];
if (Plot === null) {
continue;
}
const Plant =
Plants?.[Plot.Plant];
if (Plant === undefined) {
continue;
}
const Effects =
GetPlantFunctionalEffects(
Plant
);
for (
let EffectIndex = 0;
EffectIndex < Effects.length;
EffectIndex++
) {
const Effect =
Effects[EffectIndex];
if (
!IsFunctionalEffectActive(
Effect
)
) {
continue;
}
if (
DoesFunctionalEffectRequireMaturity(
Effect
) &&
!IsFunctionalPlantMatureAtTime(
Plot,
Plant,
AtTime
)
) {
continue;
}
const Handler =
GetFunctionalEffectHandler(
Effect.Type
);
if (Handler === null) {
continue;
}
if (
IsFunctionalEffectOnCooldown(
Plot,
Effect,
EffectIndex,
AtTime
)
) {
continue;
}
Result.Attempted = true;
const ResolvedCooldown =
GetResolvedFunctionalEffectCooldown(
SaveData,
GardenIndex,
PlotIndex,
Effect,
AtTime
);
StartFunctionalEffectCooldown(
Plot,
Effect,
EffectIndex,
AtTime,
ResolvedCooldown
);
const HandlerResult =
Handler({
SaveData,
GardenIndex,
Garden,
PlotIndex,
Plot,
Plant,
Effect,
EffectIndex,
AtTime,
GetTargets: (
Area = null
) =>
GetFunctionalAreaTargets(
SaveData,
GardenIndex,
PlotIndex,
Area ??
GetFunctionalEffectArea(
Effect
)
)
});
MergeFunctionalHandlerResult(
Result,
HandlerResult
);
Result.Activations.push({
GardenIndex,
PlotIndex,
PlantKey:
Plot.Plant,
Type:
Effect.Type,
EffectIndex,
AtTime
});
if (
Garden.Plots[PlotIndex] !==
Plot
) {
break;
}
}
}
}
return Result;
}
function GetNextFunctionalEffectEventTime(
SaveData,
CurrentTime,
TargetTime
) {
if (
!Array.isArray(
SaveData?.Gardens
)
) {
return null;
}
let NextTime = null;
for (
let GardenIndex = 0;
GardenIndex < SaveData.Gardens.length;
GardenIndex++
) {
const Garden =
SaveData.Gardens[
GardenIndex
];
for (
let PlotIndex = 0;
PlotIndex < Garden.Plots.length;
PlotIndex++
) {
const Plot =
Garden.Plots[
PlotIndex
];
if (Plot === null) {
continue;
}
const Plant =
Plants?.[Plot.Plant];
if (Plant === undefined) {
continue;
}
const Effects =
GetPlantFunctionalEffects(
Plant
);
if (Effects.length === 0) {
continue;
}
const MaturityTime =
Number(
Plot.PlantedAt ?? 0
) +
Math.max(
0,
Number(
Plant.GrowthTime ?? 0
) || 0
);
for (
let EffectIndex = 0;
EffectIndex < Effects.length;
EffectIndex++
) {
const Effect =
Effects[EffectIndex];
if (
!IsFunctionalEffectActive(
Effect
) ||
!HasFunctionalEffectHandler(
Effect.Type
)
) {
continue;
}
let EventTime;
if (
DoesFunctionalEffectRequireMaturity(
Effect
) &&
MaturityTime > CurrentTime
) {
EventTime = MaturityTime;
} else {
const CooldownState =
GetFunctionalEffectCooldownState(
Plot,
Effect,
EffectIndex,
CurrentTime
);
if (
CooldownState === null ||
CooldownState.Ready
) {
EventTime = CurrentTime;
} else {
EventTime =
CooldownState.Until;
}
}
if (
EventTime <= CurrentTime ||
EventTime > TargetTime
) {
continue;
}
if (
NextTime === null ||
EventTime < NextTime
) {
NextTime = EventTime;
}
}
}
}
return NextTime;
}