mirror of
https://git.srhtnin.garden/sarah/srhtnin.github.io.git
synced 2026-10-14 10:48:12 +02:00
Added more Admin QOL stuff. DW.
This commit is contained in:
1 parent
ab0d858fef
commit
24842c65fb
12 files changed
+4236
No files matched your search
@@ -0,0 +1,820 @@
|
||||
function IsAdminPlainObject(Value) {
|
||||
return (
|
||||
Value !== null &&
|
||||
typeof Value === "object" &&
|
||||
!Array.isArray(Value)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function ValidateAdminInteger(
|
||||
Value,
|
||||
Name,
|
||||
Minimum = null
|
||||
) {
|
||||
if (
|
||||
!Number.isInteger(Value) ||
|
||||
(
|
||||
Minimum !== null &&
|
||||
Value < Minimum
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
Name + " must be " +
|
||||
(
|
||||
Minimum === null
|
||||
? "an integer."
|
||||
: "an integer of at least " +
|
||||
Minimum + "."
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ValidateAdminKey(
|
||||
Value,
|
||||
Name,
|
||||
MaximumLength = 64
|
||||
) {
|
||||
if (
|
||||
typeof Value !== "string" ||
|
||||
Value.length === 0
|
||||
) {
|
||||
throw new Error(
|
||||
Name + " cannot be empty."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
Value.length > MaximumLength ||
|
||||
!/^[A-Za-z0-9_-]+$/.test(Value)
|
||||
) {
|
||||
throw new Error(
|
||||
Name +
|
||||
" may only contain letters, numbers, underscores and hyphens."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ValidateAdminPlant(
|
||||
Plant,
|
||||
Catalogue,
|
||||
EditingKey = null
|
||||
) {
|
||||
if (!IsAdminPlainObject(Plant)) {
|
||||
throw new Error(
|
||||
"Plant data is invalid."
|
||||
);
|
||||
}
|
||||
|
||||
ValidateAdminInteger(
|
||||
Number(Plant.Id),
|
||||
"ID",
|
||||
1
|
||||
);
|
||||
|
||||
const PlantKey =
|
||||
String(
|
||||
Plant.PlantKey ?? ""
|
||||
).trim();
|
||||
|
||||
ValidateAdminKey(
|
||||
PlantKey,
|
||||
"Plant Key"
|
||||
);
|
||||
|
||||
const ExistingAtId =
|
||||
Object.entries(
|
||||
Catalogue ?? {}
|
||||
).find(
|
||||
([, Candidate]) =>
|
||||
Number(Candidate?.Id) ===
|
||||
Number(Plant.Id)
|
||||
);
|
||||
|
||||
if (
|
||||
ExistingAtId !== undefined &&
|
||||
ExistingAtId[0] !== PlantKey
|
||||
) {
|
||||
throw new Error(
|
||||
"ID " + Plant.Id +
|
||||
" already belongs to " +
|
||||
ExistingAtId[0] + "."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
EditingKey !== null &&
|
||||
EditingKey !== PlantKey
|
||||
) {
|
||||
throw new Error(
|
||||
"An existing Plant Key cannot be changed."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
EditingKey !== null &&
|
||||
Number(
|
||||
Catalogue?.[EditingKey]?.Id
|
||||
) !== Number(Plant.Id)
|
||||
) {
|
||||
throw new Error(
|
||||
"An existing plant ID cannot be changed."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
EditingKey === null &&
|
||||
Catalogue?.[PlantKey] !== undefined
|
||||
) {
|
||||
throw new Error(
|
||||
"Plant Key " + PlantKey +
|
||||
" already exists."
|
||||
);
|
||||
}
|
||||
|
||||
const Name =
|
||||
String(
|
||||
Plant.Name ?? ""
|
||||
).trim();
|
||||
|
||||
if (
|
||||
Name.length === 0 ||
|
||||
Name.length > 64
|
||||
) {
|
||||
throw new Error(
|
||||
"Name must contain between 1 and 64 characters."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
typeof Plant.Description !==
|
||||
"string"
|
||||
) {
|
||||
throw new Error(
|
||||
"Description must be text."
|
||||
);
|
||||
}
|
||||
|
||||
if (!Array.isArray(Plant.Tags)) {
|
||||
throw new Error(
|
||||
"Tags must be an array."
|
||||
);
|
||||
}
|
||||
|
||||
for (const Tag of Plant.Tags) {
|
||||
if (
|
||||
typeof Tag !== "string" ||
|
||||
Tag.trim().length === 0 ||
|
||||
Tag.trim().length > 64
|
||||
) {
|
||||
throw new Error(
|
||||
"Tags contains an invalid tag."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ValidateAdminInteger(
|
||||
Number(Plant.GrowthTime),
|
||||
"Growth time",
|
||||
0
|
||||
);
|
||||
|
||||
const HarvestMultiplier =
|
||||
Number(
|
||||
Plant.HarvestMultiplier
|
||||
);
|
||||
|
||||
if (
|
||||
!Number.isFinite(
|
||||
HarvestMultiplier
|
||||
) ||
|
||||
HarvestMultiplier < 0 ||
|
||||
HarvestMultiplier > 9999.9999
|
||||
) {
|
||||
throw new Error(
|
||||
"Harvest multiplier must be between 0 and 9999.9999."
|
||||
);
|
||||
}
|
||||
|
||||
if (!IsAdminPlainObject(Plant.Effects)) {
|
||||
throw new Error(
|
||||
"Effects JSON must be an object."
|
||||
);
|
||||
}
|
||||
|
||||
if (Plant.ShopPlant === true) {
|
||||
ValidateAdminInteger(
|
||||
Number(Plant.BaseCost),
|
||||
"Base seed cost",
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function GetAdminPlantKeyById(
|
||||
PlantId,
|
||||
PlantCatalogue
|
||||
) {
|
||||
return Object.entries(
|
||||
PlantCatalogue ?? {}
|
||||
).find(
|
||||
([, Plant]) =>
|
||||
Number(Plant?.Id) ===
|
||||
Number(PlantId)
|
||||
)?.[0] ?? null;
|
||||
}
|
||||
|
||||
|
||||
function ValidateAdminMutationMatrix(
|
||||
Matrix,
|
||||
Name,
|
||||
Width = null,
|
||||
Height = null
|
||||
) {
|
||||
if (
|
||||
!Array.isArray(Matrix) ||
|
||||
Matrix.length < 1 ||
|
||||
Matrix.length > 7
|
||||
) {
|
||||
throw new Error(
|
||||
Name +
|
||||
" must contain between 1 and 7 rows."
|
||||
);
|
||||
}
|
||||
|
||||
const MatrixWidth =
|
||||
Array.isArray(Matrix[0])
|
||||
? Matrix[0].length
|
||||
: 0;
|
||||
|
||||
if (
|
||||
MatrixWidth < 1 ||
|
||||
MatrixWidth > 7
|
||||
) {
|
||||
throw new Error(
|
||||
Name +
|
||||
" rows must contain between 1 and 7 cells."
|
||||
);
|
||||
}
|
||||
|
||||
for (const Row of Matrix) {
|
||||
if (
|
||||
!Array.isArray(Row) ||
|
||||
Row.length !== MatrixWidth
|
||||
) {
|
||||
throw new Error(
|
||||
Name +
|
||||
" must be rectangular."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
Width !== null &&
|
||||
MatrixWidth !== Width
|
||||
) {
|
||||
throw new Error(
|
||||
Name +
|
||||
" width must match the pattern."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
Height !== null &&
|
||||
Matrix.length !== Height
|
||||
) {
|
||||
throw new Error(
|
||||
Name +
|
||||
" height must match the pattern."
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
Width: MatrixWidth,
|
||||
Height: Matrix.length
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function ValidateAdminMutationTagList(
|
||||
Value,
|
||||
Name
|
||||
) {
|
||||
if (!Array.isArray(Value)) {
|
||||
throw new Error(
|
||||
Name + " must be an array."
|
||||
);
|
||||
}
|
||||
|
||||
for (const Tag of Value) {
|
||||
if (
|
||||
typeof Tag !== "string" ||
|
||||
Tag.trim().length === 0 ||
|
||||
Tag.trim().length > 64
|
||||
) {
|
||||
throw new Error(
|
||||
Name +
|
||||
" contains an invalid tag."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ValidateAdminCaptureName(
|
||||
Capture
|
||||
) {
|
||||
if (
|
||||
typeof Capture !== "string" ||
|
||||
!/^[A-Za-z][A-Za-z0-9_]*$/.test(
|
||||
Capture
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
"Capture names must start with a letter and contain only letters, numbers and underscores."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ValidateAdminMutationPatternCell(
|
||||
Cell,
|
||||
PlantCatalogue,
|
||||
Captures
|
||||
) {
|
||||
if (
|
||||
Cell === null ||
|
||||
Cell === "Any" ||
|
||||
Cell === "Empty"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof Cell === "string") {
|
||||
if (
|
||||
PlantCatalogue?.[Cell] ===
|
||||
undefined
|
||||
) {
|
||||
throw new Error(
|
||||
"Pattern references unknown plant: " +
|
||||
Cell
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsAdminPlainObject(Cell)) {
|
||||
throw new Error(
|
||||
"Pattern contains an invalid matcher."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
Cell.Plant !== undefined &&
|
||||
(
|
||||
typeof Cell.Plant !==
|
||||
"string" ||
|
||||
PlantCatalogue?.[
|
||||
Cell.Plant
|
||||
] === undefined
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
"Pattern matcher references an unknown plant."
|
||||
);
|
||||
}
|
||||
|
||||
for (
|
||||
const TagField
|
||||
of [
|
||||
"Tags",
|
||||
"TagsAny",
|
||||
"TagsNot"
|
||||
]
|
||||
) {
|
||||
if (Cell[TagField] !== undefined) {
|
||||
ValidateAdminMutationTagList(
|
||||
Cell[TagField],
|
||||
TagField
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (Cell.Capture !== undefined) {
|
||||
ValidateAdminCaptureName(
|
||||
Cell.Capture
|
||||
);
|
||||
|
||||
Captures.add(
|
||||
Cell.Capture
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ValidateAdminMutationResultCell(
|
||||
Cell,
|
||||
PlantCatalogue,
|
||||
Captures
|
||||
) {
|
||||
if (
|
||||
Cell === null ||
|
||||
Cell === "Keep" ||
|
||||
Cell === "Empty"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let PlantValue = null;
|
||||
|
||||
if (typeof Cell === "string") {
|
||||
PlantValue = Cell;
|
||||
} else if (
|
||||
IsAdminPlainObject(Cell) &&
|
||||
typeof Cell.Plant === "string"
|
||||
) {
|
||||
PlantValue = Cell.Plant;
|
||||
}
|
||||
|
||||
if (PlantValue === null) {
|
||||
throw new Error(
|
||||
"Result contains an invalid value."
|
||||
);
|
||||
}
|
||||
|
||||
if (PlantValue.startsWith("$")) {
|
||||
const Capture =
|
||||
PlantValue.slice(1);
|
||||
|
||||
if (!Captures.has(Capture)) {
|
||||
throw new Error(
|
||||
"Result references unknown capture: " +
|
||||
Capture
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
PlantCatalogue?.[
|
||||
PlantValue
|
||||
] === undefined
|
||||
) {
|
||||
throw new Error(
|
||||
"Result references unknown plant: " +
|
||||
PlantValue
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ValidateAdminMutationRelations(
|
||||
Relations,
|
||||
PlantCatalogue
|
||||
) {
|
||||
if (!IsAdminPlainObject(Relations)) {
|
||||
throw new Error(
|
||||
"Relations must be an object."
|
||||
);
|
||||
}
|
||||
|
||||
const ValidIds =
|
||||
new Set(
|
||||
Object.values(
|
||||
PlantCatalogue ?? {}
|
||||
).map(
|
||||
Plant =>
|
||||
Number(Plant.Id)
|
||||
)
|
||||
);
|
||||
|
||||
for (
|
||||
const Name
|
||||
of [
|
||||
"PlantsUsed",
|
||||
"PlantsCreated"
|
||||
]
|
||||
) {
|
||||
const Values =
|
||||
Relations[Name] ?? [];
|
||||
|
||||
if (!Array.isArray(Values)) {
|
||||
throw new Error(
|
||||
Name + " must be an array."
|
||||
);
|
||||
}
|
||||
|
||||
for (const PlantId of Values) {
|
||||
if (
|
||||
!Number.isInteger(
|
||||
Number(PlantId)
|
||||
) ||
|
||||
!ValidIds.has(
|
||||
Number(PlantId)
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
Name +
|
||||
" references a plant that does not exist."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ValidateAdminMutation(
|
||||
Mutation,
|
||||
MutationCatalogue,
|
||||
PlantCatalogue,
|
||||
EditingKey = null
|
||||
) {
|
||||
if (!IsAdminPlainObject(Mutation)) {
|
||||
throw new Error(
|
||||
"Mutation data is invalid."
|
||||
);
|
||||
}
|
||||
|
||||
ValidateAdminInteger(
|
||||
Number(Mutation.Id),
|
||||
"ID",
|
||||
1
|
||||
);
|
||||
|
||||
const MutationKey =
|
||||
String(
|
||||
Mutation.MutationKey ?? ""
|
||||
).trim();
|
||||
|
||||
ValidateAdminKey(
|
||||
MutationKey,
|
||||
"Mutation Key"
|
||||
);
|
||||
|
||||
const ExistingAtId =
|
||||
Object.entries(
|
||||
MutationCatalogue ?? {}
|
||||
).find(
|
||||
([, Candidate]) =>
|
||||
Number(Candidate?.Id) ===
|
||||
Number(Mutation.Id)
|
||||
);
|
||||
|
||||
if (
|
||||
ExistingAtId !== undefined &&
|
||||
ExistingAtId[0] !== MutationKey
|
||||
) {
|
||||
throw new Error(
|
||||
"ID " + Mutation.Id +
|
||||
" already belongs to " +
|
||||
ExistingAtId[0] + "."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
EditingKey !== null &&
|
||||
EditingKey !== MutationKey
|
||||
) {
|
||||
throw new Error(
|
||||
"An existing Mutation Key cannot be changed."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
EditingKey !== null &&
|
||||
Number(
|
||||
MutationCatalogue
|
||||
?.[EditingKey]?.Id
|
||||
) !== Number(Mutation.Id)
|
||||
) {
|
||||
throw new Error(
|
||||
"An existing mutation ID cannot be changed."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
EditingKey === null &&
|
||||
MutationCatalogue?.[
|
||||
MutationKey
|
||||
] !== undefined
|
||||
) {
|
||||
throw new Error(
|
||||
"Mutation Key " +
|
||||
MutationKey +
|
||||
" already exists."
|
||||
);
|
||||
}
|
||||
|
||||
const Name =
|
||||
String(
|
||||
Mutation.Name ?? ""
|
||||
).trim();
|
||||
|
||||
if (
|
||||
Name.length === 0 ||
|
||||
Name.length > 128
|
||||
) {
|
||||
throw new Error(
|
||||
"Name must contain between 1 and 128 characters."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
typeof Mutation.Description !==
|
||||
"string"
|
||||
) {
|
||||
throw new Error(
|
||||
"Description must be text."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
Mutation.Hint !== null &&
|
||||
Mutation.Hint !== undefined &&
|
||||
typeof Mutation.Hint !== "string"
|
||||
) {
|
||||
throw new Error(
|
||||
"Hint must be text."
|
||||
);
|
||||
}
|
||||
|
||||
ValidateAdminInteger(
|
||||
Number(Mutation.Priority),
|
||||
"Priority"
|
||||
);
|
||||
|
||||
const Chance =
|
||||
Number(
|
||||
Mutation.Chance
|
||||
);
|
||||
|
||||
if (
|
||||
!Number.isFinite(Chance) ||
|
||||
Chance < 0 ||
|
||||
Chance > 1
|
||||
) {
|
||||
throw new Error(
|
||||
"Chance must be between 0 and 100%."
|
||||
);
|
||||
}
|
||||
|
||||
ValidateAdminInteger(
|
||||
Number(Mutation.Cooldown),
|
||||
"Cooldown",
|
||||
0
|
||||
);
|
||||
|
||||
if (
|
||||
Mutation.Rotation !== "None" &&
|
||||
Mutation.Rotation !== "Any"
|
||||
) {
|
||||
throw new Error(
|
||||
"Rotation must be None or Any."
|
||||
);
|
||||
}
|
||||
|
||||
const PatternSize =
|
||||
ValidateAdminMutationMatrix(
|
||||
Mutation.Pattern,
|
||||
"Pattern"
|
||||
);
|
||||
|
||||
const Captures =
|
||||
new Set();
|
||||
|
||||
for (const Row of Mutation.Pattern) {
|
||||
for (const Cell of Row) {
|
||||
ValidateAdminMutationPatternCell(
|
||||
Cell,
|
||||
PlantCatalogue,
|
||||
Captures
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ValidateAdminMutationMatrix(
|
||||
Mutation.Success,
|
||||
"Success result",
|
||||
PatternSize.Width,
|
||||
PatternSize.Height
|
||||
);
|
||||
|
||||
for (const Row of Mutation.Success) {
|
||||
for (const Cell of Row) {
|
||||
ValidateAdminMutationResultCell(
|
||||
Cell,
|
||||
PlantCatalogue,
|
||||
Captures
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
Mutation.Failure !== "Keep" &&
|
||||
Mutation.Failure !== "Clear"
|
||||
) {
|
||||
ValidateAdminMutationMatrix(
|
||||
Mutation.Failure,
|
||||
"Failure result",
|
||||
PatternSize.Width,
|
||||
PatternSize.Height
|
||||
);
|
||||
|
||||
for (const Row of Mutation.Failure) {
|
||||
for (const Cell of Row) {
|
||||
ValidateAdminMutationResultCell(
|
||||
Cell,
|
||||
PlantCatalogue,
|
||||
Captures
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ValidateAdminMutationRelations(
|
||||
Mutation.Relations ?? {},
|
||||
PlantCatalogue
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function DownloadAdminJson(
|
||||
FileName,
|
||||
Data
|
||||
) {
|
||||
const BlobData =
|
||||
new Blob(
|
||||
[
|
||||
JSON.stringify(
|
||||
Data,
|
||||
null,
|
||||
4
|
||||
) + "\n"
|
||||
],
|
||||
{
|
||||
type: "application/json"
|
||||
}
|
||||
);
|
||||
|
||||
const Url =
|
||||
URL.createObjectURL(
|
||||
BlobData
|
||||
);
|
||||
|
||||
const Link =
|
||||
document.createElement("a");
|
||||
|
||||
Link.href = Url;
|
||||
Link.download = FileName;
|
||||
Link.click();
|
||||
|
||||
URL.revokeObjectURL(Url);
|
||||
}
|
||||
|
||||
|
||||
function ReadAdminJsonFile(File) {
|
||||
return new Promise(
|
||||
(Resolve, Reject) => {
|
||||
const Reader =
|
||||
new FileReader();
|
||||
|
||||
Reader.onload = () => {
|
||||
try {
|
||||
Resolve(
|
||||
JSON.parse(
|
||||
String(
|
||||
Reader.result ?? ""
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (Error) {
|
||||
Reject(
|
||||
new Error(
|
||||
"The selected file is not valid JSON."
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Reader.onerror = () => {
|
||||
Reject(
|
||||
new Error(
|
||||
"Couldn't read the selected JSON file."
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
Reader.readAsText(File);
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user