Commit 86b0f261 authored by nanahira's avatar nanahira

tag spawn 3 bots

parent 9c10c96b
...@@ -74,11 +74,10 @@ export class DefaultHostInfoProvider { ...@@ -74,11 +74,10 @@ export class DefaultHostInfoProvider {
} }
const rulePrefix = name.match(/(.+)#/); const rulePrefix = name.match(/(.+)#/);
if (!rulePrefix) { const rule = rulePrefix ? rulePrefix[1].toUpperCase() : '';
if (!rule) {
return hostinfo; return hostinfo;
} }
const rule = rulePrefix[1].toUpperCase();
if (/(^|,|,)(M|MATCH)(,|,|$)/.test(rule)) { if (/(^|,|,)(M|MATCH)(,|,|$)/.test(rule)) {
hostinfo.mode = setWinMatchCountBits(hostinfo.mode, 2); hostinfo.mode = setWinMatchCountBits(hostinfo.mode, 2);
} }
......
export * from './room'; export * from './room';
export * from './room-manager'; export * from './room-manager';
export * from './room-event/on-room-finalize'; export * from './room-event/on-room-finalize';
export * from './default-hostinfo-provder';
...@@ -3,3 +3,4 @@ export * from './windbot-spawner'; ...@@ -3,3 +3,4 @@ export * from './windbot-spawner';
export * from './join-windbot-ai'; export * from './join-windbot-ai';
export * from './join-windbot-token'; export * from './join-windbot-token';
export * from './windbot-module'; export * from './windbot-module';
export * from './utility';
...@@ -3,6 +3,7 @@ import { Context } from '../app'; ...@@ -3,6 +3,7 @@ import { Context } from '../app';
import { WindBotProvider } from './windbot-provider'; import { WindBotProvider } from './windbot-provider';
import { RoomManager } from '../room'; import { RoomManager } from '../room';
import { fillRandomString } from '../utility/fill-random-string'; import { fillRandomString } from '../utility/fill-random-string';
import { parseWindbotOptions } from './utility';
const getDisplayLength = (text: string) => const getDisplayLength = (text: string) =>
text.replace(/[^\x00-\xff]/g, '00').length; text.replace(/[^\x00-\xff]/g, '00').length;
...@@ -52,16 +53,21 @@ export class JoinWindbotAi { ...@@ -52,16 +53,21 @@ export class JoinWindbotAi {
room.windbot = { room.windbot = {
name: '', name: '',
deck: '', deck: '',
} };
const windbotOptions = parseWindbotOptions(room.name);
await room.join(client); await room.join(client);
const requestOk = await this.windbotProvider.requestWindbotJoin( const requestCount = room.isTag ? 3 : 1;
room, for (let i = 0; i < requestCount; i += 1) {
requestedBotName, const requestOk = await this.windbotProvider.requestWindbotJoin(
); room,
if (!requestOk) { requestedBotName,
await room.finalize(); windbotOptions,
return; );
if (!requestOk) {
await room.finalize();
return;
}
} }
this.logger.debug( this.logger.debug(
...@@ -69,6 +75,7 @@ export class JoinWindbotAi { ...@@ -69,6 +75,7 @@ export class JoinWindbotAi {
player: client.name, player: client.name,
roomName: room.name, roomName: room.name,
botName: room.windbot?.name, botName: room.windbot?.name,
requestCount,
}, },
'Created windbot room', 'Created windbot room',
); );
......
...@@ -20,18 +20,18 @@ export class JoinWindbotToken { ...@@ -20,18 +20,18 @@ export class JoinWindbotToken {
} }
const token = msg.pass.slice('AIJOIN#'.length); const token = msg.pass.slice('AIJOIN#'.length);
const roomName = this.windbotProvider.consumeJoinToken(token); const tokenData = this.windbotProvider.consumeJoinToken(token);
if (!roomName) { if (!tokenData) {
return client.die('#{invalid_password_not_found}', ChatColor.RED); return client.die('#{invalid_password_not_found}', ChatColor.RED);
} }
const room = this.roomManager.findByName(roomName); const room = this.roomManager.findByName(tokenData.roomName);
if (!room) { if (!room) {
return client.die('#{invalid_password_not_found}', ChatColor.RED); return client.die('#{invalid_password_not_found}', ChatColor.RED);
} }
client.isInternal = true; client.isInternal = true;
client.windbot = room.windbot; client.windbot = tokenData.windbot;
return room.join(client); return room.join(client);
}); });
} }
......
export * from './types';
export * from './parse-rule-prefix';
export * from './parse-windbot-options';
export const parseRulePrefix = (name: string) => {
const rulePrefix = name.match(/(.+)#/);
if (!rulePrefix) {
return '';
}
return rulePrefix[1].toUpperCase();
};
import type { RequestWindbotJoinOptions } from './types';
import { parseRulePrefix } from './parse-rule-prefix';
export const parseWindbotOptions = (name: string): RequestWindbotJoinOptions => {
const rule = parseRulePrefix(name);
const options: RequestWindbotJoinOptions = {};
if (!rule) {
return options;
}
if (/(^|,|,)(SS|SCISSORS)(,|,|$)/.test(rule)) {
options.hand = 1;
} else if (/(^|,|,)(ROCK)(,|,|$)/.test(rule)) {
options.hand = 2;
} else if (/(^|,|,)(PAPER)(,|,|$)/.test(rule)) {
options.hand = 3;
}
return options;
};
export interface WindbotData {
name: string;
deck: string;
dialog?: string;
hidden?: boolean;
deckcode?: string;
}
export interface RequestWindbotJoinOptions {
hand?: 1 | 2 | 3;
}
export interface WindbotJoinTokenData {
roomName: string;
windbot: WindbotData;
}
...@@ -3,14 +3,11 @@ import * as fs from 'node:fs/promises'; ...@@ -3,14 +3,11 @@ import * as fs from 'node:fs/promises';
import { ChatColor } from 'ygopro-msg-encode'; import { ChatColor } from 'ygopro-msg-encode';
import { Context } from '../app'; import { Context } from '../app';
import { OnRoomFinalize, Room } from '../room'; import { OnRoomFinalize, Room } from '../room';
import type {
export interface WindbotData { RequestWindbotJoinOptions,
name: string; WindbotData,
deck: string; WindbotJoinTokenData,
dialog?: string; } from './utility';
hidden?: boolean;
deckcode?: string;
}
declare module '../client' { declare module '../client' {
interface Client { interface Client {
...@@ -36,8 +33,8 @@ export class WindBotProvider { ...@@ -36,8 +33,8 @@ export class WindBotProvider {
public botlistPath = this.ctx.config.getString('WINDBOT_BOTLIST'); public botlistPath = this.ctx.config.getString('WINDBOT_BOTLIST');
private bots: WindbotData[] = []; private bots: WindbotData[] = [];
private tokenRoomMap = new Map<string, string>(); private tokenDataMap = new Map<string, WindbotJoinTokenData>();
private roomTokenMap = new Map<string, string>(); private roomTokenMap = new Map<string, Set<string>>();
constructor(private ctx: Context) { constructor(private ctx: Context) {
if (!this.enabled) { if (!this.enabled) {
...@@ -73,60 +70,70 @@ export class WindBotProvider { ...@@ -73,60 +70,70 @@ export class WindBotProvider {
return this.bots.find((bot) => bot.name === name || bot.deck === name); return this.bots.find((bot) => bot.name === name || bot.deck === name);
} }
issueJoinToken(roomName: string) { issueJoinToken(roomName: string, windbot: WindbotData) {
const oldToken = this.roomTokenMap.get(roomName);
if (oldToken) {
this.tokenRoomMap.delete(oldToken);
}
let token = ''; let token = '';
do { do {
token = cryptoRandomString({ token = cryptoRandomString({
length: 12, length: 12,
type: 'alphanumeric', type: 'alphanumeric',
}); });
} while (this.tokenRoomMap.has(token)); } while (this.tokenDataMap.has(token));
this.logger.debug( this.logger.debug(
{ roomName, token }, { roomName, token },
'Issuing windbot join token for room', 'Issuing windbot join token for room',
); );
this.tokenRoomMap.set(token, roomName); this.tokenDataMap.set(token, {
this.roomTokenMap.set(roomName, token); roomName,
windbot: { ...windbot },
});
let roomTokens = this.roomTokenMap.get(roomName);
if (!roomTokens) {
roomTokens = new Set<string>();
this.roomTokenMap.set(roomName, roomTokens);
}
roomTokens.add(token);
return token; return token;
} }
consumeJoinToken(token: string) { consumeJoinToken(token: string) {
const roomName = this.tokenRoomMap.get(token); const data = this.tokenDataMap.get(token);
this.logger.debug({ roomName, token }, 'Consuming windbot join token'); this.logger.debug({ roomName: data?.roomName, token }, 'Consuming windbot join token');
if (!roomName) { if (!data) {
return undefined; return undefined;
} }
this.tokenRoomMap.delete(token); this.tokenDataMap.delete(token);
const mappedToken = this.roomTokenMap.get(roomName); const roomTokens = this.roomTokenMap.get(data.roomName);
if (mappedToken === token) { if (roomTokens) {
this.roomTokenMap.delete(roomName); roomTokens.delete(token);
if (roomTokens.size === 0) {
this.roomTokenMap.delete(data.roomName);
}
} }
return roomName; return data;
} }
deleteRoomToken(roomName: string) { deleteRoomToken(roomName: string) {
const token = this.roomTokenMap.get(roomName); const roomTokens = this.roomTokenMap.get(roomName);
if (!token) { if (!roomTokens) {
return; return;
} }
this.roomTokenMap.delete(roomName); this.roomTokenMap.delete(roomName);
const mappedRoomName = this.tokenRoomMap.get(token); for (const token of roomTokens) {
if (mappedRoomName === roomName) { const mappedData = this.tokenDataMap.get(token);
this.tokenRoomMap.delete(token); if (mappedData?.roomName === roomName) {
this.tokenDataMap.delete(token);
}
} }
} }
async requestWindbotJoin(room: Room, botname?: string) { async requestWindbotJoin(
const roomWindbot = room: Room,
this.isValidBot(room.windbot) && room.windbot ? room.windbot : undefined; botname?: string,
options: RequestWindbotJoinOptions = {},
) {
const bot = const bot =
(botname && this.getBotByNameOrDeck(botname)) || roomWindbot || this.getRandomBot(); (botname && this.getBotByNameOrDeck(botname)) || this.getRandomBot();
if (!bot) { if (!bot) {
await room.sendChat('#{windbot_deck_not_found}', ChatColor.RED); await room.sendChat('#{windbot_deck_not_found}', ChatColor.RED);
return false; return false;
...@@ -138,7 +145,7 @@ export class WindBotProvider { ...@@ -138,7 +145,7 @@ export class WindBotProvider {
}; };
} }
Object.assign(room.windbot, bot); Object.assign(room.windbot, bot);
const token = this.issueJoinToken(room.name); const token = this.issueJoinToken(room.name, bot);
let url: URL; let url: URL;
try { try {
...@@ -164,6 +171,9 @@ export class WindBotProvider { ...@@ -164,6 +171,9 @@ export class WindBotProvider {
if (bot.deckcode) { if (bot.deckcode) {
url.searchParams.set('deckcode', bot.deckcode); url.searchParams.set('deckcode', bot.deckcode);
} }
if (options.hand) {
url.searchParams.set('hand', options.hand.toString());
}
this.logger.debug( this.logger.debug(
{ url: url.toString(), roomName: room.name }, { url: url.toString(), roomName: room.name },
...@@ -187,18 +197,6 @@ export class WindBotProvider { ...@@ -187,18 +197,6 @@ export class WindBotProvider {
} }
} }
private isValidBot(
bot: WindbotData | undefined,
): bot is WindbotData {
return !!(
bot &&
typeof bot.name === 'string' &&
bot.name.length &&
typeof bot.deck === 'string' &&
bot.deck.length
);
}
private async loadBotList() { private async loadBotList() {
try { try {
const text = await fs.readFile(this.botlistPath, 'utf-8'); const text = await fs.readFile(this.botlistPath, 'utf-8');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment