Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support searching multi formats #10275

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
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
1 change: 0 additions & 1 deletion config/formats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ export const Formats: FormatList = [
mod: 'gen9',
team: 'random',
gameType: 'multi',
searchShow: false,
tournamentShow: false,
rated: false,
ruleset: [
Expand Down
48 changes: 48 additions & 0 deletions server/chat-commands/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,54 @@ export const commands: Chat.ChatCommands = {
`If no format is given, cancels searches for all formats.`,
],

requestpartner(target, room, user) {
const {targetUser, rest} = this.requireUser(target);
if (targetUser.locked && !user.locked) {
return this.popupReply(`That user is locked and cannot be invited to battles.`);
}
if (targetUser.id === user.id) {
return this.popupReply(`You cannot be your own partner.`);
}
if (user.locked && !targetUser.locked) {
return this.errorReply(`You are locked and cannot invite others to battles.`);
}
const format = Dex.formats.get(rest);
if (!format.exists) return this.popupReply(`Invalid format: ${rest}`);
if (format.gameType !== 'multi') {
return this.popupReply(`You cannot invite people to non-multibattle formats. Challenge them instead.`);
}
Ladders.challenges.add(new Ladders.GameChallenge(user.id, targetUser.id, format.id, {
acceptCommand: `/acceptpartner ${user.id}`,
rejectCommand: `/denypartner ${user.id}`,
message: `${user.name} wants you to play ${format.name} with them!`,
}));
},
async acceptpartner(target, room, user, connection) {
const challenge = Ladders.challenges.resolveAcceptCommand(this);
Ladders.challenges.remove(challenge, true);
const format = Dex.formats.get(challenge.format);
const targetUser = Users.get(challenge.from);
if (!targetUser) return this.popupReply(`${challenge.from} is not available right now.`);
const search = await Ladders(format.id).prepBattle(connection, 'rated', user.battleSettings.team, !!format.rated, true);
if (search === null) return null;
targetUser.battleSettings.teammate = search;
const latestConn = Utils.sortBy(targetUser.connections.slice(), b => -b.lastActiveTime)[0];
targetUser.chat(`/search ${format.id}`, null, latestConn);
targetUser.popup(`Your teammate has accepted, and a battle search has been started.`);
this.pmTarget = targetUser;
this.sendReply(`You accepted ${targetUser.name}'s partnership request!`);
user.send(`|updatesearch|${JSON.stringify({searching: [format.id], games: null})}`);
},
denypartner(target, room, user) {
const {targetUser} = this.splitUser(target);
if (!targetUser) return this.popupReply(`User not found.`);
const chall = Ladders.challenges.search(user.id, targetUser.id);
if (!chall) return this.popupReply(`Challenge not found between you and ${targetUser.name}`);
Ladders.challenges.remove(chall, false);
this.popupReply(`Request denied.`);
targetUser.popup(`${user.id} denied your teammate request.`);
},

chall: 'challenge',
challenge(target, room, user, connection) {
const {targetUser, targetUsername, rest: formatName} = this.splitUser(target);
Expand Down
6 changes: 6 additions & 0 deletions server/ladders-challenges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ export class BattleReady {
this.challengeType = challengeType;
this.time = Date.now();
}
static averageRatings(readies: BattleReady[]) {
const average = Math.round(readies.map(r => r.rating).reduce((a, b) => a + b) / readies.length);
for (const ready of readies) {
(ready as any).rating = average;
}
}
}

export abstract class AbstractChallenge {
Expand Down
29 changes: 27 additions & 2 deletions server/ladders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ class Ladder extends LadderStore {
super(formatid);
}

async prepBattle(connection: Connection, challengeType: ChallengeType, team: string | null = null, isRated = false) {
async prepBattle(
connection: Connection,
challengeType: ChallengeType,
team: string | null = null,
isRated = false,
noPartner = false
) {
// all validation for a battle goes through here
const user = connection.user;
const userid = user.id;
Expand Down Expand Up @@ -137,6 +143,15 @@ class Ladder extends LadderStore {
return null;
}

if (Dex.formats.get(this.formatid).gameType === 'multi' && !noPartner) {
if (!user.battleSettings.teammate) {
connection.popup(
`You must have a teammate consent to play with you before playing this tier. Just fill out their name in the box and hit enter.`
);
return null;
}
}

const settings = {...user.battleSettings, team: valResult.slice(1)};
user.battleSettings.inviteOnly = false;
user.battleSettings.hidden = false;
Expand Down Expand Up @@ -250,6 +265,10 @@ class Ladder extends LadderStore {
formatTable.searches.delete(user.id);
cancelCount++;
}
if (user.battleSettings.teammate) {
const partner = Users.get(user.battleSettings.teammate.userid);
if (partner) Ladder.updateSearch(partner);
}

Ladder.updateSearch(user);
return cancelCount;
Expand Down Expand Up @@ -329,6 +348,9 @@ class Ladder extends LadderStore {
if (oldUserid !== user.id) return;
if (!search) return;

if (user.battleSettings.teammate) {
BattleReady.averageRatings([search, user.battleSettings.teammate]);
}
this.addSearch(search, user);
}

Expand Down Expand Up @@ -422,7 +444,6 @@ class Ladder extends LadderStore {
static periodicMatch() {
// In order from longest waiting to shortest waiting
for (const [formatid, formatTable] of Ladders.searches) {
if (formatTable.playerCount > 2) continue; // TODO: implement
const matchmaker = Ladders(formatid);
let longest: [BattleReady, User] | null = null;
for (const search of formatTable.searches.values()) {
Expand Down Expand Up @@ -459,6 +480,10 @@ class Ladder extends LadderStore {
missingUser = ready.userid;
break;
}
if (user.battleSettings.teammate) {
readies.push(user.battleSettings.teammate);
delete user.battleSettings.teammate;
}
players.push({
user,
team: ready.settings.team,
Expand Down
3 changes: 3 additions & 0 deletions server/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,9 @@ export class GlobalRoomState {
if (level === 50) displayCode |= 16;
// 32 was previously used for Multi Battles
if (format.bestOfDefault) displayCode |= 64;
if (format.gameType === 'multi') {
displayCode |= 32;
}
this.formatList += ',' + displayCode.toString(16);
}
return this.formatList;
Expand Down
2 changes: 2 additions & 0 deletions server/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {FS, Utils, ProcessManager} from '../lib';
import {
Auth, GlobalAuth, SECTIONLEADER_SYMBOL, PLAYER_SYMBOL, HOST_SYMBOL, RoomPermission, GlobalPermission,
} from './user-groups';
import {BattleReady} from './ladders-challenges';

const MINUTES = 60 * 1000;
const IDLE_TIMER = 60 * MINUTES;
Expand Down Expand Up @@ -385,6 +386,7 @@ export class User extends Chat.MessageContext {
hidden: boolean,
inviteOnly: boolean,
special?: string,
teammate?: BattleReady,
};

isSysop: boolean;
Expand Down
Loading