263 lines
6.5 KiB
TypeScript
263 lines
6.5 KiB
TypeScript
// data/builderSlots.ts
|
|
|
|
/**
|
|
* Builder "slots" represent the major assemblies in a Battl Builder rifle:
|
|
* - Lower Receiver Assembly
|
|
* - Upper Receiver Assembly
|
|
* - Fire Control / Optics / Accessories, etc.
|
|
*
|
|
* Each slot can be satisfied by one or more "patterns" of categories.
|
|
* Example:
|
|
* Lower Assembly is satisfied by EITHER:
|
|
* - a single Complete Lower
|
|
* - OR a combination of stripped lower + parts + buffer + stock + grip + safety
|
|
*
|
|
* The frontend can use this file to:
|
|
* - show build progress for each slot
|
|
* - grey out / collapse sub-categories when a complete assembly is selected
|
|
* - validate that a build is "complete" before sharing/saving
|
|
*/
|
|
|
|
export type SlotGroup = "LOWER" | "UPPER" | "SYSTEM" | "ACCESSORIES";
|
|
|
|
/**
|
|
* CategoryId should correspond to CATEGORIES[id] from data/gunbuilderParts.ts
|
|
* We keep it loose here to avoid circular type imports.
|
|
*/
|
|
export type CategoryId = string;
|
|
|
|
export type BuilderSlotId =
|
|
| "lowerAssembly"
|
|
| "upperAssembly"
|
|
| "opticSystem"
|
|
| "sightSystem"
|
|
| "suppressorSystem"
|
|
| "magazineSystem"
|
|
| "accessoriesSupport";
|
|
|
|
export interface BuilderSlot {
|
|
id: BuilderSlotId;
|
|
label: string;
|
|
group: SlotGroup;
|
|
/**
|
|
* Human-facing description for tooltips / help text.
|
|
*/
|
|
description: string;
|
|
/**
|
|
* All category IDs that are visually associated with this slot.
|
|
* (Useful for layout / grouping in the UI.)
|
|
*/
|
|
categories: CategoryId[];
|
|
/**
|
|
* Each inner array is a valid way to "satisfy" the slot.
|
|
*
|
|
* Example (lowerAssembly):
|
|
* [
|
|
* ["completeLower"], // Pattern 1: single complete lower
|
|
* ["lower", "lowerParts", "trigger", "grip", "safety", "buffer", "stock"] // Pattern 2: custom lower
|
|
* ]
|
|
*/
|
|
satisfactionPatterns: CategoryId[][];
|
|
}
|
|
|
|
/**
|
|
* Core Battl Builder slots.
|
|
*
|
|
* These line up with your existing CATEGORIES in data/gunbuilderParts.ts:
|
|
*
|
|
* LOWER:
|
|
* - lower
|
|
* - completeLower
|
|
* - lowerParts
|
|
* - trigger
|
|
* - grip
|
|
* - safety
|
|
* - buffer
|
|
* - stock
|
|
*
|
|
* UPPER:
|
|
* - upper
|
|
* - completeUpper
|
|
* - bcg
|
|
* - barrel
|
|
* - gasBlock
|
|
* - gasTube
|
|
* - muzzleDevice
|
|
* - suppressor
|
|
* - handguard
|
|
* - chargingHandle
|
|
* - sights
|
|
* - optic
|
|
*
|
|
* ACCESSORIES:
|
|
* - magazine
|
|
* - weaponLight
|
|
* - foregrip
|
|
* - bipod
|
|
* - sling
|
|
* - railAccessory
|
|
* - tools
|
|
*/
|
|
export const BUILDER_SLOTS: BuilderSlot[] = [
|
|
{
|
|
id: "lowerAssembly",
|
|
label: "Lower Receiver Assembly",
|
|
group: "LOWER",
|
|
description:
|
|
"The serialized foundation of the rifle: receiver, controls, fire control, stock, and buffer system.",
|
|
categories: [
|
|
"lower",
|
|
"completeLower",
|
|
"lowerParts",
|
|
"trigger",
|
|
"grip",
|
|
"safety",
|
|
"buffer",
|
|
"stock",
|
|
],
|
|
satisfactionPatterns: [
|
|
// Pattern 1: single complete lower does it all
|
|
["completeLower"],
|
|
// Pattern 2: fully custom lower built from individual parts
|
|
["lower", "lowerParts", "trigger", "grip", "safety", "buffer", "stock"],
|
|
],
|
|
},
|
|
|
|
{
|
|
id: "upperAssembly",
|
|
label: "Upper Receiver Assembly",
|
|
group: "UPPER",
|
|
description:
|
|
"The working end of the rifle: upper, barrel, gas system, handguard, BCG, charging handle, and muzzle device.",
|
|
categories: [
|
|
"upper",
|
|
"completeUpper",
|
|
"bcg",
|
|
"barrel",
|
|
"gasBlock",
|
|
"gasTube",
|
|
"handguard",
|
|
"muzzleDevice",
|
|
"chargingHandle",
|
|
],
|
|
satisfactionPatterns: [
|
|
// Pattern 1: complete upper
|
|
["completeUpper"],
|
|
// Pattern 2: custom upper from parts
|
|
[
|
|
"upper",
|
|
"barrel",
|
|
"gasBlock",
|
|
"gasTube",
|
|
"handguard",
|
|
"bcg",
|
|
"chargingHandle",
|
|
"muzzleDevice",
|
|
],
|
|
],
|
|
},
|
|
|
|
{
|
|
id: "opticSystem",
|
|
label: "Optic System",
|
|
group: "SYSTEM",
|
|
description:
|
|
"Primary glass for the rifle: LPVOs, red dots, prisms, and magnifiers.",
|
|
categories: ["optic"],
|
|
satisfactionPatterns: [
|
|
// For now: any optic fills the slot.
|
|
["optic"],
|
|
],
|
|
},
|
|
|
|
{
|
|
id: "sightSystem",
|
|
label: "Backup / Iron Sights",
|
|
group: "SYSTEM",
|
|
description:
|
|
"Backup irons or primary sights for no-glass builds and hard-use setups.",
|
|
categories: ["sights"],
|
|
satisfactionPatterns: [["sights"]],
|
|
},
|
|
|
|
{
|
|
id: "suppressorSystem",
|
|
label: "Muzzle / Suppressor",
|
|
group: "SYSTEM",
|
|
description:
|
|
"Dedicated suppressor slot for QD or direct-thread cans. (Muzzle device is handled in the upper assembly patterns.)",
|
|
categories: ["suppressor"],
|
|
satisfactionPatterns: [["suppressor"]],
|
|
},
|
|
|
|
{
|
|
id: "magazineSystem",
|
|
label: "Magazines",
|
|
group: "ACCESSORIES",
|
|
description:
|
|
"Feeding the beast: mags appropriate to the platform and caliber.",
|
|
categories: ["magazine"],
|
|
satisfactionPatterns: [["magazine"]],
|
|
},
|
|
|
|
{
|
|
id: "accessoriesSupport",
|
|
label: "Support Gear",
|
|
group: "ACCESSORIES",
|
|
description:
|
|
"Lights, lasers, foregrips, bipods, slings, rail widgets, and tools that keep the rifle honest.",
|
|
categories: [
|
|
"weaponLight",
|
|
"foregrip",
|
|
"bipod",
|
|
"sling",
|
|
"railAccessory",
|
|
"tools",
|
|
],
|
|
// Accessories are all optional; this slot is never strictly "required"
|
|
satisfactionPatterns: [
|
|
// An empty pattern would technically always be satisfied,
|
|
// but we can treat this slot as "nice to have" in the UI instead.
|
|
["weaponLight"],
|
|
["foregrip"],
|
|
["bipod"],
|
|
["sling"],
|
|
["railAccessory"],
|
|
["tools"],
|
|
],
|
|
},
|
|
];
|
|
|
|
/**
|
|
* Helper: determine whether a slot is satisfied given currently selected parts.
|
|
*
|
|
* `selectedByCategory` is expected to be a map of categoryId → truthy if selected.
|
|
* The value can be the actual Part, or just `true` if you track it elsewhere.
|
|
*/
|
|
export function isSlotSatisfied(
|
|
slot: BuilderSlot,
|
|
selectedByCategory: Record<CategoryId, unknown>
|
|
): boolean {
|
|
return slot.satisfactionPatterns.some((pattern) =>
|
|
pattern.every((categoryId) => Boolean(selectedByCategory[categoryId]))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Optional helper: get the first pattern that is currently satisfied.
|
|
* Can be used to:
|
|
* - show which "path" the user took (complete vs custom)
|
|
* - conditionally collapse/expand sub-categories in the UI
|
|
*/
|
|
export function getSatisfiedPattern(
|
|
slot: BuilderSlot,
|
|
selectedByCategory: Record<CategoryId, unknown>
|
|
): CategoryId[] | null {
|
|
for (const pattern of slot.satisfactionPatterns) {
|
|
const ok = pattern.every((categoryId) =>
|
|
Boolean(selectedByCategory[categoryId])
|
|
);
|
|
if (ok) return pattern;
|
|
}
|
|
return null;
|
|
} |