Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
ShivaD173 committed Jan 6, 2025
2 parents d837499 + e93b150 commit a20a46e
Show file tree
Hide file tree
Showing 15 changed files with 218 additions and 78 deletions.
26 changes: 14 additions & 12 deletions config/formats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
name: "[Gen 9] Little Colosseum LC",
desc: `A Little Cup metagame that aims to buff weaker Pokemon and nerf LC Ubers to create a more diverse metagame.`,
mod: 'littlecolosseum',
searchShow: false,
ruleset: ['Little Cup', 'Standard'],
banlist: [
'Aipom', 'Basculin-White-Striped', 'Diglett-Base', 'Dunsparce', 'Duraludon', 'Flittle', 'Girafarig', 'Gligar',
Expand All @@ -809,6 +810,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
desc: `A Gen 2 metagame that adds doubles to the game.`,
mod: 'gen2doubles',
gameType: 'doubles',
searchShow: false,
ruleset: ['Standard Doubles', 'Swagger Clause'],
banlist: ['Uber', 'Bright Powder', 'King\'s Rock', 'Quick Claw'],
onBegin() {
Expand Down Expand Up @@ -933,12 +935,12 @@ export const Formats: import('../sim/dex-formats').FormatList = [
mod: 'gen9',
ruleset: ['Standard OMs', 'Sleep Moves Clause'],
banlist: [
'Annihilape', 'Araquanid', 'Arceus', 'Azumarill', 'Blissey', 'Calyrex-Ice', 'Calyrex-Shadow', 'Chansey', 'Chi-Yu', 'Chien-Pao', 'Cloyster', 'Deoxys', 'Deoxys-Attack',
'Deoxys-Defense', 'Deoxys-Speed', 'Dialga', 'Dialga-Origin', 'Espathra', 'Eternatus', 'Giratina', 'Giratina-Origin', 'Groudon', 'Ho-Oh', 'Hoopa-Unbound', 'Iron Bundle',
'Koraidon', 'Kyogre', 'Kyurem', 'Kyurem-Black', 'Kyurem-White', 'Landorus-Incarnate', 'Lugia', 'Lunala', 'Magearna', 'Mewtwo', 'Miraidon', 'Necrozma-Dawn-Wings',
'Necrozma-Dusk-Mane', 'Palafin', 'Palkia', 'Palkia-Origin', 'Rayquaza', 'Regieleki', 'Reshiram', 'Shaymin-Sky', 'Sneasler', 'Solgaleo', 'Terapagos', 'Ursaluna',
'Ursaluna-Bloodmoon', 'Urshifu-Single-Strike', 'Urshifu-Rapid-Strike', 'Zacian', 'Zacian-Crowned', 'Zamazenta', 'Zamazenta-Crowned', 'Zekrom', 'Arena Trap', 'Moody',
'Shadow Tag', 'Baton Pass', 'Last Respects', 'Shed Tail',
'Annihilape', 'Arceus', 'Azumarill', 'Blissey', 'Calyrex-Ice', 'Calyrex-Shadow', 'Chansey', 'Chi-Yu', 'Chien-Pao', 'Cloyster', 'Deoxys', 'Deoxys-Attack', 'Deoxys-Defense',
'Deoxys-Speed', 'Dialga', 'Dialga-Origin', 'Espathra', 'Eternatus', 'Giratina', 'Giratina-Origin', 'Groudon', 'Ho-Oh', 'Hoopa-Unbound', 'Iron Bundle', 'Koraidon',
'Kyogre', 'Kyurem', 'Kyurem-Black', 'Kyurem-White', 'Landorus-Incarnate', 'Lugia', 'Lunala', 'Magearna', 'Mewtwo', 'Miraidon', 'Necrozma-Dawn-Wings', 'Necrozma-Dusk-Mane',
'Palafin', 'Palkia', 'Palkia-Origin', 'Rayquaza', 'Regieleki', 'Reshiram', 'Shaymin-Sky', 'Sneasler', 'Solgaleo', 'Terapagos', 'Ursaluna', 'Ursaluna-Bloodmoon',
'Urshifu-Single-Strike', 'Urshifu-Rapid-Strike', 'Zacian', 'Zacian-Crowned', 'Zamazenta-Crowned', 'Zekrom', 'Arena Trap', 'Moody', 'Shadow Tag', 'Baton Pass',
'Last Respects', 'Shed Tail',
],
battle: {
spreadModify(baseStats, set) {
Expand Down Expand Up @@ -1044,11 +1046,11 @@ export const Formats: import('../sim/dex-formats').FormatList = [
'Swift Swim', 'Bright Powder', 'Focus Band', 'King\'s Rock', 'Quick Claw', 'Razor Fang', 'Baton Pass', 'Last Respects', 'Shed Tail',
],
restricted: [
'Alomomola', 'Annihilape', 'Arceus', 'Baxcalibur', 'Calyrex-Ice', 'Chien-Pao', 'Chi-Yu', 'Crawdaunt', 'Deoxys-Normal', 'Deoxys-Speed', 'Dialga', 'Dialga-Origin', 'Dragapult',
'Espathra', 'Eternatus', 'Flutter Mane', 'Gholdengo', 'Giratina', 'Giratina-Origin', 'Gliscor', 'Gouging Fire', 'Groudon', 'Hawlucha', 'Ho-Oh', 'Iron Bundle', 'Kingambit',
'Kyogre', 'Kyurem', 'Kyurem-White', 'Lugia', 'Lunala', 'Magearna', 'Mewtwo', 'Necrozma-Dawn-Wings', 'Necrozma-Dusk-Mane', 'Ogerpon-Hearthflame', 'Palafin', 'Palkia', 'Palkia-Origin',
'Raging Bolt', 'Rayquaza', 'Regieleki', 'Reshiram', 'Serperior', 'Shaymin-Sky', 'Smeargle', 'Solgaleo', 'Spectrier', 'Terapagos', 'Toxapex', 'Ursaluna', 'Ursaluna-Bloodmoon',
'Volcarona', 'Zacian', 'Zacian-Crowned', 'Zamazenta-Crowned', 'Zekrom',
'Alomomola', 'Annihilape', 'Arceus', 'Baxcalibur', 'Calyrex-Ice', 'Chien-Pao', 'Chi-Yu', 'Crawdaunt', 'Deoxys-Normal', 'Deoxys-Speed', 'Dialga', 'Dialga-Origin', 'Dragapult', 'Espathra',
'Eternatus', 'Flutter Mane', 'Gholdengo', 'Giratina', 'Giratina-Origin', 'Gliscor', 'Gouging Fire', 'Groudon', 'Hawlucha', 'Ho-Oh', 'Iron Bundle', 'Kingambit', 'Kyogre', 'Kyurem',
'Kyurem-White', 'Lugia', 'Lunala', 'Magearna', 'Mewtwo', 'Necrozma-Dawn-Wings', 'Necrozma-Dusk-Mane', 'Ogerpon-Hearthflame', 'Palafin', 'Palkia', 'Palkia-Origin', 'Raging Bolt', 'Rayquaza',
'Regieleki', 'Reshiram', 'Serperior', 'Shaymin-Sky', 'Smeargle', 'Solgaleo', 'Spectrier', 'Talonflame', 'Terapagos', 'Toxapex', 'Ursaluna', 'Ursaluna-Bloodmoon', 'Volcarona', 'Zacian',
'Zacian-Crowned', 'Zamazenta-Crowned', 'Zekrom',
],
},
{
Expand Down Expand Up @@ -2824,7 +2826,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
'Pheromosa', 'Raging Bolt', 'Rayquaza', 'Regigigas', 'Reshiram', 'Salamence-Mega', 'Shaymin-Sky', 'Shedinja', 'Slaking', 'Sneasler', 'Solgaleo', 'Spectrier', 'Urshifu', 'Urshifu-Rapid-Strike',
'Weavile', 'Xerneas', 'Xurkitree', 'Yveltal', 'Zacian', 'Zacian-Crowned', 'Zamazenta-Hero', 'Zekrom', 'Zeraora', 'Zygarde-50%', 'Arena Trap', 'Comatose', 'Contrary', 'Fur Coat', 'Gorilla Tactics',
'Huge Power', 'Ice Scales', 'Illusion', 'Imposter', 'Innards Out', 'Magic Bounce', 'Magnet Pull', 'Moody', 'Neutralizing Gas', 'Orichalcum Pulse', 'Parental Bond', 'Poison Heal', 'Pure Power',
'Shadow Tag', 'Simple', 'Speed Boost', 'Stakeout', 'Triage', 'Unburden', 'Water Bubble', 'Wonder Guard', 'King\'s Rock', 'Assist', 'Baton Pass', 'Electrify', 'Last Respects', 'Shed Tail',
'Shadow Tag', 'Simple', 'Speed Boost', 'Stakeout', 'Triage', 'Unburden', 'Water Bubble', 'Wonder Guard', 'King\'s Rock', 'Light Clay', 'Assist', 'Baton Pass', 'Electrify', 'Last Respects', 'Shed Tail',
],
},
{
Expand Down
2 changes: 1 addition & 1 deletion data/formats-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5253,7 +5253,7 @@ export const FormatsData: import('../sim/dex-species').SpeciesFormatsDataTable =
tier: "LC",
},
palafin: {
tier: "(OU)",
tier: "Uber",
doublesTier: "(DUU)",
natDexTier: "Uber",
},
Expand Down
60 changes: 60 additions & 0 deletions data/mods/gen4/moves.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,33 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
},
},
},
assist: {
inherit: true,
onHit(target) {
const moves = [];
for (const pokemon of target.side.pokemon) {
if (pokemon === target) continue;
for (const moveSlot of pokemon.moveSlots) {
const moveid = moveSlot.id;
const move = this.dex.moves.get(moveid);
if (
move.flags['noassist'] ||
(this.field.pseudoWeather['gravity'] && move.flags['gravity']) ||
(target.volatiles['healblock'] && move.flags['heal'])
) {
continue;
}
moves.push(moveid);
}
}
let randomMove = '';
if (moves.length) randomMove = this.sample(moves);
if (!randomMove) {
return false;
}
this.actions.useMove(randomMove, target);
},
},
beatup: {
inherit: true,
basePower: 10,
Expand Down Expand Up @@ -198,6 +225,22 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
this.add('-start', target, 'typechange', type);
},
},
copycat: {
inherit: true,
onHit(pokemon) {
const move: Move | ActiveMove | null = this.lastMove;
if (!move) return;

if (
move.flags['failcopycat'] ||
(this.field.pseudoWeather['gravity'] && move.flags['gravity']) ||
(pokemon.volatiles['healblock'] && move.flags['heal'])
) {
return false;
}
this.actions.useMove(move.id, pokemon);
},
},
cottonspore: {
inherit: true,
accuracy: 85,
Expand Down Expand Up @@ -1007,6 +1050,23 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
metronome: {
inherit: true,
flags: {noassist: 1, failcopycat: 1, nosleeptalk: 1, failmimic: 1},
onHit(pokemon) {
const moves = this.dex.moves.all().filter(move => (
(![2, 4].includes(this.gen) || !pokemon.moves.includes(move.id)) &&
(!move.isNonstandard || move.isNonstandard === 'Unobtainable') &&
move.flags['metronome'] &&
!(this.field.pseudoWeather['gravity'] && move.flags['gravity']) &&
!(pokemon.volatiles['healblock'] && move.flags['heal'])
));
let randomMove = '';
if (moves.length) {
moves.sort((a, b) => a.num - b.num);
randomMove = this.sample(moves).id;
}
if (!randomMove) return false;
pokemon.side.lastSelectedMove = this.toID(randomMove);
this.actions.useMove(randomMove, pokemon);
},
},
mimic: {
inherit: true,
Expand Down
6 changes: 2 additions & 4 deletions data/moves.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12184,9 +12184,8 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = {
pp: 10,
priority: 0,
flags: {failencore: 1, nosleeptalk: 1, noassist: 1, failcopycat: 1, failmimic: 1, failinstruct: 1},
onHit(target, source, effect) {
onHit(pokemon) {
const moves = this.dex.moves.all().filter(move => (
(![2, 4].includes(this.gen) || !source.moves.includes(move.id)) &&
(!move.isNonstandard || move.isNonstandard === 'Unobtainable') &&
move.flags['metronome']
));
Expand All @@ -12196,8 +12195,7 @@ export const Moves: import('../sim/dex-moves').MoveDataTable = {
randomMove = this.sample(moves).id;
}
if (!randomMove) return false;
source.side.lastSelectedMove = this.toID(randomMove);
this.actions.useMove(randomMove, target);
this.actions.useMove(randomMove, pokemon);
},
callsMove: true,
secondary: null,
Expand Down
103 changes: 63 additions & 40 deletions server/chat-plugins/auction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ interface Player {
name: string;
team?: Team;
price: number;
tiers?: string[];
tiersPlayed: string[];
tiersNotPlayed: string[];
}

interface Manager {
Expand Down Expand Up @@ -148,7 +149,7 @@ export class Auction extends Rooms.SimpleRoomGame {
let buf = `<span style="font-size: 85%">`;
buf += players.slice(0, max).map(p => {
if (typeof p === 'object') {
return `<username title="Tiers: ${p.tiers?.length ? `${Utils.escapeHTML(p.tiers.join(', '))}` : 'N/A'}"${clickable ? ' class="username"' : ''}>${Utils.escapeHTML(p.name)}</username>`;
return `<username title="Tiers: ${p.tiersPlayed.length ? `${Utils.escapeHTML(p.tiersPlayed.join(', '))}` : 'N/A'}"${clickable ? ' class="username"' : ''}>${Utils.escapeHTML(p.name)}</username>`;
}
return `<username${clickable ? ' class="username"' : ''}>${Utils.escapeHTML(p)}</username>`;
}).join(', ');
Expand Down Expand Up @@ -211,8 +212,7 @@ export class Auction extends Rooms.SimpleRoomGame {
const players = Utils.sortBy(this.getUndraftedPlayers(), p => p.name);
const tierArrays = new Map<string, Player[]>();
for (const player of players) {
if (!player.tiers) continue;
for (const tier of player.tiers) {
for (const tier of player.tiersPlayed) {
if (!tierArrays.has(tier)) tierArrays.set(tier, []);
tierArrays.get(tier)!.push(player);
}
Expand Down Expand Up @@ -245,8 +245,9 @@ export class Auction extends Rooms.SimpleRoomGame {
let buf = `<div class="infobox">`;
buf += Utils.html`Player: <username>${this.nominatedPlayer.name}</username> `;
buf += `Top bid: <b>${this.highestBid}</b> `;
buf += Utils.html`Top bidder: <b>${this.highestBidder.name}</b> `;
buf += Utils.html`Tiers: <b>${this.nominatedPlayer.tiers?.length ? `${this.nominatedPlayer.tiers.join(', ')}` : 'N/A'}</b>`;
buf += Utils.html`Top bidder: <b>${this.highestBidder.name}</b><br/>`;
buf += Utils.html`Tiers Played: <b>${this.nominatedPlayer.tiersPlayed.length ? `${this.nominatedPlayer.tiersPlayed.join(', ')}` : 'N/A'}</b><br/>`;
buf += Utils.html`Tiers Not Played: <b>${this.nominatedPlayer.tiersNotPlayed.length ? `${this.nominatedPlayer.tiersNotPlayed.join(', ')}` : 'N/A'}</b>`;
buf += `</div>`;
this.room.add(`|uhtml|bid-${this.nominatedPlayer.id}|${buf}`).update();
}
Expand Down Expand Up @@ -322,43 +323,53 @@ export class Auction extends Rooms.SimpleRoomGame {
}
const rows = data.replace('\r', '').split('\n');
const tierNames = rows.shift()!.split('\t').slice(1);
if (tierNames.some(tier => tier.length > 30)) {
throw new Chat.ErrorMessage(`Tier names must be 30 characters or less.`);
}

const playerList = new Map<string, Player>();
for (const row of rows) {
const tiers = [];
const tiersPlayed = [];
const tiersNotPlayed = [];
const [name, ...tierData] = row.split('\t');
for (let i = 0; i < tierData.length; i++) {
if (['y', 'Y', '\u2713', '\u2714'].includes(tierData[i].trim())) {
switch (tierData[i].trim().toLowerCase()) {
case 'y':
if (!tierNames[i]) throw new Chat.ErrorMessage(`Invalid tier data found in the pastebin.`);
if (tierNames[i].length > 30) throw new Chat.ErrorMessage(`Tier names must be 30 characters or less.`);
tiers.push(tierNames[i]);
tiersPlayed.push(tierNames[i]);
break;
case 'n':
if (!tierNames[i]) throw new Chat.ErrorMessage(`Invalid tier data found in the pastebin.`);
tiersNotPlayed.push(tierNames[i]);
break;
}
}
if (name.length > 25) throw new Chat.ErrorMessage(`Player names must be 25 characters or less.`);
const player: Player = {
id: toID(name),
name,
price: 0,
tiersPlayed,
tiersNotPlayed,
};
if (tiers.length) player.tiers = tiers;
playerList.set(player.id, player);
}
this.auctionPlayers = playerList;
}

addAuctionPlayer(name: string, tiers?: string[]) {
addAuctionPlayer(name: string, tiersPlayed: string[], tiersNotPlayed: string[]) {
if (this.state === 'bid') throw new Chat.ErrorMessage(`Players cannot be added during a nomination.`);
if (name.length > 25) throw new Chat.ErrorMessage(`Player names must be 25 characters or less.`);
if (tiersPlayed.some(tier => tier.length > 30) || tiersNotPlayed.some(tier => tier.length > 30)) {
throw new Chat.ErrorMessage(`Tier names must be 30 characters or less.`);
}
const player: Player = {
id: toID(name),
name,
price: 0,
tiersPlayed,
tiersNotPlayed,
};
if (tiers?.length) {
if (tiers.some(tier => tier.length > 30)) {
throw new Chat.ErrorMessage(`Tier names must be 30 characters or less.`);
}
player.tiers = tiers;
}
this.auctionPlayers.set(player.id, player);
return player;
}
Expand Down Expand Up @@ -541,14 +552,15 @@ export class Auction extends Rooms.SimpleRoomGame {
this.state = 'bid';
this.highestBid = this.minBid;
this.highestBidder = this.nominatingTeam;

this.sendMessage(Utils.html`/html <username class="username">${user.name}</username> from team <b>${this.nominatingTeam.name}</b> has nominated <username>${player.name}</username> for auction!`);
const notifyMsg = Utils.html`|notify|${this.room.title} Auction|${player.name} has been nominated!`;
for (const currManager of this.managers.values()) {
if (currManager.team === this.nominatingTeam) continue;
Users.getExact(currManager.id)?.sendTo(this.room, notifyMsg);
const curUser = Users.getExact(currManager.id);
curUser?.sendTo(this.room, notifyMsg);
curUser?.sendTo(this.room,
`|raw|Send a message with the amount you want to bid (e.g. <code>.5</code> or <code>5</code> will place a bid of <b>5000</b>)!`);
}

this.sendMessage(Utils.html`/html <username class="username">${user.name}</username> from team <b>${this.nominatingTeam.name}</b> has nominated <username>${player.name}</username> for auction. Use /bid or type a number to place a bid!`);
this.sendBidInfo();
this.startBidTimer();
}
Expand Down Expand Up @@ -591,8 +603,26 @@ export class Auction extends Rooms.SimpleRoomGame {
}

onChatMessage(message: string, user: User) {
if (this.state === 'bid' && Number(message.replace(',', '.'))) {
this.bid(user, parseCredits(message));
if (this.state !== 'bid') return;

const originalMsg = message;
if (message.startsWith('.')) message = message.slice(1);
if (Number(message.replace(',', '.'))) {
if (this.type === 'blind') {
this.bid(user, parseCredits(message));
} else {
// If bid is unsuccessful, the error message and the original message are sent to the room
try {
this.bid(user, parseCredits(message));
} catch (e) {
this.room.add(`|c|${user.getIdentity()}|${originalMsg}`);
if (e instanceof Chat.ErrorMessage) {
this.sendMessage(Utils.html`/html <span class="message-error">${e.message}</span>`);
} else {
this.sendMessage(`/html <span class="message-error">An unexpected error occurred while placing your bid.</span>`);
}
}
}
return '';
}
}
Expand Down Expand Up @@ -876,13 +906,16 @@ export const commands: Chat.ChatCommands = {
const auction = this.requireGame(Auction);
auction.checkOwner(user);

const [name, ...tiers] = target.split(',').map(x => x.trim());
const name = target.slice(0, target.indexOf(',')).trim();
const [tiersPlayed, tiersNotPlayed] = target.slice(target.indexOf(',') + 1).split(';')
.map(tiers => tiers.split(',').map(t => t.trim()));

if (!name) return this.parse('/help auction addplayer');
const player = auction.addAuctionPlayer(name, tiers);
const player = auction.addAuctionPlayer(name, tiersPlayed, tiersNotPlayed);
this.addModAction(`${user.name} added player ${player.name} to the auction.`);
},
addplayerhelp: [
`/auction addplayer [name], [tier1], [tier2], ... - Adds a player to the auction. Requires: # ~ auction owner`,
`/auction addplayer [name], [tierPlayed1], [tierPlayed2], ... ; [tierNotPlayed1], [tierNotPlayed2], ... - Adds a player to the auction. Requires: # ~ auction owner`,
],
removeplayer(target, room, user) {
const auction = this.requireGame(Auction);
Expand Down Expand Up @@ -1009,15 +1042,6 @@ export const commands: Chat.ChatCommands = {
nominatehelp: [
`/auction nominate OR /nom [player] - Nominates a player for auction.`,
],
bid(target, room, user) {
const auction = this.requireGame(Auction);
if (!target) return this.parse('/help auction bid');
auction.bid(user, parseCredits(target));
},
bidhelp: [
`/auction bid OR /bid [amount] - Bids on a player for the specified amount. If the amount is less than 500, it will be multiplied by 1000.`,
`During the bidding phase, all numbers that are sent in the chat will be treated as bids.`,
],
skip: 'skipnom',
skipnom(target, room, user) {
const auction = this.requireGame(Auction);
Expand Down Expand Up @@ -1076,8 +1100,7 @@ export const commands: Chat.ChatCommands = {
`- display: Displays the current state of the auction.<br/>` +
`- pricelist: Displays the current prices of players by team.<br/>` +
`- nom [player]: Nominates a player for auction.<br/>` +
`- bid [amount]: Bids on a player for the specified amount. If the amount is less than 500, it will be multiplied by 1000.<br/>` +
`You may use /bid and /nom directly without the /auction prefix.<br/>` +
`You may use /nom directly without the /auction prefix.<br/>` +
`During the bidding phase, all numbers that are sent in the chat will be treated as bids.<br/><br/>` +
`<details class="readmore"><summary>Configuration Commands</summary>` +
`- minbid [amount]: Sets the minimum bid.<br/>` +
Expand Down Expand Up @@ -1107,8 +1130,8 @@ export const commands: Chat.ChatCommands = {
nom(target) {
this.parse(`/auction nominate ${target}`);
},
bid(target) {
this.parse(`/auction bid ${target}`);
bid() {
this.errorReply(`/bid is no longer supported. Send the amount by itself in the chat to place your bid.`);
},
overpay() {
this.requireGame(Auction);
Expand Down
2 changes: 1 addition & 1 deletion server/chat-plugins/calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export const commands: Chat.ChatCommands = {
const [result, inferredBase] = solveRPN(parseMathematicalExpression(expression));
if (!base) base = inferredBase;
let baseResult = '';
if (result && base !== 10) {
if (Number.isFinite(result) && base !== 10) {
baseResult = `${BASE_PREFIXES[base]}${result.toString(base).toUpperCase()}`;
if (baseResult === expression) baseResult = '';
}
Expand Down
Loading

0 comments on commit a20a46e

Please sign in to comment.