Skip to content

Commit

Permalink
Scavengers: Support HTML in hunts (#10414)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas-Meijer authored Aug 21, 2024
1 parent e6d3806 commit 5b5653c
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 35 deletions.
6 changes: 3 additions & 3 deletions server/chat-plugins/scavenger-games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ const TWISTS: {[k: string]: Twist} = {
`${this.completed.length > sliceIndex ? `Consolation Prize: ${this.completed.slice(sliceIndex).map(e => `<em>${Utils.escapeHTML(e.name)}</em> <span style="color: lightgreen;">[${e.time}]</span>`).join(', ')}<br />` : ''}<br />` +
`<details style="cursor: pointer;"><summary>Solution: </summary><br />` +
`${this.questions.map((q, i) => (
`${i + 1}) ${Chat.formatText(q.hint)} <span style="color: lightgreen">[<em>${Utils.escapeHTML(q.answer.join(' / '))}</em>]</span><br/>` +
`${i + 1}) ${this.formatOutput(q.hint)} <span style="color: lightgreen">[<em>${Utils.escapeHTML(q.answer.join(' / '))}</em>]</span><br/>` +
`<details style="cursor: pointer;"><summary>Mines: </summary>${mines[i].map(({mine, users}) => Utils.escapeHTML(`${mine}: ${users.join(' / ') || '-'}`)).join('<br />')}</details>`
)).join("<br />")}` +
`</details>`
Expand Down Expand Up @@ -781,11 +781,11 @@ const MODES: {[k: string]: GameMode | string} = {
if (staffHost) staffHost.sendTo(this.room, `${targetUser.name} has received their first hint early.`);
targetUser.sendTo(
this.room,
`|raw|<strong>The first hint to the next hunt is:</strong> ${Chat.formatText(this.questions[0].hint)}`
`|raw|<strong>The first hint to the next hunt is:</strong> <div style="overflow:auto; max-height: 50vh"> ${this.formatOutput(this.questions[0].hint)}</div>`
);
targetUser.sendTo(
this.room,
`|notify|Early Hint|The first hint to the next hunt is: ${Chat.formatText(this.questions[0].hint)}`
`|notify|Early Hint|The first hint to the next hunt is: <div style="overflow:auto; max-height: 50vh"> ${this.formatOutput(this.questions[0].hint)}</div>`
);
}
}, (maxTime - time) * 1000 + 5000);
Expand Down
138 changes: 106 additions & 32 deletions server/chat-plugins/scavengers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type GameTypes = 'official' | 'regular' | 'mini' | 'unrated' | 'practice' | 'rec
export interface QueuedHunt {
hosts: {id: string, name: string, noUpdate?: boolean}[];
questions: (string | string[])[];
isHTML: boolean;
staffHostId: string;
staffHostName: string;
gameType: GameTypes;
Expand Down Expand Up @@ -226,19 +227,19 @@ function formatQueue(queue: QueuedHunt[] | undefined, viewer: User, room: Room,
return Utils.html`<span style="color: green"><em>[${q.join(' / ')}]</em></span><br />`;
} else {
q = q as string;
return Utils.escapeHTML(q);
return item.isHTML ? q : Utils.escapeHTML(q);
}
}
).join(" ");
} else {
questions = `[${item.questions.length / 2} hidden questions]`;
}
return `<tr><td>${removeButton}${startButton}&nbsp;${unratedText}${hosts}${queuedBy}</td><td>${questions}</td></tr>`;
return `<tr><td>${removeButton}${startButton}&nbsp;${unratedText}${hosts}${queuedBy}</td><td><div style="overflow:auto; max-width: 100vw; max-height: 30vh">${questions}</div></td></tr>`;
}).join("");
} else {
buffer = `<tr><td colspan=3>The scavenger queue is currently empty.</td></tr>`;
}
let template = `<div class="ladder"><table style="width: 100%"><tr><th>By</th><th>Questions</th></tr>${showStaff ? buffer : buffer.replace(/<button.*?>.+?<\/button>/gi, '')}</table></div>`;
let template = `<div class="ladder"><table style="width: 100%;table-layout:fixed;"><tr><th>By</th><th>Questions</th></tr>${showStaff ? buffer : buffer.replace(/<button.*?>.+?<\/button>/gi, '')}</table></div>`;
if (showStaff) {
template += `<table style="width: 100%"><tr><td style="text-align: left;">Auto Timer Duration: ${timerDuration} minutes</td><td>Auto Dequeue: <button class="button${!queueDisabled ?
'" name="send" value="/scav disablequeue"' :
Expand Down Expand Up @@ -327,6 +328,7 @@ export class ScavengerHunt extends Rooms.RoomGame<ScavengerHuntPlayer> {
completed: AnyObject[];
leftHunt: {[userid: string]: 1 | undefined};
hosts: FakeUser[];
isHTML: boolean;
modsList: string[];
mods: {[k: string]: ModEvent[]};
staffHostId: string;
Expand All @@ -338,14 +340,16 @@ export class ScavengerHunt extends Rooms.RoomGame<ScavengerHuntPlayer> {
readonly checkChat = true;

[k: string]: any; // for purposes of adding new temporary properties for the purpose of twists.
constructor(
constructor({room, staffHost, hosts, gameType, questions, isHTML, mod}:
{
room: Room,
staffHost: User | FakeUser,
hosts: FakeUser[],
gameType: GameTypes,
questions: (string | string[])[],
mod?: string | string[]
) {
isHTML?: boolean,
mod?: string | string[],
}) {
super(room);

this.allowRenames = true;
Expand All @@ -362,6 +366,8 @@ export class ScavengerHunt extends Rooms.RoomGame<ScavengerHuntPlayer> {

this.hosts = hosts;

this.isHTML = !!isHTML;

this.modsList = [];
this.mods = {};

Expand Down Expand Up @@ -430,6 +436,10 @@ export class ScavengerHunt extends Rooms.RoomGame<ScavengerHuntPlayer> {
this.runEvent('Connect', user, connection);
}

formatOutput(text: string): string {
return this.isHTML ? text : Chat.formatText(text);
}

getCreationMessage(newHunt?: boolean): string {
const message = this.runEvent('CreateCallback');
if (message) return message;
Expand All @@ -443,8 +453,8 @@ export class ScavengerHunt extends Rooms.RoomGame<ScavengerHuntPlayer> {
const huntType = `${article} ${newHunt ? 'new ' : ''}${this.gameType}`;

return `|raw|<div class="broadcast-blue"><strong>${huntType} scavenger hunt by <em>${hosts}</em> has been started${staffHost}.</strong>` +
`<div style="border:1px solid #CCC;padding:4px 6px;margin:4px 1px">` +
`<strong><em>Hint #1:</em> ${Chat.formatText(this.questions[0].hint)}</strong>` +
`<div style="border:1px solid #CCC;padding:4px 6px;margin:4px 1px; overflow:scroll; max-height: 50vh">` +
`<strong><em>Hint #1:</em> ${this.formatOutput(this.questions[0].hint)}</strong>` +
`</div>` +
`(To answer, use <kbd>/scavenge <em>ANSWER</em></kbd>)</div>`;
}
Expand Down Expand Up @@ -614,16 +624,16 @@ export class ScavengerHunt extends Rooms.RoomGame<ScavengerHuntPlayer> {
};
const finalHint = current.number === this.questions.length ? "Final " : "";

return `|raw|<div class="ladder"><table><tr>` +
`<td><strong style="white-space: nowrap">${finalHint}Hint #${current.number}:</strong></td>` +
`<td>${
Chat.formatText(current.question.hint) +
return `|raw|<div class="ladder"><table style="width:100%;table-layout:fixed;"><tr>` +
`<td style="width: 20%;"><strong style="white-space: nowrap">${finalHint}Hint #${current.number}:</strong></td>` +
`<td><div style="overflow:auto; max-width: 100vw; max-height: 50vh">${
this.formatOutput(current.question.hint) +
(showHints && current.question.spoilers.length ?
`<details><summary>Extra Hints:</summary>${
current.question.spoilers.map(p => `- ${p}`).join('<br />')
}</details>` :
``)
}</td>` +
}</div></td>` +
`</tr></table></div>`;
}

Expand Down Expand Up @@ -654,19 +664,19 @@ export class ScavengerHunt extends Rooms.RoomGame<ScavengerHuntPlayer> {

user.sendTo(
this.room,
`|raw|<div class="ladder"><table style="width: 100%">` +
`<tr><th style="width: 10%;">#</th><th>Hint</th><th>Answer</th></tr>` +
`|raw|<div class="ladder"><table style="width: 100%;table-layout:fixed;">` +
`<tr><th style="width: 10%;">#</th><th style="width: 70%;">Hint</th><th style="width: 20%;">Answer</th></tr>` +
this.questions.slice(0, qLimit).map((q, i) => (
`<tr><td>${
i + 1
}</td><td>${
Chat.formatText(q.hint) +
}</td><td><div style="overflow:auto; max-width: 100vw; max-height: 30vh">${
this.formatOutput(q.hint) +
(q.spoilers.length ?
`<details><summary>Extra Hints:</summary>${
q.spoilers.map(s => `- ${s}`).join('<br />')
}</details>` :
``)
}</td><td>${
}</div></td><td>${
i + 1 >= qLimit ?
`` :
Utils.escapeHTMLForceWrap(q.answer.join(' ; '))
Expand Down Expand Up @@ -710,8 +720,8 @@ export class ScavengerHunt extends Rooms.RoomGame<ScavengerHuntPlayer> {
`The ${this.gameType ? `${this.gameType} ` : ""}scavenger hunt by ${hosts} was ended ${(endedBy ? "by " + Utils.escapeHTML(endedBy.name) : "automatically")}.<br />` +
`${this.completed.slice(0, sliceIndex).map((p, i) => `${Utils.formatOrder(i + 1)} place: <em>${Utils.escapeHTML(p.name)}</em> <span style="color: lightgreen;">[${p.time}]</span>.<br />`).join("")}` +
`${this.completed.length > sliceIndex ? `Consolation Prize: ${this.completed.slice(sliceIndex).map(e => `<em>${Utils.escapeHTML(e.name)}</em> <span style="color: lightgreen;">[${e.time}]</span>`).join(', ')}<br />` : ''}<br />` +
`<details style="cursor: pointer;"><summary>Solution: </summary><br />` +
`${this.questions.map((q, i) => `${i + 1}) ${Chat.formatText(q.hint)} <span style="color: lightgreen">[<em>${Utils.escapeHTML(q.answer.join(' / '))}</em>]</span>`).join("<br />")}` +
`<details style="cursor: pointer; overflow:scroll; max-height: 50vh"><summary>Solution: </summary><br />` +
`${this.questions.map((q, i) => `${i + 1}) ${this.formatOutput(q.hint)} <span style="color: lightgreen">[<em>${Utils.escapeHTML(q.answer.join(' / '))}</em>]</span>`).join("<br />")}` +
`</details>`
);
}
Expand All @@ -731,6 +741,7 @@ export class ScavengerHunt extends Rooms.RoomGame<ScavengerHuntPlayer> {
hosts: next.hosts,
questions: correctlyFormattedQuestions,
staffHostId: 'scavengermanager',
isHTML: next.isHTML,
staffHostName: 'Scavenger Manager',
gameType: 'unrated',
});
Expand Down Expand Up @@ -821,16 +832,20 @@ export class ScavengerHunt extends Rooms.RoomGame<ScavengerHuntPlayer> {
const next = room.settings.scavQueue.shift()!;
const duration = room.settings.scavSettings?.defaultScavTimer || DEFAULT_TIMER_DURATION;
room.game = new ScavengerHunt(
room,
{id: next.staffHostId, name: next.staffHostName},
next.hosts,
next.gameType,
next.questions
{
room: room,
staffHost: {id: next.staffHostId, name: next.staffHostName},
hosts: next.hosts,
gameType: next.gameType,
questions: next.questions,
isHTML: next.isHTML,
}
);
const game = room.getGame(ScavengerHunt);
if (game) {
game.setTimer(duration); // auto timer for queue'd games.
room.add(`|c|~|[ScavengerManager] A scavenger hunt by ${Chat.toListString(next.hosts.map(h => h.name))} has been automatically started. It will automatically end in ${duration} minutes.`).update(); // highlight the users with "hunt by"
this.modlog('SCAV NEW', null, `${game.gameType.toUpperCase()}: creators - ${game.hosts.map(h => h.id)}`);
}

// update the saved queue.
Expand Down Expand Up @@ -1016,7 +1031,7 @@ export class ScavengerHuntPlayer extends Rooms.RoomGamePlayer<ScavengerHunt> {
onNotifyChange(num: number) {
this.game.runEvent('NotifyChange', this, num);
if (num === this.currentQuestion) {
this.sendRoom(`|raw|<strong>The hint has been changed to:</strong> ${Chat.formatText(this.game.questions[num].hint)}`);
this.sendRoom(`|raw|<div style="overflow:scroll; max-height: 50vh"><strong>The hint has been changed to:</strong> ${this.game.formatOutput(this.game.questions[num].hint)}</div>`);
}
}

Expand Down Expand Up @@ -1339,6 +1354,20 @@ const ScavengerCommands: Chat.ChatCommands = {
forcecreate: 'create',
forcecreateunrated: 'create',
createrecycled: 'create',

createhtmltwist: 'create',
createhtmltwistofficial: 'create',
createhtmltwistmini: 'create',
createhtmltwistpractice: 'create',
createhtmltwistunrated: 'create',
createhtmlpractice: 'create',
createhtmlofficial: 'create',
createhtmlunrated: 'create',
createhtmlmini: 'create',
forcecreatehtml: 'create',
forcecreatehtmlunrated: 'create',
createhtmlrecycled: 'create',
createhtml: 'create',
create(target, room, user, connection, cmd) {
room = this.requireRoom();
if (!getScavsRoom(room)) {
Expand All @@ -1359,6 +1388,8 @@ const ScavengerCommands: Chat.ChatCommands = {
gameType = 'recycled';
}

const isHTML = cmd.includes('html');

let mod;
let questions = target;

Expand Down Expand Up @@ -1410,7 +1441,17 @@ const ScavengerCommands: Chat.ChatCommands = {
const res = ScavengerHunt.parseQuestions(params);
if (res.err) return this.errorReply(res.err);

room.game = new ScavengerHunt(room, user, hosts, gameType, res.result, mod);
room.game = new ScavengerHunt(
{
room: room,
staffHost: user,
hosts: hosts,
gameType: gameType,
questions: res.result,
isHTML: isHTML,
mod: mod,
}
);

this.privateModAction(`A new scavenger hunt was created by ${user.name}.`);
this.modlog('SCAV NEW', null, `${gameType.toUpperCase()}: creators - ${hosts.map(h => h.id)}`);
Expand Down Expand Up @@ -1526,6 +1567,7 @@ const ScavengerCommands: Chat.ChatCommands = {
const hunt: QueuedHunt = {
hosts: game.hosts,
questions: [],
isHTML: game.isHTML,
staffHostId: game.staffHostId,
staffHostName: game.StaffHostName,
gameType: game.gameType,
Expand Down Expand Up @@ -1699,6 +1741,10 @@ const ScavengerCommands: Chat.ChatCommands = {
queueunrated: 'queue',
queuerated: 'queue',
queuerecycled: 'queue',
queuehtmlunrated: 'queue',
queuehtmlrated: 'queue',
queuehtmlrecycled: 'queue',
queuehtml: 'queue',
queue(target, room, user) {
room = this.requireRoom();
if (!getScavsRoom(room)) {
Expand All @@ -1714,6 +1760,8 @@ const ScavengerCommands: Chat.ChatCommands = {
return this.parse('/scavhelp staff');
}

const isHTML = this.cmd.includes('html');

this.checkCan('mute', null, room);

if (this.cmd === 'queuerecycled') {
Expand All @@ -1736,6 +1784,7 @@ const ScavengerCommands: Chat.ChatCommands = {
room.settings.scavQueue.push({
hosts: next.hosts,
questions: correctlyFormattedQuestions,
isHTML: isHTML,
staffHostId: 'scavengermanager',
staffHostName: 'Scavenger Manager',
gameType: 'unrated',
Expand All @@ -1755,6 +1804,7 @@ const ScavengerCommands: Chat.ChatCommands = {
room.settings.scavQueue.push({
hosts: hosts,
questions: results.result,
isHTML: isHTML,
staffHostId: user.id,
staffHostName: user.name,
gameType: (this.cmd.includes('unrated') ? 'unrated' : 'regular'),
Expand Down Expand Up @@ -1811,11 +1861,14 @@ const ScavengerCommands: Chat.ChatCommands = {

const next = room.settings.scavQueue.splice(huntId, 1)[0];
room.game = new ScavengerHunt(
room,
{id: next.staffHostId, name: next.staffHostName},
next.hosts,
next.gameType,
next.questions
{
room: room,
staffHost: {id: next.staffHostId, name: next.staffHostName},
hosts: next.hosts,
gameType: next.gameType,
questions: next.questions,
isHTML: next.isHTML,
}
);

if (huntId) this.sendReply(`|uhtmlchange|scav-queue|${formatQueue(room.settings.scavQueue, user, room)}`);
Expand Down Expand Up @@ -2501,6 +2554,7 @@ export const commands: Chat.ChatCommands = {
// old game aliases
scavenge: ScavengerCommands.guess,
startpracticehunt: 'starthunt',
startofficial: 'starthunt',
startofficialhunt: 'starthunt',
startminihunt: 'starthunt',
startunratedhunt: 'starthunt',
Expand All @@ -2515,6 +2569,24 @@ export const commands: Chat.ChatCommands = {
forcestartunrated: 'starthunt',
forcestartpractice: 'starthunt',

starthtmlpracticehunt: 'starthunt',
starthtmlofficial: 'starthunt',
starthtmlofficialhunt: 'starthunt',
starthtmlminihunt: 'starthunt',
starthtmlunratedhunt: 'starthunt',
starthtmlrecycledhunt: 'starthunt',
starthtmltwisthunt: 'starthunt',
starthtmltwistofficial: 'starthunt',
starthtmltwistpractice: 'starthunt',
starthtmltwistmini: 'starthunt',
starthtmltwistunrated: 'starthunt',

forcehtmlstarthunt: 'starthunt',
forcehtmlstartunrated: 'starthunt',
forcehtmlstartpractice: 'starthunt',

starthtmlhunt: 'starthunt',

starthunt: ScavengerCommands.create,
joinhunt: ScavengerCommands.join,
leavehunt: ScavengerCommands.leave,
Expand Down Expand Up @@ -2580,7 +2652,9 @@ export const commands: Chat.ChatCommands = {
"- /teamscavshelp: Explains the team scavs plugin.",
"<br />As a <strong>room driver (%)</strong>, you can also use the following Scavengers commands:",
"- /scav queue (unrated) <em>[host(s)]</em> | <em>[hint]</em> | <em>[answer]</em> | <em>[hint]</em> | <em>[answer]</em> | <em>[hint]</em> | <em>[answer]</em> | ...: Queue a scavenger hunt to be started after the current hunt is finished.",
"- /scav queuehtml (unrated) <em>[host(s)]</em> | <em>[hint]</em> | <em>[answer]</em> | <em>[hint]</em> | <em>[answer]</em> | <em>[hint]</em> | <em>[answer]</em> | ...: Queue a scavenger hunt that uses HTML to be started after the current hunt is finished.",
"- /start(official/practice/mini/unrated)hunt <em>[host]</em> | <em>[hint]</em> | <em>[answer]</em> | <em>[hint]</em> | <em>[answer]</em> | <em>[hint]</em> | </em>[answer]</em> | ...: Create a new (official/practice/mini/unrated) scavenger hunt and start it immediately.",
"- /starthtml(official/practice/mini/unrated)hunt <em>[host]</em> | <em>[hint]</em> | <em>[answer]</em> | <em>[hint]</em> | <em>[answer]</em> | <em>[hint]</em> | </em>[answer]</em> | ...: Create a new (official/practice/mini/unrated) scavenger hunt that uses HTML and start it immediately.",
"- /scav viewqueue (or /scav queue): Look at the list of queued scavenger hunts. Now also includes the option to remove hunts from the queue.",
"- /resethunt: Reset the current scavenger hunt without revealing the hints and answers, nor giving out points.",
"- /resethunttoqueue: Reset the ongoing scavenger hunt without revealing the hints and answers, nor giving out points. Then, add it directly to the queue.",
Expand Down

0 comments on commit 5b5653c

Please sign in to comment.