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> </div>
</main> </main>
<script src="/Scripts/Garden/FunctionalEffects.js"></script>
<script src="/Scripts/Garden/Save.js"></script> <script src="/Scripts/Garden/Save.js"></script>
<script src="/Scripts/Garden/AdminValidation.js"></script> <script src="/Scripts/Garden/AdminValidation.js"></script>
<script src="/Scripts/Garden/AdminMassPlantEditor.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/PlantImages.js"></script>
<script src="/Scripts/Garden/Plants.js"></script> <script src="/Scripts/Garden/Plants.js"></script>
<script src="/Scripts/Garden/Mutations.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/Save.js"></script>
<script src="/Scripts/Garden/Economy.js"></script> <script src="/Scripts/Garden/Economy.js"></script>
<script src="/Scripts/Garden/AdminValidation.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/PlantImages.js"></script>
<script src="/Scripts/Garden/Plants.js"></script> <script src="/Scripts/Garden/Plants.js"></script>
<script src="/Scripts/Garden/Mutations.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/Save.js"></script>
<script src="/Scripts/Garden/Content.js"></script> <script src="/Scripts/Garden/Content.js"></script>
<script src="/Scripts/Garden/Economy.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( function ValidateAdminPlant(
Plant, Plant,
Catalogue, Catalogue,
@@ -205,6 +352,10 @@ function ValidateAdminPlant(
); );
} }
ValidateAdminFunctionalEffects(
Plant.Effects
);
if ( if (
Plant.DirectionalSprites !== undefined && Plant.DirectionalSprites !== undefined &&
typeof Plant.DirectionalSprites !== "boolean" 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 Plant
); );
const CooldownState =
Progress >= 1 &&
typeof GetFunctionalPlantCooldownDisplayState ===
"function"
? GetFunctionalPlantCooldownDisplayState(
Plot,
Plant,
GetSimulationTime(
GameSave
)
)
: null;
const BorderProgress =
CooldownState === null
? Progress
: CooldownState.Progress;
Tile.style.setProperty( Tile.style.setProperty(
"--GrowthProgress", "--GrowthProgress",
`${Progress * 100}%` `${BorderProgress * 100}%`
); );
if (CooldownState !== null) {
Tile.classList.add(
"GardenPlotFunctionalCooldown"
);
}
const ImagePath = const ImagePath =
GetPlantImage( 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) { if (Progress >= 1) {
Timer.textContent = Timer.textContent =
"Ready"; "Ready";
+72 -1
View File
@@ -15,7 +15,7 @@ const MaximumGardenNameLength = 32;
const StartingGardenWidth = 4; const StartingGardenWidth = 4;
const StartingGardenHeight = 4; const StartingGardenHeight = 4;
const CurrentSaveVersion = 7; const CurrentSaveVersion = 8;
function CreateSimulationRandomState() { function CreateSimulationRandomState() {
@@ -288,6 +288,77 @@ function NormalizeGardenData(
? Plot.Rotation ? Plot.Rotation
: "East"; : "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.VisualVariant;
delete Plot.AddedTags; 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( function CreateSimulationResult(
StartTime, StartTime,
TargetTime TargetTime
@@ -100,6 +122,7 @@ function CreateSimulationResult(
TargetTime, TargetTime,
Advanced: TargetTime > StartTime, Advanced: TargetTime > StartTime,
Attempted: false, Attempted: false,
FunctionalAttempted: false,
Changed: false, Changed: false,
Dirty: false, Dirty: false,
CaughtUp: StartTime >= TargetTime, CaughtUp: StartTime >= TargetTime,
@@ -107,7 +130,8 @@ function CreateSimulationResult(
Steps: 0, Steps: 0,
SuccessfulMutations: [], SuccessfulMutations: [],
NewlyDiscoveredMutations: [], NewlyDiscoveredMutations: [],
NewlyDiscoveredPlants: [] NewlyDiscoveredPlants: [],
FunctionalActivations: []
}; };
} }
@@ -135,6 +159,7 @@ function FinalizeSimulationResult(
Result.Dirty = Result.Dirty =
Result.Attempted || Result.Attempted ||
Result.FunctionalAttempted ||
Result.Changed; Result.Changed;
return Result; return Result;
@@ -324,18 +349,27 @@ function GetNextSimulationEventTime(
TargetTime TargetTime
); );
if (MaturityTime === null) { const FunctionalTime =
return MutationTime; typeof GetNextFunctionalEffectEventTime ===
} "function"
? GetNextFunctionalEffectEventTime(
SaveData,
CurrentTime,
TargetTime
)
: null;
if (MutationTime === null) { const EventTimes = [
return MaturityTime;
}
return Math.min(
MaturityTime, MaturityTime,
MutationTime MutationTime,
FunctionalTime
].filter(
Time => Time !== null
); );
return EventTimes.length === 0
? null
: Math.min(...EventTimes);
} }
@@ -387,6 +421,22 @@ function AdvanceGameSimulation(
MutationResult MutationResult
); );
if (
typeof ProcessFunctionalEffectsAtTime ===
"function"
) {
const FunctionalResult =
ProcessFunctionalEffectsAtTime(
SaveData,
CurrentTime
);
MergeSimulationFunctionalResult(
Result,
FunctionalResult
);
}
if (CurrentTime >= SafeTargetTime) { if (CurrentTime >= SafeTargetTime) {
Result.CaughtUp = true; Result.CaughtUp = true;
break; break;
+9
View File
@@ -754,6 +754,15 @@ input, button, textarea, select {
--GrowthColour: var(--Yellow); --GrowthColour: var(--Yellow);
} }
.GardenPlotFunctionalCooldown,
.GardenPlotFunctionalCooldown:hover {
--GrowthColour: var(--Blue);
}
.GardenPlotFunctionalCooldown .GardenPlotTimer {
color: var(--Blue);
}
.SeedList { .SeedList {
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;