feat: add community publishing and news syndication
This commit is contained in:
+116
-78
@@ -1,100 +1,138 @@
|
||||
export const WEAPON_MOD_SLOT_TYPES = [
|
||||
'Optics',
|
||||
'Muzzle',
|
||||
'Underbarrel',
|
||||
'Magazine',
|
||||
'Barrel',
|
||||
'Upper Receiver',
|
||||
'Lower Receiver',
|
||||
'Trigger'
|
||||
'Optics',
|
||||
'Muzzle',
|
||||
'Underbarrel',
|
||||
'Magazine',
|
||||
'Barrel',
|
||||
'Upper Receiver',
|
||||
'Lower Receiver',
|
||||
'Trigger',
|
||||
] as const;
|
||||
|
||||
export type WeaponModSlotConfig = {
|
||||
tier: number;
|
||||
slots: string[];
|
||||
tier: number;
|
||||
slots: string[];
|
||||
};
|
||||
|
||||
export function normalizeWeaponModSlotPayload(payload: unknown): WeaponModSlotConfig[] {
|
||||
if (!Array.isArray(payload)) return [];
|
||||
const byTier = new Map<number, Set<string>>();
|
||||
for (const row of payload) {
|
||||
if (!row || typeof row !== 'object') continue;
|
||||
const tier = Number('tier' in row ? row.tier : Number.NaN);
|
||||
if (!Number.isInteger(tier) || tier < 0 || tier > 2) continue;
|
||||
const slots = 'slots' in row && Array.isArray(row.slots) ? row.slots.map((slot: unknown) => String(slot)) : [];
|
||||
const collected = byTier.get(tier) ?? new Set<string>();
|
||||
for (const slot of slots) collected.add(slot);
|
||||
byTier.set(tier, collected);
|
||||
}
|
||||
return [...byTier.entries()]
|
||||
.sort(([left], [right]) => left - right)
|
||||
.map(([tier, slots]) => ({ tier, slots: [...slots] }));
|
||||
export function normalizeWeaponModSlotPayload(
|
||||
payload: unknown,
|
||||
): WeaponModSlotConfig[] {
|
||||
if (!Array.isArray(payload)) return [];
|
||||
const byTier = new Map<number, Set<string>>();
|
||||
for (const row of payload) {
|
||||
if (!row || typeof row !== 'object') continue;
|
||||
const tier = Number('tier' in row ? row.tier : Number.NaN);
|
||||
if (!Number.isInteger(tier) || tier < 0 || tier > 2) continue;
|
||||
const slots =
|
||||
'slots' in row && Array.isArray(row.slots)
|
||||
? row.slots.map((slot: unknown) => String(slot))
|
||||
: [];
|
||||
const collected = byTier.get(tier) ?? new Set<string>();
|
||||
for (const slot of slots) collected.add(slot);
|
||||
byTier.set(tier, collected);
|
||||
}
|
||||
return [...byTier.entries()]
|
||||
.sort(([left], [right]) => left - right)
|
||||
.map(([tier, slots]) => ({ tier, slots: [...slots] }));
|
||||
}
|
||||
|
||||
export function requiredWeaponModSlotCount(tier: number): number {
|
||||
return tier === 0 ? 4 : 5;
|
||||
return tier === 0 ? 4 : 5;
|
||||
}
|
||||
|
||||
export function sharedWeaponModSlots(configurations: WeaponModSlotConfig[]): string[] {
|
||||
return [...new Set(
|
||||
configurations
|
||||
.filter((row) => Number(row.tier) === 1 || Number(row.tier) === 2)
|
||||
.flatMap((row) => row.slots)
|
||||
)];
|
||||
export function sharedWeaponModSlots(
|
||||
configurations: WeaponModSlotConfig[],
|
||||
): string[] {
|
||||
return [
|
||||
...new Set(
|
||||
configurations
|
||||
.filter((row) => Number(row.tier) === 1 || Number(row.tier) === 2)
|
||||
.flatMap((row) => row.slots),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
export function excludedTierZeroWeaponModSlot(
|
||||
sharedSlots: string[],
|
||||
configurations: WeaponModSlotConfig[]
|
||||
sharedSlots: string[],
|
||||
configurations: WeaponModSlotConfig[],
|
||||
): string {
|
||||
const tierZeroSlots = configurations.find((row) => Number(row.tier) === 0)?.slots ?? [];
|
||||
const excluded = sharedSlots.filter((slot) => !tierZeroSlots.includes(slot));
|
||||
return excluded.length === 1 && tierZeroSlots.every((slot) => sharedSlots.includes(slot)) ? excluded[0] : '';
|
||||
const tierZeroSlots =
|
||||
configurations.find((row) => Number(row.tier) === 0)?.slots ?? [];
|
||||
const excluded = sharedSlots.filter((slot) => !tierZeroSlots.includes(slot));
|
||||
return excluded.length === 1 &&
|
||||
tierZeroSlots.every((slot) => sharedSlots.includes(slot))
|
||||
? excluded[0]
|
||||
: '';
|
||||
}
|
||||
|
||||
export function expandSharedWeaponModSlots(sharedSlots: string[], tierZeroExcludedSlot: string): WeaponModSlotConfig[] {
|
||||
const uniqueSharedSlots = [...new Set(sharedSlots)];
|
||||
return [
|
||||
{
|
||||
tier: 0,
|
||||
slots: tierZeroExcludedSlot
|
||||
? uniqueSharedSlots.filter((slot) => slot !== tierZeroExcludedSlot)
|
||||
: []
|
||||
},
|
||||
{ tier: 1, slots: [...uniqueSharedSlots] },
|
||||
{ tier: 2, slots: [...uniqueSharedSlots] }
|
||||
];
|
||||
export function expandSharedWeaponModSlots(
|
||||
sharedSlots: string[],
|
||||
tierZeroExcludedSlot: string,
|
||||
): WeaponModSlotConfig[] {
|
||||
const uniqueSharedSlots = [...new Set(sharedSlots)];
|
||||
return [
|
||||
{
|
||||
tier: 0,
|
||||
slots: tierZeroExcludedSlot
|
||||
? uniqueSharedSlots.filter((slot) => slot !== tierZeroExcludedSlot)
|
||||
: [],
|
||||
},
|
||||
{ tier: 1, slots: [...uniqueSharedSlots] },
|
||||
{ tier: 2, slots: [...uniqueSharedSlots] },
|
||||
];
|
||||
}
|
||||
|
||||
export function weaponModSlotsComplete(nativeTier: number, configurations: WeaponModSlotConfig[]): boolean {
|
||||
for (let tier = nativeTier; tier <= 2; tier += 1) {
|
||||
const configured = configurations.find((row) => Number(row.tier) === tier);
|
||||
if (configured?.slots.length !== requiredWeaponModSlotCount(tier)) return false;
|
||||
}
|
||||
if (nativeTier <= 1) {
|
||||
const tierOne = configurations.find((row) => Number(row.tier) === 1)?.slots ?? [];
|
||||
const tierTwo = configurations.find((row) => Number(row.tier) === 2)?.slots ?? [];
|
||||
if (tierOne.some((slot) => !tierTwo.includes(slot)) || tierTwo.some((slot) => !tierOne.includes(slot))) return false;
|
||||
}
|
||||
if (nativeTier === 0) {
|
||||
const tierZero = configurations.find((row) => Number(row.tier) === 0)?.slots ?? [];
|
||||
const tierOne = configurations.find((row) => Number(row.tier) === 1)?.slots ?? [];
|
||||
if (tierZero.some((slot) => !tierOne.includes(slot))) return false;
|
||||
}
|
||||
return true;
|
||||
export function weaponModSlotsComplete(
|
||||
nativeTier: number,
|
||||
configurations: WeaponModSlotConfig[],
|
||||
): boolean {
|
||||
for (let tier = nativeTier; tier <= 2; tier += 1) {
|
||||
const configured = configurations.find((row) => Number(row.tier) === tier);
|
||||
if (configured?.slots.length !== requiredWeaponModSlotCount(tier))
|
||||
return false;
|
||||
}
|
||||
if (nativeTier <= 1) {
|
||||
const tierOne =
|
||||
configurations.find((row) => Number(row.tier) === 1)?.slots ?? [];
|
||||
const tierTwo =
|
||||
configurations.find((row) => Number(row.tier) === 2)?.slots ?? [];
|
||||
if (
|
||||
tierOne.some((slot) => !tierTwo.includes(slot)) ||
|
||||
tierTwo.some((slot) => !tierOne.includes(slot))
|
||||
)
|
||||
return false;
|
||||
}
|
||||
if (nativeTier === 0) {
|
||||
const tierZero =
|
||||
configurations.find((row) => Number(row.tier) === 0)?.slots ?? [];
|
||||
const tierOne =
|
||||
configurations.find((row) => Number(row.tier) === 1)?.slots ?? [];
|
||||
if (tierZero.some((slot) => !tierOne.includes(slot))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function validateWeaponModSlots(nativeTier: number, configurations: WeaponModSlotConfig[]): string | null {
|
||||
for (let tier = nativeTier; tier <= 2; tier += 1) {
|
||||
const configured = configurations.find((row) => Number(row.tier) === tier);
|
||||
const slots = configured?.slots ?? [];
|
||||
const required = requiredWeaponModSlotCount(tier);
|
||||
if (slots.some((slot) => !WEAPON_MOD_SLOT_TYPES.includes(slot as (typeof WEAPON_MOD_SLOT_TYPES)[number]))) {
|
||||
return `Tier ${tier} includes an unavailable Weapon Mod slot.`;
|
||||
}
|
||||
if (slots.length > required) {
|
||||
return `Tier ${tier} supports at most ${required} unique Weapon Mod slots.`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
export function validateWeaponModSlots(
|
||||
nativeTier: number,
|
||||
configurations: WeaponModSlotConfig[],
|
||||
): string | null {
|
||||
for (let tier = nativeTier; tier <= 2; tier += 1) {
|
||||
const configured = configurations.find((row) => Number(row.tier) === tier);
|
||||
const slots = configured?.slots ?? [];
|
||||
const required = requiredWeaponModSlotCount(tier);
|
||||
if (
|
||||
slots.some(
|
||||
(slot) =>
|
||||
!WEAPON_MOD_SLOT_TYPES.includes(
|
||||
slot as (typeof WEAPON_MOD_SLOT_TYPES)[number],
|
||||
),
|
||||
)
|
||||
) {
|
||||
return `Tier ${tier} includes an unavailable Weapon Mod slot.`;
|
||||
}
|
||||
if (slots.length > required) {
|
||||
return `Tier ${tier} supports at most ${required} unique Weapon Mod slots.`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user