Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions src/stores/__tests__/members.store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,4 +520,206 @@ describe('members store', () => {
}
}
});

test('it skips lust when all providers conflict with captain role', { repeats: 10 }, async () => {
const store = useMembersStore();
const config = useConfigStore();
const teams = useTeamsStore();

config.spreadLust = true;
config.autoPug = false;
config.fancy = false;

// Healer captain with ONLY healer lust providers available
const members: Member[] = [
{
character: {
name: 'CaptainHealer',
class: 'Priest',
active_spec_name: 'Holy',
active_spec_role: 'HEALING',
realm: 'Test'
},
captain: true,
rank: 1
},
{
character: {
name: 'LustHealer1',
class: 'Shaman',
active_spec_name: 'Restoration',
active_spec_role: 'HEALING',
realm: 'Test'
},
rank: 1
},
{
character: {
name: 'Tank1',
class: 'Warrior',
active_spec_name: 'Protection',
active_spec_role: 'TANK',
realm: 'Test'
},
rank: 1
},
{
character: {
name: 'Tank2',
class: 'Warrior',
active_spec_name: 'Protection',
active_spec_role: 'TANK',
realm: 'Test'
},
rank: 1
},
...Array.from({ length: 6 }, (_, i) => ({
character: {
name: `DPS${i + 1}`,
class: 'Rogue' as const,
active_spec_name: 'Outlaw' as const,
active_spec_role: 'DPS' as const,
realm: 'Test'
},
rank: 1
}))
];

store.selectedMembers = members;

await store.randomise();

// Must produce 2 teams (previously the healer captain team got 6 members and was dropped)
expect(teams.teams.length).toBe(2);
for (const team of teams.teams) {
expect(team.members.length).toBe(5);
expect(
team.members.filter((m) => m.character.active_spec_role === 'TANK').length
).toBe(1);
expect(
team.members.filter((m) => m.character.active_spec_role === 'HEALING').length
).toBe(1);
expect(
team.members.filter((m) => m.character.active_spec_role === 'DPS').length
).toBe(3);
}
});

test('it handles multi-spec lust players without depleting healer pool', { repeats: 25 }, async () => {
const store = useMembersStore();
const config = useConfigStore();
const teams = useTeamsStore();

config.spreadLust = true;
config.autoPug = false;
config.fancy = false;

// Shaman selected with both Resto (HEALING) and Enhance (DPS) specs
// If the DPS entry is used as lust, pruneWorkingMembers removes the healer entry too,
// depleting the healer pool and leaving another team without a healer
const members: Member[] = [
{
character: {
name: 'FlexShaman',
class: 'Shaman',
active_spec_name: 'Restoration',
active_spec_role: 'HEALING',
realm: 'Test'
},
rank: 1
},
{
character: {
name: 'FlexShaman',
class: 'Shaman',
active_spec_name: 'Enhancement',
active_spec_role: 'DPS',
realm: 'Test'
},
rank: 1
},
{
character: {
name: 'Healer2',
class: 'Paladin',
active_spec_name: 'Holy',
active_spec_role: 'HEALING',
realm: 'Test'
},
rank: 1
},
{
character: {
name: 'Tank1',
class: 'Warrior',
active_spec_name: 'Protection',
active_spec_role: 'TANK',
realm: 'Test'
},
rank: 1
},
{
character: {
name: 'Tank2',
class: 'Death Knight',
active_spec_name: 'Blood',
active_spec_role: 'TANK',
realm: 'Test'
},
rank: 1
},
{
character: {
name: 'LustDPS',
class: 'Mage',
active_spec_name: 'Fire',
active_spec_role: 'DPS',
realm: 'Test'
},
rank: 1
},
...Array.from({ length: 5 }, (_, i) => ({
character: {
name: `DPS${i + 1}`,
class: 'Rogue' as const,
active_spec_name: 'Outlaw' as const,
active_spec_role: 'DPS' as const,
realm: 'Test'
},
rank: 1
}))
];

store.selectedMembers = members;

await store.randomise();

// 10 unique players = 2 teams (FlexShaman counted once)
expect(teams.teams.length).toBe(2);
for (const team of teams.teams) {
expect(team.members.length).toBe(5);
expect(
team.members.filter((m) => m.character.active_spec_role === 'TANK').length
).toBe(1);
expect(
team.members.filter((m) => m.character.active_spec_role === 'HEALING').length
).toBe(1);
expect(
team.members.filter((m) => m.character.active_spec_role === 'DPS').length
).toBe(3);

// No duplicate players within a team
const unique = new Set(
team.members.map((m) => `${m.character.name}-${m.character.realm}`)
);
expect(unique.size).toBe(5);
}

// No duplicate players across teams
const allPlayers = teams.teams.flatMap((t) =>
t.members.map((m) => `${m.character.name}-${m.character.realm}`)
);
const allUnique = new Set(allPlayers);
expect(allUnique.size).toBe(allPlayers.length);
});
});
30 changes: 17 additions & 13 deletions src/stores/members.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,9 @@ export const useMembersStore = defineStore('members', () => {
error.value = 'Too many team captains';
return;
}
// if (captains.length) {
// console.log(`captains: ${captains.map((m) => m?.character.name).join(', ')}`);
// }
if (captains.length) {
console.log(`captains: ${captains.map((m) => m?.character.name).join(', ')}`);
}

/* prune picked characters */
function pruneWorkingMembers(member: Member) {
Expand All @@ -367,10 +367,19 @@ export const useMembersStore = defineStore('members', () => {
const isHealer = (member: Member) => member.character.active_spec_role === 'HEALING';
const isDamageDealer = (member: Member) => member.character.active_spec_role === 'DPS';

// lust allocation logic
const availableLustProviders = workingMembers.filter(
(member) => !member.captain && classSpecLust[member.character.class]
);
// lust allocation logic - deduplicate by player, preferring healer entries
// Using a healer entry as lust fills both the lust and healer slot,
// preventing healer pool depletion when multi-spec players are selected
const lustProviderMap = new Map<string, Member>();
for (const member of workingMembers) {
if (member.captain || !classSpecLust[member.character.class]) continue;
const key = `${member.character.name}-${member.character.realm}`;
const existing = lustProviderMap.get(key);
if (!existing || (isHealer(member) && !isHealer(existing))) {
lustProviderMap.set(key, member);
}
}
const availableLustProviders = [...lustProviderMap.values()];
shuffle(availableLustProviders);

for (const team of workingTeams) {
Expand All @@ -387,19 +396,14 @@ export const useMembersStore = defineStore('members', () => {
if (!captainHasLust) {
// pick a lust provider
// Try to find one that doesn't conflict with captain's role (specifically Healer/Tank)
let providerIndex = availableLustProviders.findIndex((m) => {
const providerIndex = availableLustProviders.findIndex((m) => {
// If team has Healer, avoid Healer Lust
if (team.members.find(isHealer) && isHealer(m)) return false;
// If team has Tank, avoid Tank Lust (though none exist currently)
if (team.members.find(isTank) && isTank(m)) return false;
return true;
});

// If no optimal provider found, take the first one (fallback)
if (providerIndex === -1 && availableLustProviders.length > 0) {
providerIndex = 0;
}

if (providerIndex !== -1) {
const lust = availableLustProviders[providerIndex];
pruneWorkingMembers(lust);
Expand Down
Loading