Added effects engine.

This commit is contained in:
sarah committed 2026-09-05 16:50:52 +02:00
1 parent 132aba0b9a
commit 9de8af0d2b
9 files changed
+1684 -12

No files matched your search

+1
View File
@@ -211,6 +211,7 @@ $PageSection = "Admin";
</div>
</main>
<script src="/Scripts/Garden/FunctionalEffects.js"></script>
<script src="/Scripts/Garden/Save.js"></script>
<script src="/Scripts/Garden/AdminValidation.js"></script>
<script src="/Scripts/Garden/AdminMassPlantEditor.js"></script>
+1
View File
@@ -443,6 +443,7 @@ $PageSection = "Admin";
<script src="/Scripts/Garden/PlantImages.js"></script>
<script src="/Scripts/Garden/Plants.js"></script>
<script src="/Scripts/Garden/Mutations.js"></script>
<script src="/Scripts/Garden/FunctionalEffects.js"></script>
<script src="/Scripts/Garden/Save.js"></script>
<script src="/Scripts/Garden/Economy.js"></script>
<script src="/Scripts/Garden/AdminValidation.js"></script>
+1
View File
@@ -290,6 +290,7 @@ $PageSection = "Garden";
<script src="/Scripts/Garden/PlantImages.js"></script>
<script src="/Scripts/Garden/Plants.js"></script>
<script src="/Scripts/Garden/Mutations.js"></script>
<script src="/Scripts/Garden/FunctionalEffects.js"></script>
<script src="/Scripts/Garden/Save.js"></script>
<script src="/Scripts/Garden/Content.js"></script>
<script src="/Scripts/Garden/Economy.js"></script>
+151
View File
@@ -58,6 +58,153 @@ function ValidateAdminKey(
}
function ValidateAdminFunctionalEffects(
Effects
) {
const Functional =
Effects?.Functional;
if (Functional === undefined) {
return true;
}
if (!Array.isArray(Functional)) {
throw new Error(
"Effects.Functional must be an array."
);
}
if (Functional.length > 32) {
throw new Error(
"Effects.Functional cannot contain more than 32 effects."
);
}
const SeenKeys = new Set();
for (
let EffectIndex = 0;
EffectIndex < Functional.length;
EffectIndex++
) {
const Effect =
Functional[EffectIndex];
if (!IsAdminPlainObject(Effect)) {
throw new Error(
"Functional effect " +
(EffectIndex + 1) +
" must be an object."
);
}
ValidateAdminKey(
String(
Effect.Type ?? ""
).trim(),
"Functional effect type"
);
if (Effect.Key !== undefined) {
const EffectKey = String(
Effect.Key ?? ""
).trim();
ValidateAdminKey(
EffectKey,
"Functional effect key"
);
if (SeenKeys.has(EffectKey)) {
throw new Error(
"Functional effect keys must be unique within a plant."
);
}
SeenKeys.add(EffectKey);
}
for (const Field of [
"Area",
"Source",
"Target"
]) {
if (Effect[Field] === undefined) {
continue;
}
const Area = String(
Effect[Field]
);
if (
typeof FunctionalEffectAreas !==
"undefined" &&
!FunctionalEffectAreas.includes(
Area
)
) {
throw new Error(
"Functional effect " +
Field.toLowerCase() +
" must be Front, Behind, Cardinal, Adjacent or Farm."
);
}
}
if (Effect.Cooldown !== undefined) {
ValidateAdminInteger(
Number(
Effect.Cooldown
),
"Functional effect cooldown",
typeof FunctionalEffectMinimumCooldown !==
"undefined"
? FunctionalEffectMinimumCooldown
: 1000
);
}
if (Effect.Amount !== undefined) {
const Amount = Number(
Effect.Amount
);
if (!Number.isFinite(Amount)) {
throw new Error(
"Functional effect amount must be a number."
);
}
}
if (
Effect.RequireMature !== undefined &&
typeof Effect.RequireMature !==
"boolean"
) {
throw new Error(
"Functional effect RequireMature must be On or Off."
);
}
if (
typeof IsFunctionalEffectActive ===
"function" &&
IsFunctionalEffectActive(
Effect
) &&
Effect.Cooldown === undefined
) {
throw new Error(
"Active functional effects need a cooldown."
);
}
}
return true;
}
function ValidateAdminPlant(
Plant,
Catalogue,
@@ -205,6 +352,10 @@ function ValidateAdminPlant(
);
}
ValidateAdminFunctionalEffects(
Plant.Effects
);
if (
Plant.DirectionalSprites !== undefined &&
typeof Plant.DirectionalSprites !== "boolean"
File diff suppressed because it is too large. Load diff
+50 -1
View File
@@ -1669,11 +1669,35 @@ function CreatePlotElement(
Plant
);
const CooldownState =
Progress >= 1 &&
typeof GetFunctionalPlantCooldownDisplayState ===
"function"
? GetFunctionalPlantCooldownDisplayState(
Plot,
Plant,
GetSimulationTime(
GameSave
)
)
: null;
const BorderProgress =
CooldownState === null
? Progress
: CooldownState.Progress;
Tile.style.setProperty(
"--GrowthProgress",
`${Progress * 100}%`
`${BorderProgress * 100}%`
);
if (CooldownState !== null) {
Tile.classList.add(
"GardenPlotFunctionalCooldown"
);
}
const ImagePath =
GetPlantImage(
@@ -2004,6 +2028,31 @@ function CreateGardenPlotTimer(
}
if (
Progress >= 1 &&
typeof GetFunctionalPlantCooldownDisplayState ===
"function"
) {
const CooldownState =
GetFunctionalPlantCooldownDisplayState(
Plot,
Plant,
GetSimulationTime(
GameSave
)
);
if (CooldownState !== null) {
Timer.textContent =
FormatGardenRemainingTime(
CooldownState.Remaining
);
return Timer;
}
}
if (Progress >= 1) {
Timer.textContent =
"Ready";
+72 -1
View File
@@ -15,7 +15,7 @@ const MaximumGardenNameLength = 32;
const StartingGardenWidth = 4;
const StartingGardenHeight = 4;
const CurrentSaveVersion = 7;
const CurrentSaveVersion = 8;
function CreateSimulationRandomState() {
@@ -288,6 +288,77 @@ function NormalizeGardenData(
? Plot.Rotation
: "East";
if (
Plot.FunctionalCooldowns === null ||
typeof Plot.FunctionalCooldowns !==
"object" ||
Array.isArray(
Plot.FunctionalCooldowns
)
) {
Plot.FunctionalCooldowns = {};
}
for (
const [CooldownKey, CooldownValue]
of Object.entries(
Plot.FunctionalCooldowns
)
) {
let StartedAt = 0;
let Until = 0;
if (
CooldownValue !== null &&
typeof CooldownValue ===
"object" &&
!Array.isArray(
CooldownValue
)
) {
StartedAt = Math.max(
0,
Math.floor(
Number(
CooldownValue.StartedAt ?? 0
) || 0
)
);
Until = Math.max(
StartedAt,
Math.floor(
Number(
CooldownValue.Until ?? 0
) || 0
)
);
} else {
Until = Math.max(
0,
Math.floor(
Number(
CooldownValue
) || 0
)
);
}
if (Until <= 0) {
delete Plot.FunctionalCooldowns[
CooldownKey
];
continue;
}
Plot.FunctionalCooldowns[
CooldownKey
] = {
StartedAt,
Until
};
}
delete Plot.VisualVariant;
delete Plot.AddedTags;
}
+60 -10
View File
@@ -91,6 +91,28 @@ function MergeSimulationMutationResult(
}
function MergeSimulationFunctionalResult(
Target,
Source
) {
if (Source === null || Source === undefined) {
return;
}
Target.FunctionalAttempted =
Target.FunctionalAttempted ||
Source.Attempted === true;
Target.Changed =
Target.Changed ||
Source.Changed === true;
Target.FunctionalActivations.push(
...(Source.Activations ?? [])
);
}
function CreateSimulationResult(
StartTime,
TargetTime
@@ -100,6 +122,7 @@ function CreateSimulationResult(
TargetTime,
Advanced: TargetTime > StartTime,
Attempted: false,
FunctionalAttempted: false,
Changed: false,
Dirty: false,
CaughtUp: StartTime >= TargetTime,
@@ -107,7 +130,8 @@ function CreateSimulationResult(
Steps: 0,
SuccessfulMutations: [],
NewlyDiscoveredMutations: [],
NewlyDiscoveredPlants: []
NewlyDiscoveredPlants: [],
FunctionalActivations: []
};
}
@@ -135,6 +159,7 @@ function FinalizeSimulationResult(
Result.Dirty =
Result.Attempted ||
Result.FunctionalAttempted ||
Result.Changed;
return Result;
@@ -324,18 +349,27 @@ function GetNextSimulationEventTime(
TargetTime
);
if (MaturityTime === null) {
return MutationTime;
}
const FunctionalTime =
typeof GetNextFunctionalEffectEventTime ===
"function"
? GetNextFunctionalEffectEventTime(
SaveData,
CurrentTime,
TargetTime
)
: null;
if (MutationTime === null) {
return MaturityTime;
}
return Math.min(
const EventTimes = [
MaturityTime,
MutationTime
MutationTime,
FunctionalTime
].filter(
Time => Time !== null
);
return EventTimes.length === 0
? null
: Math.min(...EventTimes);
}
@@ -387,6 +421,22 @@ function AdvanceGameSimulation(
MutationResult
);
if (
typeof ProcessFunctionalEffectsAtTime ===
"function"
) {
const FunctionalResult =
ProcessFunctionalEffectsAtTime(
SaveData,
CurrentTime
);
MergeSimulationFunctionalResult(
Result,
FunctionalResult
);
}
if (CurrentTime >= SafeTargetTime) {
Result.CaughtUp = true;
break;
+9
View File
@@ -754,6 +754,15 @@ input, button, textarea, select {
--GrowthColour: var(--Yellow);
}
.GardenPlotFunctionalCooldown,
.GardenPlotFunctionalCooldown:hover {
--GrowthColour: var(--Blue);
}
.GardenPlotFunctionalCooldown .GardenPlotTimer {
color: var(--Blue);
}
.SeedList {
display: flex;
gap: 0.5rem;