mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-04-26 10:48:53 -05:00
Mafia: Add Search Capability (#10682)
Some checks are pending
Node.js CI / build (18.x) (push) Waiting to run
Some checks are pending
Node.js CI / build (18.x) (push) Waiting to run
This commit is contained in:
parent
c7c2f81ab2
commit
7e6cea3605
|
|
@ -146,6 +146,78 @@ function writeFile(path: string, data: AnyObject) {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mafiaSearch(
|
||||||
|
entries: [string, MafiaDataAlignment | MafiaDataRole | MafiaDataTheme | MafiaDataIDEA | MafiaDataTerm][],
|
||||||
|
searchTarget: string, searchType: keyof MafiaData
|
||||||
|
) {
|
||||||
|
if (typeof (entries) === 'undefined' || searchType === `aliases` || searchTarget.length === 0) return entries;
|
||||||
|
|
||||||
|
// Handle negation
|
||||||
|
const negation = searchTarget.startsWith('!');
|
||||||
|
if (negation) searchTarget = searchTarget.substring(1).trim();
|
||||||
|
|
||||||
|
const entriesCopy = entries.slice();
|
||||||
|
|
||||||
|
// Check if the search term is an alias of something
|
||||||
|
const alias = toID((toID(searchTarget) in MafiaData[`aliases`]) ?
|
||||||
|
MafiaData[`aliases`][toID(searchTarget)] : searchTarget);
|
||||||
|
|
||||||
|
if (searchType === `themes` && searchTarget.includes(`players`) && searchTarget.includes(`pl`)) {
|
||||||
|
// Search themes by playercount
|
||||||
|
const inequalities = ['<=', '>=', '=', '<', '>'];
|
||||||
|
const inequality = inequalities.find(x => searchTarget.includes(x));
|
||||||
|
if (!inequality) return entries;
|
||||||
|
|
||||||
|
const players = Number(searchTarget.split(inequality)[1].trim());
|
||||||
|
if (!!players && !isNaN(players)) {
|
||||||
|
if (inequality === '=') {
|
||||||
|
// Filter based on themes with the exact player count
|
||||||
|
entries = entries.filter(([key]) => players in MafiaData[`themes`][key]);
|
||||||
|
} else if (inequality === '<' || inequality === '<=') {
|
||||||
|
// Filter based on themes less than / at most a certain amount of players
|
||||||
|
// Creates an array of the potential playercounts, and then looks if any in the theme matches that
|
||||||
|
entries = entries.filter(([key]) => ([...Array(players + (inequality === '<=' ? 1 : 0)).keys()])
|
||||||
|
.some(playerCount => playerCount in (MafiaData[`themes`][key])));
|
||||||
|
} else if (inequality === '>' || inequality === '>=') {
|
||||||
|
// Filter based on themes greater than / at least a certain amount of players
|
||||||
|
// Creates an array of the potential playercounts, and then looks if any in the theme matches that
|
||||||
|
entries = entries.filter(([key]) => ([...Array(50 - Number(players)).keys()]
|
||||||
|
.map(num => num + players + (inequality === '>=' ? 0 : 1)))
|
||||||
|
.some(playerCount => playerCount in (MafiaData[`themes`][key])));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
} else if (searchType === `themes` && alias in MafiaData[`roles`]) {
|
||||||
|
// Search themes that contain a role
|
||||||
|
// Creates a list of all potential playercounts, then verifies if any of them contains the role that we seek
|
||||||
|
entries = entries.filter(([key, data]) => ([...Array(50).keys()])
|
||||||
|
.some(playerCount => playerCount in (MafiaData[`themes`][key]) &&
|
||||||
|
(MafiaData[`themes`][key])[playerCount].toString().toLowerCase().includes(alias)));
|
||||||
|
} else if (searchType === `IDEAs` && alias in MafiaData[`roles`]) {
|
||||||
|
// Search IDEAs that contain a role
|
||||||
|
entries = entries.filter(([key, data]) => MafiaData[`IDEAs`][key].roles.map(role =>
|
||||||
|
toID((toID(role) in MafiaData[`aliases`]) ? MafiaData[`aliases`][toID(role)] : role)).includes(alias));
|
||||||
|
} else if (searchType === `roles` && alias in MafiaData[`themes`]) {
|
||||||
|
// Search roles that appear in a theme
|
||||||
|
// Filters entries based on whether the list of roles in the given theme contains it (or an alias of it)
|
||||||
|
entries = entries.filter(([key, data]) => Object.keys(MafiaData[`themes`][alias])
|
||||||
|
.filter((newKey: any) => toID((MafiaData[`themes`][alias])[newKey].toString()).includes(key)).length > 0);
|
||||||
|
} else if (searchType === `roles` && alias in MafiaData[`IDEAs`]) {
|
||||||
|
// Search roles that appear in an IDEA
|
||||||
|
// Filters entries based on whether the list of roles in the given IDEA contains it (or an alias of it)
|
||||||
|
entries = entries.filter(([key, data]) => MafiaData[`IDEAs`][alias].roles.map(role =>
|
||||||
|
toID((toID(role) in MafiaData[`aliases`]) ? MafiaData[`aliases`][toID(role)] : role)).includes(toID(key)));
|
||||||
|
} else {
|
||||||
|
// Any other search type matches just on whether it is included in the text
|
||||||
|
// Filters entries based on whether it contains the given string anywhere
|
||||||
|
entries = entries.filter(([key]) => Object.entries(MafiaData[searchType][key])
|
||||||
|
.some(([newKey, value]) => value.toString().toLowerCase().includes(searchTarget)));
|
||||||
|
}
|
||||||
|
// Inverses the found results for negation
|
||||||
|
return negation ? entriesCopy.filter(element => !entries.includes(element)) : entries;
|
||||||
|
}
|
||||||
|
|
||||||
// data assumptions -
|
// data assumptions -
|
||||||
// the alignments "town" and "solo" always exist (defaults)
|
// the alignments "town" and "solo" always exist (defaults)
|
||||||
// <role>.alignment is always a valid key in data.alignments
|
// <role>.alignment is always a valid key in data.alignments
|
||||||
|
|
@ -156,12 +228,16 @@ function writeFile(path: string, data: AnyObject) {
|
||||||
MafiaData = readFile(DATA_FILE) || { alignments: {}, roles: {}, themes: {}, IDEAs: {}, terms: {}, aliases: {} };
|
MafiaData = readFile(DATA_FILE) || { alignments: {}, roles: {}, themes: {}, IDEAs: {}, terms: {}, aliases: {} };
|
||||||
if (!MafiaData.alignments.town) {
|
if (!MafiaData.alignments.town) {
|
||||||
MafiaData.alignments.town = {
|
MafiaData.alignments.town = {
|
||||||
name: 'town', plural: 'town', memo: [`This alignment is required for the script to function properly.`],
|
name: 'Town',
|
||||||
|
plural: 'Town',
|
||||||
|
memo: [`This alignment is required for the script to function properly.`],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!MafiaData.alignments.solo) {
|
if (!MafiaData.alignments.solo) {
|
||||||
MafiaData.alignments.solo = {
|
MafiaData.alignments.solo = {
|
||||||
name: 'solo', plural: 'solo', memo: [`This alignment is required for the script to function properly.`],
|
name: 'Solo',
|
||||||
|
plural: 'Solo',
|
||||||
|
memo: [`This alignment is required for the script to function properly.`],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -328,6 +404,7 @@ class Mafia extends Rooms.RoomGame<MafiaPlayer> {
|
||||||
dlAt: number;
|
dlAt: number;
|
||||||
|
|
||||||
IDEA: MafiaIDEAModule;
|
IDEA: MafiaIDEAModule;
|
||||||
|
|
||||||
constructor(room: ChatRoom, host: User) {
|
constructor(room: ChatRoom, host: User) {
|
||||||
super(room);
|
super(room);
|
||||||
|
|
||||||
|
|
@ -340,6 +417,7 @@ class Mafia extends Rooms.RoomGame<MafiaPlayer> {
|
||||||
|
|
||||||
this.hostid = host.id;
|
this.hostid = host.id;
|
||||||
this.host = Utils.escapeHTML(host.name);
|
this.host = Utils.escapeHTML(host.name);
|
||||||
|
|
||||||
this.cohostids = [];
|
this.cohostids = [];
|
||||||
this.cohosts = [];
|
this.cohosts = [];
|
||||||
|
|
||||||
|
|
@ -2207,7 +2285,6 @@ export const commands: Chat.ChatCommands = {
|
||||||
}
|
}
|
||||||
return this.parse('/help mafia');
|
return this.parse('/help mafia');
|
||||||
},
|
},
|
||||||
|
|
||||||
forcehost: 'host',
|
forcehost: 'host',
|
||||||
nexthost: 'host',
|
nexthost: 'host',
|
||||||
host(target, room, user, connection, cmd) {
|
host(target, room, user, connection, cmd) {
|
||||||
|
|
@ -4137,23 +4214,156 @@ export const commands: Chat.ChatCommands = {
|
||||||
this.sendReply(`The entry ${entry} was deleted from the ${source} database.`);
|
this.sendReply(`The entry ${entry} was deleted from the ${source} database.`);
|
||||||
},
|
},
|
||||||
deletedatahelp: [`/mafia deletedata source,entry - Removes an entry from the database. Requires % @ # ~`],
|
deletedatahelp: [`/mafia deletedata source,entry - Removes an entry from the database. Requires % @ # ~`],
|
||||||
listdata(target, room, user) {
|
|
||||||
if (!(target in MafiaData)) {
|
randtheme: 'listdata',
|
||||||
throw new Chat.ErrorMessage(`Invalid source. Valid sources are ${Object.keys(MafiaData).join(', ')}`);
|
randrole: 'listdata',
|
||||||
|
randalignment: 'listdata',
|
||||||
|
randidea: 'listdata',
|
||||||
|
randterm: 'listdata',
|
||||||
|
randroles: 'listdata',
|
||||||
|
randalignments: 'listdata',
|
||||||
|
randideas: 'listdata',
|
||||||
|
randterms: 'listdata',
|
||||||
|
randdata: 'listdata',
|
||||||
|
randomtheme: 'listdata',
|
||||||
|
randomrole: 'listdata',
|
||||||
|
randomalignment: 'listdata',
|
||||||
|
randomidea: 'listdata',
|
||||||
|
randomterm: 'listdata',
|
||||||
|
randomdata: 'listdata',
|
||||||
|
randomthemes: 'listdata',
|
||||||
|
randomroles: 'listdata',
|
||||||
|
randomalignments: 'listdata',
|
||||||
|
randomideas: 'listdata',
|
||||||
|
randomterms: 'listdata',
|
||||||
|
randthemes: 'listdata',
|
||||||
|
listthemes: 'listdata',
|
||||||
|
listroles: 'listdata',
|
||||||
|
listalignments: 'listdata',
|
||||||
|
listideas: 'listdata',
|
||||||
|
listterms: 'listdata',
|
||||||
|
themes: 'listdata',
|
||||||
|
roles: 'listdata',
|
||||||
|
alignments: 'listdata',
|
||||||
|
ideas: 'listdata',
|
||||||
|
terms: 'listdata',
|
||||||
|
ds: 'listdata',
|
||||||
|
search: 'listdata',
|
||||||
|
random: 'listdata',
|
||||||
|
list: 'listdata',
|
||||||
|
listdata(target, room, user, connection, cmd, message) {
|
||||||
|
if (!this.runBroadcast()) return false;
|
||||||
|
|
||||||
|
// Determine non-search targets first, afterward searching is done with the remainder
|
||||||
|
const targets = target.split(',').map(x => x.trim().toLowerCase());
|
||||||
|
|
||||||
|
// Determine search type
|
||||||
|
let searchType: keyof MafiaData = 'aliases';
|
||||||
|
let foundSearchType = false;
|
||||||
|
const searchTypes: (keyof MafiaData)[] = ['themes', 'roles', 'alignments', 'IDEAs', 'terms', 'aliases'];
|
||||||
|
for (const type of searchTypes) {
|
||||||
|
const typeID = toID(type.substring(0, type.length - 1));
|
||||||
|
if (cmd.includes(typeID) || targets.includes(typeID)) {
|
||||||
|
searchType = type;
|
||||||
|
foundSearchType = true;
|
||||||
|
if (targets.includes(type)) targets.splice(targets.indexOf(type), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const dataSource = MafiaData[target as keyof MafiaData];
|
if (cmd === 'random' || cmd === 'randomdata' || cmd === 'randdata') {
|
||||||
if (dataSource === MafiaData.aliases) {
|
searchType =
|
||||||
const aliases = Object.entries(MafiaData.aliases)
|
([`themes`, `roles`, `alignments`, `IDEAs`, `terms`] as (keyof MafiaData)[])[Math.floor(Math.random() * 5)];
|
||||||
.map(([from, to]) => `${from}: ${to}`)
|
foundSearchType = true;
|
||||||
.join('<br/>');
|
}
|
||||||
return this.sendReplyBox(`Mafia aliases:<br/>${aliases}`);
|
|
||||||
} else {
|
if (!foundSearchType) {
|
||||||
const entries = Object.entries(dataSource)
|
return this.errorReply(`Invalid source. Valid sources are ${Object.keys(MafiaData).filter(key => key !== `aliases`).join(', ')}.`);
|
||||||
.map(([key, data]) => `<button class="button" name="send" value="/mafia dt ${key}">${data.name}</button>`)
|
}
|
||||||
.join('');
|
|
||||||
return this.sendReplyBox(`Mafia ${target}:<br/>${entries}`);
|
const dataSource = MafiaData[searchType];
|
||||||
|
|
||||||
|
// determine whether the command should return a random subset of results
|
||||||
|
const random = (cmd.includes('rand') || targets.includes(`random`));
|
||||||
|
if (targets.includes(`random`)) targets.splice(targets.indexOf(`random`), 1);
|
||||||
|
|
||||||
|
// TODO: hide certain roles from appearing (unless the command includes the 'hidden' parameter)
|
||||||
|
|
||||||
|
// Number of results
|
||||||
|
let number = random ? 1 : 0;
|
||||||
|
for (let i = 0; i < targets.length; i++) {
|
||||||
|
if ((!!targets[i] &&
|
||||||
|
!isNaN(Number(targets[i].toString())))) {
|
||||||
|
number = Number(targets[i]);
|
||||||
|
targets.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to rows
|
||||||
|
const themeRow = function (theme: MafiaDataTheme, players = 0) {
|
||||||
|
return `<tr><td style="text-align:left;width:30%" ><button class="button" name = "send" value = "/mafia theme ${theme.name}" > ${theme.name} </button> </td><td style="text-align:left;width:70%">${players > 0 ? theme[players] : theme.desc} </td></tr >`;
|
||||||
|
};
|
||||||
|
const ideaRow = function (idea: MafiaDataIDEA) {
|
||||||
|
return `<tr><td style="text-align:left;width:100%" ><button class="button" name = "send" value = "/mafia dt ${idea.name}" > ${idea.name} </button> </td></tr >`;
|
||||||
|
};
|
||||||
|
const row = function (role: MafiaDataRole | MafiaDataTerm | MafiaDataAlignment) {
|
||||||
|
return `<tr><td style="text-align:left;width:30%" ><button class="button" name = "send" value = "/mafia role ${role.name}" > ${role.name} </button> </td><td style="text-align:left;width:70%">${role.memo.join(' ')} </td></tr >`;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (searchType === `aliases`) {
|
||||||
|
// Handle aliases separately for differing functionality
|
||||||
|
room = this.requireRoom();
|
||||||
|
this.checkCan('mute', null, room);
|
||||||
|
const aliases = Object.entries(MafiaData.aliases)
|
||||||
|
.map(([from, to]) => `${from}: ${to}`)
|
||||||
|
.join('<br/>');
|
||||||
|
return this.sendReplyBox(`Mafia aliases:<br/>${aliases}`);
|
||||||
|
} else {
|
||||||
|
// Create a table for a pleasant viewing experience
|
||||||
|
let table = `<div style="max-height:300px;overflow:auto;"><table border="1" style="border: 1px solid black;width: 100%">`;
|
||||||
|
let entries: [string, MafiaDataAlignment | MafiaDataRole | MafiaDataTheme | MafiaDataIDEA | MafiaDataTerm][] =
|
||||||
|
Object.entries(dataSource).sort();
|
||||||
|
|
||||||
|
for (const targetString of targets) {
|
||||||
|
entries = targetString.split('|').map(x => x.trim())
|
||||||
|
.map(searchTerm => mafiaSearch(entries.slice(), searchTerm, searchType))
|
||||||
|
.reduce((aggregate, result) => [...new Set([...aggregate, ...result])]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof (entries) === 'undefined') return;
|
||||||
|
|
||||||
|
if (random) entries = Utils.shuffle(entries);
|
||||||
|
if (number > 0) entries = entries.slice(0, number);
|
||||||
|
|
||||||
|
if (entries.length === 0) {
|
||||||
|
return this.errorReply(`No ${searchType} found.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.length === 1) {
|
||||||
|
this.target = entries[0][0];
|
||||||
|
return this.run((Chat.commands.mafia as Chat.ChatCommands).data as Chat.AnnotatedChatHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
table += entries
|
||||||
|
.map(([key, data]) => searchType === `themes` ?
|
||||||
|
themeRow(MafiaData[searchType][key]) : searchType === `IDEAs` ?
|
||||||
|
ideaRow(MafiaData[searchType][key]) : row(MafiaData[searchType][key]))
|
||||||
|
.join('');
|
||||||
|
table += `</table></div>`;
|
||||||
|
return this.sendReplyBox(table);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
listdatahelp: [
|
||||||
|
`/mafia roles [parameter, paramater, ...] - Views all Mafia roles. Parameters: theme that must include role, text included in role data.`,
|
||||||
|
`/mafia themes [parameter, paramater, ...] - Views all Mafia themes. Parameters: roles in theme, players(< | <= | = | => | >)[x] for playercounts, text included in theme data.`,
|
||||||
|
`/mafia alignments [parameter, paramater, ...] - Views all Mafia alignments. Parameters: text included in alignment data.`,
|
||||||
|
`/mafia ideas [parameter, paramater, ...] - Views all Mafia IDEAs. Parameters: roles in IDEA, text included in IDEA data.`,
|
||||||
|
`/mafia terms [parameter, paramater, ...] - Views all Mafia terms. Parameters: text included in term data.`,
|
||||||
|
`/mafia randomrole [parameter, paramater, ...] - View a random Mafia role. Parameters: number of roles to be randomly generated, theme that must include role, text included in role data.`,
|
||||||
|
`/mafia randomtheme [parameter, paramater, ...] - View a random Mafia theme. Parameters: number of themes to be randomly generated, roles in theme, players(< | <= | = | => | >)[x] for playercounts, text included in theme data.`,
|
||||||
|
`/mafia randomalignment [parameter, paramater, ...] - View a random Mafia alignment. Parameters: number of alignments to be randomly generated, text included in alignment data.`,
|
||||||
|
`/mafia randomidea [parameter, paramater, ...] - View a random Mafia IDEA. Parameters: number of IDEAs to be randomly generated, roles in IDEA, text included in IDEA data.`,
|
||||||
|
`/mafia randomterm [parameter, paramater, ...] - View a random Mafia term. Parameters: number of terms to be randomly generated, text included in term data.`,
|
||||||
|
],
|
||||||
|
|
||||||
disable(target, room, user) {
|
disable(target, room, user) {
|
||||||
room = this.requireRoom();
|
room = this.requireRoom();
|
||||||
|
|
@ -4286,6 +4496,22 @@ export const commands: Chat.ChatCommands = {
|
||||||
`/mafia (un)gameban [user], [duration] - Ban a user from playing games for [duration] days. Requires % @ # ~`,
|
`/mafia (un)gameban [user], [duration] - Ban a user from playing games for [duration] days. Requires % @ # ~`,
|
||||||
].join('<br/>');
|
].join('<br/>');
|
||||||
buf += `</details>`;
|
buf += `</details>`;
|
||||||
|
buf += `</details><details><summary class="button">Mafia Dexsearch Commands</summary>`;
|
||||||
|
buf += [
|
||||||
|
`<br/><strong>Commands to search Mafia data</strong>:<br/>`,
|
||||||
|
`/mafia dt [data] - Views Mafia data.`,
|
||||||
|
`/mafia roles [parameter, paramater, ...] - Views all Mafia roles. Parameters: theme that must include role, text included in role data.`,
|
||||||
|
`/mafia themes [parameter, paramater, ...] - Views all Mafia themes. Parameters: roles in theme, players(< | <= | = | => | >)[x] for playercounts, text included in theme data.`,
|
||||||
|
`/mafia alignments [parameter, paramater, ...] - Views all Mafia alignments. Parameters: text included in alignment data.`,
|
||||||
|
`/mafia ideas [parameter, paramater, ...] - Views all Mafia IDEAs. Parameters: roles in IDEA, text included in IDEA data.`,
|
||||||
|
`/mafia terms [parameter, paramater, ...] - Views all Mafia terms. Parameters: text included in term data.`,
|
||||||
|
`/mafia randomrole [parameter, paramater, ...] - View a random Mafia role. Parameters: number of roles to be randomly generated, theme that must include role, text included in role data.`,
|
||||||
|
`/mafia randomtheme [parameter, paramater, ...] - View a random Mafia theme. Parameters: number of themes to be randomly generated, roles in theme, players(< | <= | = | => | >)[x] for playercounts, text included in theme data.`,
|
||||||
|
`/mafia randomalignment [parameter, paramater, ...] - View a random Mafia alignment. Parameters: number of alignments to be randomly generated, text included in alignment data.`,
|
||||||
|
`/mafia randomidea [parameter, paramater, ...] - View a random Mafia IDEA. Parameters: number of IDEAs to be randomly generated, roles in IDEA, text included in IDEA data.`,
|
||||||
|
`/mafia randomterm [parameter, paramater, ...] - View a random Mafia term. Parameters: number of terms to be randomly generated, text included in term data.`,
|
||||||
|
].join('<br/>');
|
||||||
|
buf += `</details>`;
|
||||||
|
|
||||||
return this.sendReplyBox(buf);
|
return this.sendReplyBox(buf);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user