diff --git a/common.js b/common.js index 631d3b3..c64d7d9 100644 --- a/common.js +++ b/common.js @@ -200,26 +200,87 @@ async function validateMapAsset(assetId, game) { } } - const mapParts = root.FindDescendantsOfClass("BasePart", (part) => part.Name === "MapStart" || part.Name === "MapFinish"); - if (mapParts.length !== 2 || mapParts[0].Name === mapParts[1].Name) { - errors.push("Your map must have exactly one part named `MapStart` and one part named `MapFinish`."); + const mapStarts = root.FindDescendantsOfClass("BasePart", (part) => part.Name === "MapStart"); + if (mapStarts.length !== 1) { + errors.push("Your map must have exactly one part named `MapStart`."); } - else if (mapParts[0].CanCollide || mapParts[1].CanCollide) { - errors.push("The `MapStart` and `MapFinish` parts in your map must have the `CanCollide` property disabled."); + + const mapFinishes = root.FindDescendantsOfClass("BasePart", (part) => part.Name === "MapFinish"); + if (mapFinishes.length < 1) { + errors.push("Your map must have at least one part named `MapFinish`."); + } + + const mapParts = Array.from(mapStarts); + mapParts.push(...mapFinishes); + for (const part of mapParts) { + if (!part.CanCollide) { + errors.push("The `MapStart` and `MapFinish` parts in your map must have the `CanCollide` property disabled."); + break; + } } - const spawnOnes = root.FindDescendantsOfClass("BasePart", (part) => part.Name === "Spawn1"); - if (spawnOnes.length !== 1) { - errors.push("Your map must have exactly one part named `Spawn1`."); + const allSpawns = root.FindDescendantsOfClass("BasePart", (part) => part.Name.startsWith("Spawn") && !isNaN(part.Name.slice(5))); + const spawnNameSet = new Set(); + const duplicateSpawns = new Set(); + for (const spawn of allSpawns) { + const name = spawn.Name; + if (spawnNameSet.has(name)) { + duplicateSpawns.add(name); + } + else { + spawnNameSet.add(name); + } + } + + if (!spawnNameSet.has("Spawn1")) { + errors.push("Your map must have a part named `Spawn1`."); + } + + const numDups = duplicateSpawns.size; + if (numDups > 0) { + const sortedSpawns = Array.from(duplicateSpawns); + sortedSpawns.sort(); + const firstFive = sortedSpawns.slice(0, 5).map((name) => `\`${name}\``); + if (numDups > 1 && numDups <= 5) { + firstFive[firstFive.length - 1] = "and " + firstFive[firstFive.length - 1]; + } + let msg = numDups === 1 ? "Your map has a duplicate `Spawn` part: " : "Your map has duplicate `Spawn` parts: "; + msg += firstFive.join(", "); + if (numDups > 5) { + msg += `, and ${numDups - 5} more`; + } + msg += ". There can only be one instance of each `Spawn` part."; + errors.push(msg); } // Why does ModuleScript not inherit from Script, and/or why does BaseScript not have a Source property? - const illegalScript = root.FindFirstDescendantOfClass("Script", (script) => sourceHasIllegalKeywords(script.Source)); - const illegalModuleScript = root.FindFirstDescendantOfClass("ModuleScript", (script) => sourceHasIllegalKeywords(script.Source)); - if (illegalScript || illegalModuleScript) { + const scripts = root.FindDescendantsOfClass("Script"); + scripts.push(...root.FindDescendantsOfClass("ModuleScript")); + + const sourceSet = new Set(); + let numDuplicateScripts = 0; + let hasIllegalKeywords = false; + for (const script of scripts) { + const source = script.Source; + if (!hasIllegalKeywords && sourceHasIllegalKeywords(source)) { + hasIllegalKeywords = true; + } + if (sourceSet.has(source)) { + ++numDuplicateScripts; + } + else { + sourceSet.add(source); + } + } + + if (hasIllegalKeywords) { errors.push("Your map has a `Script` that contains the keyword `getfenv` or `require`. You must remove these."); } + if (numDuplicateScripts > 50) { + errors.push("Your map has over 50 duplicate `Script`s. You must consolidate your scripts to less than 50 duplicates."); + } + if (errors.length > 0) { return { valid: false,