Add Governments.
New betting achievement. Add !pet Move from Sage to Admin naming. Add cursed pics for hauntings (and reduce haunting odds) Add saveGame() reason messages. Add mining upgrades. Add semi-live updating leaderboards. Add thorough emoji validation. Move user IDs to a separate json file. More details (like current coin count) in !buy menu. Limit temperature polls to prevent spam. Some work on a prestige menu. Several additional quackgrades. Change text games to try editing messages live. Named upgrades.
This commit is contained in:
parent
ad021cf9a5
commit
4433c19d04
File diff suppressed because it is too large
Load Diff
|
@ -76,7 +76,7 @@ const checkDiagonals = board => {
|
|||
const checkFull = board => {
|
||||
for (const row of board) {
|
||||
for (const col of row) {
|
||||
if (col !== ' ') {
|
||||
if (col === ' ') {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,11 @@ module.exports = {
|
|||
description: 'H I G H R O L L E R',
|
||||
emoji: '8ball'
|
||||
},
|
||||
sigmaBets: {
|
||||
name: 'Make a bet over 100 Quintillion',
|
||||
description: 'Return to monke',
|
||||
emoji: 'yeknom'
|
||||
},
|
||||
ignited: {
|
||||
name: 'You light my fire, baby',
|
||||
description: 'And you pay attention to descriptions!',
|
||||
|
@ -120,6 +125,11 @@ module.exports = {
|
|||
description: `I mean... that's basically all of them.`,
|
||||
emoji: 'office'
|
||||
},
|
||||
government100: {
|
||||
name: 'Run 100 Governments',
|
||||
description: `I hope you're using them for something good...`,
|
||||
emoji: 'japanese_castle'
|
||||
},
|
||||
|
||||
weAllNeedHelp: {
|
||||
name: 'View the \'!coin\' help',
|
||||
|
@ -159,7 +169,7 @@ module.exports = {
|
|||
},
|
||||
bookWorm: {
|
||||
name: 'Take a peek at the lore',
|
||||
description: 'It\'t gotta be worth your time somehow.',
|
||||
description: 'It\'s gotta be worth your time somehow.',
|
||||
emoji: 'books'
|
||||
},
|
||||
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
const buyableItems = require('./buyableItems')
|
||||
const { commas, setHighestCoins, addAchievement, getUser, singleItemCps, chaosFilter, fuzzyMatcher } = require('./utils')
|
||||
const { commas, setHighestCoins, addAchievement, getUser, singleItemCps, chaosFilter, fuzzyMatcher, calculateCost } = require('./utils')
|
||||
const slack = require('../../slack')
|
||||
|
||||
const calculateCost = ({ itemName, user, quantity = 1 }) => {
|
||||
let currentlyOwned = user.items[itemName] || 0
|
||||
let realCost = 0
|
||||
for (let i = 0; i < quantity; i++) {
|
||||
realCost += Math.ceil(buyableItems[itemName].baseCost * Math.pow(1.15, currentlyOwned || 0))
|
||||
currentlyOwned += 1
|
||||
}
|
||||
return realCost
|
||||
}
|
||||
const leaderboardUpdater = {}
|
||||
|
||||
const getItemHeader = user => ([itemName, { baseCost, description, emoji }]) => {
|
||||
const itemCost = commas(user ? calculateCost({ itemName, user }) : baseCost)
|
||||
|
@ -69,16 +61,28 @@ const buildBlock2 = ({ user, itemName, cost, cps }) => ({
|
|||
]
|
||||
})
|
||||
|
||||
const buyText2 = (highestCoins, user) => {
|
||||
const buyText2 = (highestCoins, user, extraMessage = '') => {
|
||||
return ({
|
||||
text: buyableText(highestCoins, user),
|
||||
blocks: Object.entries(buyableItems)
|
||||
text: (extraMessage && extraMessage + '\n')
|
||||
+ `You have ${commas(user.coins)} HVAC to spend.\n`
|
||||
+ buyableText(highestCoins, user),
|
||||
blocks: [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: (extraMessage && extraMessage + '\n')
|
||||
+ `You have ${commas(user.coins)} HVAC to spend.\n`
|
||||
},
|
||||
},
|
||||
...Object.entries(buyableItems)
|
||||
.filter(([, item]) => canView(item, highestCoins))
|
||||
.map(([itemName]) => {
|
||||
const cost = calculateCost({ itemName, user, quantity: 1 })
|
||||
const cps = Math.round(singleItemCps(user, itemName))
|
||||
return ({ user, itemName, cost, cps })
|
||||
}).map(buildBlock)
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -136,7 +140,7 @@ const buyRoute = async ({ event, say, args, user }) => {
|
|||
return say(`Buying ${quantity} ${buyableName} would cost you ${commas(realCost)} HVAC`)
|
||||
}
|
||||
if (currentCoins < realCost) {
|
||||
await say(`You don't have enough coins! You have ${commas(currentCoins)}, but you need ${commas(realCost)}`)
|
||||
await say(`You don't have enough coins! You need ${commas(realCost)}`)
|
||||
return
|
||||
}
|
||||
user.coins -= realCost
|
||||
|
@ -161,15 +165,20 @@ const buyButton = async ({ body, ack, say, payload }) => {
|
|||
const user = getUser(event.user)
|
||||
const words = ['', buying, body.actions[0].text]
|
||||
const [commandName, ...args] = words
|
||||
|
||||
let extraMessage = ''
|
||||
say = async text => extraMessage = text
|
||||
await buyRoute({ event, say, words, args, commandName, user })
|
||||
|
||||
const highestCoins = user.highestEver || user.coins || 1
|
||||
await slack.app.client.chat.update({
|
||||
channel: body.channel.id,
|
||||
ts: body.message.ts,
|
||||
...buyText2(highestCoins, user)
|
||||
...buyText2(highestCoins, user, extraMessage)
|
||||
})
|
||||
await leaderboardUpdater.updateAllLeaderboards()
|
||||
}
|
||||
|
||||
Object.keys(buyableItems).forEach(itemName => slack.app.action('buy_' + itemName, buyButton))
|
||||
|
||||
module.exports = buyRoute
|
||||
module.exports = { buyRoute, leaderboardUpdater }
|
||||
|
|
|
@ -25,14 +25,14 @@ module.exports = {
|
|||
earning: 260,
|
||||
emoji: 'train2',
|
||||
description: 'Efficiently ship your most valuable coins.',
|
||||
own100Achievement: 'fire100',
|
||||
own100Achievement: 'train100',
|
||||
},
|
||||
fire: {
|
||||
baseCost: 1_400_000,
|
||||
earning: 1_400,
|
||||
emoji: 'fire',
|
||||
description: 'Return to the roots of HVAC.',
|
||||
own100Achievement: 'train100',
|
||||
own100Achievement: 'fire100',
|
||||
},
|
||||
boomerang: {
|
||||
baseCost: 20_000_000,
|
||||
|
@ -46,7 +46,7 @@ module.exports = {
|
|||
earning: 44_000,
|
||||
emoji: 'new_moon_with_face',
|
||||
description: 'Convert dark new-moon energy into HVAC Coins.',
|
||||
own100Achievement: 'mirror100',
|
||||
own100Achievement: 'moon100',
|
||||
},
|
||||
butterfly: {
|
||||
baseCost: 5_100_000_000,
|
||||
|
@ -60,41 +60,48 @@ module.exports = {
|
|||
earning: 1_600_000,
|
||||
emoji: 'mirror',
|
||||
description: 'Only by gazing inward can you collect enough Coin to influence the thermostat.',
|
||||
own100Achievement: 'quade100',
|
||||
own100Achievement: 'mirror100',
|
||||
},
|
||||
quade: {
|
||||
baseCost: 1_000_000_000_000,
|
||||
earning: 10_000_000,
|
||||
emoji: 'quade',
|
||||
description: 'Has thumbs capable of physically manipulating the thermostat.',
|
||||
own100Achievement: 'hvacker100',
|
||||
own100Achievement: 'quade100',
|
||||
},
|
||||
hvacker: {
|
||||
baseCost: 14_000_000_000_000,
|
||||
earning: 65_000_000,
|
||||
emoji: 'hvacker_angery',
|
||||
description: 'Harness the power of the mad god himself.',
|
||||
own100Achievement: 'creator100',
|
||||
own100Achievement: 'hvacker100',
|
||||
},
|
||||
creator: {
|
||||
baseCost: 170_000_000_000_000,
|
||||
earning: 430_000_000,
|
||||
emoji: 'question',
|
||||
description: 'The elusive creator of Hvacker takes a favorable look at your CPS.',
|
||||
own100Achievement: 'smallBusiness100',
|
||||
own100Achievement: 'creator100',
|
||||
},
|
||||
smallBusiness: {
|
||||
baseCost: 2_210_000_000_000_000,
|
||||
earning: 2_845_000_000,
|
||||
emoji: 'convenience_store',
|
||||
description: 'The place where the creator of Hvacker goes to work.',
|
||||
own100Achievement: 'bigBusiness100',
|
||||
own100Achievement: 'smallBusiness100',
|
||||
},
|
||||
bigBusiness: {
|
||||
baseCost: 26_210_000_000_000_000,
|
||||
earning: 23_650_000_000,
|
||||
emoji: 'office',
|
||||
description: 'The place where the smallBusiness goes to work.',
|
||||
own100Achievement: 'ratGod',
|
||||
}
|
||||
own100Achievement: 'bigBusiness100',
|
||||
},
|
||||
government: {
|
||||
baseCost: 367_210_000_000_000_000,
|
||||
earning: 185_000_000_000,
|
||||
emoji: 'japanese_castle',
|
||||
description: 'By the people, for the people, etc. etc.',
|
||||
own100Achievement: 'government100',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
const { game, getUser, saveGame, petBoost, getCoins, updateAll, getCPS, addCoins, commas} = require('./utils')
|
||||
const slack = require('../../slack')
|
||||
const maxValue = 10
|
||||
|
||||
const makePet = () => ({
|
||||
food: 0,
|
||||
fun: 0,
|
||||
})
|
||||
|
||||
const bad = (text = '') => `
|
||||
_^__^_
|
||||
/ x x \\${text && ` "${text}"`}
|
||||
>\\ o /<
|
||||
----
|
||||
`
|
||||
|
||||
const normal = (text = '') => `
|
||||
_^__^_
|
||||
/ o o \\${text && ` "${text}"`}
|
||||
>\\ __ /<
|
||||
----
|
||||
`
|
||||
|
||||
const great = (text = '') => `
|
||||
_^__^_
|
||||
/ ^ ^ \\${text && ` "${text}"`}
|
||||
>\\ \\__/ /<
|
||||
----
|
||||
`
|
||||
|
||||
const makeBar = (name, value) => {
|
||||
const left = '#'.repeat(value)
|
||||
const right = ' '.repeat(maxValue - value)
|
||||
return `${name}:`.padEnd(6) + `[${left}${right}]`
|
||||
}
|
||||
|
||||
const buildBlocks = ({text}) => [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'actions',
|
||||
elements: [
|
||||
buildBlock('Feed'),
|
||||
buildBlock('Play'),
|
||||
]
|
||||
}]
|
||||
|
||||
const buildBlock = actionName => (
|
||||
{
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: actionName,
|
||||
emoji: true
|
||||
},
|
||||
value: actionName,
|
||||
action_id: actionName
|
||||
}
|
||||
)
|
||||
|
||||
// game.channelPets ??= {}
|
||||
|
||||
const petToText = (pet, additional, say) => {
|
||||
const stats = Object.values(pet)
|
||||
const hasTerribleStat = stats.filter(value => value < 1).length > 0
|
||||
const averageStat = stats.reduce((total, current) => total + current, 0) / stats.length
|
||||
let pic
|
||||
if (hasTerribleStat && averageStat < 3) {
|
||||
pic = bad
|
||||
} else if (!hasTerribleStat && averageStat > 8) {
|
||||
pic = great
|
||||
} else {
|
||||
pic = normal
|
||||
}
|
||||
|
||||
let speech
|
||||
if (pic === bad || Math.random() < 0.5) {
|
||||
speech = pic()
|
||||
} else {
|
||||
speech = pic('Mrow')
|
||||
}
|
||||
additional ??= ''
|
||||
if (additional) {
|
||||
additional = `\n${additional}\n`
|
||||
}
|
||||
|
||||
const text =
|
||||
'```\n'
|
||||
+ `Current HVAC Multiplier: ${petBoost()}x\n`
|
||||
+ `${makeBar('Food', pet.food)}`
|
||||
+ `\n${makeBar('Fun', pet.fun)}`
|
||||
+ '\n'
|
||||
+ speech
|
||||
+ '```'
|
||||
+ additional
|
||||
saveGame('pet generation')
|
||||
const ret = ({
|
||||
text,
|
||||
blocks: buildBlocks({text})
|
||||
})
|
||||
// If there's a say() this is a new message, otherwise, we're editing.
|
||||
if (say) {
|
||||
say(ret).then(({ channel, ts }) => {
|
||||
// game.channelPets[channel] = ts
|
||||
return updateAll({ name: 'pet', add: { channel, ts }})
|
||||
}).catch(console.error)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
const updateEveryone = async additional =>
|
||||
updateAll({ name: 'pet', ...petToText(game.pet, additional) })
|
||||
|
||||
const statDown = () => {
|
||||
const pet = (game.pet ??= makePet())
|
||||
pet.food = Math.max(pet.food - 1, 0)
|
||||
pet.fun = Math.max(pet.fun - 1, 0)
|
||||
|
||||
updateEveryone().catch(console.error)
|
||||
|
||||
setTimeout(() => {
|
||||
Object.entries(game.users).forEach(([id, u]) => u.coins = getCoins(id))
|
||||
statDown()
|
||||
}, 1000 * 60 * 90) // Every 90 minutes
|
||||
}
|
||||
|
||||
statDown()
|
||||
|
||||
const addInteraction = ({ actionId, perform }) =>
|
||||
slack.app.action(actionId, async ({ body, ack, say, payload }) => {
|
||||
try {
|
||||
await ack()
|
||||
game.pet ??= makePet()
|
||||
const [everyone, local] = perform(game.pet, getUser(body.user.id))
|
||||
await updateEveryone(everyone)
|
||||
if (local) {
|
||||
await say(local)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
|
||||
const oneMinuteOfCps = user => Math.floor(getCPS(user) * 60)
|
||||
|
||||
addInteraction({ actionId: 'Feed', perform: (pet, user) => {
|
||||
if (pet.food >= 10) {
|
||||
return [`I'm too full to eat more, ${user.name}!`]
|
||||
}
|
||||
const oneMinute = oneMinuteOfCps(user)
|
||||
addCoins(user, oneMinute)
|
||||
pet.food += 1
|
||||
return [`Thanks for the grub, ${user.name}!`, `Earned ${commas(oneMinute)} HVAC for feeding our pet!`]
|
||||
} })
|
||||
|
||||
addInteraction({ actionId: 'Play', perform: (pet, user) => {
|
||||
if (pet.fun >= 10) {
|
||||
return [`I'm too tired for more games, ${user.name}.`]
|
||||
}
|
||||
const oneMinute = oneMinuteOfCps(user)
|
||||
addCoins(user, oneMinute)
|
||||
pet.fun += 1
|
||||
return [`Thanks for playing, ${user.name}!`, `Earned ${commas(oneMinute)} HVAC for playing with our pet!`]
|
||||
}})
|
||||
|
||||
module.exports = {
|
||||
makePet,
|
||||
petToText
|
||||
}
|
|
@ -24,8 +24,11 @@ const {
|
|||
userHasCheckedQuackgrade,
|
||||
fuzzyMatcher,
|
||||
addCoins,
|
||||
game: { nfts, squad, users, horrors, stonkMarket },
|
||||
game, updateAll
|
||||
} = require('./utils')
|
||||
const { nfts, squad, users, horrors, stonkMarket, pet } = game
|
||||
const pets = require('./gotcha')
|
||||
|
||||
const slack = require('../../slack')
|
||||
const buyableItems = require('./buyableItems')
|
||||
const upgrades = require('./upgrades')
|
||||
|
@ -42,7 +45,7 @@ const settings = require('./settings')
|
|||
// })
|
||||
// const read = () => {
|
||||
// readline.question(`What do YOU want? `, async want => {
|
||||
// want && await slack.messageSage(want)
|
||||
// want && await slack.messageAdmin(want)
|
||||
// read()
|
||||
// })
|
||||
// }
|
||||
|
@ -56,8 +59,8 @@ const getUpgradeEmoji = upgrade => upgrade.emoji || buyableItems[upgrade.type].e
|
|||
const upgradeText = (user, showOwned = false) => {
|
||||
const userDoesNotHave = ([upgradeName, upgrade]) => hasUpgrade(user, upgrade, upgradeName) === showOwned
|
||||
const userMeetsCondition = ([, upgrade]) => upgrade.condition(user, getCompletedSquadgradeNames())
|
||||
const format = ([key, value]) => `:${getUpgradeEmoji(value)}: *${key}* - ${commas(value.cost)}\n_${value.description}_`
|
||||
return '\n\n' +
|
||||
const format = ([, value]) => `:${getUpgradeEmoji(value)}: *${value.name}* - ${commas(value.cost)}\n_${value.description}_`
|
||||
const subtotal = '\n\n' +
|
||||
Object.entries(upgrades)
|
||||
.filter(userDoesNotHave)
|
||||
.filter(userMeetsCondition)
|
||||
|
@ -65,6 +68,7 @@ const upgradeText = (user, showOwned = false) => {
|
|||
.join('\n\n') +
|
||||
'\n\n:grey_question::grey_question::grey_question:' +
|
||||
'\n\nJust type \'!upgrade upgrade_name\' to purchase'
|
||||
return subtotal.trim()
|
||||
}
|
||||
|
||||
const hasUpgrade = (user, upgrade, upgradeName) => !!user.upgrades[upgrade.type]?.includes(upgradeName)
|
||||
|
@ -109,6 +113,15 @@ const commands = new Map()
|
|||
let commandHelpText = ''
|
||||
let shortCommandHelpText = 'Use `!help full` to show details for all commands, or `!<command> help` to show for just one.\n```'
|
||||
const defaultAccess = { hidden: false, condition: alwaysAccessible }
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {[string]} commandNames
|
||||
* @param {string} helpText
|
||||
* @param {function({ event, say, trueSay, words, args, commandName, user, userId: event.user, haunted })} action: boolean
|
||||
* @param {boolean} hidden
|
||||
* @param condition
|
||||
*/
|
||||
const command = (commandNames, helpText, action, { hidden, condition } = defaultAccess) => {
|
||||
if (!hidden) {
|
||||
console.log(`Initializing command '${commandNames[0]}'`)
|
||||
|
@ -157,7 +170,7 @@ const getHorrorMessageOdds = (offset = 0) => {
|
|||
command(
|
||||
['!odds'],
|
||||
'Show shuffle odds re: !horror',
|
||||
async ({ say, args }) => {
|
||||
async ({ say, args, user }) => {
|
||||
const percentOrOneIn = odds => `${(odds * 100).toPrecision(3)}%, or about 1 in ${Math.round(1 / odds)}`
|
||||
if (!args[0]) {
|
||||
return say(
|
||||
|
@ -168,7 +181,7 @@ command(
|
|||
//`Tomorrow's horror message odds will be ${percentOrOneIn(getHorrorMessageOdds(1))}`
|
||||
)
|
||||
}
|
||||
const num = parseAll(args[0], 99)
|
||||
const num = parseAll(args[0], 99, user)
|
||||
return say(
|
||||
`Shuffle odds in ${num} days will be ${percentOrOneIn(getShuffleOdds(num))}\n`
|
||||
//`Horror message odds in ${num} days will be ${percentOrOneIn(getHorrorMessageOdds(num))}`
|
||||
|
@ -178,9 +191,9 @@ command(
|
|||
command(
|
||||
['!shuffle'],
|
||||
'!shuffle daysFromNow message',
|
||||
async ({ say, args }) => {
|
||||
async ({ say, args, user }) => {
|
||||
const percentOrOneIn = odds => `${(odds * 100).toPrecision(3)}%, or 1 in ${Math.round(1 / odds)}`
|
||||
const num = parseAll(args[0], 99)
|
||||
const num = parseAll(args[0], 99, user)
|
||||
const [, ...message] = args
|
||||
return say(
|
||||
`Shuffle odds in ${num} days will be ${percentOrOneIn(getShuffleOdds(num))}\n` +
|
||||
|
@ -213,12 +226,12 @@ if (settings.horrorEnabled) {
|
|||
['!horror'],
|
||||
'help help help help help',
|
||||
async ({ event, say }) => {
|
||||
if (event.user === slack.users.Sage) {
|
||||
if (event.user === slack.users.Admin) {
|
||||
return slack.postToTechThermostatChannel(shufflePercent(event.text.substring(7).trim(), getShuffleOdds()))
|
||||
}
|
||||
horrors.commandCalls ??= 0
|
||||
horrors.commandCalls += 1
|
||||
await slack.messageSage(`<@${event.user}> found !horror.`)
|
||||
await slack.messageAdmin(`<@${event.user}> found !horror.`)
|
||||
await say('_Do you think you can help me?_') // TODO horror horrors change help to !help
|
||||
}, { hidden: true })
|
||||
}
|
||||
|
@ -232,7 +245,7 @@ const buildHorrorSay = ({ say, event, commandName, c }) => async message => {
|
|||
if (shuffled.length > 100 && Math.random() < getShuffleOdds()) {
|
||||
const middle = (shuffled.length / 2) + Math.round((Math.random() - 1) * (shuffled.length / 5))
|
||||
shuffled = shuffled.substring(0, middle) + definitelyShuffle(getRandomFromArray(horrorMessages), shuffleOdds * 1.5) + shuffled.substring(middle)
|
||||
await slack.messageSage(`Just sent a hidden horror to ${slack.users[event.user]}:\n\n${shuffled}`)
|
||||
await slack.messageAdmin(`Just sent a hidden horror to ${slack.users[event.user]}:\n\n${shuffled}`)
|
||||
}
|
||||
await say(shuffled)
|
||||
} else {
|
||||
|
@ -244,6 +257,7 @@ const buildSayWithPayload = ({ say, event }) => async msg => {
|
|||
const payload = {
|
||||
event: {
|
||||
text: event.text,
|
||||
user: event.user
|
||||
}
|
||||
}
|
||||
if (typeof(msg) === 'string') {
|
||||
|
@ -261,7 +275,7 @@ command(
|
|||
['!!peter-griffin-family-guy'],
|
||||
'Delete',
|
||||
async ({ say, user }) => {
|
||||
if (user.isDisabled === false) {
|
||||
if (user.isDisabled === false) { // As opposed to null/undefined
|
||||
return say(`Silly, silly, ${user.name}.\nYou can't just leave us again.`)
|
||||
}
|
||||
user.isDisabled = true
|
||||
|
@ -269,7 +283,14 @@ command(
|
|||
return say('.')
|
||||
}, { hidden: true })
|
||||
|
||||
const garble = text => text.split('').map(() => '□').join('')
|
||||
const badChars = [' ', '□', '-']
|
||||
const garble = text => {
|
||||
let garbled
|
||||
do {
|
||||
garbled = text.split('').map(c => Math.random() < 0.075 ? getRandomFromArray(badChars) : c).join('')
|
||||
} while (garbled.length > 0 && garbled === text)
|
||||
return garbled
|
||||
}
|
||||
|
||||
const noWinner = 'NO WINNER'
|
||||
|
||||
|
@ -343,15 +364,39 @@ command(
|
|||
}
|
||||
)
|
||||
|
||||
const messageHandler = async ({ event, say, isRecycle = false }) => {
|
||||
const cursedPics = {
|
||||
U0B8W0AF3: ['https://i.imgur.com/UNYKS0c.png'], //'Charles',
|
||||
//U0B8RTK5L: '', //'Zane',
|
||||
U02KYLVK1GV: ['https://i.imgur.com/VTSob8w.png', 'https://i.imgur.com/BdXqA5d.jpeg'], //'Quade',
|
||||
UTDLFGZA5: ['https://i.imgur.com/YIrz4JQ.png'], // 'Tyler',
|
||||
U017PG4EL1Y: ['https://i.imgur.com/d65EuaQ.png'],//'Max',
|
||||
// hole viscera
|
||||
U02AAB54V34: ['https://i.imgur.com/ewT3AoL.png', 'https://i.imgur.com/LLyzybV.png'] // 'Houston'
|
||||
}
|
||||
|
||||
const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) => {
|
||||
if (event?.subtype === 'bot_message') {
|
||||
return botMessageHandler({ event, say })
|
||||
}
|
||||
const startTime = new Date()
|
||||
const words = event?.text?.split(/\s+/) || []
|
||||
const [commandName, ...args] = words
|
||||
const c = commands.get(commandName)
|
||||
if (!c && words[0]?.startsWith('!') && event.user !== slack.users.Sage) {
|
||||
return slack.messageSage(`${slack.users[event.user]} tried to use \`${event.text}\`, if you wanted to add that.`)
|
||||
let user = getUser(event.user)
|
||||
if (user.isDisabled) {
|
||||
return
|
||||
}
|
||||
user.name = slack.users[event.user]
|
||||
if (!skipCounting && words[0]?.startsWith('!')) {
|
||||
user.commandCounts ??= {}
|
||||
user.commandCounts[words[0]] ??= 0
|
||||
user.commandCounts[words[0]] += 1
|
||||
}
|
||||
|
||||
if (!c && words[0]?.startsWith('!') && event.user !== slack.users.Admin) {
|
||||
if (!slack.pollTriggers.includes(words[0])) {
|
||||
return slack.messageAdmin(`${slack.users[event.user]} tried to use \`${event.text}\`, if you wanted to add that.`)
|
||||
}
|
||||
}
|
||||
|
||||
const trueSay = say
|
||||
|
@ -361,27 +406,39 @@ const messageHandler = async ({ event, say, isRecycle = false }) => {
|
|||
say = buildHorrorSay({say, event, args, commandName, c})
|
||||
}
|
||||
|
||||
let user = getUser(event.user)
|
||||
if (user.isDisabled) {
|
||||
return
|
||||
}
|
||||
// if (user.isPrestiging) {
|
||||
// return say(`Finish prestiging first!`)
|
||||
// }
|
||||
|
||||
const hauntOdds = 0.03
|
||||
const hauntOdds = 0.005
|
||||
const disabledUsers = Object.entries(users).filter(([, user]) => user.isDisabled)
|
||||
|
||||
let haunted = false
|
||||
if (disabledUsers.length !== 0) {
|
||||
if (user.expectingPossession) {
|
||||
if (disabledUsers.length === 0) {
|
||||
user.expectingPossession = false
|
||||
} else {
|
||||
const hauntless = ['!lore']
|
||||
if (user.expectingPossession && !hauntless.includes(commandName)) {
|
||||
console.log(`Haunting ${user.name}`)
|
||||
user.expectingPossession = false
|
||||
//saveGame()
|
||||
haunted = true
|
||||
const [disabledId, disabledUser] = getRandomFromArray(disabledUsers)
|
||||
const [disabledId] = getRandomFromArray(disabledUsers)
|
||||
event.user = disabledId
|
||||
user = getUser(event.user)
|
||||
if (Math.random() < 0.2) {
|
||||
say = slack.buildSayPrepend({ say, prepend: `_You feel haunted..._\n_"Hey, it's me, ${(garble(disabledUser.name))}"_\n` })
|
||||
} else {
|
||||
say = slack.buildSayPrepend({ say, prepend: `_You feel haunted..._\n` })
|
||||
const userInfo = await slack.app.client.users.info({
|
||||
user: disabledId
|
||||
})
|
||||
say = async msg => {
|
||||
let icon_url = userInfo.user.profile.image_original
|
||||
if (cursedPics[event.user]?.length > 0) {
|
||||
icon_url = getRandomFromArray(cursedPics[event.user])
|
||||
}
|
||||
trueSay({
|
||||
text: msg,
|
||||
username: garble(userInfo.user.profile.real_name),
|
||||
icon_url
|
||||
})
|
||||
}
|
||||
} else if (Math.random() < hauntOdds) {
|
||||
user.expectingPossession = true
|
||||
|
@ -390,17 +447,17 @@ const messageHandler = async ({ event, say, isRecycle = false }) => {
|
|||
say = slack.buildSayPrepend({ say, prepend: `_You feel a chill..._\n` })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
user.expectingPossession = false
|
||||
}
|
||||
user.coins = getCoins(event.user)
|
||||
const canUse = await c?.condition({ event, say, words, commandName, args, user, userId: event.user, isAdmin: event.user.includes(slack.users.Sage) })
|
||||
Object.entries(users).forEach(([id, usr]) => usr.coins = getCoins(id))
|
||||
//user.coins = getCoins(event.user)
|
||||
const isAdmin = event.user?.includes(slack.users.Admin)
|
||||
const canUse = await c?.condition({ event, say, words, commandName, args, user, userId: event.user, isAdmin })
|
||||
if (!canUse) {
|
||||
// const matcher = fuzzyMatcher(commandName)
|
||||
// for (const key of commands.keys()) {
|
||||
// if (matcher.test(key)) {
|
||||
// const fetched = commands.get(key)
|
||||
// if (!fetched.hidden && await fetched.condition({ event, say: () => {}, words, commandName, args, user, userId: event.user, isAdmin: event.user.includes(slack.users.Sage) })) {
|
||||
// if (!fetched.hidden && await fetched.condition({ event, say: () => {}, words, commandName, args, user, userId: event.user, isAdmin: event.user.includes(slack.users.Admin) })) {
|
||||
// //return say(`Did you mean '${key}'?`)
|
||||
// }
|
||||
// }
|
||||
|
@ -430,7 +487,7 @@ const messageHandler = async ({ event, say, isRecycle = false }) => {
|
|||
return
|
||||
}
|
||||
|
||||
await c.action({ event, say, trueSay, words, args, commandName, user, userId: event.user, haunted })
|
||||
await c.action({ event, say, trueSay, words, args, commandName, user, userId: event.user, haunted, isAdmin })
|
||||
if (!isRecycle) {
|
||||
const userQuackgrades = (user.quackUpgrades?.lightning || []).map(name => quackStore[name])
|
||||
const defaultOdds = 0.005
|
||||
|
@ -443,7 +500,8 @@ const messageHandler = async ({ event, say, isRecycle = false }) => {
|
|||
setTimeout(() => lightning({ channel: event.channel, say, trueSay, words, user }), 10000)
|
||||
}
|
||||
}
|
||||
saveGame()
|
||||
const endTime = new Date()
|
||||
saveGame(`command ${event.text} finished in ${endTime - startTime}ms`)
|
||||
}
|
||||
|
||||
slack.onReaction(async ({ event }) => {
|
||||
|
@ -517,17 +575,10 @@ const lightning = async ({ user, ms = 5000, channel, multiplier = 1 }) => {
|
|||
channel: message.channel,
|
||||
ts: message.ts,
|
||||
})
|
||||
// await slack.messageSage(`${user.name} failed to bottle some lighting!`)
|
||||
// await slack.messageAdmin(`${user.name} failed to bottle some lighting!`)
|
||||
}, msToBottle)
|
||||
}
|
||||
|
||||
const dedicatedPlayers = [
|
||||
slack.users.Sage,
|
||||
slack.users.Houston,
|
||||
slack.users.Adam,
|
||||
slack.users.Fernando,
|
||||
]
|
||||
|
||||
command(
|
||||
['!bolt'],
|
||||
'Send a lighting strike to the given player.',
|
||||
|
@ -564,11 +615,15 @@ slack.app.action('lightningStrike', async ({ body, ack }) => {
|
|||
const c = getCoins(body.user.id)
|
||||
const user = getUser(body.user.id)
|
||||
const secondsOfCps = seconds => Math.floor(getCPS(user) * seconds)
|
||||
let payout = Math.floor(c * 0.10) + secondsOfCps(60 * 30)
|
||||
let payout = secondsOfCps(60 * 30)
|
||||
if (payout > (0.2 * c)) {
|
||||
payout = 0.2 * c
|
||||
}
|
||||
|
||||
payout = (500 + chaosFilter(payout, 1, user)) * strikes[body.message.ts]
|
||||
addCoins(user, (c + payout) - user.coins)
|
||||
delete strikes[body.message.ts]
|
||||
saveGame()
|
||||
saveGame('user bottled a lightning strike')
|
||||
|
||||
await slack.app.client.chat.update({
|
||||
channel: body.channel.id,
|
||||
|
@ -577,7 +632,7 @@ slack.app.action('lightningStrike', async ({ body, ack }) => {
|
|||
blocks: []
|
||||
})
|
||||
await ack()
|
||||
return slack.messageSage(`Lighting bottled by <@${body.user.id}>`)
|
||||
return slack.messageAdmin(`Lighting bottled by <@${body.user.id}>`)
|
||||
})
|
||||
|
||||
slack.onMessage(async msg => {
|
||||
|
@ -785,7 +840,7 @@ command(
|
|||
command(
|
||||
['!a', '!ach', '!achievements'],
|
||||
'List your glorious achievements',
|
||||
async ({ event, say, user }) => {
|
||||
async ({ event, say, user, args, isAdmin }) => {
|
||||
const achievementCount = Object.keys(user.achievements).length
|
||||
const prefix = `You have ${achievementCount} achievements!\n\n`
|
||||
const mult = (Math.pow(1.01, achievementCount) - 1) * 100
|
||||
|
@ -880,36 +935,49 @@ command(['!cps'],
|
|||
const doMine = async ({ user, userId, say }) => {
|
||||
const random = Math.random()
|
||||
const c = user.coins
|
||||
const secondsOfCps = seconds => Math.floor(getCPS(user) * seconds)
|
||||
const secondsOfCps = (seconds, ceiling) => {
|
||||
const s = Math.floor(getCPS(user) * seconds)
|
||||
const ceil = ceiling * c
|
||||
if (s > ceil) {
|
||||
return ceil
|
||||
}
|
||||
return s
|
||||
}
|
||||
let diff
|
||||
let prefix
|
||||
if (random > 0.9947) {
|
||||
diff = 500 + Math.floor(c * 0.10) + secondsOfCps(60 * 30)
|
||||
diff = 500 + secondsOfCps(60 * 60, 0.2)
|
||||
prefix = `:gem: You found a lucky gem worth ${commas(diff)} HVAC!\n`
|
||||
addAchievement(user, 'luckyGem', say)
|
||||
await slack.messageSage(`${slack.users[userId]} FOUND A LUCKY GEM COIN WORTH ${commas(diff)} HVAC!`)
|
||||
await slack.messageAdmin(`${slack.users[userId]} FOUND A LUCKY GEM COIN WORTH ${commas(diff)} HVAC!`)
|
||||
} else if (random > 0.986) {
|
||||
diff = 50 + Math.floor(c * 0.025) + secondsOfCps(60)
|
||||
diff = 50 + secondsOfCps(60 * 5, 0.1)
|
||||
prefix = `:goldbrick: You found a lucky gold coin worth ${commas(diff)} HVAC!\n`
|
||||
addAchievement(user, 'goldBrick', say)
|
||||
} else if (random > 0.94) {
|
||||
diff = 10 + Math.floor(c * 0.01) + secondsOfCps(10)
|
||||
diff = 10 + secondsOfCps(60, 0.1)
|
||||
prefix = `:money_with_wings: You found a lucky green coin worth ${commas(diff)} HVAC!\n`
|
||||
addAchievement(user, 'greenCoin', say)
|
||||
} else {
|
||||
prefix = 'You mined one HVAC.\n'
|
||||
diff = 1
|
||||
const miningUpgrades = (user.upgrades.mining || []).map(name => upgrades[name])
|
||||
diff = miningUpgrades.reduce((total, upgrade) => upgrade.effect(total, user), 1)
|
||||
console.log({ miningUpgrades, diff, user: user.upgrades.mining })
|
||||
prefix = `You mined ${commas(diff)} HVAC.\n`
|
||||
}
|
||||
addCoins(user, diff)
|
||||
//saveGame()
|
||||
return `${prefix}You now have ${commas(user.coins)} HVAC coin${c !== 1 ? 's' : ''}. Spend wisely.`
|
||||
}
|
||||
|
||||
let lbIndex = 0
|
||||
command(
|
||||
['!c', '!coin', '!mine', '!'],
|
||||
'Mine HVAC coins',
|
||||
async ({ say, user, userId }) => {
|
||||
await say(await doMine({ user, userId, say }))
|
||||
if ((lbIndex++) % 5 == 0) {
|
||||
return updateAllLeaderboards()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -919,10 +987,11 @@ command(
|
|||
async ({ event, args, trueSay }) => {
|
||||
const [impersonating, ...newWords] = args
|
||||
event.user = idFromWord(impersonating)
|
||||
getUser(event.user)
|
||||
const isDisabled = users[event.user].isDisabled
|
||||
users[event.user].isDisabled = false
|
||||
event.text = newWords.join(' ')
|
||||
await messageHandler({ event, say: trueSay, isRecycle: false})
|
||||
await messageHandler({ event, say: trueSay, isRecycle: false, skipCounting: true })
|
||||
users[event.user].isDisabled = isDisabled
|
||||
}, adminOnly)
|
||||
|
||||
|
@ -951,21 +1020,40 @@ command(
|
|||
if (user.isDisabled) {
|
||||
user.isDisabled = false
|
||||
//saveGame()
|
||||
addAchievement(user, 'theOtherSide', slack.messageSage)
|
||||
addAchievement(user, 'theOtherSide', slack.messageAdmin)
|
||||
await slack.postToTechThermostatChannel(`_${user.name} has returned..._`)
|
||||
}
|
||||
}
|
||||
)
|
||||
}, adminOnly)
|
||||
|
||||
command(
|
||||
['!disable'],
|
||||
'Disable the given user',
|
||||
async ({ args }) => {
|
||||
const user = getUser(idFromWord(args[0]))
|
||||
user.isDisabled = true
|
||||
}, adminOnly)
|
||||
|
||||
command(
|
||||
['!g', '!gamble'],
|
||||
'Gamble away your HVAC\n' +
|
||||
' To use, say \'gamble coin_amount\' or \'!gamble all\'',
|
||||
async ({ say, args, user }) => {
|
||||
const requestedWager = parseAll(args.join(' '), user.coins)
|
||||
async ({ say, trueSay, args, user, event }) => {
|
||||
if (event.text?.includes(`y'all`)) {
|
||||
if (event.user !== slack.users.Admin) {
|
||||
return say('Perhaps another time...')
|
||||
}
|
||||
await say(`Gambling the souls of all players...`)
|
||||
setTimeout(async () => {
|
||||
say('You bet the souls of your coworkers and won 1 HVAC!')
|
||||
addCoins(user, 1)
|
||||
}, 25000)
|
||||
return
|
||||
}
|
||||
const argText = args.join(' ')
|
||||
const requestedWager = parseAll(argText, user.coins, user)
|
||||
const n = (chaosFilter(requestedWager, 0.2, user, user.coins) + requestedWager) / 2
|
||||
if (!n || n < 0) {
|
||||
return say(`Invalid number '${n}'`)
|
||||
return say(`Invalid number '${argText}'`)
|
||||
}
|
||||
if (user.coins < n) {
|
||||
return say(`You don't have that many coins! You have ${commas(user.coins)}.`)
|
||||
|
@ -979,6 +1067,9 @@ command(
|
|||
if (n >= 100_000_000_000_000_000) {
|
||||
addAchievement(user, 'mondoBets', say)
|
||||
}
|
||||
if (n >= 100_000_000_000_000_000_000) {
|
||||
addAchievement(user, 'sigmaBets', say)
|
||||
}
|
||||
user.coins -= n
|
||||
let outcome
|
||||
if (Math.random() > 0.5) {
|
||||
|
@ -991,30 +1082,45 @@ command(
|
|||
//saveGame()
|
||||
await say(`You bet ${commas(n)} coins and ${outcome}! You now have ${commas(user.coins)}.`)
|
||||
if (outcome === 'lost' && user.lostBetMessage) {
|
||||
await say(user.lostBetMessage)
|
||||
await trueSay(user.lostBetMessage)
|
||||
} else if (outcome === 'won' && user.wonBetMessage) {
|
||||
await say(user.wonBetMessage)
|
||||
await trueSay(user.wonBetMessage)
|
||||
}
|
||||
return updateAllLeaderboards()
|
||||
}
|
||||
)
|
||||
|
||||
const emojiRegex = /^:[^:\s]*:$/
|
||||
const validEmoji = async emojiText => {
|
||||
emojiText = emojiText?.trim()
|
||||
if (!emojiText || !emojiRegex.test(emojiText)) {
|
||||
return false
|
||||
}
|
||||
const validEmojis = (await getEmojis()).emoji
|
||||
const noColons = emojiText.replace(/:/g, '')
|
||||
|
||||
// console.log('validEmojis', validEmojis)
|
||||
return !!validEmojis[noColons]
|
||||
}
|
||||
const getEmojis = async () => await slack.app.client.emoji.list()
|
||||
|
||||
command(
|
||||
['!setloss'],
|
||||
'!setloss <emoji>',
|
||||
async ({ args, user, say }) => {
|
||||
const emoji = args[0]
|
||||
if (!emoji || !emoji.startsWith(':') || !emoji.endsWith(':') || emoji.includes(' ') || emoji.includes('\n')) {
|
||||
if (!await validEmoji(emoji)) {
|
||||
return say(`Argument must be a single emoji!`)
|
||||
}
|
||||
user.lostBetMessage = emoji
|
||||
}, {hidden: true})
|
||||
|
||||
command(
|
||||
['!setwon'],
|
||||
['!setwon', '!setwin'],
|
||||
'!setwon <emoji>',
|
||||
async ({ args, user, say }) => {
|
||||
const emoji = args[0]
|
||||
if (!emoji || !emoji.startsWith(':') || !emoji.endsWith(':')) {
|
||||
if (!await validEmoji(emoji)) {
|
||||
return say(`Argument must be a single emoji!`)
|
||||
}
|
||||
user.wonBetMessage = emoji
|
||||
|
@ -1052,55 +1158,68 @@ command(
|
|||
await say(upgradeText(user, true))
|
||||
}, dmsOnly)
|
||||
|
||||
const upgradeText2 = user => {
|
||||
const upgradeText2 = (user, extraMessage = '') => {
|
||||
const userDoesNotHave = ([upgradeName, upgrade]) => !hasUpgrade(user, upgrade, upgradeName)
|
||||
const userMeetsCondition = ([, upgrade]) => upgrade.condition(user, getCompletedSquadgradeNames())
|
||||
return ({
|
||||
text: upgradeText(user, false),
|
||||
blocks: Object.entries(upgrades)
|
||||
text: (extraMessage && extraMessage + '\n') + upgradeText(user, false),
|
||||
blocks: [
|
||||
(extraMessage && {
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: extraMessage + '\n'
|
||||
},
|
||||
}),
|
||||
...Object.entries(upgrades)
|
||||
.filter(userDoesNotHave)
|
||||
.filter(userMeetsCondition)
|
||||
.map(([upgradeName]) => upgradeBlock(upgradeName))
|
||||
].filter(block => block)
|
||||
})
|
||||
}
|
||||
|
||||
command(
|
||||
['!upgrade', '!u'],
|
||||
'Improve the performance of your HVAC-generators.\n' +
|
||||
' Say \'!upgrade\' to list available upgrades, or \'!upgrade upgrade_name\' to purchase.',
|
||||
' Say \'!upgrade\' to list available upgrades, or \'!upgrade upgrade_name\' to purchase directly.',
|
||||
async ({ say, args, user }) => {
|
||||
const upgradeName = args[0]
|
||||
if (!upgradeName) {
|
||||
if (!args[0]) {
|
||||
return say(upgradeText2(user))
|
||||
}
|
||||
const upgrade = upgrades[upgradeName]
|
||||
if (!upgrade) {
|
||||
return say('An upgrade with that name does not exist!')
|
||||
console.log({args: args.join(' ')})
|
||||
const matcher = fuzzyMatcher(args.join(' '))
|
||||
const u = Object.entries(upgrades).find(([name, upgrade]) => matcher.test(name) || matcher.test(upgrade.name))
|
||||
if (!u) {
|
||||
return say(`Could not find an upgrade matching "${args.join(' ')}"!`)
|
||||
}
|
||||
const [id, upgrade] = u
|
||||
if (!user.upgrades[upgrade.type]) {
|
||||
user.upgrades[upgrade.type] = []
|
||||
}
|
||||
if (hasUpgrade(user, upgrade, upgradeName)) {
|
||||
return say('You already have that upgrade!')
|
||||
if (hasUpgrade(user, upgrade, id)) {
|
||||
return say(`You already have ${upgrade.name}!`)
|
||||
}
|
||||
if (!upgrade.condition(user, getCompletedSquadgradeNames())) {
|
||||
return say('That item does not exist!')
|
||||
}
|
||||
const c = user.coins
|
||||
if (c < upgrade.cost) {
|
||||
return say(`You don't have enough coins! You have ${commas(c)}, but you need ${commas(upgrade.cost)}`)
|
||||
return say(`You don't have enough coins to buy ${upgrade.name}!\nYou have ${commas(c)}, but you need ${commas(upgrade.cost)}`)
|
||||
}
|
||||
user.coins -= upgrade.cost
|
||||
user.upgrades[upgrade.type].push(upgradeName)
|
||||
user.upgrades[upgrade.type].push(id)
|
||||
//saveGame()
|
||||
await say(`You bought ${upgradeName}!`)
|
||||
await say(`You bought ${id}!`)
|
||||
}, dmsOnly)
|
||||
|
||||
const upgradeBlock = upgradeName => ({
|
||||
const upgradeBlock = upgradeName => {
|
||||
const upgrade = upgrades[upgradeName]
|
||||
return ({
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `${upgradeName} :${(buyableItems[upgrades[upgradeName].type].emoji)}: - H${commas(upgrades[upgradeName].cost)}\n_${upgrades[upgradeName].description}_`
|
||||
text: `${upgrade.name} :${buyableItems[upgrade.type]?.emoji || upgrade.emoji}: - H${commas(upgrade.cost)}\n_${upgrade.description}_`
|
||||
},
|
||||
accessory: {
|
||||
type: 'button',
|
||||
|
@ -1113,6 +1232,7 @@ const upgradeBlock = upgradeName => ({
|
|||
action_id: 'upgrade_' + upgradeName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const upgradeButton = async ({ body, ack, say, payload }) => {
|
||||
await ack()
|
||||
|
@ -1121,16 +1241,19 @@ const upgradeButton = async ({ body, ack, say, payload }) => {
|
|||
const event = {
|
||||
user: body.user.id
|
||||
}
|
||||
const user = getUser(event.user)
|
||||
const user = getUser(event.user, true)
|
||||
const words = ['!upgrade', upgrade]
|
||||
const [commandName, ...args] = words
|
||||
let extraMessage = ''
|
||||
say = async text => extraMessage = text
|
||||
await commands.get('!u').action({ event, say, words, args, commandName, user })
|
||||
//const highestCoins = user.highestEver || user.coins || 1
|
||||
await slack.app.client.chat.update({
|
||||
channel: body.channel.id,
|
||||
ts: body.message.ts,
|
||||
...upgradeText2(user)
|
||||
...upgradeText2(user, extraMessage)
|
||||
})
|
||||
await updateAllLeaderboards()
|
||||
}
|
||||
|
||||
Object.keys(upgrades).forEach(upgradeName => slack.app.action('upgrade_' + upgradeName, upgradeButton))
|
||||
|
@ -1146,15 +1269,13 @@ const getCurrentSquadgrade = () => {
|
|||
}
|
||||
return {
|
||||
name,
|
||||
upgrade,
|
||||
remaining: squad.upgrades[name],
|
||||
emoji: upgrade.emoji,
|
||||
description: upgrade.description
|
||||
}
|
||||
}
|
||||
|
||||
const squadgradeText = ([name, { emoji, description }]) =>
|
||||
`:${emoji}: *${name}*\n_${description}_`
|
||||
|
||||
const squadText = () => {
|
||||
const current = getCurrentSquadgrade()
|
||||
if (current) {
|
||||
|
@ -1163,8 +1284,6 @@ const squadText = () => {
|
|||
return currentUpgradeText(current)
|
||||
}
|
||||
return 'No more squadgrades currently available.'
|
||||
// const squadIsMissing = ([name, upgrade]) => squad.upgrades[name] === false
|
||||
// return squadgradeText(Object.entries(squadUpgrades).find(squadIsMissing))
|
||||
}
|
||||
|
||||
command(
|
||||
|
@ -1181,7 +1300,7 @@ command(
|
|||
return say('No squadgrades are currently available')
|
||||
}
|
||||
const currentCoins = user.coins
|
||||
let amount = parseAll(args.join(' '), currentCoins)
|
||||
let amount = parseAll(args.join(' '), currentCoins, user)
|
||||
if (amount > currentCoins) {
|
||||
return say(`You don't have that much HVAC! You have ${currentCoins}.`)
|
||||
}
|
||||
|
@ -1210,7 +1329,7 @@ command(
|
|||
}
|
||||
)
|
||||
|
||||
const buyRoute = require('./buy')
|
||||
const { buyRoute, leaderboardUpdater } = require('./buy')
|
||||
command(
|
||||
['!buy', '!b', '?b', '?buy'],
|
||||
'Buy new items to earn HVAC with\n' +
|
||||
|
@ -1270,7 +1389,7 @@ command(
|
|||
}
|
||||
let [target, ...amountText] = args
|
||||
amountText = amountText.join(' ')
|
||||
const amount = parseAll(amountText, user.coins)
|
||||
const amount = parseAll(amountText, user.coins, user)
|
||||
const targetId = idFromWord(target)
|
||||
if (!amount || amount < 0) {
|
||||
return say('Amount must be a positive integer!')
|
||||
|
@ -1281,10 +1400,6 @@ command(
|
|||
if (user.coins < amount) {
|
||||
return say(`You don't have that many coins! You have ${commas(user.coins)} HVAC.`)
|
||||
}
|
||||
const date = new Date()
|
||||
if (targetId === slack.users.Nik && date.getFullYear() === 2022 && date.getMonth() === 4) {
|
||||
return say(`Your generosity is appreciated, but let the guy play for a bit!`)
|
||||
}
|
||||
if (amountText === 'all' && slack.users.Tyler === targetId) {
|
||||
addAchievement(user, 'walmartGiftCard', say)
|
||||
}
|
||||
|
@ -1320,7 +1435,7 @@ command(
|
|||
['!gimme'],
|
||||
'Give self x coins',
|
||||
async ({ say, args, user }) => {
|
||||
const increase = parseInt(args[0].replace(/,/g, ''))
|
||||
const increase = parseAll(args.join(' '))
|
||||
addCoins(user, increase)
|
||||
await say(`You now have ${user.coins} HVAC.`)
|
||||
}, testOnly)
|
||||
|
@ -1350,23 +1465,23 @@ const buildPEmoji = name => {
|
|||
return ret
|
||||
}
|
||||
const prestigeEmojis = [
|
||||
buildPEmoji('rock'),
|
||||
buildPEmoji('wood'),
|
||||
buildPEmoji('seedling'),
|
||||
buildPEmoji('evergreen_tree'),
|
||||
buildPEmoji('hibiscus'),
|
||||
buildPEmoji('thunder_cloud_and_rain'),
|
||||
buildPEmoji('rainbow'),
|
||||
buildPEmoji('star'),
|
||||
buildPEmoji('dizzy'),
|
||||
buildPEmoji('sparkles'),
|
||||
buildPEmoji('star2'),
|
||||
buildPEmoji('stars'),
|
||||
buildPEmoji('comet'),
|
||||
buildPEmoji('night_with_stars'),
|
||||
buildPEmoji('milky_way'),
|
||||
buildPEmoji('eye'),
|
||||
]
|
||||
'rock',
|
||||
'wood',
|
||||
'seedling',
|
||||
'evergreen_tree',
|
||||
'hibiscus',
|
||||
'thunder_cloud_and_rain',
|
||||
'rainbow',
|
||||
'star',
|
||||
'dizzy',
|
||||
'sparkles',
|
||||
'star2',
|
||||
'stars',
|
||||
'comet',
|
||||
'night_with_stars',
|
||||
'milky_way',
|
||||
'eye',
|
||||
].map(buildPEmoji)
|
||||
|
||||
const prestigeEmoji = user => {
|
||||
const p = user.prestige || 0
|
||||
|
@ -1388,7 +1503,7 @@ command(
|
|||
`Show the emoji for each prestige level you've been through.`,
|
||||
async ({ say, user, event, args }) => {
|
||||
let p = user.prestige || 0
|
||||
if (event.user === slack.users.Sage && args[0] === 'all') {
|
||||
if (event.user === slack.users.Admin && args[0] === 'all') {
|
||||
p = 99999999
|
||||
}
|
||||
let message = ''
|
||||
|
@ -1402,16 +1517,18 @@ command(
|
|||
}, prestigeOnly)
|
||||
|
||||
command(
|
||||
['!leaderboard', '!lb'],
|
||||
'Show the top HVAC-earners, ranked by prestige, then CPS',
|
||||
async ({ say, user }) => {
|
||||
// if ((event.user === slack.users.Houston || event.user === slack.users.Sage) && event.channel_type.includes('im')) {
|
||||
// return say('```' + `Hvacker - 9 souls - Taking from them whatever it desires\nSome other losers - who cares - whatever` + '```')
|
||||
// }
|
||||
['!pet'],
|
||||
`Take care of the office pet!\nPet bonuses are shared between all players!`,
|
||||
async ({ say, user, event, args }) => {
|
||||
pets.petToText(pet, null, say)
|
||||
})
|
||||
|
||||
const strike = user => user.isDisabled ? '~' : ''
|
||||
|
||||
const generateLeaderboard = ({ args }) => {
|
||||
let index = 1
|
||||
await say(
|
||||
Object.entries(users)
|
||||
.filter(([, user]) => !user.isDisabled && (Object.entries(user.items).length > 0 || user.prestige))
|
||||
return Object.entries(users)
|
||||
.filter(([, user]) => (!user.isDisabled || args[0] === 'all') && (Object.entries(user.items).length > 0 || user.prestige))
|
||||
.sort(([id, user1], [id2, user2]) => {
|
||||
const leftPrestige = getUser(id).prestige
|
||||
const rightPrestige = getUser(id2).prestige
|
||||
|
@ -1423,21 +1540,43 @@ command(
|
|||
}
|
||||
return getCPS(user1) > getCPS(user2)
|
||||
})
|
||||
.map(([id, u]) => `${index++}. ${slack.users[id] || '???'} ${prestigeEmoji(u) || '-'} ${commas(getCPS(getUser(id)))} CPS - ${commas(getCoins(id))} HVAC`)
|
||||
.map(([id, u]) => `${strike(u)}${index++}. ${slack.users[id] || '???'} ${prestigeEmoji(u) || '-'} ${commas(getCPS(getUser(id)))} CPS - ${commas(getCoins(id))} HVAC${strike(u)}`)
|
||||
.join('\n')
|
||||
).then(() => addAchievement(user, 'leaderBoardViewer', say))
|
||||
}
|
||||
|
||||
command(
|
||||
['!leaderboard', '!lb'],
|
||||
'Show the top HVAC-earners, ranked by prestige, then CPS',
|
||||
async ({ say, user, args }) => {
|
||||
// if ((event.user === slack.users.Houston || event.user === slack.users.Admin) && event.channel_type.includes('im')) {
|
||||
// return say('```' + `Hvacker - 9 souls - Taking from them whatever it desires\nSome other losers - who cares - whatever` + '```')
|
||||
// }
|
||||
game.leaderboardChannels ??= {}
|
||||
await say(generateLeaderboard({ args })).then(({ channel, ts }) => {
|
||||
addAchievement(user, 'leaderBoardViewer', say)
|
||||
game.leaderboardChannels[channel] = ts
|
||||
return updateAllLeaderboards({ channel, ts })
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const updateAllLeaderboards = async (add) => {
|
||||
const currentBoard = generateLeaderboard({ args: [] })
|
||||
return updateAll({ name: 'leaderboard', text: currentBoard, add })
|
||||
}
|
||||
|
||||
leaderboardUpdater.updateAllLeaderboards = updateAllLeaderboards
|
||||
|
||||
const hints = []
|
||||
const oneShot = (name, helpText, message, achievementName) => {
|
||||
if (helpText) {
|
||||
hints.push(helpText)
|
||||
}
|
||||
const names = Array.isArray(name) ? name : [name]
|
||||
|
||||
command([name], helpText, async ({ say, user }) => {
|
||||
command(names, helpText, async ({ say, user }) => {
|
||||
await say(message)
|
||||
await slack.messageSage(`Wow buddy they like your ${name} joke.`)
|
||||
await slack.messageAdmin(`Wow buddy they like your ${name} joke.`)
|
||||
if (achievementName) {
|
||||
addAchievement(user, achievementName, say)
|
||||
}
|
||||
|
@ -1449,9 +1588,10 @@ oneShot('!santa', 'Ho ho ho!', '<https://i.imgur.com/dBWgFfQ.png|I\'m Santa Quac
|
|||
oneShot('!sugma', 'Not very original.', ':hvacker_angery:')
|
||||
oneShot('!pog', 'One poggers hvacker', '<https://i.imgur.com/XCg7WDz.png|poggers>')
|
||||
oneShot('!ligma', 'Not very original.', '<https://i.imgur.com/i1YtW7m.png|no>')
|
||||
oneShot('!dab', 'ACTIVATE COOL GUY MODE', '<https://i.imgur.com/FKYdeqo.jpg|I go XD style>', 'certifiedCoolGuy')
|
||||
oneShot(['!dab', '!dabs'], 'ACTIVATE COOL GUY MODE', '<https://i.imgur.com/FKYdeqo.jpg|I go XD style>', 'certifiedCoolGuy')
|
||||
oneShot('!based', 'Sorry, it\'s a little hard to hear you!', '<https://i.imgur.com/IUX6R26.png|What?>')
|
||||
oneShot('!shrek', 'Is love and is life.', '<https://i.imgur.com/QwuCQZA.png|Donkey!>')
|
||||
oneShot('!sugondese', 'I don\'t like you.', '<https://i.imgur.com/VCvfvdz.png|rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr>')
|
||||
// oneShot('!imaginedragons', 'The worst part about any group of white people is that they could be Imagine Dragons and you\'d have no way of knowing.', '<https://i.imgur.com/QwuCQZA.png|Donkey!>')
|
||||
|
||||
command(
|
||||
|
@ -1493,13 +1633,27 @@ command(
|
|||
'Confirm your prestige activation.',
|
||||
prestige.prestigeConfirmRoute, { hidden: true })
|
||||
|
||||
command(
|
||||
['!pbeta'],
|
||||
'Confirm your prestige activation.',
|
||||
async ({ event, say, user, YEET }) => {
|
||||
await prestige.prestigeConfirmRoute({ event, say, user, YEET: true})
|
||||
}, { hidden: true })
|
||||
|
||||
command(
|
||||
['!quack', '!quackstore'],
|
||||
'Spend your prestigious quackings\n\n' +
|
||||
'Say \'!quack upgrade_name\' to purchase a quackgrade',
|
||||
prestige.quackStoreRoute,
|
||||
prestigeOnly
|
||||
)
|
||||
'Confirm your prestige activation.',
|
||||
async ({ event, say, user, YEET }) => {
|
||||
await prestige.prestigeConfirmRoute({ event, say, user, YEET: true})
|
||||
}, prestigeOnly)
|
||||
|
||||
// command(
|
||||
// ['!quack', '!quackstore'],
|
||||
// 'Spend your prestigious quackings\n\n' +
|
||||
// 'Say \'!quack upgrade_name\' to purchase a quackgrade',
|
||||
// prestige.quackStoreRoute,
|
||||
// prestigeOnly
|
||||
// )
|
||||
|
||||
command(
|
||||
['!myquack', '!myq'],
|
||||
|
@ -1592,7 +1746,7 @@ command(
|
|||
return say('What, are you trying to steal from yourself? What, are you stupid?')
|
||||
}
|
||||
if (!targetId) {
|
||||
targetId = slack.users.Sage
|
||||
targetId = slack.users.Admin
|
||||
}
|
||||
if (user.coins < amount) {
|
||||
return
|
||||
|
@ -1609,7 +1763,7 @@ command(
|
|||
async ({ event, say, user }) => {
|
||||
const currentDate = new Date().getDate()
|
||||
const lastLotto = user.lastLotto === undefined ? currentDate - 1 : user.lastLotto
|
||||
if (lastLotto === currentDate && event.user !== slack.users.Sage) {
|
||||
if (lastLotto === currentDate && event.user !== slack.users.Admin) {
|
||||
return say('Hey, only one lotto ticket per day, alright?')
|
||||
}
|
||||
let msg
|
||||
|
@ -1635,8 +1789,8 @@ command(
|
|||
command(
|
||||
['!giveach'],
|
||||
'!giveach @player ach_name',
|
||||
async ({ args, say, user }) => {
|
||||
addAchievement(user, args[1], say)
|
||||
async ({ args, say, }) => {
|
||||
addAchievement(getUser(idFromWord(args[0])), args[1], say)
|
||||
//saveGame()
|
||||
}, adminOnly)
|
||||
|
||||
|
@ -1646,6 +1800,45 @@ command(
|
|||
async ({ args, say }) => say(`<@${args[0]}>`),
|
||||
adminOnly)
|
||||
|
||||
command(
|
||||
['!!postas'],
|
||||
'',
|
||||
async ({ args }) => {
|
||||
let channel = slack.temperatureChannelId
|
||||
if (args[0].startsWith('<#')) {
|
||||
channel = args[0].substring(2, args[0].indexOf('|'))
|
||||
const [, ...rest] = args
|
||||
args = rest
|
||||
}
|
||||
if (args[0].startsWith('<@')) {
|
||||
channel = args[0].substring(2, args[0].length - 1)
|
||||
const [, ...rest] = args
|
||||
args = rest
|
||||
}
|
||||
|
||||
console.log({args, channel})
|
||||
const target = idFromWord(args[0])
|
||||
const [, ...rest] = args
|
||||
const userInfo = await slack.app.client.users.info({
|
||||
user: target
|
||||
})
|
||||
console.log(userInfo)
|
||||
return slack.app.client.chat.postMessage({
|
||||
channel,
|
||||
text: rest.join(' '),
|
||||
username: userInfo.user.profile.real_name,
|
||||
icon_url: userInfo.user.profile.image_original
|
||||
})
|
||||
},
|
||||
adminOnly)
|
||||
|
||||
command(
|
||||
['!!startpoll'],
|
||||
'',
|
||||
async ({ args, say }) => {
|
||||
},
|
||||
adminOnly)
|
||||
|
||||
command(
|
||||
['!ngift'],
|
||||
'!ngift player_id nft_name',
|
||||
|
@ -1741,20 +1934,21 @@ const updateStonkPrices = () => {
|
|||
for (let i = stonkMarket.lastDay; i < today; i++) {
|
||||
console.log('set lastPrice')
|
||||
stonk.lastPrice = stonk.price
|
||||
console.log(stonk.pattern, stonkPatterns)
|
||||
stonk.price *= 1 + (stonkPatterns[stonk.pattern][stonk.index] / 100)
|
||||
stonk.index++
|
||||
if (stonk.index >= stonkPatterns[stonk.pattern].length) {
|
||||
if (stonk.index >= stonkPatterns[stonk.pattern]?.length) {
|
||||
stonk.index = 0
|
||||
stonk.pattern = nextPattern(stonk.pattern)
|
||||
}
|
||||
}
|
||||
})
|
||||
stonkMarket.lastDay = today
|
||||
//saveGame(true)
|
||||
//saveGame(null, true)
|
||||
}
|
||||
|
||||
const buyStonks = (user, stonkName, quantityPhrase) => {
|
||||
const quantity = parseAll(quantityPhrase, Math.floor(user.coins / stonkMarket.stonks[stonkName].price))
|
||||
const quantity = parseAll(quantityPhrase, Math.floor(user.coins / stonkMarket.stonks[stonkName].price), user)
|
||||
if (!quantity) {
|
||||
return 'Quantity must be positive integer!'
|
||||
}
|
||||
|
@ -1773,7 +1967,7 @@ const buyStonks = (user, stonkName, quantityPhrase) => {
|
|||
const sellStonks = (user, stonkName, quantityPhrase) => {
|
||||
user.holdings ??= {}
|
||||
user.holdings[stonkName] ??= 0
|
||||
const quantity = parseAll(quantityPhrase, user.holdings[stonkName])
|
||||
const quantity = parseAll(quantityPhrase, user.holdings[stonkName], user)
|
||||
if (!quantity) {
|
||||
return 'Quantity must be positive integer!'
|
||||
}
|
||||
|
@ -1847,10 +2041,16 @@ command(
|
|||
if (event.text.length > 64 + '!speak '.length) {
|
||||
return say(`That message is too long! You get 64 characters, but you gave me ${event.text.length - '!speak'.length}!`)
|
||||
}
|
||||
if (event.text.length > 64 + '!speak '.length) {
|
||||
return say(`That message is too long! You get 64 characters, but you gave me ${event.text.length - '!speak'.length}!`)
|
||||
}
|
||||
if (event.text.includes('<@')) {
|
||||
return say(`Please don't @ people when using my voice, you're just gonna get me in trouble.`)
|
||||
}
|
||||
user.lastSpeech = today
|
||||
const message = event.text.substring(7)
|
||||
await slack.postToTechThermostatChannel(message)
|
||||
//saveGame(true)
|
||||
//saveGame(null, true)
|
||||
}, {
|
||||
hidden: true,
|
||||
condition: ({ user }) => userHasCheckedQuackgrade(user, 'theVoice')
|
||||
|
@ -1882,7 +2082,7 @@ command(
|
|||
const achName = args.join(' ')
|
||||
const matcher = fuzzyMatcher(achName)
|
||||
const achievement = Object.entries(achievements)
|
||||
.find(([name, ach]) => matcher.test(ach.name) || matcher.test(name))
|
||||
.find(([name, ach]) => [name, ach.name].some(matcher.test))
|
||||
if (!achievement || !user.achievements[achievement[0]]) {
|
||||
return say(`You don't have any achievement matching '${achName}'`)
|
||||
}
|
||||
|
@ -1896,4 +2096,19 @@ command(
|
|||
{ hidden: true }
|
||||
)
|
||||
|
||||
webapi.launch()
|
||||
command(
|
||||
['!user-list'],
|
||||
'Lists all users',
|
||||
async ({ say }) => {
|
||||
const users = await slack.app.client.users.list()
|
||||
console.log(users.members.filter(m => m.real_name?.toLowerCase().includes('nik')))
|
||||
say('k')
|
||||
}, adminOnly
|
||||
)
|
||||
|
||||
//webapi.launch()
|
||||
|
||||
module.exports = {
|
||||
command,
|
||||
adminOnly
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const { setHighestCoins, addAchievement, chaosFilter, commas, saveGame, getUser } = require('./utils')
|
||||
const { addAchievement, saveGame, getUser } = require('./utils')
|
||||
const slack = require('../../slack')
|
||||
|
||||
let loreCount = 0
|
||||
|
@ -41,6 +41,7 @@ const lore = [
|
|||
l(`https://i.imgur.com/eFreg7Y.gif\n`),
|
||||
|
||||
//l(`As you might imagine, the ninth egg was I, the almighty Hvacker.`)
|
||||
// In due time, the ninth egg (I, the almighty Hvacker)
|
||||
]
|
||||
|
||||
slack.onReaction(async ({ event, say }) => {
|
||||
|
@ -61,7 +62,7 @@ slack.onReaction(async ({ event, say }) => {
|
|||
console.log('lore:', lore[user.lore])
|
||||
await say(lore[user.lore].correctResponse)
|
||||
user.lore += 1
|
||||
saveGame()
|
||||
saveGame(`updating ${user.name}'s lore counter`)
|
||||
} catch (e) {console.error('onReaction error', e)}
|
||||
})
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
const { commas, quackGradeMultiplier, prestigeMultiplier, makeBackup, userHasCheckedQuackgrade } = require('./utils')
|
||||
const { commas, quackGradeMultiplier, prestigeMultiplier, makeBackup, userHasCheckedQuackgrade, getUser } = require('./utils')
|
||||
const { quackStore } = require('./quackstore')
|
||||
const buyableItems = require('./buyableItems')
|
||||
const slack = require('../../slack')
|
||||
|
||||
const possiblePrestige = coins => {
|
||||
let p = 0
|
||||
|
@ -26,17 +28,27 @@ const prestigeRoute = async ({ say, args, user }) => {
|
|||
'Say \'!!prestige me\' to confirm.'
|
||||
)
|
||||
} else {
|
||||
const currentCost = totalCostForPrestige(possible)
|
||||
const nextCost = totalCostForPrestige(possible + 1)
|
||||
const diff = nextCost - currentCost
|
||||
const progress = user.coinsAllTime - currentCost
|
||||
const bars = Math.round((progress / diff) * 10)
|
||||
const empty = 10 - bars
|
||||
const progressBar = '[' + '='.repeat(bars) + ' '.repeat(empty) + ']'
|
||||
await say(
|
||||
`Current Prestige: ${commas(current)}\n\n` +
|
||||
`Quacks gained if you prestige now: ${commas(possible - current)}\n\n` +
|
||||
`HVAC until next quack: ${commas(totalCostForPrestige(possible + 1) - user.coinsAllTime)}\n\n` +
|
||||
`Next quack progress: \`${progressBar} ${commas(diff)} \`\n\n` +
|
||||
'Say \'!prestige me\' to start the prestige process.' +
|
||||
`\n\nYour prestige is currently boosting your CPS by ${commas((prestigeMultiplier(user) - 1) * 100)}%`
|
||||
)
|
||||
}
|
||||
}//, true, adminOnly)
|
||||
|
||||
const prestigeConfirmRoute = async ({ event, say, user }) => {
|
||||
const prestigeConfirmRoute = async ({ event, say, user, YEET }) => {
|
||||
if (YEET) {
|
||||
return say(prestigeMenu(user))
|
||||
}
|
||||
const possible = possiblePrestige(user.coinsAllTime)
|
||||
const current = user.prestige
|
||||
if (possible <= current) {
|
||||
|
@ -49,18 +61,23 @@ const prestigeConfirmRoute = async ({ event, say, user }) => {
|
|||
}
|
||||
await makeBackup()
|
||||
|
||||
user.isPrestiging = true
|
||||
|
||||
user.quacks ??= 0
|
||||
user.quacks += (possible - user.prestige)
|
||||
|
||||
user.prestige = possible
|
||||
user.highestEver = 0
|
||||
user.coins = 0
|
||||
user.items = {};
|
||||
user.items = {}
|
||||
user.holdings = {}
|
||||
const starterUpgrades = (user.quackUpgrades?.starter || [])
|
||||
starterUpgrades.forEach(upgradeName => quackStore[upgradeName].effect(user))
|
||||
user.upgrades = {}
|
||||
|
||||
await say('You prestiged! Check out !quackstore to see what you can buy!')
|
||||
await say(prestigeMenu(user))
|
||||
await say(`Say !quack _upgrade-name_ to purchase new quackgrades!`)
|
||||
//await say('You prestiged! Check out !quackstore to see what you can buy!')
|
||||
}
|
||||
|
||||
const quackStoreListing = (showCost = true) => ([name, upgrade]) =>
|
||||
|
@ -75,7 +92,6 @@ const hasPreReqs = user => ([name, upgrade]) => {
|
|||
return true
|
||||
}
|
||||
const allUserUpgrades = allUserQuackUpgrades(user)
|
||||
console.log('allUserUpgrades', allUserUpgrades)
|
||||
return upgrade.preReqs.every(preReq => allUserUpgrades.includes(preReq))
|
||||
}
|
||||
|
||||
|
@ -93,19 +109,18 @@ const quackStoreText = user =>
|
|||
`\n\nYou have ${user.quacks ??= 0} quacks to spend.` +
|
||||
`\nQuackStore upgrades are currently boosting your CPS by ${commas((quackGradeMultiplier(user) - 1) * 100)}%`
|
||||
|
||||
const quackStoreRoute = async ({ user, say, args }) => {
|
||||
const quackStoreRoute = async ({ user, say, args, YEET }) => {
|
||||
user.quackUpgrades ??= {}
|
||||
const quacks = user.quacks ??= 0
|
||||
if (!args[0]) {
|
||||
if (!args[0] || !YEET) {
|
||||
await say(quackStoreText(user))
|
||||
return
|
||||
}
|
||||
console.log(`Trying to buy ${args[0]}`)
|
||||
const quackItem = quackStore[args[0]]
|
||||
if (!quackItem || !unownedQuackItems(user).find(([name]) => name === args[0])) {
|
||||
await say(`'${args[0]}' is not available in the quack store!`)
|
||||
return
|
||||
}
|
||||
const quacks = user.quacks ??= 0
|
||||
if (quackItem.cost > quacks) {
|
||||
await say(`${args[0]} costs ${quackItem.cost} Quacks, but you only have ${quacks}!`)
|
||||
return
|
||||
|
@ -117,7 +132,151 @@ const quackStoreRoute = async ({ user, say, args }) => {
|
|||
quackItem.effect(user)
|
||||
}
|
||||
await say(`You bought ${args[0]}!`)
|
||||
//saveGame()
|
||||
}
|
||||
|
||||
const buyQuackGradeButton = quackgrade => {
|
||||
//console.log('buyQuackGradeButton', quackgrade[1])
|
||||
const [name, object] = quackgrade
|
||||
return {
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `:${object.emoji}: ${object.name} - ${object.cost} Quacks\n_${object.description}_`
|
||||
},
|
||||
accessory: {
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'Buy',
|
||||
emoji: true
|
||||
},
|
||||
value: 'click_me_123',
|
||||
action_id: `buy-quackgrade-${name}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const prestigeMenu = (user, extraMessage = '') => {
|
||||
user.quackUpgrades ??= {}
|
||||
const quacks = user.quacks ??= 0
|
||||
return {
|
||||
text: 'Prestige menu',
|
||||
blocks: [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `${extraMessage && extraMessage + '\n'}\n_You have ${quacks} quacks to spend._`
|
||||
// text: `${extraMessage && extraMessage + '\n'}_*PRESTIGE IN PROGRESS*_\n_You have ${quacks} quacks to spend. You may ONLY spend quacks on this menu._`
|
||||
}
|
||||
},
|
||||
// {
|
||||
// type: 'section',
|
||||
// text: {
|
||||
// type: 'mrkdwn',
|
||||
// text: '~Challenge mode~ _TODO_'
|
||||
// },
|
||||
// accessory: {
|
||||
// type: 'static_select',
|
||||
// placeholder: {
|
||||
// type: 'plain_text',
|
||||
// text: 'No challenge',
|
||||
// emoji: true
|
||||
// },
|
||||
// options: [
|
||||
// {
|
||||
// text: {
|
||||
// type: 'plain_text',
|
||||
// text: 'No challenge',
|
||||
// emoji: true
|
||||
// },
|
||||
// value: 'no-challenge'
|
||||
// },
|
||||
// /*
|
||||
// {
|
||||
// text: {
|
||||
// type: 'plain_text',
|
||||
// text: 'Clean Start (no prestige bonuses)',
|
||||
// emoji: true
|
||||
// },
|
||||
// value: 'clean-start'
|
||||
// }*/
|
||||
// ],
|
||||
// action_id: 'challenge_select-action'
|
||||
// }
|
||||
// },
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: '*Available Quackgrades:*'
|
||||
}
|
||||
},
|
||||
...unownedQuackItems(user).filter(hasPreReqs(user)).map(buyQuackGradeButton),
|
||||
// {
|
||||
// type: 'actions',
|
||||
// elements: [
|
||||
// {
|
||||
// type: 'button',
|
||||
// text: {
|
||||
// type: 'plain_text',
|
||||
// text: 'Complete Prestige',
|
||||
// emoji: true
|
||||
// },
|
||||
// value: 'complete_prestige',
|
||||
// action_id: 'complete_prestige'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const buyQuackGrade = async ({ body, ack, say, trueSay, payload }) => {
|
||||
await ack()
|
||||
const buying = payload.action_id.substring('buy-quackgrade-'.length)
|
||||
|
||||
console.log(`buyQuackGrade ${buying} clicked`)
|
||||
const user = getUser(body.user.id)
|
||||
// if (!user.isPrestiging) {
|
||||
// console.log('You must be prestiging!')
|
||||
// return say(`You must be prestiging to use this menu!`)
|
||||
// }
|
||||
const words = ['', buying, body.actions[0].text]
|
||||
const [, ...args] = words
|
||||
|
||||
let extraMessage = ''
|
||||
//say = async text => extraMessage = text
|
||||
console.log('quackStoreRoute')
|
||||
await quackStoreRoute({ say, args, user, YEET: true })
|
||||
|
||||
await slack.app.client.chat.update({
|
||||
channel: body.channel.id,
|
||||
ts: body.message.ts,
|
||||
...prestigeMenu(user, extraMessage)
|
||||
})
|
||||
}
|
||||
|
||||
Object.keys(quackStore).forEach(itemName => slack.app.action('buy-quackgrade-' + itemName, buyQuackGrade))
|
||||
|
||||
slack.app.action('complete_prestige', async ({ body, ack, say }) => {
|
||||
await ack()
|
||||
|
||||
const user = getUser(body.user.id)
|
||||
delete user.isPrestiging
|
||||
|
||||
await slack.app.client.chat.delete({
|
||||
channel: body.channel.id,
|
||||
ts: body.message.ts,
|
||||
})
|
||||
|
||||
await say(`Prestige complete!`)
|
||||
})
|
||||
|
||||
const prestigeMenuRoute = async ({ say, user }) => {
|
||||
user.quackUpgrades ??= {}
|
||||
user.quacks ??= 0
|
||||
await say(prestigeMenu(user))
|
||||
}
|
||||
|
||||
const ownedQuacksText = user =>
|
||||
|
@ -137,5 +296,6 @@ module.exports = {
|
|||
quackStoreRoute,
|
||||
prestigeRoute,
|
||||
prestigeConfirmRoute,
|
||||
prestigeMenuRoute,
|
||||
ownedQuacksRoute
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ const getRandomFromArray = array => array[Math.floor(Math.random() * array.lengt
|
|||
const chaosCpsMods = [3, 2, 0.1, 1, 1.5, 1.6, 0, 1.1, 1.1, 1.26]
|
||||
const chaosAvg = () => chaosCpsMods.reduce((total, next) => total + next, 0) / chaosCpsMods.length
|
||||
//const getChaos = offset => chaosCpsMods[(Math.floor(new Date().getSeconds() / chaosCpsMods.length) + offset) % chaosCpsMods.length]
|
||||
const getChaos = offset => chaosCpsMods[(Math.floor(new Date().getSeconds() / chaosCpsMods.length)) % chaosCpsMods.length]
|
||||
const getChaos = offset => chaosCpsMods[(Math.floor(new Date().getSeconds() / chaosCpsMods.length) + offset) % chaosCpsMods.length]
|
||||
|
||||
const quackStore = {
|
||||
ascent: {
|
||||
|
@ -18,7 +18,7 @@ const quackStore = {
|
|||
name: 'Nuclear Fuel',
|
||||
type: 'cps',
|
||||
emoji: 'atom_symbol',
|
||||
description: 'The future is now. Boosts all CPS by 20%.',
|
||||
description: 'The future is now, old man. Boosts all CPS by 20%.',
|
||||
preReqs: ['ascent'],
|
||||
effect: cps => cps * 1.2,
|
||||
cost: 5
|
||||
|
@ -70,12 +70,12 @@ const quackStore = {
|
|||
type: 'starter',
|
||||
emoji: 'baby_symbol',
|
||||
description: 'Start each prestige with 5 mice',
|
||||
preReqs: ['dryerSheet', 'chaos'],
|
||||
preReqs: ['ascent'],
|
||||
effect: user => {
|
||||
user.items.mouse ??= 0
|
||||
user.items.mouse += 5
|
||||
},
|
||||
cost: 5
|
||||
cost: 4
|
||||
},
|
||||
|
||||
silverSpoon: {
|
||||
|
@ -88,11 +88,11 @@ const quackStore = {
|
|||
user.items.accountant ??= 0
|
||||
user.items.accountant += 5
|
||||
},
|
||||
cost: 10
|
||||
cost: 16
|
||||
},
|
||||
|
||||
oceanMan: {
|
||||
name: 'Ocean Man',
|
||||
sharkBoy: {
|
||||
name: 'Shark Boy',
|
||||
type: 'starter',
|
||||
emoji: 'ocean',
|
||||
description: 'Start each prestige with 5 whales',
|
||||
|
@ -101,8 +101,73 @@ const quackStore = {
|
|||
user.items.whale ??= 0
|
||||
user.items.whale += 5
|
||||
},
|
||||
cost: 20
|
||||
}
|
||||
cost: 64
|
||||
},
|
||||
|
||||
superClumpingLitter: {
|
||||
name: 'Super-Clumping Cat Litter',
|
||||
type: 'pet',
|
||||
emoji: 'smirk_cat',
|
||||
description: 'Extra-strength pet effects',
|
||||
preReqs: ['sharkBoy'],
|
||||
effect: petMultiplier => {
|
||||
petMultiplier = Math.max(petMultiplier, 1)
|
||||
return petMultiplier * petMultiplier
|
||||
},
|
||||
cost: 128
|
||||
},
|
||||
|
||||
magnetMan: {
|
||||
name: 'Magnet Man',
|
||||
type: 'starter',
|
||||
emoji: 'magnet',
|
||||
description: 'Start each prestige with 5 Trains',
|
||||
preReqs: ['sharkBoy'],
|
||||
effect: user => {
|
||||
user.items.train ??= 0
|
||||
user.items.train += 5
|
||||
},
|
||||
cost: 256
|
||||
},
|
||||
|
||||
catFan: {
|
||||
name: 'Cat Fan',
|
||||
type: 'pet',
|
||||
emoji: 'cat',
|
||||
description: 'Super extra-strength pet effects',
|
||||
preReqs: ['magnetMan', 'superClumpingLitter'],
|
||||
effect: petMultiplier => {
|
||||
petMultiplier = Math.max(petMultiplier, 1)
|
||||
return petMultiplier * petMultiplier
|
||||
},
|
||||
cost: 512
|
||||
},
|
||||
|
||||
lavaGirl: {
|
||||
name: 'Lava Girl',
|
||||
type: 'starter',
|
||||
emoji: 'volcano',
|
||||
description: 'Start each prestige with 5 Fire',
|
||||
preReqs: ['magnetMan'],
|
||||
effect: user => {
|
||||
user.items.fire ??= 0
|
||||
user.items.fire += 5
|
||||
},
|
||||
cost: 1024
|
||||
},
|
||||
|
||||
aussie: {
|
||||
name: 'Aussie',
|
||||
type: 'starter',
|
||||
emoji: 'flag-au',
|
||||
description: 'Start each prestige with 5 Boomerangs',
|
||||
preReqs: ['lavaGirl'],
|
||||
effect: user => {
|
||||
user.items.boomerang ??= 0
|
||||
user.items.boomerang += 5
|
||||
},
|
||||
cost: 4096
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
const basic = ({ type, description, count, cost, extraCondition = () => true, effect = cps => cps * 2 }) => ({
|
||||
const { getCPS, setUpgrades } = require('./utils');
|
||||
|
||||
const basic = ({ name, type, description, count, cost, extraCondition = () => true, effect = cps => cps * 2 }) => ({
|
||||
name,
|
||||
type,
|
||||
description,
|
||||
condition: (user, squadGrades) => user.items[type] >= count && extraCondition(user, squadGrades),
|
||||
|
@ -6,7 +9,8 @@ const basic = ({ type, description, count, cost, extraCondition = () => true, ef
|
|||
effect
|
||||
})
|
||||
|
||||
const evil = ({ type, description, cost }) => basic({
|
||||
const evil = ({ name, type, description, cost }) => basic({
|
||||
name,
|
||||
type,
|
||||
description,
|
||||
count: 40,
|
||||
|
@ -14,7 +18,8 @@ const evil = ({ type, description, cost }) => basic({
|
|||
extraCondition: (user, squadGrades) => squadGrades?.includes('discardHumanMorals'),
|
||||
})
|
||||
|
||||
const heavenly = ({ type, description, cost, multiplier = 2 }) => ({
|
||||
const heavenly = ({ name, type, description, cost, multiplier = 2 }) => ({
|
||||
name,
|
||||
type,
|
||||
description,
|
||||
condition: (user, squadGrades) => user.items[type] >= 60 && squadGrades?.includes('redemption'),
|
||||
|
@ -24,7 +29,8 @@ const heavenly = ({ type, description, cost, multiplier = 2 }) => ({
|
|||
|
||||
const disabled = () => false
|
||||
|
||||
const baby = ({ type, description, cost }) => basic({
|
||||
const baby = ({ name, type, description, cost }) => basic({
|
||||
name,
|
||||
type,
|
||||
description,
|
||||
count: 70,
|
||||
|
@ -32,7 +38,8 @@ const baby = ({ type, description, cost }) => basic({
|
|||
extraCondition: disabled
|
||||
})
|
||||
|
||||
const geometry = ({ type, description, cost }) => basic({
|
||||
const geometry = ({ name, type, description, cost }) => basic({
|
||||
name,
|
||||
type,
|
||||
description,
|
||||
count: 100,
|
||||
|
@ -40,7 +47,8 @@ const geometry = ({ type, description, cost }) => basic({
|
|||
extraCondition: disabled
|
||||
})
|
||||
|
||||
const universitality = ({ type, description, cost }) => basic({
|
||||
const universitality = ({ name, type, description, cost }) => basic({
|
||||
name,
|
||||
type,
|
||||
description,
|
||||
count: 100,
|
||||
|
@ -50,134 +58,157 @@ const universitality = ({ type, description, cost }) => basic({
|
|||
|
||||
module.exports = {
|
||||
doubleClick: basic({
|
||||
name: 'Double-Click',
|
||||
type: 'mouse',
|
||||
description: 'Doubles the power of mice',
|
||||
count: 1,
|
||||
cost: 1_000
|
||||
}),
|
||||
stinkierCheese: basic({
|
||||
name: 'Stinkier Cheese',
|
||||
type: 'mouse',
|
||||
description: 'Mice are doubly motivated to hunt down HVAC Coins',
|
||||
count: 10,
|
||||
cost: 21_000
|
||||
}),
|
||||
biggerTeeth: basic({
|
||||
name: 'Bigger Teeth',
|
||||
type: 'mouse',
|
||||
description: 'Mice can intimidate twice as much HVAC out of their victims.',
|
||||
count: 25,
|
||||
cost: 50_000
|
||||
}),
|
||||
rats: evil({
|
||||
name: 'Rats',
|
||||
type: 'mouse',
|
||||
description: 'Consume the rotten remains of your foes',
|
||||
cost: 150_000,
|
||||
}),
|
||||
hoodedMice: heavenly({
|
||||
name: 'Hooded Mice',
|
||||
type: 'mouse',
|
||||
description: 'These monks have nearly reached enlightenment. 10x Mouse CPS.',
|
||||
cost: 1_000_000,
|
||||
multiplier: 10,
|
||||
}),
|
||||
babyMouse: baby({
|
||||
name: 'Baby Mouse',
|
||||
type: 'mouse',
|
||||
description: 'Squeak!',
|
||||
cost: 6_000_000,
|
||||
}),
|
||||
|
||||
fasterComputers: basic({
|
||||
name: 'Faster Computers',
|
||||
type: 'accountant',
|
||||
description: 'Accountants can ~steal~ optimize twice as much HVAC!',
|
||||
count: 1,
|
||||
cost: 11_000,
|
||||
}),
|
||||
lackOfMorality: basic({
|
||||
name: 'Lack of Morality',
|
||||
type: 'accountant',
|
||||
description: 'Accountants are taking a hint from nearby CEOs.',
|
||||
count: 10,
|
||||
cost: 200_000,
|
||||
}),
|
||||
widerBrains: basic({
|
||||
name: 'Wider Brains',
|
||||
type: 'accountant',
|
||||
description: 'For accountant do double of thinking.',
|
||||
count: 25,
|
||||
cost: 550_000,
|
||||
}),
|
||||
vastLayoffs: evil({
|
||||
name: 'Vast Layoffs',
|
||||
type: 'accountant',
|
||||
description: 'The weak are not part of our future.',
|
||||
cost: 2_450_000,
|
||||
}),
|
||||
charityFund: heavenly({
|
||||
name: 'Charity Fund',
|
||||
type: 'accountant',
|
||||
description: 'THIS one is more than just a tax break. 9x Accountant CPS.',
|
||||
cost: 16_333_333,
|
||||
multiplier: 9,
|
||||
}),
|
||||
mathBaby: baby({
|
||||
name: 'Math Baby',
|
||||
type: 'accountant',
|
||||
description: '2 + 2 = WAAH!',
|
||||
cost: 99_999_999,
|
||||
}),
|
||||
|
||||
biggerBlowhole: basic({
|
||||
name: 'Bigger Blowhole',
|
||||
type: 'whale',
|
||||
description: 'With all that extra air, whales have double power!',
|
||||
count: 1,
|
||||
cost: 120_000
|
||||
}),
|
||||
sassyWhales: basic({
|
||||
name: 'Sassy Whales',
|
||||
type: 'whale',
|
||||
description: 'These are the kind of whales that know how to get twice as much done',
|
||||
count: 10,
|
||||
cost: 3_000_000
|
||||
}),
|
||||
thinnerWater: basic({
|
||||
name: 'Thinner Water',
|
||||
type: 'whale',
|
||||
description: 'Whales can move twice as quickly through this physics-defying liquid',
|
||||
count: 25,
|
||||
cost: 6_000_000
|
||||
}),
|
||||
blightWhales: evil({
|
||||
name: 'Blight Whales',
|
||||
type: 'whale',
|
||||
description: `Infectious with evil, they swim the ocean spreading their spores.`,
|
||||
cost: 24_000_000
|
||||
}),
|
||||
whaleChoir: heavenly({
|
||||
name: 'Whale Choir',
|
||||
type: 'whale',
|
||||
description: `Their cleansing songs reverberate through the sea. 8x Whale CPS.`,
|
||||
cost: 144_000_000,
|
||||
multiplier: 8,
|
||||
}),
|
||||
smolWhales: baby({
|
||||
name: 'Smol Whales',
|
||||
type: 'whale',
|
||||
description: ``,
|
||||
cost: 8_400_000_000
|
||||
}),
|
||||
|
||||
greasyTracks: basic({
|
||||
name: 'Greasy Tracks',
|
||||
type: 'train',
|
||||
description: 'Lets trains deliver HVAC twice as efficiently',
|
||||
count: 1,
|
||||
cost: 1_300_000
|
||||
}),
|
||||
rocketThrusters: basic({
|
||||
name: 'Rocket Thrusters',
|
||||
type: 'train',
|
||||
description: 'That\'ll put some quack on your track',
|
||||
count: 10,
|
||||
cost: 22_000_000
|
||||
}),
|
||||
loudConductors: basic({
|
||||
name: 'Loud Conductors',
|
||||
type: 'train',
|
||||
description: 'Conductors can onboard twice as much HVAC',
|
||||
count: 25,
|
||||
cost: 65_000_000
|
||||
}),
|
||||
hellTrain: evil({
|
||||
name: 'Hell Train',
|
||||
type: 'train',
|
||||
description: 'Shipping blood needed for the ritual.',
|
||||
cost: 370_000_000
|
||||
}),
|
||||
toyTrain: heavenly({
|
||||
name: 'Toy Train',
|
||||
type: 'train',
|
||||
description: 'Toot toot! 8x Train CPS.',
|
||||
multiplier: 8,
|
||||
|
@ -185,64 +216,75 @@ module.exports = {
|
|||
}),
|
||||
|
||||
gasolineFire: basic({
|
||||
name: 'Gasoline Fire',
|
||||
type: 'fire',
|
||||
description: 'Extremely good for breathing in.',
|
||||
count: 1,
|
||||
cost: 14_000_000
|
||||
}),
|
||||
extremelyDryFuel: basic({
|
||||
name: 'Extremely Dry Fuel',
|
||||
type: 'fire',
|
||||
description: 'Hey, psst, hey. Use the ignite command for a secret achievement.',
|
||||
count: 10,
|
||||
cost: 163_000_000
|
||||
}),
|
||||
cavemanFire: basic({
|
||||
name: 'Caveman Fire',
|
||||
type: 'fire',
|
||||
description: 'They just don\'t make \'em like they used to.',
|
||||
count: 25,
|
||||
cost: 700_000_000
|
||||
}),
|
||||
lava: evil({
|
||||
name: 'Lava',
|
||||
type: 'fire',
|
||||
description: `Hopefully no usurpers have any "accidents".`,
|
||||
cost: 4_200_000_000
|
||||
}),
|
||||
blueFire: heavenly({
|
||||
name: 'Blue Fire',
|
||||
type: 'fire',
|
||||
description: `You can hear it singing with delight. 7x Fire CPS.`,
|
||||
multiplier: 7,
|
||||
cost: 25_200_000_000
|
||||
}),
|
||||
cuteFire: baby({
|
||||
name: 'Cute Fire',
|
||||
type: 'fire',
|
||||
description: `I just met my perfect match...`,
|
||||
cost: 150_000_000_000
|
||||
}),
|
||||
|
||||
spoonerang: basic({
|
||||
name: 'Spoonerang',
|
||||
type: 'boomerang',
|
||||
description: 'Scoops up HVAC mid-flight',
|
||||
count: 1,
|
||||
cost: 200_000_000
|
||||
}),
|
||||
boomerAng: basic({
|
||||
name: 'Boomer-ang',
|
||||
type: 'boomerang',
|
||||
description: 'It\'s... old.',
|
||||
count: 10,
|
||||
cost: 1_200_000_000
|
||||
}),
|
||||
doubleRang: basic({
|
||||
name: 'Double-rang',
|
||||
type: 'boomerang',
|
||||
description: 'You throw one, but somehow catch two',
|
||||
count: 25,
|
||||
cost: 10_000_000_000
|
||||
}),
|
||||
loyalRang: evil({
|
||||
name: 'Loyal-rang',
|
||||
type: 'boomerang',
|
||||
description: `Frequently reports back to your throne on the state of your empire.`,
|
||||
cost: 60_000_000_000
|
||||
}),
|
||||
youRang: heavenly({
|
||||
name: 'You-rang',
|
||||
type: 'boomerang',
|
||||
description: 'Your arms and legs recede into your body. You bend at the middle. You fly. And for a moment, you are free._\n_7x Boomerang CPS.',
|
||||
multiplier: 7,
|
||||
|
@ -250,29 +292,34 @@ module.exports = {
|
|||
}),
|
||||
|
||||
lunarPower: basic({
|
||||
name: 'Lunar Power',
|
||||
type: 'moon',
|
||||
description: 'Out with the sol, in with the lun!',
|
||||
count: 1,
|
||||
cost: 3_300_000_000
|
||||
}),
|
||||
womanOnTheMoon: basic({
|
||||
name: 'Woman on the Moon',
|
||||
type: 'moon',
|
||||
description: 'There\'s no reason for it not to be a woman!',
|
||||
count: 10,
|
||||
cost: 39_700_000_000
|
||||
}),
|
||||
doubleCraters: basic({
|
||||
name: 'Double-Craters',
|
||||
type: 'moon',
|
||||
description: 'Making every side look like the dark side.',
|
||||
count: 25,
|
||||
cost: 165_000_000_000
|
||||
}),
|
||||
tidalUpheaval: evil({
|
||||
name: 'Tidal Upheaval',
|
||||
type: 'moon',
|
||||
description: `The hell with the ocean. That's valuable_ *the abstract concept of more power* _we're losing.`,
|
||||
cost: 865_000_000_000
|
||||
}),
|
||||
newMoon: heavenly({
|
||||
name: 'New Moon',
|
||||
type: 'moon',
|
||||
description: `Build a second moon to provide space for affordable housing. 6x Moon CPS.`,
|
||||
multiplier: 6,
|
||||
|
@ -280,29 +327,34 @@ module.exports = {
|
|||
}),
|
||||
|
||||
glassButterfly: basic({
|
||||
name: 'Glass Butterfly',
|
||||
type: 'butterfly',
|
||||
description: 'Not your grandma\'s universe manipulation.',
|
||||
count: 1,
|
||||
cost: 51_000_000_000
|
||||
}),
|
||||
monarchMigration: basic({
|
||||
name: 'Monarch Migration',
|
||||
type: 'butterfly',
|
||||
description: 'This upgrade brought to you by milkweed.',
|
||||
count: 10,
|
||||
cost: 870_000_000_000
|
||||
}),
|
||||
quadWing: basic({
|
||||
name: 'Quad-Wing',
|
||||
type: 'butterfly',
|
||||
description: 'Sounds a lot like a trillion bees buzzing inside your head.',
|
||||
count: 25,
|
||||
cost: 2_550_000_000_000
|
||||
}),
|
||||
venomousMoths: evil({
|
||||
name: 'Venomous Moths',
|
||||
type: 'butterfly',
|
||||
description: 'Specifically manufactured for their horrifying brain-melt toxins.',
|
||||
cost: 12_550_000_000_000
|
||||
}),
|
||||
quietingNectar: heavenly({
|
||||
name: 'Quieting Nectar',
|
||||
type: 'butterfly',
|
||||
description: 'Calming and extra sweet. Soothes even human ails. 6x Butterfly CPS.',
|
||||
multiplier: 6,
|
||||
|
@ -310,29 +362,34 @@ module.exports = {
|
|||
}),
|
||||
|
||||
silverMirror: basic({
|
||||
name: 'Silver Mirror',
|
||||
type: 'mirror',
|
||||
description: 'Excellent for stabbing vampires.',
|
||||
count: 1,
|
||||
cost: 750_000_000_000
|
||||
}),
|
||||
pocketMirror: basic({
|
||||
name: 'Pocket Mirror',
|
||||
type: 'mirror',
|
||||
description: 'Take your self-reflection on the go!',
|
||||
count: 10,
|
||||
cost: 18_000_000_000_000
|
||||
}),
|
||||
window: basic({
|
||||
name: 'Window',
|
||||
type: 'mirror',
|
||||
description: 'Only through looking around you can you acquire the self reflection necessary to control the thermostat.',
|
||||
count: 25,
|
||||
cost: 37_500_000_000_000
|
||||
}),
|
||||
crackedMirror: evil({
|
||||
name: 'Cracked Mirror',
|
||||
type: 'mirror',
|
||||
description: `YOU SMILE. DO NOT FEAR, THIS IS THE FACE OF A FRIEND.`,
|
||||
cost: 222_000_000_000_000
|
||||
}),
|
||||
funHouseMirror: heavenly({
|
||||
name: 'Fun-House Mirror',
|
||||
type: 'mirror',
|
||||
description: `yoU LOok so siLLY IN thesE THINgs. 5X mIRror CpS.`,
|
||||
multiplier: 5,
|
||||
|
@ -340,29 +397,34 @@ module.exports = {
|
|||
}),
|
||||
|
||||
fzero: basic({
|
||||
name: 'F-Zero',
|
||||
type: 'quade',
|
||||
description: 'Brings out his competitive spirit.',
|
||||
count: 1,
|
||||
cost: 10_000_000_000_000
|
||||
}),
|
||||
triHumpCamel: basic({
|
||||
name: 'Trimedary Camel',
|
||||
type: 'quade',
|
||||
description: 'YEE HAW :trimedary_camel:',
|
||||
count: 10,
|
||||
cost: 200_000_000_000_000
|
||||
}),
|
||||
adam: basic({
|
||||
name: 'Adam',
|
||||
type: 'quade',
|
||||
description: 'He could probably reach the thermostat if he wanted.',
|
||||
count: 25,
|
||||
cost: 500_000_000_000_000
|
||||
}),
|
||||
thatsNotQuade: evil({
|
||||
name: `That's not Quade...`,
|
||||
type: 'quade',
|
||||
description: `The skinless face lacks even a moustache. Nevertheless, it pledges its allegiance.`,
|
||||
cost: 3_000_000_000_000_000
|
||||
}),
|
||||
hannahMontanaLinux: heavenly({
|
||||
name: 'Hannah Montana Linux',
|
||||
type: 'quade',
|
||||
description: `The patrician's choice. 4x Quade CPS.`,
|
||||
multiplier: 4,
|
||||
|
@ -370,29 +432,34 @@ module.exports = {
|
|||
}),
|
||||
|
||||
latestNode: basic({
|
||||
name: 'Latest Node',
|
||||
type: 'hvacker',
|
||||
description: 'The old one has terrible ergonomics, tsk tsk.',
|
||||
count: 1,
|
||||
cost: 140_000_000_000_000
|
||||
}),
|
||||
nativeFunctions: basic({
|
||||
name: 'Native Functions',
|
||||
type: 'hvacker',
|
||||
description: 'Sometimes javascript just isn\'t fast enough.',
|
||||
count: 10,
|
||||
cost: 3_300_000_000_000_000
|
||||
}),
|
||||
gitCommits: basic({
|
||||
name: 'Git Commits',
|
||||
type: 'hvacker',
|
||||
description: 'The heads of multiple people in a company are better than, for example, merely one head.',
|
||||
count: 25,
|
||||
cost: 7_000_000_000_000_000
|
||||
}),
|
||||
undefinedBehavior: evil({
|
||||
name: 'Undefined Behavior',
|
||||
type: 'hvacker',
|
||||
description: `skREEEFDS☐☐☐☐☐it's☐jwtoo☐laate☐☐☐☐☐`,
|
||||
cost: 42_000_000_000_000_000
|
||||
}),
|
||||
mutualUnderstanding: heavenly({
|
||||
name: 'Mutual Understanding',
|
||||
type: 'hvacker',
|
||||
description: `lol fat chance, dummy. Points for trying, though. 3x Hvacker CPS`,
|
||||
multiplier: 3,
|
||||
|
@ -400,29 +467,34 @@ module.exports = {
|
|||
}),
|
||||
|
||||
coffee: basic({
|
||||
name: 'Coffee',
|
||||
type: 'creator',
|
||||
description: `Didn't you know? It makes you smarter. No consequencAAAAAA`,
|
||||
count: 1,
|
||||
cost: 1_960_000_000_000_000
|
||||
}),
|
||||
bribery: basic({
|
||||
name: 'Bribery',
|
||||
type: 'creator',
|
||||
description: `How much could he be making that a couple bucks won't get me more HVAC?`,
|
||||
count: 10,
|
||||
cost: 32_300_000_000_000_000
|
||||
}),
|
||||
vim: basic({
|
||||
name: 'Vim',
|
||||
type: 'creator',
|
||||
description: `*teleports behind you*`,
|
||||
count: 25,
|
||||
cost: 100_000_000_000_000_000
|
||||
}),
|
||||
regrets: evil({
|
||||
name: 'Regrets',
|
||||
type: 'creator',
|
||||
description: `HE HAS NONE. HE LAUGHS.`,
|
||||
cost: 600_000_000_000_000_000
|
||||
}),
|
||||
goVegan: heavenly({
|
||||
name: 'Go Vegan',
|
||||
type: 'creator',
|
||||
description: `Unlock your vegan powers. 3x Creator CPS.`,
|
||||
multiplier: 3,
|
||||
|
@ -430,29 +502,34 @@ module.exports = {
|
|||
}),
|
||||
|
||||
angelInvestors: basic({
|
||||
name: 'Angel Investors',
|
||||
type: 'smallBusiness',
|
||||
description: 'Not so small NOW are we?',
|
||||
count: 1,
|
||||
cost: 3_140_000_000_000_000
|
||||
}),
|
||||
officeManager: basic({
|
||||
name: 'Office Manager',
|
||||
type: 'smallBusiness',
|
||||
description: 'Sate your laborers with snacks.',
|
||||
count: 10,
|
||||
cost: 80_000_000_000_000_000
|
||||
}),
|
||||
undyingLoyalty: basic({
|
||||
name: 'Undying Loyalty',
|
||||
type: 'smallBusiness',
|
||||
description: 'Your foolish employees bow to your every whim, regardless of salary.',
|
||||
count: 25,
|
||||
cost: 138_000_000_000_000_000
|
||||
}),
|
||||
deathSquad: evil({
|
||||
name: 'Death Squad',
|
||||
type: 'smallBusiness',
|
||||
description: `pwease don't unionize uwu :pleading_face:`,
|
||||
cost: 858_000_000_000_000_000
|
||||
}),
|
||||
coop: heavenly({
|
||||
name: 'Co-Op',
|
||||
type: 'smallBusiness',
|
||||
description: `By the people, for the people. 2x smallBusiness CPS`,
|
||||
multiplier: 2,
|
||||
|
@ -460,36 +537,77 @@ module.exports = {
|
|||
}),
|
||||
|
||||
corporateBuyouts: basic({
|
||||
name: 'Corporate Buyouts',
|
||||
type: 'bigBusiness',
|
||||
description: 'The cornerstone of any family-run business.',
|
||||
count: 1,
|
||||
cost: 28_140_000_000_000_000
|
||||
}),
|
||||
politicalSway: basic({
|
||||
name: 'Political Sway',
|
||||
type: 'bigBusiness',
|
||||
description: `What's a bit of lobbying between friends?`,
|
||||
count: 10,
|
||||
cost: 560_000_000_000_000_000
|
||||
}),
|
||||
humanDiscontent: basic({
|
||||
name: 'Human Discontent',
|
||||
type: 'bigBusiness',
|
||||
description: 'A sad populace is a spendy populace!',
|
||||
count: 25,
|
||||
cost: 1_372_000_000_000_000_000
|
||||
}),
|
||||
weJustKillPeopleNow: evil({
|
||||
name: 'We Just Kill People Now',
|
||||
type: 'bigBusiness',
|
||||
description: 'It is extremely difficult to get more evil than we already were. Nevertheless,',
|
||||
cost: 7_072_000_000_000_000_000
|
||||
}),
|
||||
makePublic: heavenly({
|
||||
name: 'Make Public',
|
||||
type: 'bigBusiness',
|
||||
description: `Downplay immediate profit for more long-term benefits. 2x bigBusiness CPS.`,
|
||||
multiplier: 2,
|
||||
cost: 42_000_000_000_000_000_000
|
||||
}),
|
||||
|
||||
community: basic({
|
||||
name: 'Community',
|
||||
type: 'government',
|
||||
description: `In a sense, this is really all you need.`,
|
||||
count: 1,
|
||||
cost: 280_140_000_000_000_000
|
||||
}),
|
||||
theState: basic({
|
||||
name: 'The State',
|
||||
type: 'government',
|
||||
description: 'Congratulations, you have a monopoly on violence.',
|
||||
count: 10,
|
||||
cost: 5_060_000_000_000_000_000
|
||||
}),
|
||||
openBorders: basic({
|
||||
name: 'Open Borders',
|
||||
type: 'government',
|
||||
description: 'Cigars, anyone?',
|
||||
count: 25,
|
||||
cost: 9_999_999_999_999_999_999
|
||||
}),
|
||||
capitalism: evil({
|
||||
name: 'Capitalism',
|
||||
type: 'government',
|
||||
description: 'Obviously this is the ideal economy no further questions thank you.',
|
||||
cost: 70_072_000_000_000_000_000
|
||||
}),
|
||||
socialism: heavenly({
|
||||
name: 'Socialism',
|
||||
type: 'government',
|
||||
description: `A dictatorship of the proletariat. And thank god.`,
|
||||
multiplier: 2,
|
||||
cost: 690_000_000_000_000_000_000
|
||||
}),
|
||||
|
||||
homage: {
|
||||
name: 'Homage',
|
||||
type: 'general',
|
||||
description: 'The power of original ideas increases your overall CPS by 10%',
|
||||
condition: user => Object.entries(user.items).reduce((total, [, countOwned]) => countOwned + total, 0) >= 200,
|
||||
|
@ -498,11 +616,51 @@ module.exports = {
|
|||
effect: (itemCps, user) => itemCps * 1.1
|
||||
},
|
||||
iLoveHvac: {
|
||||
name: 'iLoveHvac',
|
||||
type: 'general',
|
||||
description: 'The power of love increases your overall CPS by 10%',
|
||||
condition: user => Object.entries(user.items).reduce((total, [, countOwned]) => countOwned + total, 0) >= 400,
|
||||
emoji: 'heart',
|
||||
cost: 100_000_000_000_000,
|
||||
effect: (itemCps, user) => itemCps * 1.1
|
||||
},
|
||||
|
||||
digitalPickaxe: {
|
||||
name: 'Digital Pickaxe',
|
||||
type: 'mining',
|
||||
description: 'Break coinful digirocks into bits, increasing the power of !mine',
|
||||
condition: user => user.interactions > 100,
|
||||
emoji: 'pick',
|
||||
cost: 100_000,
|
||||
effect: (mineTotal, user) => mineTotal + (getCPS(user) * 0.1)
|
||||
},
|
||||
vacuum: {
|
||||
name: 'Digital Vacuum',
|
||||
type: 'mining',
|
||||
description: 'Suck up leftover HVAC dust, greatly increasing the power of !mine',
|
||||
condition: user => user.interactions > 500,
|
||||
emoji: 'vacuum',
|
||||
cost: 10_000_000,
|
||||
effect: (mineTotal, user) => mineTotal + (getCPS(user) * 0.1)
|
||||
},
|
||||
mineCart: {
|
||||
name: 'HVAC Mine Cart',
|
||||
type: 'mining',
|
||||
description: 'You\'d shine like a diamond, down in the !mine',
|
||||
condition: user => user.interactions > 1500,
|
||||
emoji: 'shopping_trolley',
|
||||
cost: 100_000_000_000,
|
||||
effect: (mineTotal, user) => mineTotal + (getCPS(user) * 0.1)
|
||||
},
|
||||
fpga: {
|
||||
name: 'FPGA Miner',
|
||||
type: 'mining',
|
||||
description: 'Wait, what kind of mining is this again?',
|
||||
condition: user => user.interactions > 5000,
|
||||
emoji: 'floppy_disk',
|
||||
cost: 1_000_000_000_000_000,
|
||||
effect: (mineTotal, user) => mineTotal + (getCPS(user) * 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
setUpgrades(module.exports)
|
||||
|
|
|
@ -2,9 +2,13 @@ const fs = require('fs')
|
|||
//const jokes = require('../jokes')
|
||||
const achievements = require('./achievements')
|
||||
const buyableItems = require('./buyableItems')
|
||||
const upgrades = require('./upgrades')
|
||||
const { quackStore, getChaos } = require('./quackstore')
|
||||
|
||||
let upgrades
|
||||
const setUpgrades = upg => {
|
||||
upgrades = upg
|
||||
}
|
||||
|
||||
const saveFile = 'hvacoins.json'
|
||||
|
||||
const logError = msg => msg ? console.error('logError: ', msg) : () => { /* Don't log empty message */ }
|
||||
|
@ -37,12 +41,15 @@ const chaosFilter = (num, odds, user, max = Infinity, min = -Infinity) => {
|
|||
return chaosed
|
||||
}
|
||||
|
||||
const parseOr = (parseable, orFunc) => {
|
||||
const parseOr = (parseable, fallback) => {
|
||||
try {
|
||||
if (typeof parseable === 'function') {
|
||||
parseable = parseable()
|
||||
}
|
||||
return JSON.parse(parseable)
|
||||
} catch (e) {
|
||||
logError(e)
|
||||
return orFunc()
|
||||
return fallback()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,13 +60,18 @@ const makeBackup = () => {
|
|||
}
|
||||
|
||||
let saves = 0
|
||||
const saveGame = (force = true) => {
|
||||
if (saves % 100 === 0) {
|
||||
const saveGame = (after, force = true) => {
|
||||
if (saves % 20 === 0) {
|
||||
makeBackup()
|
||||
}
|
||||
saves += 1
|
||||
if (force || saves % 10 === 0) {
|
||||
if (after) {
|
||||
console.log(`SAVING GAME after ${after}`)
|
||||
} else {
|
||||
console.log('SAVING GAME')
|
||||
}
|
||||
|
||||
fs.writeFileSync('./' + saveFile, JSON.stringify(game, null, 2))
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +104,7 @@ const bigNumberWords = [
|
|||
['decillion', 1_000_000_000_000_000_000_000_000_000_000_000],
|
||||
['nonillion', 1_000_000_000_000_000_000_000_000_000_000],
|
||||
['octillion', 1_000_000_000_000_000_000_000_000_000],
|
||||
['septtillion', 1_000_000_000_000_000_000_000_000],
|
||||
['septillion', 1_000_000_000_000_000_000_000_000],
|
||||
['sextillion', 1_000_000_000_000_000_000_000],
|
||||
['quintillion', 1_000_000_000_000_000_000],
|
||||
['quadrillion', 1_000_000_000_000_000],
|
||||
|
@ -101,18 +113,24 @@ const bigNumberWords = [
|
|||
['million', 1_000_000],
|
||||
]
|
||||
|
||||
const commas = (num, precise = false) => {
|
||||
const commas = (num, precise = false, skipWords = false) => {
|
||||
num = Math.round(num)
|
||||
if (num === 1) {
|
||||
return 'one'
|
||||
}
|
||||
const bigNum = bigNumberWords.find(([, base]) => num >= base)
|
||||
if (bigNum && !precise) {
|
||||
const [name, base] = bigNum
|
||||
const nummed = (num / base).toPrecision(3)
|
||||
if (skipWords) {
|
||||
return nummed
|
||||
}
|
||||
return `${nummed} ${name}`
|
||||
}
|
||||
return num.toLocaleString()
|
||||
}
|
||||
|
||||
const parseAll = (str, allNum) => {
|
||||
const parseAll = (str, allNum, user) => {
|
||||
if (!str) {
|
||||
return NaN
|
||||
}
|
||||
|
@ -144,11 +162,21 @@ const parseAll = (str, allNum) => {
|
|||
case 'one hunna':
|
||||
return 100
|
||||
}
|
||||
if (user && buyableItems[str]) {
|
||||
return calculateCost({ itemName: str, user, quantity: 1 })
|
||||
}
|
||||
|
||||
console.log('STR', str)
|
||||
if (str.match(/^\d+$/)) {
|
||||
return parseInt(str)
|
||||
}
|
||||
if (allNum && str.match(/^\d+%$/)) {
|
||||
const percent = parseFloat(str) / 100
|
||||
if (percent > 1 || percent < 0) {
|
||||
return NaN
|
||||
}
|
||||
return Math.round(percent * allNum)
|
||||
}
|
||||
|
||||
if (str.match(/^\d+\.\d+$/)) {
|
||||
return Math.round(parseFloat(str))
|
||||
|
@ -162,6 +190,16 @@ const parseAll = (str, allNum) => {
|
|||
return NaN
|
||||
}
|
||||
|
||||
const calculateCost = ({ itemName, user, quantity = 1 }) => {
|
||||
let currentlyOwned = user.items[itemName] || 0
|
||||
let realCost = 0
|
||||
for (let i = 0; i < quantity; i++) {
|
||||
realCost += Math.ceil(buyableItems[itemName].baseCost * Math.pow(1.15, currentlyOwned || 0))
|
||||
currentlyOwned += 1
|
||||
}
|
||||
return realCost
|
||||
}
|
||||
|
||||
const game = loadGame()
|
||||
const { users, nfts, squad } = game
|
||||
|
||||
|
@ -182,7 +220,7 @@ const addAchievement = (user, achievementName, say) => {
|
|||
}
|
||||
setTimeout(async () => {
|
||||
user.achievements[achievementName] = true
|
||||
saveGame()
|
||||
saveGame(`${user.name} earned ${achievementName}`)
|
||||
await say(`You earned the achievement ${achievements[achievementName].name}!`)
|
||||
}, 500)
|
||||
}
|
||||
|
@ -199,22 +237,17 @@ const getIdFromName = name => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const getUser = userId => {
|
||||
if (!users[userId]) {
|
||||
users[userId] = {
|
||||
coins: 0,
|
||||
items: {},
|
||||
upgrades: {},
|
||||
achievements: {},
|
||||
coinsAllTime: 0,
|
||||
prestige: 0
|
||||
}
|
||||
} else {
|
||||
const getUser = (userId, updateCoins = false) => {
|
||||
users[userId] ??= {}
|
||||
users[userId].coins ??= 0
|
||||
users[userId].items ??= {}
|
||||
users[userId].upgrades ??= {}
|
||||
users[userId].achievements ??= {}
|
||||
users[userId].coinsAllTime ??= users[userId].coins
|
||||
users[userId].prestige ??= 0
|
||||
users[userId].startDate ??= new Date()
|
||||
if (updateCoins) {
|
||||
users[userId].coins = getCoins(userId)
|
||||
}
|
||||
return users[userId]
|
||||
}
|
||||
|
@ -251,6 +284,7 @@ const squadUpgrades = {
|
|||
tastyKeyboards: {
|
||||
name: 'Tasty Keyboards',
|
||||
description: 'Delicious and sticky. Boosts CPS by 20% for everyone.',
|
||||
|
||||
effect: cps => cps * 1.2,
|
||||
cost: 10_000_000_000_000,
|
||||
emoji: 'keyboard'
|
||||
|
@ -298,6 +332,11 @@ const quackGradeMultiplier = user => {
|
|||
return userQuackgrades.reduce((total, upgrade) => quackStore[upgrade].effect(total, user), 1)
|
||||
}
|
||||
|
||||
const petQuackGradeMultiplier = user => {
|
||||
const userQuackgrades = user.quackUpgrades?.pet || []
|
||||
return userQuackgrades.reduce((total, upgrade) => quackStore[upgrade].effect(total, user), petBoost())
|
||||
}
|
||||
|
||||
const singleItemCps = (user, itemName) => {
|
||||
const baseCps = buyableItems[itemName].earning
|
||||
// console.log('')
|
||||
|
@ -326,6 +365,9 @@ const singleItemCps = (user, itemName) => {
|
|||
const squadGradeMultiplier = getCompletedSquadgrades().reduce((cps, upgrade) => upgrade.effect(cps), 1)
|
||||
// console.log('squadGradeMultiplier', squadGradeMultiplier)
|
||||
|
||||
const petMultiplier = petQuackGradeMultiplier(user)
|
||||
//console.log('petMultiplier', petMultiplier)
|
||||
|
||||
const total =
|
||||
baseCps *
|
||||
achievementMultiplier *
|
||||
|
@ -333,7 +375,8 @@ const singleItemCps = (user, itemName) => {
|
|||
generalUpgradeCps *
|
||||
quackGrade *
|
||||
pMult *
|
||||
squadGradeMultiplier
|
||||
squadGradeMultiplier *
|
||||
petMultiplier
|
||||
|
||||
// console.log('Single Item CPS:', total)
|
||||
|
||||
|
@ -372,6 +415,7 @@ const definitelyShuffle = (str, percentOdds) => {
|
|||
let shuffled = str
|
||||
while (shuffled === str) {
|
||||
shuffled = shufflePercent(str, percentOdds)
|
||||
console.log('Shuffling... "' + shuffled + '"')
|
||||
}
|
||||
return shuffled
|
||||
}
|
||||
|
@ -436,6 +480,71 @@ game.stonkMarket ??= {
|
|||
|
||||
const userHasCheckedQuackgrade = (user, quackGrade) => (user.quackUpgrades?.checked || []).includes(quackGrade)
|
||||
|
||||
const petBoost = () => {
|
||||
// game.pet ??= makePet()
|
||||
const stats = Object.values(game.pet)
|
||||
const hasTerribleStat = stats.filter(value => value < 1).length > 0
|
||||
const averageStat = stats.reduce((total, current) => total + current, 0) / stats.length
|
||||
|
||||
if (hasTerribleStat && averageStat < 3) {
|
||||
return 0.9
|
||||
}
|
||||
|
||||
if (averageStat === 10) {
|
||||
return 1.3
|
||||
}
|
||||
|
||||
if (!hasTerribleStat && averageStat > 8) {
|
||||
return 1.1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
game.channelMaps ??= {}
|
||||
let slackAppClientChatUpdate
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name String name for this channel map
|
||||
* @param text String of to send. Passed into slack.app.client.chat.update
|
||||
* @param blocks Slack blocks object to send. Passed into slack.app.client.chat.update
|
||||
* @param channel An (optional) new channel to add to the given map
|
||||
* @param ts The timestamp of the message in the new channel to update
|
||||
*/
|
||||
const updateAll = async ({ name, text, blocks, add: { channel, ts } = {} }) => {
|
||||
const channelMap = (game.channelMaps[name] ??= {})
|
||||
// if (channel && ts && !channelMap[channel]) {
|
||||
// }
|
||||
if (channel && ts) {
|
||||
channelMap[channel] = ts
|
||||
console.log({ channelMap })
|
||||
}
|
||||
if (text || blocks) {
|
||||
await Promise.all(Object.entries(channelMap).map(async ([channel, ts]) =>
|
||||
slackAppClientChatUpdate({
|
||||
channel,
|
||||
ts,
|
||||
text,
|
||||
blocks
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
if (e.toString().includes('message_not_found')) {
|
||||
delete channelMap[channel]
|
||||
saveGame(`removing message ${channel}::${ts} from the ${name} list`)
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
// // const alreadyHas = !!channelMap[channel]
|
||||
// if (channel && ts) {
|
||||
// channelMap[channel] = ts
|
||||
// console.log({ channelMap })
|
||||
// }
|
||||
// // return alreadyHas
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
saveGame,
|
||||
makeBackup,
|
||||
|
@ -468,5 +577,10 @@ module.exports = {
|
|||
userHasCheckedQuackgrade,
|
||||
fuzzyMatcher,
|
||||
addCoins,
|
||||
setKnownUsers: users => knownUsers = users
|
||||
calculateCost,
|
||||
setKnownUsers: users => knownUsers = users,
|
||||
petBoost,
|
||||
updateAll,
|
||||
setSlackAppClientChatUpdate: update => slackAppClientChatUpdate = update,
|
||||
setUpgrades
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ const addCommand = ({ commandNames, helpText, action, condition, hidden }) => {
|
|||
const user = getUser(event.user)
|
||||
const haunted = false
|
||||
//await action({ event, say, words, args, commandName })
|
||||
const canUse = await condition({ event, say, words, commandName, args, user, userId: event.user, isAdmin: event.user.includes(slack.users.Sage) })
|
||||
const canUse = await condition({ event, say, words, commandName, args, user, userId: event.user, isAdmin: event.user.includes(slack.users.Admin) })
|
||||
if (!canUse) {
|
||||
await say(`Command '${words[0]}' not found`)
|
||||
return
|
||||
|
|
|
@ -1,45 +1,63 @@
|
|||
const slack = require('../slack')
|
||||
const { updateAll } = require('../games/hvacoins/utils')
|
||||
|
||||
const tie = 'TIE'
|
||||
|
||||
const messageFromBoard = ({ dataName, gameName, textFromBoard, board, player1, player2 }) =>
|
||||
gameName + ' between ' + player1.toUpperCase() + ' and ' + player2.toUpperCase() + ' ' + encodeGame(dataName, board, [player1, player2]) + '\n' +
|
||||
const messageFromBoard = ({ dataName, gameName, textFromBoard, board, player1, player2, channelMap }) =>
|
||||
gameName + ' between ' + player1.toUpperCase() + ' and ' + player2.toUpperCase() + ' ' + encodeGame(dataName, board, [player1, player2], channelMap) + '\n' +
|
||||
'```' + textFromBoard(board) + '\n```'
|
||||
|
||||
const addChoiceEmojis = async ({ choices, channel, ts }) => {
|
||||
const addEmoji = async emojiName =>
|
||||
const addEmoji = async emojiName => {
|
||||
try {
|
||||
await slack.app.client.reactions.add({
|
||||
channel,
|
||||
timestamp: ts,
|
||||
name: emojiName
|
||||
})
|
||||
} catch (ignore) {
|
||||
}
|
||||
}
|
||||
for (const choice of choices) {
|
||||
await addEmoji(choice)
|
||||
}
|
||||
}
|
||||
|
||||
const buildGameStarter = ({ startTriggers, dataName, gameName, textFromBoard, initialBoard, turnChoiceEmojis }) => async ({ event, say }) => {
|
||||
if (event.channel_type === 'im') {
|
||||
if (event.channel_type !== 'im') {
|
||||
return;
|
||||
}
|
||||
const eventText = event.text?.toLowerCase()
|
||||
if (eventText && startTriggers.find(keyword => eventText.startsWith('!' + keyword))) {
|
||||
if (!(eventText && startTriggers.find(keyword => eventText.startsWith('!' + keyword)))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
console.log('Trigger found')
|
||||
const opponent = event.text.toUpperCase().match(/<@[^>]*>/)[0]
|
||||
console.log('Messaging opponent ' + slack.users[opponent.substring(2, opponent.length - 1)])
|
||||
const msg = messageFromBoard({
|
||||
const channelMap = {}
|
||||
const msg = () => messageFromBoard({
|
||||
dataName,
|
||||
gameName,
|
||||
textFromBoard,
|
||||
board: initialBoard(),
|
||||
player1: '<@' + event.user + '>',
|
||||
player2: opponent
|
||||
player2: opponent,
|
||||
channelMap
|
||||
})
|
||||
const sent = await say(msg)
|
||||
await addChoiceEmojis({ ...sent, choices: turnChoiceEmojis })
|
||||
const sent = await say(msg())
|
||||
channelMap[event.user] = {
|
||||
channel: sent.channel,
|
||||
ts: sent.ts
|
||||
}
|
||||
await updateAll({ name: gameName, text: msg(), add: sent })
|
||||
await addChoiceEmojis({...sent, choices: turnChoiceEmojis})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const encodeGame = (dataKey, board, players) => slack.encodeData(dataKey, { board, players })
|
||||
const encodeGame = (dataKey, board, players, channelMap = {}) => slack.encodeData(dataKey, { board, players, channelMap })
|
||||
|
||||
const decodeGame = (dataKey, message) => slack.decodeData(dataKey, message)
|
||||
|
||||
|
@ -68,7 +86,8 @@ const buildTurnHandler = ({ gameName, dataName, checkWinner, textFromBoard, turn
|
|||
return
|
||||
}
|
||||
|
||||
const { board, players } = game
|
||||
game.channelMap ??= {}
|
||||
const { board, players, channelMap } = game
|
||||
let winner = checkWinner(board)
|
||||
if (winner) {
|
||||
console.log('winner found: ' + winner)
|
||||
|
@ -85,33 +104,58 @@ const buildTurnHandler = ({ gameName, dataName, checkWinner, textFromBoard, turn
|
|||
}
|
||||
winner = checkWinner(board)
|
||||
|
||||
const boardMessage = messageFromBoard({
|
||||
const boardMessage = () => messageFromBoard({
|
||||
dataName,
|
||||
gameName,
|
||||
textFromBoard,
|
||||
board,
|
||||
player1,
|
||||
player2
|
||||
player2,
|
||||
channelMap
|
||||
})
|
||||
const winnerMessages = getMessages(winner)
|
||||
await say(boardMessage + winnerMessages.you)
|
||||
if (!winner) {
|
||||
await say('Waiting for opponent\'s response...')
|
||||
}
|
||||
|
||||
const removeEmoji = async emojiName =>
|
||||
if (winner) {
|
||||
await updateAll({ name: gameName, text: boardMessage() + '\nSomebody won! I do not yet know who!' })
|
||||
const removeEmoji = emojiName => Object.values(channelMap).forEach(({ channel, ts }) =>
|
||||
slack.app.client.reactions.remove({
|
||||
channel: event.item.channel,
|
||||
timestamp: message.messages[0]?.ts,
|
||||
channel,
|
||||
timestamp: ts,
|
||||
name: emojiName
|
||||
})
|
||||
}))
|
||||
turnChoiceEmojis.forEach(removeEmoji)
|
||||
return
|
||||
}
|
||||
const winnerMessages = getMessages(winner)
|
||||
// await say(boardMessage() + winnerMessages.you)
|
||||
console.log('TurnHandler', { gameName, boardMessage: boardMessage() })
|
||||
// await updateAll({ name: gameName, text: boardMessage() + '\nTurnHandler' })
|
||||
// if (!winner) {
|
||||
// await say('Waiting for opponent\'s response...')
|
||||
// }
|
||||
|
||||
// const removeEmoji = async emojiName =>
|
||||
// slack.app.client.reactions.remove({
|
||||
// channel: event.item.channel,
|
||||
// timestamp: message.messages[0]?.ts,
|
||||
// name: emojiName
|
||||
// })
|
||||
// turnChoiceEmojis.forEach(removeEmoji)
|
||||
console.log('SENDING to ' + opponent)
|
||||
if (!channelMap[opponent]) {
|
||||
const sentBoard = await slack.app.client.chat.postMessage({
|
||||
channel: opponent,
|
||||
text: boardMessage + winnerMessages.opponent
|
||||
text: boardMessage() + winnerMessages.opponent
|
||||
})
|
||||
channelMap[opponent] = {
|
||||
channel: sentBoard.channel,
|
||||
ts: sentBoard.ts
|
||||
}
|
||||
console.log('BOARD MESSAGE AFTER ')
|
||||
await updateAll({ name: gameName, text: boardMessage(), add: sentBoard })
|
||||
} else {
|
||||
await updateAll({ name: gameName, text: boardMessage() })
|
||||
}
|
||||
if (!winner) {
|
||||
const sentBoard = channelMap[opponent]
|
||||
await addChoiceEmojis({ ...sentBoard, choices: turnChoiceEmojis })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
const { App: SlackApp } = require('@slack/bolt')
|
||||
const config = require('../config')
|
||||
const { addReactions, saveGame } = require('../games/hvacoins/utils')
|
||||
const fs = require('fs')
|
||||
const { addReactions, saveGame, setSlackAppClientChatUpdate, parseOr } = require('../games/hvacoins/utils')
|
||||
|
||||
const temperatureChannelId = 'C034156CE03'
|
||||
const dailyStandupChannelId = 'C03L533AU3Z'
|
||||
|
||||
const pollingMinutes = 5
|
||||
const pollingPeriod = 1000 * 60 * pollingMinutes
|
||||
|
||||
const MAX_POLLS = 3
|
||||
const HOURS_PER_WINDOW = 2
|
||||
|
||||
const colderEmoji = 'snowflake'
|
||||
const hotterEmoji = 'fire'
|
||||
const goodEmoji = '+1'
|
||||
|
||||
let app
|
||||
try {
|
||||
app = new SlackApp({
|
||||
const app = new SlackApp({
|
||||
token: config.slackBotToken,
|
||||
signingSecret: config.slackSigningSecret,
|
||||
appToken: config.slackAppToken,
|
||||
|
@ -23,9 +26,6 @@ try {
|
|||
// temperatureChannelId = fetched.channels.filter(channel => channel.name === 'thermo-posting')[0].id
|
||||
// console.log('techThermostatChannelId', temperatureChannelId)
|
||||
// })
|
||||
} catch (e) {
|
||||
console.log('Failed to initialize SlackApp', e)
|
||||
}
|
||||
|
||||
const pollTriggers = ['!temp', '!temperature', '!imhot', '!imcold', '!imfreezing', '!idonthavemysweater']
|
||||
const halfTriggers = ['change temperature', "i'm cold", "i'm hot", 'quack', 'hvacker', '<@U0344TFA7HQ>']
|
||||
|
@ -41,7 +41,7 @@ const sendHelp = async (say, prefix) => {
|
|||
text: prefix +
|
||||
`Sending a message matching any of \`${pollTriggers.join('`, `')}\` will start a temperature poll.\n` +
|
||||
'\'Hotter\' and \'Colder\' votes offset. E.g. with votes Hotter - 4, Colder - 3, and Content - 2, the temp won\'t change.\n' +
|
||||
'At this time I am not capable of actually changing the temperature. Go bug Quade.'
|
||||
'At this time I am not capable of actually changing the temperature. Go bug Michael.'
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -59,34 +59,8 @@ app.event('reaction_added', async ({ event, context, client, say }) => {
|
|||
}
|
||||
})
|
||||
|
||||
const users = {
|
||||
U028BMEBWBV: 'Sage',
|
||||
U02U15RFK4Y: 'Adam',
|
||||
U02AAB54V34: 'Houston',
|
||||
U02KYLVK1GV: 'Quade',
|
||||
U017PG4EL1Y: 'Max',
|
||||
UTDLFGZA5: 'Tyler',
|
||||
U017CB5L1K3: 'Andres',
|
||||
U0344TFA7HQ: 'Hvacker',
|
||||
U0X0ZQCN6: 'Caleb',
|
||||
U03BBTD4CQZ: 'Fernando',
|
||||
U03DF152WUV: 'Nik',
|
||||
U2X0SG7BP: 'John',
|
||||
UR2H5KNHY: 'Jake',
|
||||
|
||||
Sage: 'U028BMEBWBV',
|
||||
Adam: 'U02U15RFK4Y',
|
||||
Houston: 'U02AAB54V34',
|
||||
Quade: 'U02KYLVK1GV',
|
||||
Max: 'U017PG4EL1Y',
|
||||
Tyler: 'UTDLFGZA5',
|
||||
Andres: 'U017CB5L1K3',
|
||||
Caleb: 'U0X0ZQCN6',
|
||||
Hvacker: 'U0344TFA7HQ',
|
||||
Fernando: 'U03BBTD4CQZ',
|
||||
John: 'U2X0SG7BP',
|
||||
Jake: 'UR2H5KNHY',
|
||||
}
|
||||
const users = parseOr(fs.readFileSync('./users.json', 'utf-8'),
|
||||
() => ({}))
|
||||
|
||||
const buildSayPrepend = ({ say, prepend }) => async msg => {
|
||||
if (typeof(msg) === 'string') {
|
||||
|
@ -99,10 +73,11 @@ const buildSayPrepend = ({ say, prepend }) => async msg => {
|
|||
}
|
||||
|
||||
process.once('SIGINT', code => {
|
||||
saveGame(true)
|
||||
saveGame(null, true)
|
||||
process.exit()
|
||||
})
|
||||
|
||||
let pollHistory = []
|
||||
const activePolls = {}
|
||||
const testId = 'U028BMEBWBV_TEST'
|
||||
let testMode = false
|
||||
|
@ -113,18 +88,18 @@ app.event('message', async ({ event, context, client, say }) => {
|
|||
userName: users[event.user]
|
||||
})
|
||||
}
|
||||
if (event?.user === users.Sage) {
|
||||
if (event?.user === users.Admin) {
|
||||
if (event?.text.startsWith('!')) {
|
||||
if (testMode) {
|
||||
await messageSage('Currently in test mode!')
|
||||
await messageAdmin('Currently in test mode!')
|
||||
}
|
||||
}
|
||||
if (event?.text === '!test') {
|
||||
testMode = !testMode
|
||||
await messageSage(`TestMode: ${testMode} with ID ${testId}`)
|
||||
await messageAdmin(`TestMode: ${testMode} with ID ${testId}`)
|
||||
} else if (event?.text === '!notest') {
|
||||
testMode = false
|
||||
await messageSage(`TestMode: ${testMode}`)
|
||||
await messageAdmin(`TestMode: ${testMode}`)
|
||||
}
|
||||
if (testMode) {
|
||||
event.user = testId
|
||||
|
@ -136,16 +111,21 @@ app.event('message', async ({ event, context, client, say }) => {
|
|||
if (event.user) {
|
||||
console.log('MSG', users[event.user], "'" + event.text + "'", new Date().toLocaleTimeString())
|
||||
}
|
||||
if (event.user === users.Sage && event.channel === 'D0347Q4H9FE') {
|
||||
if (event.user === users.Admin && event.channel === 'D0347Q4H9FE') {
|
||||
if (event.text === '!!kill') {
|
||||
saveGame(true)
|
||||
saveGame(null, true)
|
||||
process.exit(1)
|
||||
} else if (event.text === '!!restart') {
|
||||
saveGame(true)
|
||||
process.exit()
|
||||
if (Object.entries(activePolls).length === 0) {
|
||||
saveGame(null, true)
|
||||
process.exit(0)
|
||||
} else {
|
||||
await messageAdmin('Restart pending poll completion...')
|
||||
pendingRestart = true
|
||||
}
|
||||
}
|
||||
if (event.text?.startsWith('!say ') || event.text?.startsWith('!say\n')) {
|
||||
await postToTechThermostatChannel(event.text.substring(4).trim())
|
||||
await postToTechThermostatChannel(event.text.substring(4).trim().replace('@here', '<!here>'))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -156,17 +136,37 @@ app.event('message', async ({ event, context, client, say }) => {
|
|||
return
|
||||
}
|
||||
|
||||
if (!pollTriggers.includes(eventText)) {
|
||||
if (!pollTriggers.includes(eventText) || event.user === users.John) {
|
||||
if (halfTriggers.includes(eventText)) {
|
||||
await sendHelp(say, 'It looks like you might want to change the temperature.')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (event.channel !== temperatureChannelId) {
|
||||
return say(`Please request polls in the appropriate channel.`)
|
||||
}
|
||||
|
||||
if (activePolls[event.channel]) {
|
||||
await postToTechThermostatChannel({ text: "There's already an active poll in this channel!" })
|
||||
return
|
||||
}
|
||||
const now = new Date()
|
||||
const windowStart = new Date()
|
||||
windowStart.setHours(now.getHours() - HOURS_PER_WINDOW)
|
||||
|
||||
const pollsInWindow = pollHistory.filter(pollTime => pollTime > windowStart)
|
||||
const pollText = MAX_POLLS === 1 ? 'poll' : 'polls'
|
||||
const hourText = HOURS_PER_WINDOW === 1 ? 'hour' : 'hours'
|
||||
|
||||
if (pollsInWindow.length >= MAX_POLLS) {
|
||||
await postToTechThermostatChannel({ text: `You have exceeded the limit of ${MAX_POLLS} ${pollText} per ${HOURS_PER_WINDOW} ${hourText}!` })
|
||||
return
|
||||
}
|
||||
|
||||
if (pollHistory.push(now) > MAX_POLLS) {
|
||||
[, ...pollHistory] = pollHistory
|
||||
}
|
||||
|
||||
activePolls[event.channel] = true
|
||||
const pollTs = await startPoll()
|
||||
|
@ -215,11 +215,11 @@ app.event('message', async ({ event, context, client, say }) => {
|
|||
|
||||
let text
|
||||
if (hotterVotes > colderVotes && hotterVotes > contentVotes) {
|
||||
text = `<@${users.Adam}> The people have spoken, and would like to `
|
||||
text = `<@${users.Michael}> The people have spoken, and would like to `
|
||||
text += 'raise the temperature, quack.'
|
||||
requestTempChange('Hotter')
|
||||
} else if (colderVotes > hotterVotes && colderVotes > contentVotes) {
|
||||
text = `<@${users.Adam}> The people have spoken, and would like to `
|
||||
text = `<@${users.Michael}> The people have spoken, and would like to `
|
||||
text += 'lower the temperature, quack quack.'
|
||||
requestTempChange('Colder')
|
||||
} else {
|
||||
|
@ -230,11 +230,18 @@ app.event('message', async ({ event, context, client, say }) => {
|
|||
|
||||
await postToTechThermostatChannel({ text })
|
||||
delete activePolls[event.channel]
|
||||
if (pendingRestart && Object.entries(activePolls).length === 0) {
|
||||
await messageAdmin('Performing pending restart!')
|
||||
saveGame(null, true)
|
||||
process.exit(0)
|
||||
}
|
||||
}, pollingPeriod)
|
||||
})
|
||||
|
||||
let pendingRestart = false
|
||||
|
||||
;(async () => {
|
||||
await app.start().catch(console.error)
|
||||
await app.start()
|
||||
console.log('Slack Bolt has started')
|
||||
})()
|
||||
|
||||
|
@ -247,7 +254,7 @@ const postToTechThermostatChannel = async optionsOrText => {
|
|||
return app.client.chat.postMessage({ ...optionsOrText, channel: temperatureChannelId })
|
||||
}
|
||||
|
||||
const messageSage = async optionsOrText => messageIn(users.Sage, optionsOrText)
|
||||
const messageAdmin = async optionsOrText => messageIn(users.Admin, optionsOrText)
|
||||
|
||||
const messageIn = async (channel, optionsOrText) => {
|
||||
if (optionsOrText === null || typeof optionsOrText !== 'object') {
|
||||
|
@ -262,7 +269,7 @@ const startPoll = async () => {
|
|||
const sent = await postToTechThermostatChannel({
|
||||
text: `<!here> Temperature poll requested! In ${pollingMinutes} minutes the temperature will be adjusted.\n` +
|
||||
`Pick :${colderEmoji}: if you want it colder, :${hotterEmoji}: if you want it hotter, or :${goodEmoji}: if you like it how it is.` +
|
||||
'\n(Note that I can\'t actually change the temperature yet. Make Quade do it!)'
|
||||
'\n(Note that I can\'t actually change the temperature yet. Make Michael do it!)'
|
||||
})
|
||||
await addReactions({
|
||||
app,
|
||||
|
@ -281,10 +288,12 @@ const requestTempChange = change => {
|
|||
tempChangeListeners.forEach(listener => listener(change))
|
||||
}
|
||||
|
||||
// noinspection HttpUrlsUsage
|
||||
const encodeData = (key, data) =>
|
||||
`<http://${key}ZZZ${Buffer.from(JSON.stringify(data), 'utf-8').toString('base64')}| >`
|
||||
|
||||
const decodeData = (key, message) => {
|
||||
try {
|
||||
const regex = new RegExp(`http://${key}ZZZ[^|]*`)
|
||||
let match = message.match(regex)
|
||||
if (!match) {
|
||||
|
@ -292,14 +301,31 @@ const decodeData = (key, message) => {
|
|||
}
|
||||
match = match[0].substring(10 + key.length) // 10 === 'http://'.length + 'ZZZ'.length
|
||||
return JSON.parse(Buffer.from(match, 'base64').toString('utf-8'))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const onReaction = listener => reactionListeners.push(listener)
|
||||
|
||||
const channelIsIm = async channel => (await app.client.conversations.info({ channel }))?.channel?.is_im
|
||||
|
||||
const wasMyMessage = async event => {
|
||||
const text = (await app.client.conversations.history({
|
||||
channel: event.item.channel,
|
||||
latest: event.item.ts,
|
||||
limit: 1,
|
||||
inclusive: true
|
||||
})).messages[0].text
|
||||
|
||||
const decoded = decodeData('commandPayload', text)
|
||||
return decoded.event.user === event.user
|
||||
}
|
||||
|
||||
onReaction(async ({ event }) => {
|
||||
if (event.reaction === 'x' && (event.user === users.Sage || await channelIsIm(event.item.channel))) {
|
||||
console.log({ event })
|
||||
if (event.reaction === 'x' && (event.user === users.Admin || (await wasMyMessage(event)) || await channelIsIm(event.item.channel))) {
|
||||
try {
|
||||
await app.client.chat.delete({ channel: event.item.channel, ts: event.item.ts })
|
||||
} catch (e) {
|
||||
|
@ -307,9 +333,12 @@ onReaction(async ({ event }) => {
|
|||
}
|
||||
})
|
||||
|
||||
setSlackAppClientChatUpdate(app.client.chat.update)
|
||||
|
||||
module.exports = {
|
||||
app,
|
||||
temperatureChannelId,
|
||||
dailyStandupChannelId,
|
||||
onAction: app.action,
|
||||
getMessage,
|
||||
updateMessage: app.client.chat.update,
|
||||
|
@ -319,10 +348,12 @@ module.exports = {
|
|||
onReaction,
|
||||
encodeData,
|
||||
decodeData,
|
||||
messageSage,
|
||||
messageAdmin,
|
||||
messageIn,
|
||||
testMode,
|
||||
testId,
|
||||
users,
|
||||
buildSayPrepend
|
||||
buildSayPrepend,
|
||||
pollTriggers,
|
||||
pendingRestart
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue