mirror of
https://git.srhtnin.garden/sarah/srhtnin.github.io.git
synced 2026-10-13 18:38:11 +02:00
755 lines
15 KiB
JavaScript
755 lines
15 KiB
JavaScript
const MutationRecipePreviewCycleMilliseconds = 1200;
|
|
|
|
let MutationRecipePreviewFrame = 0;
|
|
let MutationRecipePreviewTimer = null;
|
|
|
|
const MutationRecipePreviewCells = new Set();
|
|
const MutationRecipePreviewPlantStageCells = new Set();
|
|
const MutationRecipePreviewPermutations = new Map();
|
|
|
|
|
|
function GetMutationRecipePreviewListReference(
|
|
Value
|
|
) {
|
|
let Reference = Value;
|
|
|
|
if (
|
|
Value !== null &&
|
|
typeof Value === "object" &&
|
|
!Array.isArray(Value) &&
|
|
typeof Value.Plant === "string"
|
|
) {
|
|
Reference = Value.Plant;
|
|
}
|
|
|
|
if (typeof Reference !== "string") {
|
|
return null;
|
|
}
|
|
|
|
const Match =
|
|
/^List:([1-9][0-9]*)$/.exec(
|
|
Reference
|
|
);
|
|
|
|
return Match === null
|
|
? null
|
|
: Number(Match[1]);
|
|
}
|
|
|
|
|
|
function CreateMutationRecipePreviewOffsets(
|
|
Pattern,
|
|
Result
|
|
) {
|
|
const PatternOffsets =
|
|
CreateMutationRecipePreviewOffsetMatrix(
|
|
Pattern
|
|
);
|
|
|
|
const ResultOffsets =
|
|
Array.isArray(Result)
|
|
? Result.map(
|
|
Row =>
|
|
Array.isArray(Row)
|
|
? Row.map(() => null)
|
|
: []
|
|
)
|
|
: [];
|
|
|
|
const PatternCounts = {};
|
|
|
|
for (
|
|
let Y = 0;
|
|
Y < PatternOffsets.length;
|
|
Y++
|
|
) {
|
|
for (
|
|
let X = 0;
|
|
X < PatternOffsets[Y].length;
|
|
X++
|
|
) {
|
|
const Offset =
|
|
PatternOffsets[Y][X];
|
|
|
|
if (Offset === null) {
|
|
continue;
|
|
}
|
|
|
|
const ListNumber =
|
|
GetMutationRecipePreviewListReference(
|
|
Pattern?.[Y]?.[X]
|
|
);
|
|
|
|
if (ListNumber === null) {
|
|
continue;
|
|
}
|
|
|
|
PatternCounts[ListNumber] =
|
|
Math.max(
|
|
PatternCounts[ListNumber] ?? 0,
|
|
Offset + 1
|
|
);
|
|
}
|
|
}
|
|
|
|
const UsedResultOffsets = {};
|
|
|
|
for (
|
|
let Y = 0;
|
|
Y < ResultOffsets.length;
|
|
Y++
|
|
) {
|
|
for (
|
|
let X = 0;
|
|
X < ResultOffsets[Y].length;
|
|
X++
|
|
) {
|
|
const ListNumber =
|
|
GetMutationRecipePreviewListReference(
|
|
Result?.[Y]?.[X]
|
|
);
|
|
|
|
if (ListNumber === null) {
|
|
continue;
|
|
}
|
|
|
|
const PatternListNumber =
|
|
GetMutationRecipePreviewListReference(
|
|
Pattern?.[Y]?.[X]
|
|
);
|
|
|
|
const PatternOffset =
|
|
PatternOffsets?.[Y]?.[X];
|
|
|
|
if (
|
|
PatternListNumber !== ListNumber ||
|
|
PatternOffset === null ||
|
|
PatternOffset === undefined
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
ResultOffsets[Y][X] =
|
|
PatternOffset;
|
|
|
|
if (
|
|
UsedResultOffsets[ListNumber] ===
|
|
undefined
|
|
) {
|
|
UsedResultOffsets[ListNumber] =
|
|
new Set();
|
|
}
|
|
|
|
UsedResultOffsets[ListNumber].add(
|
|
PatternOffset
|
|
);
|
|
}
|
|
}
|
|
|
|
const NextOverflowOffset = {};
|
|
|
|
for (
|
|
let Y = 0;
|
|
Y < ResultOffsets.length;
|
|
Y++
|
|
) {
|
|
for (
|
|
let X = 0;
|
|
X < ResultOffsets[Y].length;
|
|
X++
|
|
) {
|
|
if (ResultOffsets[Y][X] !== null) {
|
|
continue;
|
|
}
|
|
|
|
const ListNumber =
|
|
GetMutationRecipePreviewListReference(
|
|
Result?.[Y]?.[X]
|
|
);
|
|
|
|
if (ListNumber === null) {
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
UsedResultOffsets[ListNumber] ===
|
|
undefined
|
|
) {
|
|
UsedResultOffsets[ListNumber] =
|
|
new Set();
|
|
}
|
|
|
|
const Used =
|
|
UsedResultOffsets[ListNumber];
|
|
|
|
const PatternCount =
|
|
PatternCounts[ListNumber] ?? 0;
|
|
|
|
let Offset = 0;
|
|
|
|
while (
|
|
Offset < PatternCount &&
|
|
Used.has(Offset)
|
|
) {
|
|
Offset++;
|
|
}
|
|
|
|
if (Offset >= PatternCount) {
|
|
Offset =
|
|
NextOverflowOffset[
|
|
ListNumber
|
|
] ?? PatternCount;
|
|
|
|
NextOverflowOffset[ListNumber] =
|
|
Offset + 1;
|
|
}
|
|
|
|
Used.add(Offset);
|
|
ResultOffsets[Y][X] = Offset;
|
|
}
|
|
}
|
|
|
|
return {
|
|
Pattern: PatternOffsets,
|
|
Result: ResultOffsets
|
|
};
|
|
}
|
|
|
|
|
|
function CreateMutationRecipePreviewOffsetMatrix(
|
|
Matrix
|
|
) {
|
|
if (!Array.isArray(Matrix)) {
|
|
return [];
|
|
}
|
|
|
|
const NextOffsets = {};
|
|
|
|
return Matrix.map(
|
|
Row => {
|
|
if (!Array.isArray(Row)) {
|
|
return [];
|
|
}
|
|
|
|
return Row.map(
|
|
Value => {
|
|
const ListNumber =
|
|
GetMutationRecipePreviewListReference(
|
|
Value
|
|
);
|
|
|
|
if (ListNumber === null) {
|
|
return null;
|
|
}
|
|
|
|
const Offset =
|
|
NextOffsets[ListNumber] ?? 0;
|
|
|
|
NextOffsets[ListNumber] =
|
|
Offset + 1;
|
|
|
|
return Offset;
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
|
|
function RegisterMutationRecipePreviewListCell(
|
|
Element,
|
|
List,
|
|
ListNumber,
|
|
Offset,
|
|
PlantCatalogue,
|
|
TextClassName = "MutationRecipeText",
|
|
CyclePlantStages = false
|
|
) {
|
|
const Entry = {
|
|
Element: Element,
|
|
List: List,
|
|
ListNumber: ListNumber,
|
|
Offset:
|
|
Number.isInteger(Offset)
|
|
? Offset
|
|
: 0,
|
|
PlantCatalogue:
|
|
PlantCatalogue ?? {},
|
|
TextClassName: TextClassName,
|
|
CyclePlantStages:
|
|
CyclePlantStages === true
|
|
};
|
|
|
|
MutationRecipePreviewCells.add(
|
|
Entry
|
|
);
|
|
|
|
RenderMutationRecipePreviewListCell(
|
|
Entry,
|
|
MutationRecipePreviewFrame
|
|
);
|
|
|
|
StartMutationRecipePreviewTimer();
|
|
}
|
|
|
|
|
|
function RegisterMutationRecipePreviewPlantStageCell(
|
|
Element,
|
|
PlantOrKey
|
|
) {
|
|
const Images =
|
|
GetMutationRecipePreviewPlantStageImages(
|
|
PlantOrKey
|
|
);
|
|
|
|
if (Images.length <= 1) {
|
|
return;
|
|
}
|
|
|
|
const Entry = {
|
|
Element: Element,
|
|
Images: Images
|
|
};
|
|
|
|
MutationRecipePreviewPlantStageCells.add(
|
|
Entry
|
|
);
|
|
|
|
RenderMutationRecipePreviewPlantStageCell(
|
|
Entry,
|
|
MutationRecipePreviewFrame
|
|
);
|
|
|
|
StartMutationRecipePreviewTimer();
|
|
}
|
|
|
|
|
|
function StartMutationRecipePreviewTimer() {
|
|
if (
|
|
MutationRecipePreviewTimer !== null ||
|
|
(
|
|
MutationRecipePreviewCells.size === 0 &&
|
|
MutationRecipePreviewPlantStageCells.size === 0
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
window.matchMedia?.(
|
|
"(prefers-reduced-motion: reduce)"
|
|
).matches === true
|
|
) {
|
|
return;
|
|
}
|
|
|
|
MutationRecipePreviewTimer =
|
|
window.setInterval(
|
|
AdvanceMutationRecipePreviewFrame,
|
|
MutationRecipePreviewCycleMilliseconds
|
|
);
|
|
}
|
|
|
|
|
|
function AdvanceMutationRecipePreviewFrame() {
|
|
MutationRecipePreviewFrame++;
|
|
|
|
for (
|
|
const Entry
|
|
of Array.from(
|
|
MutationRecipePreviewCells
|
|
)
|
|
) {
|
|
if (!Entry.Element.isConnected) {
|
|
MutationRecipePreviewCells.delete(
|
|
Entry
|
|
);
|
|
continue;
|
|
}
|
|
|
|
RenderMutationRecipePreviewListCell(
|
|
Entry,
|
|
MutationRecipePreviewFrame
|
|
);
|
|
}
|
|
|
|
for (
|
|
const Entry
|
|
of Array.from(
|
|
MutationRecipePreviewPlantStageCells
|
|
)
|
|
) {
|
|
if (!Entry.Element.isConnected) {
|
|
MutationRecipePreviewPlantStageCells.delete(
|
|
Entry
|
|
);
|
|
continue;
|
|
}
|
|
|
|
RenderMutationRecipePreviewPlantStageCell(
|
|
Entry,
|
|
MutationRecipePreviewFrame
|
|
);
|
|
}
|
|
|
|
if (
|
|
MutationRecipePreviewCells.size === 0 &&
|
|
MutationRecipePreviewPlantStageCells.size === 0 &&
|
|
MutationRecipePreviewTimer !== null
|
|
) {
|
|
window.clearInterval(
|
|
MutationRecipePreviewTimer
|
|
);
|
|
|
|
MutationRecipePreviewTimer = null;
|
|
}
|
|
}
|
|
|
|
|
|
function RenderMutationRecipePreviewListCell(
|
|
Entry,
|
|
Frame
|
|
) {
|
|
const Items =
|
|
Array.isArray(Entry.List?.Items)
|
|
? Entry.List.Items
|
|
: [];
|
|
|
|
if (Items.length === 0) {
|
|
ApplyMutationRecipePreviewCellDisplay(
|
|
Entry,
|
|
{
|
|
Label:
|
|
"List " +
|
|
Entry.ListNumber,
|
|
Image: null,
|
|
ClassName:
|
|
"MutationRecipeMatcher"
|
|
}
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
const ItemIndex =
|
|
GetMutationRecipePreviewItemIndex(
|
|
Entry,
|
|
Frame,
|
|
Items.length
|
|
);
|
|
|
|
ApplyMutationRecipePreviewCellDisplay(
|
|
Entry,
|
|
GetMutationRecipePreviewItemDisplay(
|
|
Items[ItemIndex],
|
|
Entry.PlantCatalogue,
|
|
Entry.CyclePlantStages,
|
|
Frame
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
function GetMutationRecipePreviewItemIndex(
|
|
Entry,
|
|
Frame,
|
|
ItemCount
|
|
) {
|
|
if (Entry.List?.Mode !== "Once") {
|
|
return (
|
|
Frame +
|
|
Entry.Offset
|
|
) % ItemCount;
|
|
}
|
|
|
|
const Permutation =
|
|
CreateMutationRecipePreviewPermutation(
|
|
ItemCount,
|
|
Entry.ListNumber,
|
|
Frame
|
|
);
|
|
|
|
return Permutation[
|
|
Entry.Offset % ItemCount
|
|
];
|
|
}
|
|
|
|
|
|
function CreateMutationRecipePreviewPermutation(
|
|
ItemCount,
|
|
ListNumber,
|
|
Frame
|
|
) {
|
|
const CacheKey =
|
|
ListNumber + ":" + ItemCount;
|
|
|
|
const Cached =
|
|
MutationRecipePreviewPermutations.get(
|
|
CacheKey
|
|
);
|
|
|
|
if (Cached?.Frame === Frame) {
|
|
return Cached.Permutation;
|
|
}
|
|
|
|
const Permutation =
|
|
Array.from(
|
|
{ length: ItemCount },
|
|
(_, Index) => Index
|
|
);
|
|
|
|
if (ItemCount >= 2 && Frame !== 0) {
|
|
let State =
|
|
(
|
|
Math.imul(
|
|
Frame + 1,
|
|
0x9e3779b1
|
|
) ^
|
|
Math.imul(
|
|
ListNumber + 1,
|
|
0x85ebca6b
|
|
) ^
|
|
Math.imul(
|
|
ItemCount,
|
|
0xc2b2ae35
|
|
)
|
|
) >>> 0;
|
|
|
|
if (State === 0) {
|
|
State = 0x6d2b79f5;
|
|
}
|
|
|
|
for (
|
|
let Index = ItemCount - 1;
|
|
Index > 0;
|
|
Index--
|
|
) {
|
|
State ^= State << 13;
|
|
State ^= State >>> 17;
|
|
State ^= State << 5;
|
|
State >>>= 0;
|
|
|
|
const SwapIndex =
|
|
State % (Index + 1);
|
|
|
|
[
|
|
Permutation[Index],
|
|
Permutation[SwapIndex]
|
|
] = [
|
|
Permutation[SwapIndex],
|
|
Permutation[Index]
|
|
];
|
|
}
|
|
}
|
|
|
|
if (
|
|
ItemCount >= 2 &&
|
|
Cached?.Frame === Frame - 1 &&
|
|
Permutation.every(
|
|
(Value, Index) =>
|
|
Value ===
|
|
Cached.Permutation[Index]
|
|
)
|
|
) {
|
|
[
|
|
Permutation[0],
|
|
Permutation[1]
|
|
] = [
|
|
Permutation[1],
|
|
Permutation[0]
|
|
];
|
|
}
|
|
|
|
MutationRecipePreviewPermutations.set(
|
|
CacheKey,
|
|
{
|
|
Frame: Frame,
|
|
Permutation: Permutation
|
|
}
|
|
);
|
|
|
|
return Permutation;
|
|
}
|
|
|
|
|
|
function GetMutationRecipePreviewItemDisplay(
|
|
Item,
|
|
PlantCatalogue,
|
|
CyclePlantStages = false,
|
|
Frame = 0
|
|
) {
|
|
if (Item?.Type === "Plant") {
|
|
const Plant =
|
|
PlantCatalogue?.[
|
|
Item.Value
|
|
];
|
|
|
|
if (Plant === undefined) {
|
|
return {
|
|
Label:
|
|
Item.Value ??
|
|
"Unknown plant",
|
|
Image: null,
|
|
ClassName:
|
|
"MutationRecipeMatcher"
|
|
};
|
|
}
|
|
|
|
return {
|
|
Label:
|
|
Plant.Name ??
|
|
Item.Value,
|
|
Image:
|
|
GetMutationRecipePreviewPlantImageSource(
|
|
Plant,
|
|
CyclePlantStages,
|
|
Frame
|
|
),
|
|
ClassName:
|
|
"GuideRecipePlant"
|
|
};
|
|
}
|
|
|
|
if (Item?.Type === "Tag") {
|
|
return {
|
|
Label:
|
|
Item.Value ?? "",
|
|
Image: null,
|
|
ClassName:
|
|
"MutationRecipeMatcher"
|
|
};
|
|
}
|
|
|
|
if (Item?.Type === "Empty") {
|
|
return {
|
|
Label: "Empty",
|
|
Image: null,
|
|
ClassName:
|
|
"GuideRecipeEmpty"
|
|
};
|
|
}
|
|
|
|
return {
|
|
Label: "Any",
|
|
Image: null,
|
|
ClassName:
|
|
"MutationRecipeAny"
|
|
};
|
|
}
|
|
|
|
|
|
function GetMutationRecipePreviewPlantStageImages(
|
|
PlantOrKey
|
|
) {
|
|
return GetPlantImageSources(
|
|
PlantOrKey
|
|
).filter(
|
|
Image =>
|
|
typeof Image === "string" &&
|
|
Image.length > 0
|
|
);
|
|
}
|
|
|
|
|
|
function GetMutationRecipePreviewPlantImageSource(
|
|
PlantOrKey,
|
|
CyclePlantStages,
|
|
Frame
|
|
) {
|
|
if (CyclePlantStages !== true) {
|
|
return GetPlantMatureImageSource(
|
|
PlantOrKey
|
|
);
|
|
}
|
|
|
|
const Images =
|
|
GetMutationRecipePreviewPlantStageImages(
|
|
PlantOrKey
|
|
);
|
|
|
|
if (Images.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return Images[
|
|
Frame % Images.length
|
|
];
|
|
}
|
|
|
|
|
|
function RenderMutationRecipePreviewPlantStageCell(
|
|
Entry,
|
|
Frame
|
|
) {
|
|
const Image =
|
|
Entry.Element.querySelector(
|
|
"img.GuideRecipeImage"
|
|
);
|
|
|
|
if (Image === null) {
|
|
return;
|
|
}
|
|
|
|
Image.src =
|
|
Entry.Images[
|
|
Frame % Entry.Images.length
|
|
];
|
|
}
|
|
|
|
|
|
function ApplyMutationRecipePreviewCellDisplay(
|
|
Entry,
|
|
Display
|
|
) {
|
|
const Element = Entry.Element;
|
|
|
|
Element.classList.remove(
|
|
"GuideRecipePlant",
|
|
"GuideRecipeEmpty",
|
|
"MutationRecipeAny",
|
|
"MutationRecipeMatcher"
|
|
);
|
|
|
|
Element.classList.add(
|
|
Display.ClassName
|
|
);
|
|
|
|
Element.replaceChildren();
|
|
|
|
if (Display.Image !== null) {
|
|
const Image =
|
|
document.createElement(
|
|
"img"
|
|
);
|
|
|
|
Image.className =
|
|
"PlantSprite GuideRecipeImage";
|
|
|
|
Image.src = Display.Image;
|
|
Image.alt = "";
|
|
|
|
Element.appendChild(Image);
|
|
}
|
|
|
|
const Label =
|
|
document.createElement(
|
|
"span"
|
|
);
|
|
|
|
Label.className =
|
|
Display.Image === null
|
|
? Entry.TextClassName
|
|
: "GuideRecipeLabel";
|
|
|
|
Label.textContent =
|
|
Display.Label;
|
|
|
|
Element.appendChild(Label);
|
|
|
|
Element.title =
|
|
"List " +
|
|
Entry.ListNumber +
|
|
": " +
|
|
Display.Label;
|
|
}
|